@ixo/oracle-runtime 0.0.1

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 (583) hide show
  1. package/dist/bootstrap/ambient-factory.d.ts +32 -0
  2. package/dist/bootstrap/ambient-factory.d.ts.map +1 -0
  3. package/dist/bootstrap/ambient-factory.js +128 -0
  4. package/dist/bootstrap/create-oracle-app.d.ts +112 -0
  5. package/dist/bootstrap/create-oracle-app.d.ts.map +1 -0
  6. package/dist/bootstrap/create-oracle-app.js +530 -0
  7. package/dist/bootstrap/graceful-shutdown.d.ts +20 -0
  8. package/dist/bootstrap/graceful-shutdown.d.ts.map +1 -0
  9. package/dist/bootstrap/graceful-shutdown.js +61 -0
  10. package/dist/bootstrap/index.d.ts +13 -0
  11. package/dist/bootstrap/index.d.ts.map +1 -0
  12. package/dist/bootstrap/index.js +6 -0
  13. package/dist/bootstrap/inspect.d.ts +74 -0
  14. package/dist/bootstrap/inspect.d.ts.map +1 -0
  15. package/dist/bootstrap/inspect.js +111 -0
  16. package/dist/bootstrap/plugin-loader.d.ts +50 -0
  17. package/dist/bootstrap/plugin-loader.d.ts.map +1 -0
  18. package/dist/bootstrap/plugin-loader.js +119 -0
  19. package/dist/bootstrap/runtime-app-module.d.ts +38 -0
  20. package/dist/bootstrap/runtime-app-module.d.ts.map +1 -0
  21. package/dist/bootstrap/runtime-app-module.js +114 -0
  22. package/dist/bootstrap/schema-composer.d.ts +46 -0
  23. package/dist/bootstrap/schema-composer.d.ts.map +1 -0
  24. package/dist/bootstrap/schema-composer.js +65 -0
  25. package/dist/config/base-env-config.d.ts +31 -0
  26. package/dist/config/base-env-config.d.ts.map +1 -0
  27. package/dist/config/base-env-config.js +70 -0
  28. package/dist/config/base-env-schema.d.ts +77 -0
  29. package/dist/config/base-env-schema.d.ts.map +1 -0
  30. package/dist/config/base-env-schema.js +102 -0
  31. package/dist/events/scoped-emitter.d.ts +33 -0
  32. package/dist/events/scoped-emitter.d.ts.map +1 -0
  33. package/dist/events/scoped-emitter.js +32 -0
  34. package/dist/graph/index.d.ts +8 -0
  35. package/dist/graph/index.d.ts.map +1 -0
  36. package/dist/graph/index.js +6 -0
  37. package/dist/graph/main-agent-types.d.ts +88 -0
  38. package/dist/graph/main-agent-types.d.ts.map +1 -0
  39. package/dist/graph/main-agent-types.js +20 -0
  40. package/dist/graph/main-agent.d.ts +16 -0
  41. package/dist/graph/main-agent.d.ts.map +1 -0
  42. package/dist/graph/main-agent.js +251 -0
  43. package/dist/graph/middlewares/capability-gate-middleware.d.ts +35 -0
  44. package/dist/graph/middlewares/capability-gate-middleware.d.ts.map +1 -0
  45. package/dist/graph/middlewares/capability-gate-middleware.js +54 -0
  46. package/dist/graph/middlewares/index.d.ts +7 -0
  47. package/dist/graph/middlewares/index.d.ts.map +1 -0
  48. package/dist/graph/middlewares/index.js +6 -0
  49. package/dist/graph/middlewares/page-context-middleware.d.ts +23 -0
  50. package/dist/graph/middlewares/page-context-middleware.d.ts.map +1 -0
  51. package/dist/graph/middlewares/page-context-middleware.js +68 -0
  52. package/dist/graph/middlewares/safety-guardrail-middleware.d.ts +26 -0
  53. package/dist/graph/middlewares/safety-guardrail-middleware.d.ts.map +1 -0
  54. package/dist/graph/middlewares/safety-guardrail-middleware.js +88 -0
  55. package/dist/graph/middlewares/summarization-middleware.d.ts +17 -0
  56. package/dist/graph/middlewares/summarization-middleware.d.ts.map +1 -0
  57. package/dist/graph/middlewares/summarization-middleware.js +62 -0
  58. package/dist/graph/middlewares/tool-repetition-guard-middleware.d.ts +24 -0
  59. package/dist/graph/middlewares/tool-repetition-guard-middleware.d.ts.map +1 -0
  60. package/dist/graph/middlewares/tool-repetition-guard-middleware.js +112 -0
  61. package/dist/graph/middlewares/tool-validation-middleware.d.ts +24 -0
  62. package/dist/graph/middlewares/tool-validation-middleware.d.ts.map +1 -0
  63. package/dist/graph/middlewares/tool-validation-middleware.js +61 -0
  64. package/dist/graph/prompt-composer.d.ts +69 -0
  65. package/dist/graph/prompt-composer.d.ts.map +1 -0
  66. package/dist/graph/prompt-composer.js +315 -0
  67. package/dist/graph/state.d.ts +65 -0
  68. package/dist/graph/state.d.ts.map +1 -0
  69. package/dist/graph/state.js +60 -0
  70. package/dist/graph/sub-agent-fallback.d.ts +56 -0
  71. package/dist/graph/sub-agent-fallback.d.ts.map +1 -0
  72. package/dist/graph/sub-agent-fallback.js +79 -0
  73. package/dist/graph/subagent-as-tool.d.ts +77 -0
  74. package/dist/graph/subagent-as-tool.d.ts.map +1 -0
  75. package/dist/graph/subagent-as-tool.js +197 -0
  76. package/dist/graph/wrap-plugin-tool.d.ts +28 -0
  77. package/dist/graph/wrap-plugin-tool.d.ts.map +1 -0
  78. package/dist/graph/wrap-plugin-tool.js +30 -0
  79. package/dist/index.d.ts +33 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +28 -0
  82. package/dist/llm/index.d.ts +2 -0
  83. package/dist/llm/index.d.ts.map +1 -0
  84. package/dist/llm/index.js +1 -0
  85. package/dist/llm/llm-provider.d.ts +41 -0
  86. package/dist/llm/llm-provider.d.ts.map +1 -0
  87. package/dist/llm/llm-provider.js +130 -0
  88. package/dist/manifest/index.d.ts +6 -0
  89. package/dist/manifest/index.d.ts.map +1 -0
  90. package/dist/manifest/index.js +3 -0
  91. package/dist/manifest/schema.d.ts +31 -0
  92. package/dist/manifest/schema.d.ts.map +1 -0
  93. package/dist/manifest/schema.js +44 -0
  94. package/dist/manifest/tier1-renderer.d.ts +31 -0
  95. package/dist/manifest/tier1-renderer.d.ts.map +1 -0
  96. package/dist/manifest/tier1-renderer.js +68 -0
  97. package/dist/manifest/validator.d.ts +34 -0
  98. package/dist/manifest/validator.d.ts.map +1 -0
  99. package/dist/manifest/validator.js +111 -0
  100. package/dist/matrix/checkpointer/matrix-upload-utils.d.ts +66 -0
  101. package/dist/matrix/checkpointer/matrix-upload-utils.d.ts.map +1 -0
  102. package/dist/matrix/checkpointer/matrix-upload-utils.js +228 -0
  103. package/dist/matrix/checkpointer/type.d.ts +4 -0
  104. package/dist/matrix/checkpointer/type.d.ts.map +1 -0
  105. package/dist/matrix/checkpointer/type.js +1 -0
  106. package/dist/matrix/checkpointer/user-matrix-sqlite-sync-service.module.d.ts +3 -0
  107. package/dist/matrix/checkpointer/user-matrix-sqlite-sync-service.module.d.ts.map +1 -0
  108. package/dist/matrix/checkpointer/user-matrix-sqlite-sync-service.module.js +22 -0
  109. package/dist/matrix/checkpointer/user-matrix-sqlite-sync-service.service.d.ts +93 -0
  110. package/dist/matrix/checkpointer/user-matrix-sqlite-sync-service.service.d.ts.map +1 -0
  111. package/dist/matrix/checkpointer/user-matrix-sqlite-sync-service.service.js +856 -0
  112. package/dist/matrix/room-membership.d.ts +11 -0
  113. package/dist/matrix/room-membership.d.ts.map +1 -0
  114. package/dist/matrix/room-membership.js +33 -0
  115. package/dist/meta-tools/index.d.ts +28 -0
  116. package/dist/meta-tools/index.d.ts.map +1 -0
  117. package/dist/meta-tools/index.js +24 -0
  118. package/dist/meta-tools/list-capabilities.d.ts +12 -0
  119. package/dist/meta-tools/list-capabilities.d.ts.map +1 -0
  120. package/dist/meta-tools/list-capabilities.js +55 -0
  121. package/dist/meta-tools/load-capability.d.ts +24 -0
  122. package/dist/meta-tools/load-capability.d.ts.map +1 -0
  123. package/dist/meta-tools/load-capability.js +82 -0
  124. package/dist/modules/auth/auth-header.middleware.d.ts +37 -0
  125. package/dist/modules/auth/auth-header.middleware.d.ts.map +1 -0
  126. package/dist/modules/auth/auth-header.middleware.js +115 -0
  127. package/dist/modules/auth/auth.module.d.ts +9 -0
  128. package/dist/modules/auth/auth.module.d.ts.map +1 -0
  129. package/dist/modules/auth/auth.module.js +27 -0
  130. package/dist/modules/auth/index.d.ts +3 -0
  131. package/dist/modules/auth/index.d.ts.map +1 -0
  132. package/dist/modules/auth/index.js +2 -0
  133. package/dist/modules/auth/validate-ucan-delegation.d.ts +31 -0
  134. package/dist/modules/auth/validate-ucan-delegation.d.ts.map +1 -0
  135. package/dist/modules/auth/validate-ucan-delegation.js +36 -0
  136. package/dist/modules/blob-store/blob-store.module.d.ts +10 -0
  137. package/dist/modules/blob-store/blob-store.module.d.ts.map +1 -0
  138. package/dist/modules/blob-store/blob-store.module.js +25 -0
  139. package/dist/modules/blob-store/blob-store.service.d.ts +66 -0
  140. package/dist/modules/blob-store/blob-store.service.d.ts.map +1 -0
  141. package/dist/modules/blob-store/blob-store.service.js +108 -0
  142. package/dist/modules/blob-store/index.d.ts +3 -0
  143. package/dist/modules/blob-store/index.d.ts.map +1 -0
  144. package/dist/modules/blob-store/index.js +2 -0
  145. package/dist/modules/health/health.controller.d.ts +18 -0
  146. package/dist/modules/health/health.controller.d.ts.map +1 -0
  147. package/dist/modules/health/health.controller.js +48 -0
  148. package/dist/modules/health/health.module.d.ts +8 -0
  149. package/dist/modules/health/health.module.d.ts.map +1 -0
  150. package/dist/modules/health/health.module.js +21 -0
  151. package/dist/modules/index.d.ts +8 -0
  152. package/dist/modules/index.d.ts.map +1 -0
  153. package/dist/modules/index.js +6 -0
  154. package/dist/modules/messages/__test-fixtures__/deps.d.ts +41 -0
  155. package/dist/modules/messages/__test-fixtures__/deps.d.ts.map +1 -0
  156. package/dist/modules/messages/__test-fixtures__/deps.js +73 -0
  157. package/dist/modules/messages/__test-fixtures__/fake-agent.d.ts +27 -0
  158. package/dist/modules/messages/__test-fixtures__/fake-agent.d.ts.map +1 -0
  159. package/dist/modules/messages/__test-fixtures__/fake-agent.js +41 -0
  160. package/dist/modules/messages/__test-fixtures__/fake-response.d.ts +29 -0
  161. package/dist/modules/messages/__test-fixtures__/fake-response.d.ts.map +1 -0
  162. package/dist/modules/messages/__test-fixtures__/fake-response.js +55 -0
  163. package/dist/modules/messages/agent-builder.d.ts +64 -0
  164. package/dist/modules/messages/agent-builder.d.ts.map +1 -0
  165. package/dist/modules/messages/agent-builder.js +219 -0
  166. package/dist/modules/messages/batch-invoker.d.ts +36 -0
  167. package/dist/modules/messages/batch-invoker.d.ts.map +1 -0
  168. package/dist/modules/messages/batch-invoker.js +58 -0
  169. package/dist/modules/messages/dto/list-messages.dto.d.ts +4 -0
  170. package/dist/modules/messages/dto/list-messages.dto.d.ts.map +1 -0
  171. package/dist/modules/messages/dto/list-messages.dto.js +17 -0
  172. package/dist/modules/messages/dto/send-message.dto.d.ts +73 -0
  173. package/dist/modules/messages/dto/send-message.dto.d.ts.map +1 -0
  174. package/dist/modules/messages/dto/send-message.dto.js +318 -0
  175. package/dist/modules/messages/file-processing-credit-sink.port.d.ts +20 -0
  176. package/dist/modules/messages/file-processing-credit-sink.port.d.ts.map +1 -0
  177. package/dist/modules/messages/file-processing-credit-sink.port.js +1 -0
  178. package/dist/modules/messages/file-processing.service.d.ts +195 -0
  179. package/dist/modules/messages/file-processing.service.d.ts.map +1 -0
  180. package/dist/modules/messages/file-processing.service.js +1278 -0
  181. package/dist/modules/messages/homeserver-cache.d.ts +11 -0
  182. package/dist/modules/messages/homeserver-cache.d.ts.map +1 -0
  183. package/dist/modules/messages/homeserver-cache.js +31 -0
  184. package/dist/modules/messages/matrix-listener-bridge.d.ts +63 -0
  185. package/dist/modules/messages/matrix-listener-bridge.d.ts.map +1 -0
  186. package/dist/modules/messages/matrix-listener-bridge.js +280 -0
  187. package/dist/modules/messages/messages.controller.d.ts +13 -0
  188. package/dist/modules/messages/messages.controller.d.ts.map +1 -0
  189. package/dist/modules/messages/messages.controller.js +95 -0
  190. package/dist/modules/messages/messages.module.d.ts +3 -0
  191. package/dist/modules/messages/messages.module.d.ts.map +1 -0
  192. package/dist/modules/messages/messages.module.js +75 -0
  193. package/dist/modules/messages/messages.service.d.ts +112 -0
  194. package/dist/modules/messages/messages.service.d.ts.map +1 -0
  195. package/dist/modules/messages/messages.service.js +279 -0
  196. package/dist/modules/messages/oracle-runtime-bundle.d.ts +38 -0
  197. package/dist/modules/messages/oracle-runtime-bundle.d.ts.map +1 -0
  198. package/dist/modules/messages/oracle-runtime-bundle.js +40 -0
  199. package/dist/modules/messages/post-message-syncer.d.ts +30 -0
  200. package/dist/modules/messages/post-message-syncer.d.ts.map +1 -0
  201. package/dist/modules/messages/post-message-syncer.js +73 -0
  202. package/dist/modules/messages/request-preparer.d.ts +53 -0
  203. package/dist/modules/messages/request-preparer.d.ts.map +1 -0
  204. package/dist/modules/messages/request-preparer.js +139 -0
  205. package/dist/modules/messages/sse-stream-runner.d.ts +73 -0
  206. package/dist/modules/messages/sse-stream-runner.d.ts.map +1 -0
  207. package/dist/modules/messages/sse-stream-runner.js +352 -0
  208. package/dist/modules/messages/sse.utils.d.ts +29 -0
  209. package/dist/modules/messages/sse.utils.d.ts.map +1 -0
  210. package/dist/modules/messages/sse.utils.js +77 -0
  211. package/dist/modules/messages/user-context-fetcher.d.ts +41 -0
  212. package/dist/modules/messages/user-context-fetcher.d.ts.map +1 -0
  213. package/dist/modules/messages/user-context-fetcher.js +117 -0
  214. package/dist/modules/secrets/index.d.ts +2 -0
  215. package/dist/modules/secrets/index.d.ts.map +1 -0
  216. package/dist/modules/secrets/index.js +1 -0
  217. package/dist/modules/secrets/secrets.service.d.ts +29 -0
  218. package/dist/modules/secrets/secrets.service.d.ts.map +1 -0
  219. package/dist/modules/secrets/secrets.service.js +107 -0
  220. package/dist/modules/sessions/dto/create-session.dto.d.ts +6 -0
  221. package/dist/modules/sessions/dto/create-session.dto.d.ts.map +1 -0
  222. package/dist/modules/sessions/dto/create-session.dto.js +5 -0
  223. package/dist/modules/sessions/dto/delete-session.dto.d.ts +6 -0
  224. package/dist/modules/sessions/dto/delete-session.dto.d.ts.map +1 -0
  225. package/dist/modules/sessions/dto/delete-session.dto.js +5 -0
  226. package/dist/modules/sessions/dto/list-sessions.dto.d.ts +7 -0
  227. package/dist/modules/sessions/dto/list-sessions.dto.d.ts.map +1 -0
  228. package/dist/modules/sessions/dto/list-sessions.dto.js +6 -0
  229. package/dist/modules/sessions/session-history-processor.service.d.ts +48 -0
  230. package/dist/modules/sessions/session-history-processor.service.d.ts.map +1 -0
  231. package/dist/modules/sessions/session-history-processor.service.js +254 -0
  232. package/dist/modules/sessions/sessions.controller.d.ts +13 -0
  233. package/dist/modules/sessions/sessions.controller.d.ts.map +1 -0
  234. package/dist/modules/sessions/sessions.controller.js +113 -0
  235. package/dist/modules/sessions/sessions.module.d.ts +3 -0
  236. package/dist/modules/sessions/sessions.module.d.ts.map +1 -0
  237. package/dist/modules/sessions/sessions.module.js +49 -0
  238. package/dist/modules/sessions/sessions.service.d.ts +23 -0
  239. package/dist/modules/sessions/sessions.service.d.ts.map +1 -0
  240. package/dist/modules/sessions/sessions.service.js +168 -0
  241. package/dist/modules/subscription/subscription.middleware.d.ts +37 -0
  242. package/dist/modules/subscription/subscription.middleware.d.ts.map +1 -0
  243. package/dist/modules/subscription/subscription.middleware.js +141 -0
  244. package/dist/modules/subscription/subscription.module.d.ts +12 -0
  245. package/dist/modules/subscription/subscription.module.d.ts.map +1 -0
  246. package/dist/modules/subscription/subscription.module.js +26 -0
  247. package/dist/modules/throttler/throttler.module.d.ts +11 -0
  248. package/dist/modules/throttler/throttler.module.d.ts.map +1 -0
  249. package/dist/modules/throttler/throttler.module.js +32 -0
  250. package/dist/modules/ucan/index.d.ts +7 -0
  251. package/dist/modules/ucan/index.d.ts.map +1 -0
  252. package/dist/modules/ucan/index.js +6 -0
  253. package/dist/modules/ucan/ucan.config.d.ts +87 -0
  254. package/dist/modules/ucan/ucan.config.d.ts.map +1 -0
  255. package/dist/modules/ucan/ucan.config.js +114 -0
  256. package/dist/modules/ucan/ucan.module.d.ts +29 -0
  257. package/dist/modules/ucan/ucan.module.d.ts.map +1 -0
  258. package/dist/modules/ucan/ucan.module.js +46 -0
  259. package/dist/modules/ucan/ucan.service.d.ts +129 -0
  260. package/dist/modules/ucan/ucan.service.d.ts.map +1 -0
  261. package/dist/modules/ucan/ucan.service.js +582 -0
  262. package/dist/modules/ws/emitter.d.ts +12 -0
  263. package/dist/modules/ws/emitter.d.ts.map +1 -0
  264. package/dist/modules/ws/emitter.js +12 -0
  265. package/dist/modules/ws/ws.gateway.d.ts +34 -0
  266. package/dist/modules/ws/ws.gateway.d.ts.map +1 -0
  267. package/dist/modules/ws/ws.gateway.js +241 -0
  268. package/dist/modules/ws/ws.module.d.ts +3 -0
  269. package/dist/modules/ws/ws.module.d.ts.map +1 -0
  270. package/dist/modules/ws/ws.module.js +21 -0
  271. package/dist/modules/ws/ws.service.d.ts +25 -0
  272. package/dist/modules/ws/ws.service.d.ts.map +1 -0
  273. package/dist/modules/ws/ws.service.js +113 -0
  274. package/dist/plugin-api/define-plugin.d.ts +19 -0
  275. package/dist/plugin-api/define-plugin.d.ts.map +1 -0
  276. package/dist/plugin-api/define-plugin.js +25 -0
  277. package/dist/plugin-api/oracle-plugin.d.ts +92 -0
  278. package/dist/plugin-api/oracle-plugin.d.ts.map +1 -0
  279. package/dist/plugin-api/oracle-plugin.js +15 -0
  280. package/dist/plugin-api/tool-helper.d.ts +33 -0
  281. package/dist/plugin-api/tool-helper.d.ts.map +1 -0
  282. package/dist/plugin-api/tool-helper.js +40 -0
  283. package/dist/plugin-api/types.d.ts +378 -0
  284. package/dist/plugin-api/types.d.ts.map +1 -0
  285. package/dist/plugin-api/types.js +1 -0
  286. package/dist/plugins/agui/agui-agent.d.ts +10 -0
  287. package/dist/plugins/agui/agui-agent.d.ts.map +1 -0
  288. package/dist/plugins/agui/agui-agent.js +110 -0
  289. package/dist/plugins/agui/agui.plugin.d.ts +16 -0
  290. package/dist/plugins/agui/agui.plugin.d.ts.map +1 -0
  291. package/dist/plugins/agui/agui.plugin.js +117 -0
  292. package/dist/plugins/agui/index.d.ts +3 -0
  293. package/dist/plugins/agui/index.d.ts.map +1 -0
  294. package/dist/plugins/agui/index.js +2 -0
  295. package/dist/plugins/composio/composio-tools.d.ts +59 -0
  296. package/dist/plugins/composio/composio-tools.d.ts.map +1 -0
  297. package/dist/plugins/composio/composio-tools.js +88 -0
  298. package/dist/plugins/composio/composio-ucan.d.ts +14 -0
  299. package/dist/plugins/composio/composio-ucan.d.ts.map +1 -0
  300. package/dist/plugins/composio/composio-ucan.js +32 -0
  301. package/dist/plugins/composio/composio.plugin.d.ts +45 -0
  302. package/dist/plugins/composio/composio.plugin.d.ts.map +1 -0
  303. package/dist/plugins/composio/composio.plugin.js +116 -0
  304. package/dist/plugins/composio/index.d.ts +4 -0
  305. package/dist/plugins/composio/index.d.ts.map +1 -0
  306. package/dist/plugins/composio/index.js +3 -0
  307. package/dist/plugins/credits/claim-processing.module.d.ts +28 -0
  308. package/dist/plugins/credits/claim-processing.module.d.ts.map +1 -0
  309. package/dist/plugins/credits/claim-processing.module.js +37 -0
  310. package/dist/plugins/credits/claim-processing.service.d.ts +92 -0
  311. package/dist/plugins/credits/claim-processing.service.d.ts.map +1 -0
  312. package/dist/plugins/credits/claim-processing.service.js +406 -0
  313. package/dist/plugins/credits/credits-middleware.d.ts +23 -0
  314. package/dist/plugins/credits/credits-middleware.d.ts.map +1 -0
  315. package/dist/plugins/credits/credits-middleware.js +154 -0
  316. package/dist/plugins/credits/credits.plugin.d.ts +55 -0
  317. package/dist/plugins/credits/credits.plugin.d.ts.map +1 -0
  318. package/dist/plugins/credits/credits.plugin.js +95 -0
  319. package/dist/plugins/credits/file-processing-sink.module.d.ts +18 -0
  320. package/dist/plugins/credits/file-processing-sink.module.d.ts.map +1 -0
  321. package/dist/plugins/credits/file-processing-sink.module.js +56 -0
  322. package/dist/plugins/credits/index.d.ts +8 -0
  323. package/dist/plugins/credits/index.d.ts.map +1 -0
  324. package/dist/plugins/credits/index.js +7 -0
  325. package/dist/plugins/credits/subscription-sink.module.d.ts +23 -0
  326. package/dist/plugins/credits/subscription-sink.module.d.ts.map +1 -0
  327. package/dist/plugins/credits/subscription-sink.module.js +54 -0
  328. package/dist/plugins/credits/token-limiter.d.ts +139 -0
  329. package/dist/plugins/credits/token-limiter.d.ts.map +1 -0
  330. package/dist/plugins/credits/token-limiter.js +302 -0
  331. package/dist/plugins/domain-indexer/domain-indexer-agent.d.ts +7 -0
  332. package/dist/plugins/domain-indexer/domain-indexer-agent.d.ts.map +1 -0
  333. package/dist/plugins/domain-indexer/domain-indexer-agent.js +48 -0
  334. package/dist/plugins/domain-indexer/domain-indexer-tools.d.ts +8 -0
  335. package/dist/plugins/domain-indexer/domain-indexer-tools.d.ts.map +1 -0
  336. package/dist/plugins/domain-indexer/domain-indexer-tools.js +194 -0
  337. package/dist/plugins/domain-indexer/domain-indexer.plugin.d.ts +20 -0
  338. package/dist/plugins/domain-indexer/domain-indexer.plugin.d.ts.map +1 -0
  339. package/dist/plugins/domain-indexer/domain-indexer.plugin.js +78 -0
  340. package/dist/plugins/domain-indexer/index.d.ts +2 -0
  341. package/dist/plugins/domain-indexer/index.d.ts.map +1 -0
  342. package/dist/plugins/domain-indexer/index.js +1 -0
  343. package/dist/plugins/editor/apply-sandbox-output.d.ts +33 -0
  344. package/dist/plugins/editor/apply-sandbox-output.d.ts.map +1 -0
  345. package/dist/plugins/editor/apply-sandbox-output.js +302 -0
  346. package/dist/plugins/editor/block-actions.d.ts +84 -0
  347. package/dist/plugins/editor/block-actions.d.ts.map +1 -0
  348. package/dist/plugins/editor/block-actions.js +471 -0
  349. package/dist/plugins/editor/blocknote-helper.d.ts +147 -0
  350. package/dist/plugins/editor/blocknote-helper.d.ts.map +1 -0
  351. package/dist/plugins/editor/blocknote-helper.js +542 -0
  352. package/dist/plugins/editor/blocknote-tools.d.ts +364 -0
  353. package/dist/plugins/editor/blocknote-tools.d.ts.map +1 -0
  354. package/dist/plugins/editor/blocknote-tools.js +2123 -0
  355. package/dist/plugins/editor/editor-agent.d.ts +47 -0
  356. package/dist/plugins/editor/editor-agent.d.ts.map +1 -0
  357. package/dist/plugins/editor/editor-agent.js +158 -0
  358. package/dist/plugins/editor/editor-mx.d.ts +56 -0
  359. package/dist/plugins/editor/editor-mx.d.ts.map +1 -0
  360. package/dist/plugins/editor/editor-mx.js +100 -0
  361. package/dist/plugins/editor/editor.plugin.d.ts +32 -0
  362. package/dist/plugins/editor/editor.plugin.d.ts.map +1 -0
  363. package/dist/plugins/editor/editor.plugin.js +189 -0
  364. package/dist/plugins/editor/index.d.ts +6 -0
  365. package/dist/plugins/editor/index.d.ts.map +1 -0
  366. package/dist/plugins/editor/index.js +5 -0
  367. package/dist/plugins/editor/mint-invocation-tool.d.ts +72 -0
  368. package/dist/plugins/editor/mint-invocation-tool.d.ts.map +1 -0
  369. package/dist/plugins/editor/mint-invocation-tool.js +173 -0
  370. package/dist/plugins/editor/page-functions.d.ts +100 -0
  371. package/dist/plugins/editor/page-functions.d.ts.map +1 -0
  372. package/dist/plugins/editor/page-functions.js +317 -0
  373. package/dist/plugins/editor/page-tools.d.ts +75 -0
  374. package/dist/plugins/editor/page-tools.d.ts.map +1 -0
  375. package/dist/plugins/editor/page-tools.js +238 -0
  376. package/dist/plugins/editor/prompts.d.ts +22 -0
  377. package/dist/plugins/editor/prompts.d.ts.map +1 -0
  378. package/dist/plugins/editor/prompts.js +451 -0
  379. package/dist/plugins/editor/provider.d.ts +101 -0
  380. package/dist/plugins/editor/provider.d.ts.map +1 -0
  381. package/dist/plugins/editor/provider.js +249 -0
  382. package/dist/plugins/editor/standalone-editor-tool.d.ts +17 -0
  383. package/dist/plugins/editor/standalone-editor-tool.d.ts.map +1 -0
  384. package/dist/plugins/editor/standalone-editor-tool.js +136 -0
  385. package/dist/plugins/editor/survey-helpers.d.ts +112 -0
  386. package/dist/plugins/editor/survey-helpers.d.ts.map +1 -0
  387. package/dist/plugins/editor/survey-helpers.js +358 -0
  388. package/dist/plugins/firecrawl/firecrawl-agent.d.ts +7 -0
  389. package/dist/plugins/firecrawl/firecrawl-agent.d.ts.map +1 -0
  390. package/dist/plugins/firecrawl/firecrawl-agent.js +84 -0
  391. package/dist/plugins/firecrawl/firecrawl-tools.d.ts +37 -0
  392. package/dist/plugins/firecrawl/firecrawl-tools.d.ts.map +1 -0
  393. package/dist/plugins/firecrawl/firecrawl-tools.js +129 -0
  394. package/dist/plugins/firecrawl/firecrawl.plugin.d.ts +32 -0
  395. package/dist/plugins/firecrawl/firecrawl.plugin.d.ts.map +1 -0
  396. package/dist/plugins/firecrawl/firecrawl.plugin.js +69 -0
  397. package/dist/plugins/firecrawl/index.d.ts +3 -0
  398. package/dist/plugins/firecrawl/index.d.ts.map +1 -0
  399. package/dist/plugins/firecrawl/index.js +2 -0
  400. package/dist/plugins/index.d.ts +47 -0
  401. package/dist/plugins/index.d.ts.map +1 -0
  402. package/dist/plugins/index.js +69 -0
  403. package/dist/plugins/matrix-group-chats/channel-memory.module.d.ts +7 -0
  404. package/dist/plugins/matrix-group-chats/channel-memory.module.d.ts.map +1 -0
  405. package/dist/plugins/matrix-group-chats/channel-memory.module.js +22 -0
  406. package/dist/plugins/matrix-group-chats/channel-memory.repo.d.ts +62 -0
  407. package/dist/plugins/matrix-group-chats/channel-memory.repo.d.ts.map +1 -0
  408. package/dist/plugins/matrix-group-chats/channel-memory.repo.js +311 -0
  409. package/dist/plugins/matrix-group-chats/channel-memory.service.d.ts +89 -0
  410. package/dist/plugins/matrix-group-chats/channel-memory.service.d.ts.map +1 -0
  411. package/dist/plugins/matrix-group-chats/channel-memory.service.js +565 -0
  412. package/dist/plugins/matrix-group-chats/channel-memory.summarizer.d.ts +18 -0
  413. package/dist/plugins/matrix-group-chats/channel-memory.summarizer.d.ts.map +1 -0
  414. package/dist/plugins/matrix-group-chats/channel-memory.summarizer.js +128 -0
  415. package/dist/plugins/matrix-group-chats/channel-memory.types.d.ts +50 -0
  416. package/dist/plugins/matrix-group-chats/channel-memory.types.d.ts.map +1 -0
  417. package/dist/plugins/matrix-group-chats/channel-memory.types.js +2 -0
  418. package/dist/plugins/matrix-group-chats/guard.d.ts +83 -0
  419. package/dist/plugins/matrix-group-chats/guard.d.ts.map +1 -0
  420. package/dist/plugins/matrix-group-chats/guard.js +138 -0
  421. package/dist/plugins/matrix-group-chats/index.d.ts +59 -0
  422. package/dist/plugins/matrix-group-chats/index.d.ts.map +1 -0
  423. package/dist/plugins/matrix-group-chats/index.js +140 -0
  424. package/dist/plugins/matrix-group-chats/middleware.d.ts +31 -0
  425. package/dist/plugins/matrix-group-chats/middleware.d.ts.map +1 -0
  426. package/dist/plugins/matrix-group-chats/middleware.js +186 -0
  427. package/dist/plugins/matrix-group-chats/power-levels.d.ts +15 -0
  428. package/dist/plugins/matrix-group-chats/power-levels.d.ts.map +1 -0
  429. package/dist/plugins/matrix-group-chats/power-levels.js +36 -0
  430. package/dist/plugins/matrix-group-chats/room-info.d.ts +23 -0
  431. package/dist/plugins/matrix-group-chats/room-info.d.ts.map +1 -0
  432. package/dist/plugins/matrix-group-chats/room-info.js +34 -0
  433. package/dist/plugins/matrix-group-chats/tools.d.ts +8 -0
  434. package/dist/plugins/matrix-group-chats/tools.d.ts.map +1 -0
  435. package/dist/plugins/matrix-group-chats/tools.js +154 -0
  436. package/dist/plugins/memory/index.d.ts +5 -0
  437. package/dist/plugins/memory/index.d.ts.map +1 -0
  438. package/dist/plugins/memory/index.js +3 -0
  439. package/dist/plugins/memory/memory-tools.d.ts +52 -0
  440. package/dist/plugins/memory/memory-tools.d.ts.map +1 -0
  441. package/dist/plugins/memory/memory-tools.js +88 -0
  442. package/dist/plugins/memory/memory-ucan.d.ts +17 -0
  443. package/dist/plugins/memory/memory-ucan.d.ts.map +1 -0
  444. package/dist/plugins/memory/memory-ucan.js +43 -0
  445. package/dist/plugins/memory/memory.plugin.d.ts +51 -0
  446. package/dist/plugins/memory/memory.plugin.d.ts.map +1 -0
  447. package/dist/plugins/memory/memory.plugin.js +76 -0
  448. package/dist/plugins/memory/types.d.ts +75 -0
  449. package/dist/plugins/memory/types.d.ts.map +1 -0
  450. package/dist/plugins/memory/types.js +2 -0
  451. package/dist/plugins/portal/index.d.ts +3 -0
  452. package/dist/plugins/portal/index.d.ts.map +1 -0
  453. package/dist/plugins/portal/index.js +2 -0
  454. package/dist/plugins/portal/portal-agent.d.ts +9 -0
  455. package/dist/plugins/portal/portal-agent.d.ts.map +1 -0
  456. package/dist/plugins/portal/portal-agent.js +70 -0
  457. package/dist/plugins/portal/portal.plugin.d.ts +16 -0
  458. package/dist/plugins/portal/portal.plugin.d.ts.map +1 -0
  459. package/dist/plugins/portal/portal.plugin.js +115 -0
  460. package/dist/plugins/sandbox/index.d.ts +3 -0
  461. package/dist/plugins/sandbox/index.d.ts.map +1 -0
  462. package/dist/plugins/sandbox/index.js +2 -0
  463. package/dist/plugins/sandbox/sandbox-mcp.d.ts +46 -0
  464. package/dist/plugins/sandbox/sandbox-mcp.d.ts.map +1 -0
  465. package/dist/plugins/sandbox/sandbox-mcp.js +80 -0
  466. package/dist/plugins/sandbox/sandbox-write-blob.d.ts +22 -0
  467. package/dist/plugins/sandbox/sandbox-write-blob.d.ts.map +1 -0
  468. package/dist/plugins/sandbox/sandbox-write-blob.js +80 -0
  469. package/dist/plugins/sandbox/sandbox.plugin.d.ts +80 -0
  470. package/dist/plugins/sandbox/sandbox.plugin.d.ts.map +1 -0
  471. package/dist/plugins/sandbox/sandbox.plugin.js +204 -0
  472. package/dist/plugins/skills/index.d.ts +3 -0
  473. package/dist/plugins/skills/index.d.ts.map +1 -0
  474. package/dist/plugins/skills/index.js +2 -0
  475. package/dist/plugins/skills/skills-tools.d.ts +20 -0
  476. package/dist/plugins/skills/skills-tools.d.ts.map +1 -0
  477. package/dist/plugins/skills/skills-tools.js +204 -0
  478. package/dist/plugins/skills/skills-ucan.d.ts +24 -0
  479. package/dist/plugins/skills/skills-ucan.d.ts.map +1 -0
  480. package/dist/plugins/skills/skills-ucan.js +28 -0
  481. package/dist/plugins/skills/skills.plugin.d.ts +37 -0
  482. package/dist/plugins/skills/skills.plugin.d.ts.map +1 -0
  483. package/dist/plugins/skills/skills.plugin.js +82 -0
  484. package/dist/plugins/slack/index.d.ts +4 -0
  485. package/dist/plugins/slack/index.d.ts.map +1 -0
  486. package/dist/plugins/slack/index.js +3 -0
  487. package/dist/plugins/slack/slack.module.d.ts +9 -0
  488. package/dist/plugins/slack/slack.module.d.ts.map +1 -0
  489. package/dist/plugins/slack/slack.module.js +27 -0
  490. package/dist/plugins/slack/slack.plugin.d.ts +30 -0
  491. package/dist/plugins/slack/slack.plugin.d.ts.map +1 -0
  492. package/dist/plugins/slack/slack.plugin.js +40 -0
  493. package/dist/plugins/slack/slack.service.d.ts +32 -0
  494. package/dist/plugins/slack/slack.service.d.ts.map +1 -0
  495. package/dist/plugins/slack/slack.service.js +157 -0
  496. package/dist/plugins/user-preferences/index.d.ts +4 -0
  497. package/dist/plugins/user-preferences/index.d.ts.map +1 -0
  498. package/dist/plugins/user-preferences/index.js +3 -0
  499. package/dist/plugins/user-preferences/service/user-preferences.service.d.ts +61 -0
  500. package/dist/plugins/user-preferences/service/user-preferences.service.d.ts.map +1 -0
  501. package/dist/plugins/user-preferences/service/user-preferences.service.js +105 -0
  502. package/dist/plugins/user-preferences/user-preferences-http.module.d.ts +10 -0
  503. package/dist/plugins/user-preferences/user-preferences-http.module.d.ts.map +1 -0
  504. package/dist/plugins/user-preferences/user-preferences-http.module.js +23 -0
  505. package/dist/plugins/user-preferences/user-preferences-tool.d.ts +22 -0
  506. package/dist/plugins/user-preferences/user-preferences-tool.d.ts.map +1 -0
  507. package/dist/plugins/user-preferences/user-preferences-tool.js +103 -0
  508. package/dist/plugins/user-preferences/user-preferences.controller.d.ts +18 -0
  509. package/dist/plugins/user-preferences/user-preferences.controller.d.ts.map +1 -0
  510. package/dist/plugins/user-preferences/user-preferences.controller.js +72 -0
  511. package/dist/plugins/user-preferences/user-preferences.plugin.d.ts +27 -0
  512. package/dist/plugins/user-preferences/user-preferences.plugin.d.ts.map +1 -0
  513. package/dist/plugins/user-preferences/user-preferences.plugin.js +66 -0
  514. package/dist/registries/config-schema-registry.d.ts +24 -0
  515. package/dist/registries/config-schema-registry.d.ts.map +1 -0
  516. package/dist/registries/config-schema-registry.js +27 -0
  517. package/dist/registries/index.d.ts +13 -0
  518. package/dist/registries/index.d.ts.map +1 -0
  519. package/dist/registries/index.js +6 -0
  520. package/dist/registries/manifest-registry.d.ts +49 -0
  521. package/dist/registries/manifest-registry.d.ts.map +1 -0
  522. package/dist/registries/manifest-registry.js +53 -0
  523. package/dist/registries/middleware-registry.d.ts +41 -0
  524. package/dist/registries/middleware-registry.d.ts.map +1 -0
  525. package/dist/registries/middleware-registry.js +52 -0
  526. package/dist/registries/shared-state-registry.d.ts +41 -0
  527. package/dist/registries/shared-state-registry.d.ts.map +1 -0
  528. package/dist/registries/shared-state-registry.js +65 -0
  529. package/dist/registries/subagent-registry.d.ts +55 -0
  530. package/dist/registries/subagent-registry.d.ts.map +1 -0
  531. package/dist/registries/subagent-registry.js +106 -0
  532. package/dist/registries/test-fixtures.d.ts +47 -0
  533. package/dist/registries/test-fixtures.d.ts.map +1 -0
  534. package/dist/registries/test-fixtures.js +168 -0
  535. package/dist/registries/tool-registry.d.ts +74 -0
  536. package/dist/registries/tool-registry.d.ts.map +1 -0
  537. package/dist/registries/tool-registry.js +130 -0
  538. package/dist/runtime-context/ambient.d.ts +118 -0
  539. package/dist/runtime-context/ambient.d.ts.map +1 -0
  540. package/dist/runtime-context/ambient.js +1 -0
  541. package/dist/runtime-context/build-plugin.d.ts +20 -0
  542. package/dist/runtime-context/build-plugin.d.ts.map +1 -0
  543. package/dist/runtime-context/build-plugin.js +16 -0
  544. package/dist/runtime-context/build-runtime.d.ts +60 -0
  545. package/dist/runtime-context/build-runtime.d.ts.map +1 -0
  546. package/dist/runtime-context/build-runtime.js +81 -0
  547. package/dist/testing/create-test-runtime.d.ts +95 -0
  548. package/dist/testing/create-test-runtime.d.ts.map +1 -0
  549. package/dist/testing/create-test-runtime.js +302 -0
  550. package/dist/testing/index.d.ts +5 -0
  551. package/dist/testing/index.d.ts.map +1 -0
  552. package/dist/testing/index.js +5 -0
  553. package/dist/testing/integration/chat-client.d.ts +143 -0
  554. package/dist/testing/integration/chat-client.d.ts.map +1 -0
  555. package/dist/testing/integration/chat-client.js +238 -0
  556. package/dist/testing/integration/harness.d.ts +189 -0
  557. package/dist/testing/integration/harness.d.ts.map +1 -0
  558. package/dist/testing/integration/harness.js +461 -0
  559. package/dist/testing/integration/index.d.ts +14 -0
  560. package/dist/testing/integration/index.d.ts.map +1 -0
  561. package/dist/testing/integration/index.js +18 -0
  562. package/dist/testing/integration/setup.d.ts +2 -0
  563. package/dist/testing/integration/setup.d.ts.map +1 -0
  564. package/dist/testing/integration/setup.js +41 -0
  565. package/dist/testing/integration/sse-parser.d.ts +99 -0
  566. package/dist/testing/integration/sse-parser.d.ts.map +1 -0
  567. package/dist/testing/integration/sse-parser.js +125 -0
  568. package/dist/testing/integration/ucan.d.ts +74 -0
  569. package/dist/testing/integration/ucan.d.ts.map +1 -0
  570. package/dist/testing/integration/ucan.js +95 -0
  571. package/dist/testing/integration/wait-for-matrix-loaded.d.ts +19 -0
  572. package/dist/testing/integration/wait-for-matrix-loaded.d.ts.map +1 -0
  573. package/dist/testing/integration/wait-for-matrix-loaded.js +31 -0
  574. package/dist/testing/mocks.d.ts +64 -0
  575. package/dist/testing/mocks.d.ts.map +1 -0
  576. package/dist/testing/mocks.js +141 -0
  577. package/dist/testing/nest-doubles.d.ts +10 -0
  578. package/dist/testing/nest-doubles.d.ts.map +1 -0
  579. package/dist/testing/nest-doubles.js +19 -0
  580. package/dist/utils/emoji.d.ts +3 -0
  581. package/dist/utils/emoji.d.ts.map +1 -0
  582. package/dist/utils/emoji.js +36 -0
  583. package/package.json +102 -0
@@ -0,0 +1,2123 @@
1
+ /* eslint-disable no-console */
2
+ /**
3
+ * LangChain Tools for BlockNote Y.js Editing
4
+ */
5
+ import { tool } from '@langchain/core/tools';
6
+ import * as z from 'zod';
7
+ import { Logger } from '@nestjs/common';
8
+ import { randomUUID } from 'node:crypto';
9
+ import * as Y from 'yjs';
10
+ import { appendBlock, collectAllBlocks, deleteBlock, editBlock, evaluateBlockConditions, extractBlockProperties, findBlockById, getBlockDetail, readAuditTrailForBlock, readDelegations, readFlowMetadata, readFlowNodes, readInvocations, readRuntimeState, resolveBlockReferences, simplifyBlockForAgent, updateRuntimeState, } from './blocknote-helper.js';
11
+ import { emojify, unemojify } from '../../utils/emoji.js';
12
+ import { findAndReplaceInDoc, insertBlock, moveBlock, } from './block-actions.js';
13
+ import { MatrixProviderManager } from './provider.js';
14
+ import { extractSurveyQuestions, getMissingRequiredFields, getVisibleQuestions, validateAnswersAgainstSchema, } from './survey-helpers.js';
15
+ import { getAction, getAllActions, buildFlowNodeFromBlock, executeNode, } from '@ixo/editor/core';
16
+ /**
17
+ * Build the default `BLOCKNOTE_TOOLS_CONFIG` from a runtime config bag. The
18
+ * editor plugin builds this once at boot from its declared env vars and
19
+ * threads it through every tool factory, replacing the legacy module-level
20
+ * `getConfig()` call.
21
+ */
22
+ export function buildBlocknoteToolsConfig(matrix) {
23
+ return {
24
+ matrix: {
25
+ baseUrl: matrix.baseUrl,
26
+ accessToken: matrix.accessToken,
27
+ userId: matrix.userId,
28
+ initialSyncTimeoutMs: 30_000,
29
+ },
30
+ provider: {
31
+ docName: 'document',
32
+ enableAwareness: false,
33
+ retryAttempts: 3,
34
+ retryDelayMs: 5_000,
35
+ },
36
+ blocknote: {
37
+ defaultBlockId: undefined,
38
+ blockNamespace: undefined,
39
+ mutableAttributeKeys: [],
40
+ },
41
+ };
42
+ }
43
+ /**
44
+ * Track active provider managers for cleanup
45
+ */
46
+ const logger = new Logger('BlocknoteTools');
47
+ // ── Emoji Helpers ─────────────────────────────────────────────────────
48
+ /**
49
+ * Checks whether a string contains emoji shortcodes (e.g. `:tada:`) or
50
+ * actual emoji characters. Returns true if either form is present.
51
+ */
52
+ function textContainsEmoji(text) {
53
+ return /:[a-z0-9_+-]+:/i.test(text) || text !== unemojify(text);
54
+ }
55
+ /**
56
+ * Performs a case-insensitive substring match that handles emoji/shortcode
57
+ * equivalence. The search term and the target text are compared in BOTH
58
+ * their emojified (🎉) and unemojified (:tada:) forms so that the agent
59
+ * can search using either representation.
60
+ */
61
+ function emojiAwareIncludes(text, search) {
62
+ const textLower = text.toLowerCase();
63
+ const searchLower = search.toLowerCase();
64
+ // Fast path — direct match
65
+ if (textLower.includes(searchLower))
66
+ return true;
67
+ // Normalise both sides to emoji characters and compare
68
+ const textEmoji = emojify(textLower);
69
+ const searchEmoji = emojify(searchLower);
70
+ if (textEmoji.includes(searchEmoji))
71
+ return true;
72
+ // Normalise both sides to shortcodes and compare
73
+ const textCodes = unemojify(textLower);
74
+ const searchCodes = unemojify(searchLower);
75
+ if (textCodes.includes(searchCodes))
76
+ return true;
77
+ return false;
78
+ }
79
+ // ── DID Helpers ───────────────────────────────────────────────────────
80
+ /**
81
+ * Extracts a valid DID from a Matrix user ID.
82
+ * Matrix format: @did-ixo-ixo1abc123def:mx.server.com
83
+ * Result: did:ixo:ixo1abc123def
84
+ *
85
+ * The localpart encodes the DID with hyphens instead of colons.
86
+ */
87
+ function matrixUserIdToDid(matrixUserId) {
88
+ // Strip leading @ and remove homeserver (:server.com)
89
+ const localpart = matrixUserId.replace(/^@/, '').replace(/:.*$/, '');
90
+ // Convert hyphens back to colons: did-ixo-ixo1abc → did:ixo:ixo1abc
91
+ // Only replace the first two hyphens (did-method-identifier)
92
+ const parts = localpart.split('-');
93
+ if (parts.length >= 3 && parts[0] === 'did') {
94
+ return `${parts[0]}:${parts[1]}:${parts.slice(2).join('-')}`;
95
+ }
96
+ // Fallback: return as-is if it doesn't match expected pattern
97
+ return localpart;
98
+ }
99
+ // ── Flow Engine Helpers ───────────────────────────────────────────────
100
+ /**
101
+ * Creates a FlowRuntimeStateManager backed by a Y.Doc's 'runtime' map.
102
+ * Mirrors the pattern from the editor's runtime.ts.
103
+ * Wraps mutations in doc.transact for reliable CRDT sync.
104
+ */
105
+ function createYDocRuntimeManager(doc) {
106
+ const map = doc.getMap('runtime');
107
+ return {
108
+ get: (nodeId) => {
109
+ const stored = map.get(nodeId);
110
+ if (!stored || typeof stored !== 'object')
111
+ return {};
112
+ return { ...stored };
113
+ },
114
+ update: (nodeId, updates) => {
115
+ doc.transact(() => {
116
+ const current = map.get(nodeId);
117
+ const existing = current && typeof current === 'object'
118
+ ? { ...current }
119
+ : {};
120
+ map.set(nodeId, { ...existing, ...updates });
121
+ }, 'oracle-runtime-update');
122
+ },
123
+ };
124
+ }
125
+ /**
126
+ * Oracle-side ActionServices — MVP supports HTTP only.
127
+ * Additional services (email, notify) can be wired up when the oracle has those capabilities.
128
+ */
129
+ const oracleActionServices = {
130
+ http: {
131
+ request: async (params) => {
132
+ const { url, method, headers = {}, body } = params;
133
+ const res = await fetch(url, {
134
+ method,
135
+ headers: { 'Content-Type': 'application/json', ...headers },
136
+ ...(body !== undefined && { body: JSON.stringify(body) }),
137
+ });
138
+ const data = await res.text();
139
+ let parsed;
140
+ try {
141
+ parsed = JSON.parse(data);
142
+ }
143
+ catch {
144
+ parsed = data;
145
+ }
146
+ const responseHeaders = {};
147
+ res.headers.forEach((v, k) => {
148
+ responseHeaders[k] = v;
149
+ });
150
+ return { status: res.status, headers: responseHeaders, data: parsed };
151
+ },
152
+ },
153
+ };
154
+ /**
155
+ * Creates BlockNote tools that use a shared Matrix client
156
+ *
157
+ * @param matrixClient - The singleton Matrix client (already synced)
158
+ * @param config - Configuration for the provider and room
159
+ * @param readOnly - If true, only returns read-only tools (list_blocks). Write tools are disabled but code is preserved.
160
+ */
161
+ export const createBlocknoteTools = async (matrixClient, config, readOnly = false) => {
162
+ logger.log(`🔧 Creating BlockNote tools with Matrix client: ${matrixClient.getUserId()}`);
163
+ logger.log(`🔧 Target room: ${JSON.stringify(config.matrix.room)}`);
164
+ let roomId;
165
+ if (config.matrix.room.type === 'id') {
166
+ roomId = config.matrix.room.value;
167
+ }
168
+ else {
169
+ // Resolve room alias to room ID
170
+ const ret = await matrixClient.getRoomIdForAlias(config.matrix.room.value);
171
+ roomId = ret.room_id;
172
+ }
173
+ // ============================================================================
174
+ // Tool 1: List Blocks
175
+ // ============================================================================
176
+ /**
177
+ * Lists all blocks in the BlockNote document
178
+ *
179
+ * Returns detailed information about each block including:
180
+ * - Block ID
181
+ * - Block type (paragraph, proposal, checkbox, etc.)
182
+ * - All attributes/properties
183
+ * - Text content
184
+ * - Nested structure
185
+ */
186
+ const listBlocksTool = tool(async ({ includeText = true, blockType = null, start = 0, end = 10 }) => {
187
+ logger.log('📋 list_blocks tool invoked');
188
+ // Use the shared Matrix client (already synced)
189
+ const providerManager = new MatrixProviderManager(matrixClient, config);
190
+ try {
191
+ const { doc } = await providerManager.init();
192
+ const isInRoom = await checkIfInRoomAndJoinPublicRoom(matrixClient, roomId);
193
+ if (!isInRoom) {
194
+ return JSON.stringify({
195
+ success: false,
196
+ error: `Companion is not in the room ${roomId}, please invite companion to the room. companion user id: ${matrixClient.getUserId()}`,
197
+ });
198
+ }
199
+ // Get the document fragment
200
+ const fragment = doc.getXmlFragment('document');
201
+ // Collect all blocks using the working CLI logic
202
+ const blocks = collectAllBlocks(fragment, includeText);
203
+ // Filter by block type if specified
204
+ const filteredBlocks = blockType
205
+ ? blocks.filter((b) => b.blockType === blockType)
206
+ : blocks;
207
+ // Add position index for agent awareness of document order
208
+ const indexedBlocks = filteredBlocks.map((b, i) => ({
209
+ position: i,
210
+ ...b,
211
+ }));
212
+ // Apply pagination slice
213
+ const paginatedBlocks = indexedBlocks.slice(start, end);
214
+ return JSON.stringify({
215
+ success: true,
216
+ roomId,
217
+ total: indexedBlocks.length,
218
+ count: paginatedBlocks.length,
219
+ start,
220
+ end,
221
+ blocks: paginatedBlocks,
222
+ }, null, 2);
223
+ }
224
+ catch (error) {
225
+ return JSON.stringify({
226
+ success: false,
227
+ error: error instanceof Error ? error.message : String(error),
228
+ });
229
+ }
230
+ finally {
231
+ // Schedule cleanup after delay to allow Y.Doc to finish syncing
232
+ await providerManager.dispose();
233
+ }
234
+ }, {
235
+ name: 'list_blocks',
236
+ description: `Lists all blocks in the BlockNote document with their complete structure and UUIDs.
237
+
238
+ **⚠️ CRITICAL: Always call this tool FIRST before any edit operation to get valid UUID block IDs.**
239
+
240
+ Use this tool to:
241
+ - Get exact UUIDs needed for editing (UUIDs are like: 550e8400-e29b-41d4-a716-446655440000)
242
+ - View all blocks in the document
243
+ - Filter blocks by type
244
+ - Check current block properties and state
245
+ - Understand document structure
246
+
247
+ **Parameter Examples:**
248
+
249
+ List all blocks with text:
250
+ \`\`\`json
251
+ {"includeText": true}
252
+ \`\`\`
253
+
254
+ List only proposal blocks:
255
+ \`\`\`json
256
+ {"includeText": true, "blockType": "proposal"}
257
+ \`\`\`
258
+
259
+ List blocks without text content (faster):
260
+ \`\`\`json
261
+ {"includeText": false}
262
+ \`\`\`
263
+
264
+ List all blocks (override default pagination):
265
+ \`\`\`json
266
+ {"includeText": true, "start": 0, "end": 9999}
267
+ \`\`\`
268
+
269
+ List blocks 10-20:
270
+ \`\`\`json
271
+ {"start": 10, "end": 20}
272
+ \`\`\`
273
+
274
+ **Note:** By default, only the first 10 blocks are returned. Use start/end to paginate through larger documents. Check "total" in the response to see how many blocks exist.
275
+
276
+ **Returns clean JSON like:**
277
+ \`\`\`json
278
+ {
279
+ "success": true,
280
+ "total": 25,
281
+ "count": 3,
282
+ "blocks": [
283
+ {
284
+ "id": "550e8400-e29b-41d4-a716-446655440000",
285
+ "type": "proposal",
286
+ "properties": {
287
+ "status": "draft",
288
+ "title": "My Proposal",
289
+ "description": "Proposal details",
290
+ "icon": "square-check"
291
+ },
292
+ "text": "Optional text content"
293
+ }
294
+ ]
295
+ }
296
+ \`\`\`
297
+
298
+ **Block types available:**
299
+ - paragraph: Simple text
300
+ - proposal: Blockchain proposals (status: draft/open/passed/rejected/executed/closed/execution_failed/veto_timelock)
301
+ - checkbox: Interactive checkboxes
302
+ - apiRequest: API calls (GET/POST/PUT/DELETE)
303
+ - list: Data lists
304
+ - domainCreator: Survey forms with surveySchema and answers (use read_survey, fill_survey_answers, validate_survey_answers tools)
305
+
306
+ **Note for domainCreator blocks:**
307
+ - surveySchema and answers are automatically parsed as structured JSON
308
+ - Use read_survey tool for detailed survey information
309
+ - Use fill_survey_answers to update answers
310
+ - Use validate_survey_answers to check completeness
311
+
312
+ **Important:** Block IDs are UUIDs - never guess them. Always extract exact IDs from this tool's response before calling edit_block.`,
313
+ schema: z.object({
314
+ includeText: z
315
+ .boolean()
316
+ .optional()
317
+ .default(true)
318
+ .describe('Whether to include text content in the response'),
319
+ blockType: z
320
+ .string()
321
+ .optional()
322
+ .nullable()
323
+ .describe('Optional: filter by block type (paragraph, proposal, checkbox, apiRequest, list, etc.)'),
324
+ start: z
325
+ .number()
326
+ .int()
327
+ .optional()
328
+ .default(0)
329
+ .describe('Starting index (0-based) for pagination. Defaults to 0.'),
330
+ end: z
331
+ .number()
332
+ .int()
333
+ .optional()
334
+ .default(10)
335
+ .describe('Ending index (exclusive) for pagination. Defaults to 10.'),
336
+ }),
337
+ });
338
+ // ============================================================================
339
+ // Tool 2: Edit Block
340
+ // ============================================================================
341
+ /**
342
+ * Edits an existing block's properties
343
+ *
344
+ * Uses the production-tested editBlock helper from blockActions.ts
345
+ * which includes:
346
+ * - Dual-storage pattern (attrs.props + direct attributes)
347
+ * - Proper attribute merging
348
+ * - Text update handling
349
+ * - Consistent with CLI edit-block command
350
+ *
351
+ * Can update:
352
+ * - Block attributes (status, title, description, etc.)
353
+ * - Text content
354
+ * - Remove specific attributes
355
+ *
356
+ * Changes are synced to all connected clients via Matrix CRDT
357
+ */
358
+ const editBlockTool = tool(async ({ blockId, updates, removeAttributes = [], text = null, runtimeUpdates = undefined, }) => {
359
+ Logger.log(`✏️ edit_block tool invoked for block: ${blockId}`);
360
+ const isInRoom = await checkIfInRoomAndJoinPublicRoom(matrixClient, roomId);
361
+ if (!isInRoom) {
362
+ return JSON.stringify({
363
+ success: false,
364
+ error: `Companion is not in the room ${roomId}, please invite companion to the room. companion user id: ${matrixClient.getUserId()}`,
365
+ });
366
+ }
367
+ // Use the shared Matrix client (already synced)
368
+ const providerManager = new MatrixProviderManager(matrixClient, config);
369
+ try {
370
+ const { doc } = await providerManager.init();
371
+ // Guard: reject runtimeUpdates on action blocks in flow mode — use execute_action instead
372
+ if (runtimeUpdates &&
373
+ typeof runtimeUpdates === 'object' &&
374
+ Object.keys(runtimeUpdates).length > 0) {
375
+ const blockDetail = getBlockDetail(doc, blockId, false);
376
+ const blockProps = blockDetail
377
+ ? extractBlockProperties(blockDetail)
378
+ : {};
379
+ const hasActionType = blockProps.actionType !== undefined;
380
+ if (hasActionType) {
381
+ const flowMeta = readFlowMetadata(doc);
382
+ const isFlow = flowMeta['_type'] === 'ixo.flow.crdt';
383
+ if (isFlow) {
384
+ return JSON.stringify({
385
+ success: false,
386
+ blockId,
387
+ error: `Cannot apply runtimeUpdates directly to action block "${blockId}" in a flow document. ` +
388
+ `Use the execute_action tool instead — it runs the action through the flow engine ` +
389
+ `(activation → authorization → execution → runtime state update) for a proper audit trail.`,
390
+ });
391
+ }
392
+ }
393
+ }
394
+ // Snapshot before changes for diff
395
+ const beforeBlock = getBlockDetail(doc, blockId, true);
396
+ const beforeProps = beforeBlock
397
+ ? extractBlockProperties(beforeBlock)
398
+ : {};
399
+ const beforeText = beforeBlock?.text;
400
+ // Wrap updates in 'props' for consistency with CLI pattern
401
+ const attributes = Object.keys(updates).length > 0 ? { props: updates } : {};
402
+ // Use the production-tested editBlock helper
403
+ const _snapshot = editBlock(doc, {
404
+ blockId,
405
+ attributes,
406
+ removeAttributes,
407
+ text: text === null ? undefined : text,
408
+ docName: 'document',
409
+ });
410
+ // Apply runtime state updates if provided
411
+ let updatedRuntimeState;
412
+ if (runtimeUpdates &&
413
+ typeof runtimeUpdates === 'object' &&
414
+ Object.keys(runtimeUpdates).length > 0) {
415
+ doc.transact(() => {
416
+ updatedRuntimeState = updateRuntimeState(doc, blockId, runtimeUpdates);
417
+ }, 'blocknote-crdt-playground');
418
+ }
419
+ // Create simplified response for agents with change tracking
420
+ const updatedBlock = getBlockDetail(doc, blockId, true);
421
+ const afterProps = updatedBlock
422
+ ? extractBlockProperties(updatedBlock)
423
+ : {};
424
+ // Build list of what actually changed
425
+ const updatedFields = [];
426
+ for (const key of Object.keys(updates)) {
427
+ if (JSON.stringify(beforeProps[key]) !== JSON.stringify(afterProps[key])) {
428
+ updatedFields.push(key);
429
+ }
430
+ }
431
+ if (text !== null && text !== beforeText) {
432
+ updatedFields.push('text');
433
+ }
434
+ if (removeAttributes.length > 0) {
435
+ updatedFields.push(...removeAttributes.map((k) => `-${k}`));
436
+ }
437
+ if (updatedRuntimeState) {
438
+ updatedFields.push(...Object.keys(runtimeUpdates).map((k) => `runtime.${k}`));
439
+ }
440
+ const simplified = updatedBlock
441
+ ? simplifyBlockForAgent(updatedBlock)
442
+ : null;
443
+ // Build diff from before/after state
444
+ const diff = {};
445
+ for (const field of updatedFields) {
446
+ if (field.startsWith('-') || field.startsWith('runtime.'))
447
+ continue;
448
+ if (field === 'text') {
449
+ diff.text = {
450
+ old: beforeText ?? '',
451
+ new: updatedBlock?.text ?? '',
452
+ };
453
+ }
454
+ else {
455
+ diff[field] = { old: beforeProps[field], new: afterProps[field] };
456
+ }
457
+ }
458
+ // Changes are automatically synced by your Matrix provider
459
+ return JSON.stringify({
460
+ success: true,
461
+ roomId,
462
+ blockId,
463
+ blockType: simplified?.type,
464
+ message: `Updated ${updatedFields.length} field(s): ${updatedFields.join(', ') || 'no changes detected'}`,
465
+ updatedFields,
466
+ block: simplified,
467
+ diff,
468
+ ...(updatedRuntimeState && { runtimeState: updatedRuntimeState }),
469
+ });
470
+ }
471
+ catch (error) {
472
+ Logger.error('Error editing block:', error);
473
+ return JSON.stringify({
474
+ success: false,
475
+ error: error instanceof Error ? error.message : String(error),
476
+ });
477
+ }
478
+ finally {
479
+ // Schedule cleanup after delay to allow Y.Doc to finish syncing
480
+ await providerManager.dispose();
481
+ }
482
+ }, {
483
+ name: 'edit_block',
484
+ description: `Edits an existing block's properties, content, and/or runtime state.
485
+
486
+ **CRITICAL WORKFLOW:**
487
+ 1. Call list_blocks FIRST to get the exact UUID
488
+ 2. Pass updates as plain key-value pairs (tool wraps them automatically)
489
+ 3. Never guess or invent block IDs
490
+
491
+ **How Updates Work:**
492
+ - Pass properties as plain objects like \`{status: "open", title: "New Title"}\`
493
+ - Tool automatically wraps them in the internal \`props\` structure
494
+ - Use \`runtimeUpdates\` to update runtime state (execution status, timestamps, etc.) — merges with existing state, never overwrites
495
+ - Changes sync to all clients via CRDT
496
+
497
+ **JSON-string properties (inputs, links):**
498
+ - Some properties like \`inputs\` and \`links\` are stored as JSON strings internally
499
+ - When reading blocks, these are returned as parsed objects/arrays
500
+ - When updating, pass them as objects/arrays — they are auto-serialized back to JSON strings
501
+ - For \`inputs\` (object): your updates are MERGED with existing values. Example: \`{"inputs": {"credential": "abc"}}\` merges into existing inputs
502
+ - For \`links\` (array): your value REPLACES the existing array. Each link item needs: \`id\`, \`title\`, \`description\`, \`position\`. For external URLs add \`externalUrl\`. For internal flow links add \`docRoomId\`.
503
+
504
+ **Examples:**
505
+ - Update status: \`{"blockId": "uuid", "updates": {"status": "open"}}\`
506
+ - Update text: \`{"blockId": "uuid", "updates": {}, "text": "New content"}\`
507
+ - Update action inputs: \`{"blockId": "uuid", "updates": {"inputs": {"credential": "data", "roomId": "!room:server"}}}\`
508
+ - Update flowLink with external URL: \`{"blockId": "uuid", "updates": {"links": [{"id": "link-1", "title": "Verify Identity", "description": "Click to verify", "captionText": "", "position": 0, "externalUrl": "https://example.com/verify"}]}}\`
509
+ - Update runtime: \`{"blockId": "uuid", "updates": {}, "runtimeUpdates": {"evaluationStatus": "approved"}}\`
510
+ - Remove attrs: \`{"blockId": "uuid", "updates": {}, "removeAttributes": ["oldProp"]}\`
511
+
512
+ **Note:** Block properties vary by block type and may evolve. Use \`list_blocks\` or \`read_block_by_id\` to discover current properties for any block type.
513
+
514
+ **Returns:** Block details including id, type, properties, text, and runtimeState (if updated).
515
+
516
+ **Example response:**
517
+ \`\`\`json
518
+ {
519
+ "success": true,
520
+ "message": "Successfully updated block...",
521
+ "block": {
522
+ "id": "550e8400-e29b-41d4-a716-446655440000",
523
+ "type": "proposal",
524
+ "properties": {
525
+ "status": "open",
526
+ "title": "Updated Title",
527
+ "description": "Updated description"
528
+ },
529
+ "text": "Optional text content"
530
+ }
531
+ }
532
+ \`\`\``,
533
+ schema: z.object({
534
+ blockId: z
535
+ .string()
536
+ .describe('The exact ID of the block to edit (get from list_blocks)'),
537
+ updates: z
538
+ .record(z.any(), z.any())
539
+ .describe("Object with property updates. Example: {status: 'open', title: 'New Title'}"),
540
+ removeAttributes: z
541
+ .array(z.string())
542
+ .optional()
543
+ .default([])
544
+ .describe("Array of attribute keys to remove. Example: ['oldProp', 'tempData']"),
545
+ text: z
546
+ .string()
547
+ .nullable()
548
+ .optional()
549
+ .describe('New text content for the block. Use null to keep existing, empty string to clear'),
550
+ runtimeUpdates: z
551
+ .record(z.any(), z.any())
552
+ .optional()
553
+ .describe('Optional: merge updates into the block runtime state (execution status, claims, timestamps, etc.). Merges with existing state — never overwrites.'),
554
+ }),
555
+ });
556
+ // ============================================================================
557
+ // Tool 3: Create Block
558
+ // ============================================================================
559
+ /**
560
+ * Creates a new block in the document using appendBlock from blockActions.ts.
561
+ * Supports all block types — new blocks are appended to the end of the document.
562
+ */
563
+ const createBlockTool = tool(async ({ blockType, text = '', attributes = {}, blockId = null, referenceBlockId = null, placement = null, }) => {
564
+ Logger.log(`➕ create_block tool invoked for type: ${blockType}`);
565
+ // Use the shared Matrix client (already synced)
566
+ const providerManager = new MatrixProviderManager(matrixClient, config);
567
+ try {
568
+ const { doc } = await providerManager.init();
569
+ const isInRoom = await checkIfInRoomAndJoinPublicRoom(matrixClient, roomId);
570
+ if (!isInRoom) {
571
+ return JSON.stringify({
572
+ success: false,
573
+ error: `Companion is not in the room ${roomId}, please invite companion to the room. companion user id: ${matrixClient.getUserId()}`,
574
+ });
575
+ }
576
+ // Wrap attributes in 'props' for consistency with edit_block and BlockNote schema
577
+ const wrappedAttributes = Object.keys(attributes).length > 0 ? { props: attributes } : {};
578
+ const resolvedBlockId = blockId ?? randomUUID();
579
+ let snapshot;
580
+ // Use positional insertion if reference block is provided
581
+ if (referenceBlockId && placement) {
582
+ snapshot = insertBlock(doc, {
583
+ referenceBlockId,
584
+ placement,
585
+ blockId: resolvedBlockId,
586
+ blockType,
587
+ text,
588
+ attributes: wrappedAttributes,
589
+ docName: 'document',
590
+ });
591
+ }
592
+ else {
593
+ snapshot = appendBlock(doc, {
594
+ blockId: resolvedBlockId,
595
+ blockType,
596
+ text,
597
+ attributes: wrappedAttributes,
598
+ docName: 'document',
599
+ namespace: undefined,
600
+ });
601
+ }
602
+ // Get simplified view for agents
603
+ const createdBlock = getBlockDetail(doc, snapshot.id, true);
604
+ const simplified = createdBlock
605
+ ? simplifyBlockForAgent(createdBlock)
606
+ : null;
607
+ // Count blocks to determine position
608
+ const fragment = doc.getXmlFragment('document');
609
+ const allBlocks = collectAllBlocks(fragment);
610
+ const position = allBlocks.findIndex((b) => b.id === snapshot.id);
611
+ const diff = {
612
+ block: {
613
+ old: null,
614
+ new: simplified ?? {
615
+ id: snapshot.id,
616
+ type: blockType,
617
+ properties: attributes,
618
+ text,
619
+ },
620
+ },
621
+ };
622
+ return JSON.stringify({
623
+ success: true,
624
+ roomId,
625
+ blockId: snapshot.id,
626
+ blockType,
627
+ position: position >= 0 ? position : allBlocks.length - 1,
628
+ message: referenceBlockId
629
+ ? `Created ${blockType} block ${placement} block ${referenceBlockId}`
630
+ : `Created ${blockType} block at end of document`,
631
+ block: simplified || snapshot,
632
+ diff,
633
+ });
634
+ }
635
+ catch (error) {
636
+ Logger.error('Error creating block:', error);
637
+ return JSON.stringify({
638
+ success: false,
639
+ error: error instanceof Error ? error.message : String(error),
640
+ });
641
+ }
642
+ finally {
643
+ // Schedule cleanup after delay to allow Y.Doc to finish syncing
644
+ await providerManager.dispose();
645
+ }
646
+ }, {
647
+ name: 'create_block',
648
+ description: `Creates a new block in the BlockNote document.
649
+
650
+ **Usage:**
651
+ - By default, appends new blocks to the end of the document
652
+ - Use \`referenceBlockId\` + \`placement\` to insert before/after a specific block
653
+ - Initialize blocks with specific properties as key-value pairs
654
+ - Block ID (UUID) is auto-generated unless you provide one
655
+ - Use \`read_block_by_id\` on existing blocks to discover available properties for any block type
656
+
657
+ **Examples:**
658
+ - Append paragraph: \`{"blockType": "paragraph", "text": "Hello world"}\`
659
+ - Insert before a block: \`{"blockType": "paragraph", "text": "Inserted text", "referenceBlockId": "uuid-here", "placement": "before"}\`
660
+ - Insert after a block: \`{"blockType": "proposal", "attributes": {"status": "draft"}, "referenceBlockId": "uuid-here", "placement": "after"}\`
661
+
662
+ **Note:** Block attributes vary by type and may evolve. Use \`read_block_by_id\` on existing blocks to discover available properties.
663
+
664
+ **Returns:**
665
+ \`\`\`json
666
+ {
667
+ "success": true,
668
+ "message": "Successfully created proposal block",
669
+ "block": {
670
+ "id": "550e8400-e29b-41d4-a716-446655440000",
671
+ "type": "proposal",
672
+ "properties": {...},
673
+ "text": ""
674
+ }
675
+ }
676
+ \`\`\`
677
+
678
+ The returned block includes the auto-generated UUID that you can use for future edits.`,
679
+ schema: z.object({
680
+ blockType: z
681
+ .string()
682
+ .describe('Type of block to create: paragraph, proposal, checkbox, apiRequest, list, etc.'),
683
+ text: z
684
+ .string()
685
+ .optional()
686
+ .default('')
687
+ .describe('Text content for the block (mainly for paragraphs)'),
688
+ attributes: z
689
+ .record(z.any(), z.any())
690
+ .optional()
691
+ .default({})
692
+ .describe("Block-specific attributes as key-value pairs. Example: {status: 'draft', title: 'My Proposal'}"),
693
+ blockId: z
694
+ .string()
695
+ .optional()
696
+ .nullable()
697
+ .describe('Optional: custom block ID. If not provided, one will be generated automatically'),
698
+ referenceBlockId: z
699
+ .string()
700
+ .optional()
701
+ .nullable()
702
+ .describe('Optional: ID of an existing block to insert relative to. Must be used with placement.'),
703
+ placement: z
704
+ .enum(['before', 'after'])
705
+ .optional()
706
+ .nullable()
707
+ .describe('Optional: insert "before" or "after" the referenceBlockId. Required when referenceBlockId is provided.'),
708
+ }),
709
+ });
710
+ const readBlockByIdTool = tool(async ({ blockId, evaluateConditions: evalConds = false, resolveReferences: resolveRefs = false, }) => {
711
+ Logger.log(`📄 read_block_by_id tool invoked for block: ${blockId}`);
712
+ const providerManager = new MatrixProviderManager(matrixClient, config);
713
+ try {
714
+ const { doc } = await providerManager.init();
715
+ const block = getBlockDetail(doc, blockId, true);
716
+ if (!block) {
717
+ return JSON.stringify({
718
+ success: false,
719
+ blockId,
720
+ error: `Block with id ${blockId} not found`,
721
+ });
722
+ }
723
+ const simplified = simplifyBlockForAgent(block);
724
+ const result = {
725
+ success: true,
726
+ blockId,
727
+ blockType: simplified.type,
728
+ block: simplified,
729
+ };
730
+ // Include runtime state for this block if it exists
731
+ const blockRuntimeState = readRuntimeState(doc, blockId);
732
+ const runtimeData = blockRuntimeState[blockId];
733
+ if (runtimeData && Object.keys(runtimeData).length > 0) {
734
+ result.runtimeState = runtimeData;
735
+ }
736
+ // Optional: evaluate conditions
737
+ if (evalConds) {
738
+ const attrs = block.attributes || {};
739
+ const attrsObj = attrs.attrs || {};
740
+ const props = attrsObj.props || {};
741
+ const conditionsJson = props.conditions || attrs.conditions || '';
742
+ if (conditionsJson) {
743
+ try {
744
+ const conditionConfig = JSON.parse(conditionsJson);
745
+ const fragment = doc.getXmlFragment('document');
746
+ const allBlocks = collectAllBlocks(fragment);
747
+ result.conditionEvaluation = evaluateBlockConditions(conditionConfig, allBlocks);
748
+ }
749
+ catch {
750
+ result.conditionEvaluation = {
751
+ error: 'Failed to parse conditions JSON',
752
+ };
753
+ }
754
+ }
755
+ else {
756
+ result.conditionEvaluation = {
757
+ isVisible: true,
758
+ isEnabled: true,
759
+ actions: [],
760
+ };
761
+ }
762
+ }
763
+ // Optional: resolve references in string props
764
+ if (resolveRefs) {
765
+ const fragment = doc.getXmlFragment('document');
766
+ const allBlocks = collectAllBlocks(fragment);
767
+ const resolvedProps = {};
768
+ const blockProps = simplified.properties || {};
769
+ for (const [key, val] of Object.entries(blockProps)) {
770
+ if (typeof val === 'string' &&
771
+ val.includes('{{') &&
772
+ val.includes('}}')) {
773
+ resolvedProps[key] = resolveBlockReferences(val, allBlocks);
774
+ }
775
+ }
776
+ if (Object.keys(resolvedProps).length > 0) {
777
+ result.resolvedReferences = resolvedProps;
778
+ }
779
+ }
780
+ return JSON.stringify(result, null, 2);
781
+ }
782
+ catch (error) {
783
+ return JSON.stringify({
784
+ success: false,
785
+ error: error instanceof Error ? error.message : String(error),
786
+ });
787
+ }
788
+ finally {
789
+ await providerManager.dispose();
790
+ }
791
+ }, {
792
+ name: 'read_block_by_id',
793
+ description: `Reads a block by its ID. Returns block properties AND runtime state (execution status, claims, timestamps, etc.) in a single call.
794
+
795
+ Automatically includes runtimeState from Y.Map('runtime') when data exists for this block. Parses surveySchema and answers for survey blocks.
796
+
797
+ Optional flags:
798
+ - evaluateConditions: true → evaluates the block's condition config against all blocks, returns { isVisible, isEnabled, conditionActions[] }
799
+ - resolveReferences: true → resolves {{blockId.prop}} patterns in block props, returns resolved values`,
800
+ schema: z.object({
801
+ blockId: z.string().describe('The ID of the block to read'),
802
+ evaluateConditions: z
803
+ .boolean()
804
+ .optional()
805
+ .default(false)
806
+ .describe('If true, evaluates the block conditions and returns visibility/enabled state'),
807
+ resolveReferences: z
808
+ .boolean()
809
+ .optional()
810
+ .default(false)
811
+ .describe('If true, resolves {{blockId.prop}} template references in block props'),
812
+ }),
813
+ });
814
+ // ============================================================================
815
+ // Tool 5: Read Survey
816
+ // ============================================================================
817
+ const readSurveyTool = tool(async ({ blockId }) => {
818
+ Logger.log(`📋 read_survey tool invoked for block: ${blockId}`);
819
+ const providerManager = new MatrixProviderManager(matrixClient, config);
820
+ try {
821
+ const { doc } = await providerManager.init();
822
+ const isInRoom = await checkIfInRoomAndJoinPublicRoom(matrixClient, roomId);
823
+ if (!isInRoom) {
824
+ return JSON.stringify({
825
+ success: false,
826
+ error: `Companion is not in the room ${roomId}, please invite companion to the room. companion user id: ${matrixClient.getUserId()}`,
827
+ });
828
+ }
829
+ const block = getBlockDetail(doc, blockId, true);
830
+ console.log('🚀 ~ createBlocknoteTools ~ block:', block);
831
+ if (!block) {
832
+ return JSON.stringify({
833
+ success: false,
834
+ error: `Block with id ${blockId} not found`,
835
+ });
836
+ }
837
+ const properties = extractBlockProperties(block);
838
+ const surveySchema = properties.surveySchema;
839
+ const answers = (properties.answers || {});
840
+ if (!surveySchema) {
841
+ Logger.error('Block does not contain a surveySchema:', block);
842
+ return JSON.stringify({
843
+ success: false,
844
+ error: `Block ${blockId} does not contain a surveySchema property. This tool works with any block that has a surveySchema (domainCreator, form, governanceGroup, bid, claim, etc.)`,
845
+ });
846
+ }
847
+ // Extract all questions with visibility computed inline
848
+ const allQuestions = await extractSurveyQuestions(surveySchema, answers);
849
+ const missingRequired = await getMissingRequiredFields(answers, surveySchema);
850
+ return JSON.stringify({
851
+ success: true,
852
+ survey: {
853
+ title: surveySchema.title,
854
+ description: surveySchema.description,
855
+ },
856
+ questions: allQuestions,
857
+ answers,
858
+ missingRequiredFields: missingRequired,
859
+ totalQuestions: allQuestions.length,
860
+ }, null, 2);
861
+ }
862
+ catch (error) {
863
+ Logger.error('Error reading survey:', error);
864
+ return JSON.stringify({
865
+ success: false,
866
+ error: error instanceof Error ? error.message : String(error),
867
+ });
868
+ }
869
+ finally {
870
+ await providerManager.dispose();
871
+ }
872
+ }, {
873
+ name: 'read_survey',
874
+ description: `Reads survey schema and current answers from any block with a surveySchema property.
875
+
876
+ **Purpose:**
877
+ - View complete survey structure (ALL questions including hidden ones)
878
+ - See current answers as structured JSON
879
+ - Identify which questions are currently visible vs hidden
880
+ - Understand why fields are hidden (via visibleIf conditions)
881
+ - Find missing required fields (only for visible questions)
882
+ - Automatically fetches choices from choicesByUrl for dropdown questions
883
+ - Works with any block type that has a surveySchema (domainCreator, form, governanceGroup, bid, claim, etc.)
884
+
885
+ **Important:**
886
+ - ALL questions are returned (both visible and hidden), not just visible ones
887
+ - \`isVisible: true\` means the field is currently shown in the UI
888
+ - \`isVisible: false\` means the field is hidden by a \`visibleIf\` condition
889
+ - \`visibleIf\` field shows the condition that controls visibility
890
+ - Hidden fields can be made visible by changing the controlling answer
891
+ - Nested dynamic panel template elements are included in the questions array
892
+ - Choices from choicesByUrl are automatically fetched and included
893
+
894
+ **Note:** The \`answers\` object may contain data for hidden fields — use the \`questions\` array to understand the schema for those fields.`,
895
+ schema: z.object({
896
+ blockId: z
897
+ .string()
898
+ .describe('The ID of the block containing the survey'),
899
+ }),
900
+ });
901
+ // ============================================================================
902
+ // Tool 6: Fill Survey Answers
903
+ // ============================================================================
904
+ const fillSurveyAnswersTool = tool(async ({ blockId, answers, merge = true }) => {
905
+ Logger.log(`✏️ fill_survey_answers tool invoked for block: ${blockId}`);
906
+ const providerManager = new MatrixProviderManager(matrixClient, config);
907
+ try {
908
+ const { doc } = await providerManager.init();
909
+ const isInRoom = await checkIfInRoomAndJoinPublicRoom(matrixClient, roomId);
910
+ if (!isInRoom) {
911
+ return JSON.stringify({
912
+ success: false,
913
+ error: `Companion is not in the room ${roomId}, please invite companion to the room. companion user id: ${matrixClient.getUserId()}`,
914
+ });
915
+ }
916
+ const block = getBlockDetail(doc, blockId, true);
917
+ if (!block) {
918
+ return JSON.stringify({
919
+ success: false,
920
+ error: `Block with id ${blockId} not found`,
921
+ });
922
+ }
923
+ const properties = extractBlockProperties(block);
924
+ const surveySchema = properties.surveySchema;
925
+ if (!surveySchema) {
926
+ return JSON.stringify({
927
+ success: false,
928
+ error: `Block ${blockId} does not contain a surveySchema property. This tool works with any block that has a surveySchema (domainCreator, form, governanceGroup, bid, claim, etc.)`,
929
+ });
930
+ }
931
+ // Get current answers
932
+ const currentAnswers = (properties.answers || {});
933
+ // Merge or replace answers
934
+ const updatedAnswers = merge
935
+ ? { ...currentAnswers, ...answers }
936
+ : answers;
937
+ // Validate the updated answers
938
+ const validation = await validateAnswersAgainstSchema(updatedAnswers, surveySchema);
939
+ // Update the block's answers attribute
940
+ const fragment = doc.getXmlFragment('document');
941
+ const blockContainer = findBlockById(fragment, blockId);
942
+ if (!blockContainer) {
943
+ return JSON.stringify({
944
+ success: false,
945
+ error: `Block container not found`,
946
+ });
947
+ }
948
+ // Use Y.js transaction to update the answers
949
+ doc.transact(() => {
950
+ // Find the content child element that has an answers attribute
951
+ const contentElement = blockContainer
952
+ .toArray()
953
+ .find((node) => node instanceof Y.XmlElement &&
954
+ node.nodeName !== 'blockGroup' &&
955
+ node.nodeName !== 'blockContainer');
956
+ if (contentElement) {
957
+ // Update the answers attribute as JSON string directly on the child element
958
+ contentElement.setAttribute('answers', JSON.stringify(updatedAnswers));
959
+ }
960
+ else {
961
+ logger.error('Content element not found, falling back to edit_block helper');
962
+ // Fallback: use edit_block helper which handles the structure properly
963
+ editBlock(doc, {
964
+ blockId,
965
+ attributes: {
966
+ props: { answers: JSON.stringify(updatedAnswers) },
967
+ },
968
+ docName: 'document',
969
+ });
970
+ }
971
+ }, 'blocknote-crdt-playground');
972
+ return JSON.stringify({
973
+ success: true,
974
+ message: `Successfully ${merge ? 'merged' : 'replaced'} survey answers`,
975
+ answers: updatedAnswers,
976
+ validation,
977
+ missingRequiredFields: await getMissingRequiredFields(updatedAnswers, surveySchema),
978
+ }, null, 2);
979
+ }
980
+ catch (error) {
981
+ Logger.error('Error filling survey answers:', error);
982
+ return JSON.stringify({
983
+ success: false,
984
+ error: error instanceof Error ? error.message : String(error),
985
+ });
986
+ }
987
+ finally {
988
+ await providerManager.dispose();
989
+ }
990
+ }, {
991
+ name: 'fill_survey_answers',
992
+ description: `Fills in survey answers for any block with a surveySchema. Intelligently merges with existing answers and validates against schema.
993
+
994
+ **Purpose:**
995
+ - Fill in partial or complete survey answers
996
+ - Merge with existing answers (default) or replace them
997
+ - Automatically validates answers against schema
998
+ - Respects visibility conditions
999
+ - Works with any block type that has a surveySchema (domainCreator, form, governanceGroup, bid, claim, etc.)
1000
+
1001
+ **Example 1 - Fill single answer:**
1002
+ \`\`\`json
1003
+ {
1004
+ "blockId": "271fc5de-bcd8-4de0-8dd7-fb3dd5c13785",
1005
+ "answers": {
1006
+ "schema:name": "My New Domain",
1007
+ "schema.description": "A description of my domain"
1008
+ },
1009
+ "merge": true
1010
+ }
1011
+ \`\`\`
1012
+
1013
+ **Example 2 - Replace all answers:**
1014
+ \`\`\`json
1015
+ {
1016
+ "blockId": "271fc5de-bcd8-4de0-8dd7-fb3dd5c13785",
1017
+ "answers": {
1018
+ "schema:name": "New Domain",
1019
+ "type_2": "dao",
1020
+ "schema:validFrom": "2025-01-01"
1021
+ },
1022
+ "merge": false
1023
+ }
1024
+ \`\`\`
1025
+
1026
+ **Note:**
1027
+ - Use merge=true (default) to keep existing answers and only update specified fields
1028
+ - Use merge=false to replace all answers
1029
+ - Answers are validated automatically
1030
+ - Only visible questions (based on visibility conditions) are considered`,
1031
+ schema: z.object({
1032
+ blockId: z
1033
+ .string()
1034
+ .describe('The ID of the block containing the survey'),
1035
+ answers: z
1036
+ .record(z.any(), z.any())
1037
+ .describe('Object with answer key-value pairs. Keys should match question names from the schema.'),
1038
+ merge: z
1039
+ .boolean()
1040
+ .optional()
1041
+ .default(true)
1042
+ .describe('If true, merge with existing answers. If false, replace all answers.'),
1043
+ }),
1044
+ });
1045
+ // ============================================================================
1046
+ // Tool 7: Validate Survey Answers
1047
+ // ============================================================================
1048
+ const validateSurveyAnswersTool = tool(async ({ blockId }) => {
1049
+ Logger.log(`✅ validate_survey_answers tool invoked for block: ${blockId}`);
1050
+ const providerManager = new MatrixProviderManager(matrixClient, config);
1051
+ try {
1052
+ const { doc } = await providerManager.init();
1053
+ const isInRoom = await checkIfInRoomAndJoinPublicRoom(matrixClient, roomId);
1054
+ if (!isInRoom) {
1055
+ return JSON.stringify({
1056
+ success: false,
1057
+ error: `Companion is not in the room ${roomId}, please invite companion to the room. companion user id: ${matrixClient.getUserId()}`,
1058
+ });
1059
+ }
1060
+ const block = getBlockDetail(doc, blockId, true);
1061
+ if (!block) {
1062
+ return JSON.stringify({
1063
+ success: false,
1064
+ error: `Block with id ${blockId} not found`,
1065
+ });
1066
+ }
1067
+ const properties = extractBlockProperties(block);
1068
+ const surveySchema = properties.surveySchema;
1069
+ const answers = (properties.answers || {});
1070
+ if (!surveySchema) {
1071
+ return JSON.stringify({
1072
+ success: false,
1073
+ error: `Block ${blockId} does not contain a surveySchema property. This tool works with any block that has a surveySchema (domainCreator, form, governanceGroup, bid, claim, etc.)`,
1074
+ });
1075
+ }
1076
+ const validation = await validateAnswersAgainstSchema(answers, surveySchema);
1077
+ const missingRequired = await getMissingRequiredFields(answers, surveySchema);
1078
+ const visibleQuestions = await getVisibleQuestions(answers, surveySchema);
1079
+ return JSON.stringify({
1080
+ success: true,
1081
+ valid: validation.valid,
1082
+ errors: validation.errors,
1083
+ warnings: validation.warnings,
1084
+ missingRequiredFields: missingRequired,
1085
+ answeredQuestions: Object.keys(answers).length,
1086
+ visibleQuestionsCount: visibleQuestions.length,
1087
+ totalRequiredFields: visibleQuestions.filter((q) => q.isRequired)
1088
+ .length,
1089
+ completionPercentage: visibleQuestions.length > 0
1090
+ ? Math.round(((visibleQuestions.length - missingRequired.length) /
1091
+ visibleQuestions.length) *
1092
+ 100)
1093
+ : 0,
1094
+ }, null, 2);
1095
+ }
1096
+ catch (error) {
1097
+ Logger.error('Error validating survey answers:', error);
1098
+ return JSON.stringify({
1099
+ success: false,
1100
+ error: error instanceof Error ? error.message : String(error),
1101
+ });
1102
+ }
1103
+ finally {
1104
+ await providerManager.dispose();
1105
+ }
1106
+ }, {
1107
+ name: 'validate_survey_answers',
1108
+ description: `Validates current survey answers against the schema requirements. Works with any block that has a surveySchema.
1109
+
1110
+ **Purpose:**
1111
+ - Check if all required fields are filled
1112
+ - Validate answer types and formats
1113
+ - Identify validation errors and warnings
1114
+ - Calculate completion percentage
1115
+ - Works with any block type that has a surveySchema (domainCreator, form, governanceGroup, bid, claim, etc.)
1116
+
1117
+ **Validation Types:**
1118
+ - required: Field is required but missing or empty
1119
+ - type: Answer type doesn't match expected type (e.g., boolean vs string)
1120
+ - choice: Answer value not in allowed choices (for dropdowns)
1121
+ - format: Answer format is invalid (e.g., invalid email or URL)
1122
+
1123
+ **Note:** Only validates visible questions based on current answers and visibility conditions.`,
1124
+ schema: z.object({
1125
+ blockId: z
1126
+ .string()
1127
+ .describe('The ID of the block to validate survey answers for'),
1128
+ }),
1129
+ });
1130
+ // ============================================================================
1131
+ // Tool 8: Read Flow Context
1132
+ // ============================================================================
1133
+ const readFlowContextTool = tool(async () => {
1134
+ logger.log('📊 read_flow_context tool invoked');
1135
+ const providerManager = new MatrixProviderManager(matrixClient, config);
1136
+ try {
1137
+ const { doc } = await providerManager.init();
1138
+ const isInRoom = await checkIfInRoomAndJoinPublicRoom(matrixClient, roomId);
1139
+ if (!isInRoom) {
1140
+ return JSON.stringify({
1141
+ success: false,
1142
+ error: `Companion is not in the room ${roomId}, please invite companion to the room. companion user id: ${matrixClient.getUserId()}`,
1143
+ });
1144
+ }
1145
+ const flowMetadata = readFlowMetadata(doc);
1146
+ const fragment = doc.getXmlFragment('document');
1147
+ const blocks = collectAllBlocks(fragment);
1148
+ const flowNodes = readFlowNodes(doc);
1149
+ const runtimeMap = doc.getMap('runtime');
1150
+ const delegationsMap = doc.getMap('delegations');
1151
+ return JSON.stringify({
1152
+ success: true,
1153
+ roomId,
1154
+ flowMetadata,
1155
+ summary: {
1156
+ blockCount: blocks.length,
1157
+ flowNodeCount: flowNodes.length,
1158
+ isFlowDocument: flowMetadata['_type'] === 'ixo.flow.crdt',
1159
+ hasRuntimeState: runtimeMap.size > 0,
1160
+ hasDelegations: delegationsMap.size > 1,
1161
+ },
1162
+ }, null, 2);
1163
+ }
1164
+ catch (error) {
1165
+ return JSON.stringify({
1166
+ success: false,
1167
+ error: error instanceof Error ? error.message : String(error),
1168
+ });
1169
+ }
1170
+ finally {
1171
+ await providerManager.dispose();
1172
+ }
1173
+ }, {
1174
+ name: 'read_flow_context',
1175
+ description: `Reads flow-level metadata and document context. **Call this FIRST** in any new conversation to understand what document you're working with.
1176
+
1177
+ Returns: flow metadata (title, owner DID, doc type, schema version, creation date), block count, flow node count, and whether runtime state/delegations exist.
1178
+
1179
+ This is a lightweight call that gives you the full picture before diving into specific blocks.`,
1180
+ schema: z.object({}),
1181
+ });
1182
+ // ============================================================================
1183
+ // Tool 9: Read Flow Status
1184
+ // ============================================================================
1185
+ const readFlowStatusTool = tool(async ({ nodeId = null }) => {
1186
+ logger.log(`📈 read_flow_status tool invoked${nodeId ? ` for node: ${nodeId}` : ''}`);
1187
+ const providerManager = new MatrixProviderManager(matrixClient, config);
1188
+ try {
1189
+ const { doc } = await providerManager.init();
1190
+ const isInRoom = await checkIfInRoomAndJoinPublicRoom(matrixClient, roomId);
1191
+ if (!isInRoom) {
1192
+ return JSON.stringify({
1193
+ success: false,
1194
+ error: `Companion is not in the room ${roomId}, please invite companion to the room. companion user id: ${matrixClient.getUserId()}`,
1195
+ });
1196
+ }
1197
+ const flowNodes = readFlowNodes(doc);
1198
+ const runtimeState = readRuntimeState(doc, nodeId ?? undefined);
1199
+ // Enrich runtime state with human-readable dates
1200
+ const enrichedState = {};
1201
+ for (const [id, state] of Object.entries(runtimeState)) {
1202
+ const enriched = { ...state };
1203
+ const ts = state['executionTimestamp'];
1204
+ if (typeof ts === 'number') {
1205
+ enriched['executionDate'] = new Date(ts).toISOString();
1206
+ }
1207
+ enrichedState[id] = enriched;
1208
+ }
1209
+ // Build summary — graceful field checks for generic data
1210
+ const allRuntimeState = readRuntimeState(doc);
1211
+ const stateValues = Object.values(allRuntimeState);
1212
+ const executedNodes = stateValues.filter((s) => s['executionTimestamp']).length;
1213
+ return JSON.stringify({
1214
+ success: true,
1215
+ flowNodes,
1216
+ runtimeState: enrichedState,
1217
+ summary: {
1218
+ totalNodes: flowNodes.length,
1219
+ executedNodes,
1220
+ pendingNodes: stateValues.filter((s) => s['evaluationStatus'] === 'pending').length,
1221
+ approvedNodes: stateValues.filter((s) => s['evaluationStatus'] === 'approved').length,
1222
+ rejectedNodes: stateValues.filter((s) => s['evaluationStatus'] === 'rejected').length,
1223
+ },
1224
+ }, null, 2);
1225
+ }
1226
+ catch (error) {
1227
+ return JSON.stringify({
1228
+ success: false,
1229
+ error: error instanceof Error ? error.message : String(error),
1230
+ });
1231
+ }
1232
+ finally {
1233
+ await providerManager.dispose();
1234
+ }
1235
+ }, {
1236
+ name: 'read_flow_status',
1237
+ description: `Reads the execution status of flow nodes. Shows which blocks have been executed, by whom, when, and their evaluation status (pending/approved/rejected).
1238
+
1239
+ Use this to answer: "What's the status of this flow?", "Which steps are done?", "Who executed block X?"
1240
+
1241
+ Pass nodeId to check a specific node, or omit to get all nodes.`,
1242
+ schema: z.object({
1243
+ nodeId: z
1244
+ .string()
1245
+ .optional()
1246
+ .nullable()
1247
+ .describe('Optional: specific node ID to check. Omit for all nodes.'),
1248
+ }),
1249
+ });
1250
+ // ============================================================================
1251
+ // Tool 10: Read Block History
1252
+ // ============================================================================
1253
+ const readBlockHistoryTool = tool(async ({ blockId }) => {
1254
+ logger.log(`📜 read_block_history tool invoked for block: ${blockId}`);
1255
+ const providerManager = new MatrixProviderManager(matrixClient, config);
1256
+ try {
1257
+ const { doc } = await providerManager.init();
1258
+ const isInRoom = await checkIfInRoomAndJoinPublicRoom(matrixClient, roomId);
1259
+ if (!isInRoom) {
1260
+ return JSON.stringify({
1261
+ success: false,
1262
+ error: `Companion is not in the room ${roomId}, please invite companion to the room. companion user id: ${matrixClient.getUserId()}`,
1263
+ });
1264
+ }
1265
+ const auditEvents = readAuditTrailForBlock(doc, blockId);
1266
+ const invocations = readInvocations(doc, blockId);
1267
+ const successfulInvocations = invocations.filter((i) => i['result'] === 'success').length;
1268
+ const failedInvocations = invocations.filter((i) => i['result'] === 'failure').length;
1269
+ // Enrich invocations with human-readable dates
1270
+ const enrichedInvocations = invocations.map((inv) => {
1271
+ const enriched = { ...inv };
1272
+ const executedAt = inv['executedAt'];
1273
+ if (typeof executedAt === 'number') {
1274
+ enriched['executedDate'] = new Date(executedAt).toISOString();
1275
+ }
1276
+ else if (typeof executedAt === 'string') {
1277
+ enriched['executedDate'] = new Date(executedAt).toISOString();
1278
+ }
1279
+ return enriched;
1280
+ });
1281
+ // Find most recent activity
1282
+ const lastAuditEvent = auditEvents.length > 0
1283
+ ? auditEvents[auditEvents.length - 1]
1284
+ : undefined;
1285
+ const lastAuditMeta = lastAuditEvent &&
1286
+ typeof lastAuditEvent['meta'] === 'object' &&
1287
+ lastAuditEvent['meta'] !== null
1288
+ ? lastAuditEvent['meta']
1289
+ : undefined;
1290
+ const lastAuditTs = lastAuditMeta?.['timestamp'];
1291
+ const firstInv = enrichedInvocations.length > 0 ? enrichedInvocations[0] : undefined;
1292
+ const lastInvTs = firstInv?.['executedDate'];
1293
+ const lastActivity = lastAuditTs && lastInvTs
1294
+ ? lastAuditTs > lastInvTs
1295
+ ? lastAuditTs
1296
+ : lastInvTs
1297
+ : lastAuditTs || lastInvTs;
1298
+ return JSON.stringify({
1299
+ success: true,
1300
+ blockId,
1301
+ auditEvents,
1302
+ invocations: enrichedInvocations,
1303
+ summary: {
1304
+ totalAuditEvents: auditEvents.length,
1305
+ totalInvocations: invocations.length,
1306
+ successfulInvocations,
1307
+ failedInvocations,
1308
+ lastActivity,
1309
+ },
1310
+ }, null, 2);
1311
+ }
1312
+ catch (error) {
1313
+ return JSON.stringify({
1314
+ success: false,
1315
+ error: error instanceof Error ? error.message : String(error),
1316
+ });
1317
+ }
1318
+ finally {
1319
+ await providerManager.dispose();
1320
+ }
1321
+ }, {
1322
+ name: 'read_block_history',
1323
+ description: `Reads the complete history for a specific block: audit trail events and UCAN invocations.
1324
+
1325
+ Use this to answer: "What happened with block X?", "Who executed this?", "When was this last updated?"
1326
+
1327
+ Returns audit events (timestamped actions) and invocations (UCAN-authorized executions with results and transaction hashes).`,
1328
+ schema: z.object({
1329
+ blockId: z.string().describe('The block ID to read history for'),
1330
+ }),
1331
+ });
1332
+ // ============================================================================
1333
+ // Tool 11: Read Permissions
1334
+ // ============================================================================
1335
+ const readPermissionsTool = tool(async ({ audienceDid = null, capability = null }) => {
1336
+ logger.log('🔐 read_permissions tool invoked');
1337
+ const providerManager = new MatrixProviderManager(matrixClient, config);
1338
+ try {
1339
+ const { doc } = await providerManager.init();
1340
+ const isInRoom = await checkIfInRoomAndJoinPublicRoom(matrixClient, roomId);
1341
+ if (!isInRoom) {
1342
+ return JSON.stringify({
1343
+ success: false,
1344
+ error: `Companion is not in the room ${roomId}, please invite companion to the room. companion user id: ${matrixClient.getUserId()}`,
1345
+ });
1346
+ }
1347
+ const { rootCid, delegations } = readDelegations(doc);
1348
+ // Apply filters using bracket notation on generic records
1349
+ let filtered = delegations;
1350
+ if (audienceDid) {
1351
+ filtered = filtered.filter((d) => d['audienceDid'] === audienceDid);
1352
+ }
1353
+ if (capability) {
1354
+ filtered = filtered.filter((d) => {
1355
+ const caps = d['capabilities'];
1356
+ if (!Array.isArray(caps))
1357
+ return false;
1358
+ return caps.some((c) => c['can'] === capability ||
1359
+ (typeof c['can'] === 'string' &&
1360
+ c['can'].endsWith('/*') &&
1361
+ capability.startsWith(c['can'].slice(0, -2))));
1362
+ });
1363
+ }
1364
+ const now = Date.now();
1365
+ const enriched = filtered.map((d) => {
1366
+ const expiration = d['expiration'];
1367
+ const enrichedDelegation = { ...d };
1368
+ if (typeof expiration === 'number') {
1369
+ enrichedDelegation['expirationDate'] = new Date(expiration).toISOString();
1370
+ enrichedDelegation['isExpired'] = expiration < now;
1371
+ }
1372
+ else {
1373
+ enrichedDelegation['isExpired'] = false;
1374
+ }
1375
+ return enrichedDelegation;
1376
+ });
1377
+ const activeDelegations = enriched.filter((d) => !d['isExpired']);
1378
+ const uniqueActors = [
1379
+ ...new Set(filtered
1380
+ .map((d) => d['audienceDid'])
1381
+ .filter((v) => typeof v === 'string')),
1382
+ ];
1383
+ return JSON.stringify({
1384
+ success: true,
1385
+ rootDelegationCid: rootCid,
1386
+ delegations: enriched,
1387
+ summary: {
1388
+ totalDelegations: enriched.length,
1389
+ activeDelegations: activeDelegations.length,
1390
+ expiredDelegations: enriched.length - activeDelegations.length,
1391
+ uniqueActors,
1392
+ },
1393
+ }, null, 2);
1394
+ }
1395
+ catch (error) {
1396
+ return JSON.stringify({
1397
+ success: false,
1398
+ error: error instanceof Error ? error.message : String(error),
1399
+ });
1400
+ }
1401
+ finally {
1402
+ await providerManager.dispose();
1403
+ }
1404
+ }, {
1405
+ name: 'read_permissions',
1406
+ description: `Reads the UCAN delegation chain — who has permission to do what in this flow.
1407
+
1408
+ Use this to answer: "Who can execute block X?", "What permissions does user Y have?", "Show me the delegation chain."
1409
+
1410
+ Optionally filter by audienceDid (recipient) or capability action (e.g., "flow/block/execute"). Supports wildcard matching (e.g., "flow/*" covers "flow/block/execute").`,
1411
+ schema: z.object({
1412
+ audienceDid: z
1413
+ .string()
1414
+ .optional()
1415
+ .nullable()
1416
+ .describe('Optional: filter by recipient DID'),
1417
+ capability: z
1418
+ .string()
1419
+ .optional()
1420
+ .nullable()
1421
+ .describe('Optional: filter by capability action, e.g. "flow/block/execute"'),
1422
+ }),
1423
+ });
1424
+ // ============================================================================
1425
+ // Tool 12: Delete Block
1426
+ // ============================================================================
1427
+ const deleteBlockTool = tool(async ({ blockId, confirm }) => {
1428
+ logger.log(`🗑️ delete_block tool invoked for block: ${blockId}`);
1429
+ if (!confirm) {
1430
+ return JSON.stringify({
1431
+ success: false,
1432
+ error: 'Deletion requires confirm: true. Set confirm to true to proceed with deletion.',
1433
+ });
1434
+ }
1435
+ const isInRoom = await checkIfInRoomAndJoinPublicRoom(matrixClient, roomId);
1436
+ if (!isInRoom) {
1437
+ return JSON.stringify({
1438
+ success: false,
1439
+ error: `Companion is not in the room ${roomId}, please invite companion to the room. companion user id: ${matrixClient.getUserId()}`,
1440
+ });
1441
+ }
1442
+ const providerManager = new MatrixProviderManager(matrixClient, config);
1443
+ try {
1444
+ const { doc } = await providerManager.init();
1445
+ // Snapshot before deletion so we can report what was removed
1446
+ const beforeBlock = getBlockDetail(doc, blockId, true);
1447
+ const beforeSimplified = beforeBlock
1448
+ ? simplifyBlockForAgent(beforeBlock)
1449
+ : null;
1450
+ const deleted = deleteBlock(doc, {
1451
+ blockId,
1452
+ docName: 'document',
1453
+ });
1454
+ if (!deleted) {
1455
+ return JSON.stringify({
1456
+ success: false,
1457
+ blockId,
1458
+ error: `Block with id ${blockId} not found`,
1459
+ });
1460
+ }
1461
+ // Count remaining blocks
1462
+ const fragment = doc.getXmlFragment('document');
1463
+ const remaining = collectAllBlocks(fragment);
1464
+ return JSON.stringify({
1465
+ success: true,
1466
+ blockId,
1467
+ blockType: beforeSimplified?.type,
1468
+ message: `Deleted ${beforeSimplified?.type || 'unknown'} block "${beforeSimplified?.text?.slice(0, 60) || '(no text)'}"`,
1469
+ deletedBlock: beforeSimplified,
1470
+ remainingBlockCount: remaining.length,
1471
+ });
1472
+ }
1473
+ catch (error) {
1474
+ Logger.error('Error deleting block:', error);
1475
+ return JSON.stringify({
1476
+ success: false,
1477
+ blockId,
1478
+ error: error instanceof Error ? error.message : String(error),
1479
+ });
1480
+ }
1481
+ finally {
1482
+ await providerManager.dispose();
1483
+ }
1484
+ }, {
1485
+ name: 'delete_block',
1486
+ description: `Removes a block from the document. Requires confirm: true as a safety check.
1487
+
1488
+ **CRITICAL:** Always call list_blocks first to verify the block ID. This action cannot be undone.`,
1489
+ schema: z.object({
1490
+ blockId: z
1491
+ .string()
1492
+ .describe('The exact UUID of the block to delete (get from list_blocks)'),
1493
+ confirm: z
1494
+ .boolean()
1495
+ .describe('Must be true to confirm deletion. Safety check to prevent accidental deletions.'),
1496
+ }),
1497
+ });
1498
+ // ============================================================================
1499
+ // Tool 13: Search Blocks
1500
+ // ============================================================================
1501
+ const searchBlocksTool = tool(async ({ blockType = null, propKey = null, propValue = null, textContains = null, }) => {
1502
+ logger.log('🔍 search_blocks tool invoked');
1503
+ const providerManager = new MatrixProviderManager(matrixClient, config);
1504
+ try {
1505
+ const { doc } = await providerManager.init();
1506
+ const isInRoom = await checkIfInRoomAndJoinPublicRoom(matrixClient, roomId);
1507
+ if (!isInRoom) {
1508
+ return JSON.stringify({
1509
+ success: false,
1510
+ error: `Companion is not in the room ${roomId}, please invite companion to the room. companion user id: ${matrixClient.getUserId()}`,
1511
+ });
1512
+ }
1513
+ const fragment = doc.getXmlFragment('document');
1514
+ let blocks = collectAllBlocks(fragment);
1515
+ // Apply filters (AND logic)
1516
+ if (blockType) {
1517
+ blocks = blocks.filter((b) => {
1518
+ const simplified = simplifyBlockForAgent(b);
1519
+ return simplified.type === blockType;
1520
+ });
1521
+ }
1522
+ if (propKey && propValue !== null) {
1523
+ blocks = blocks.filter((b) => {
1524
+ const props = extractBlockProperties(b);
1525
+ const actual = String(props[propKey]);
1526
+ const expected = String(propValue);
1527
+ // Exact match or emoji-equivalent match
1528
+ return (actual === expected ||
1529
+ emojify(actual) === emojify(expected) ||
1530
+ unemojify(actual) === unemojify(expected));
1531
+ });
1532
+ }
1533
+ if (textContains) {
1534
+ blocks = blocks.filter((b) => b.text && emojiAwareIncludes(b.text, textContains));
1535
+ }
1536
+ const simplified = blocks.map(simplifyBlockForAgent);
1537
+ // Build query echo so the agent knows what filters were applied
1538
+ const appliedFilters = [];
1539
+ if (blockType)
1540
+ appliedFilters.push(`type=${blockType}`);
1541
+ if (propKey)
1542
+ appliedFilters.push(`${propKey}=${propValue}`);
1543
+ if (textContains)
1544
+ appliedFilters.push(`text~"${textContains}"`);
1545
+ return JSON.stringify({
1546
+ success: true,
1547
+ roomId,
1548
+ query: appliedFilters.join(' AND ') || '(all blocks)',
1549
+ count: simplified.length,
1550
+ blocks: simplified,
1551
+ }, null, 2);
1552
+ }
1553
+ catch (error) {
1554
+ return JSON.stringify({
1555
+ success: false,
1556
+ error: error instanceof Error ? error.message : String(error),
1557
+ });
1558
+ }
1559
+ finally {
1560
+ await providerManager.dispose();
1561
+ }
1562
+ }, {
1563
+ name: 'search_blocks',
1564
+ description: `Search blocks by type, property value, or text content. Filters combine with AND logic.
1565
+
1566
+ Examples:
1567
+ - Find all proposals: {"blockType": "proposal"}
1568
+ - Find executed blocks: {"propKey": "status", "propValue": "executed"}
1569
+ - Find blocks mentioning "KYC": {"textContains": "KYC"}
1570
+ - Combine: {"blockType": "checkbox", "propKey": "checked", "propValue": "true"}`,
1571
+ schema: z.object({
1572
+ blockType: z
1573
+ .string()
1574
+ .optional()
1575
+ .nullable()
1576
+ .describe('Filter by block type (proposal, checkbox, form, etc.)'),
1577
+ propKey: z
1578
+ .string()
1579
+ .optional()
1580
+ .nullable()
1581
+ .describe('Property key to search on (e.g., "status", "title")'),
1582
+ propValue: z
1583
+ .string()
1584
+ .optional()
1585
+ .nullable()
1586
+ .describe('Property value to match (exact string match)'),
1587
+ textContains: z
1588
+ .string()
1589
+ .optional()
1590
+ .nullable()
1591
+ .describe('Search text content of blocks (case-insensitive substring match)'),
1592
+ }),
1593
+ });
1594
+ // ============================================================================
1595
+ // Tool 14: Execute Action (flow engine integration)
1596
+ // ============================================================================
1597
+ /**
1598
+ * Executes an action block through the flow engine pipeline:
1599
+ * activation → authorization → execution → runtime state update.
1600
+ *
1601
+ * Supports: http.request, email.send, notification.push,
1602
+ * human.checkbox.set, form.submit, protocol.select
1603
+ */
1604
+ const executeActionTool = tool(async ({ blockId, inputOverrides = {} }) => {
1605
+ Logger.log(`⚡ execute_action tool invoked for block: ${blockId}`);
1606
+ const isInRoom = await checkIfInRoomAndJoinPublicRoom(matrixClient, roomId);
1607
+ if (!isInRoom) {
1608
+ return JSON.stringify({
1609
+ success: false,
1610
+ error: `Companion is not in the room ${roomId}, please invite companion to the room. companion user id: ${matrixClient.getUserId()}`,
1611
+ });
1612
+ }
1613
+ const providerManager = new MatrixProviderManager(matrixClient, config);
1614
+ try {
1615
+ const { doc } = await providerManager.init();
1616
+ // 1. Verify this is a flow document (not a template)
1617
+ const flowMeta = readFlowMetadata(doc);
1618
+ if (flowMeta['_type'] !== 'ixo.flow.crdt') {
1619
+ return JSON.stringify({
1620
+ success: false,
1621
+ error: 'execute_action is only supported on flow documents, not templates.',
1622
+ });
1623
+ }
1624
+ // 2. Read the block and extract actionType
1625
+ const blockDetail = getBlockDetail(doc, blockId, false);
1626
+ if (!blockDetail) {
1627
+ return JSON.stringify({
1628
+ success: false,
1629
+ error: `Block "${blockId}" not found.`,
1630
+ });
1631
+ }
1632
+ const blockProps = extractBlockProperties(blockDetail);
1633
+ const actionType = blockProps.actionType;
1634
+ if (!actionType) {
1635
+ return JSON.stringify({
1636
+ success: false,
1637
+ error: `Block "${blockId}" is not an action block (no actionType property).`,
1638
+ });
1639
+ }
1640
+ // 3. Look up registered action
1641
+ const actionDef = getAction(actionType);
1642
+ if (!actionDef) {
1643
+ const available = getAllActions().map((a) => a.type);
1644
+ return JSON.stringify({
1645
+ success: false,
1646
+ error: `Unknown action type "${actionType}". Available: ${available.join(', ')}`,
1647
+ });
1648
+ }
1649
+ // 4. Parse inputs from block props and merge with overrides
1650
+ let inputs = {};
1651
+ if (blockProps.inputs) {
1652
+ try {
1653
+ inputs =
1654
+ typeof blockProps.inputs === 'string'
1655
+ ? JSON.parse(blockProps.inputs)
1656
+ : blockProps.inputs;
1657
+ }
1658
+ catch {
1659
+ inputs = {};
1660
+ }
1661
+ }
1662
+ if (inputOverrides && Object.keys(inputOverrides).length > 0) {
1663
+ inputs = { ...inputs, ...inputOverrides };
1664
+ }
1665
+ // 5. Resolve {{blockId.prop}} references in input values
1666
+ const allBlocks = collectAllBlocks(doc.getXmlFragment('document'));
1667
+ for (const [key, val] of Object.entries(inputs)) {
1668
+ if (typeof val === 'string' &&
1669
+ val.includes('{{') &&
1670
+ val.includes('}}')) {
1671
+ inputs[key] = resolveBlockReferences(val, allBlocks);
1672
+ }
1673
+ }
1674
+ // 6. Build FlowNode from block
1675
+ const flowNode = buildFlowNodeFromBlock({
1676
+ id: blockId,
1677
+ type: blockDetail.blockType || 'action',
1678
+ props: blockProps,
1679
+ });
1680
+ // 7. Build runtime state manager from Y.Doc
1681
+ const runtimeManager = createYDocRuntimeManager(doc);
1682
+ // 8. Derive oracle DID from Matrix user ID
1683
+ // Matrix format: @did-ixo-ixo1abc123:mx.server.com → did:ixo:ixo1abc123
1684
+ const actorDid = matrixUserIdToDid(config.matrix.userId ?? '');
1685
+ const flowId = flowMeta.doc_id ?? roomId;
1686
+ // 9. Execute through the flow engine (V1 — no UCAN invocation for MVP)
1687
+ // executeNode handles: activation check → authorization check → action() → runtime update
1688
+ const outcome = await executeNode({
1689
+ node: flowNode,
1690
+ actorDid,
1691
+ context: {
1692
+ runtime: runtimeManager,
1693
+ },
1694
+ action: async () => {
1695
+ const result = await actionDef.run(inputs, {
1696
+ actorDid,
1697
+ flowId,
1698
+ nodeId: blockId,
1699
+ services: oracleActionServices,
1700
+ });
1701
+ return { payload: result.output };
1702
+ },
1703
+ });
1704
+ // Supplement runtime with V1 lifecycle fields + action output
1705
+ // (executeNode's updateRuntimeAfterSuccess only writes legacy compat fields)
1706
+ if (outcome.success && outcome.result) {
1707
+ runtimeManager.update(blockId, {
1708
+ state: 'completed',
1709
+ output: outcome.result.payload,
1710
+ executedByDid: actorDid,
1711
+ executedAt: Date.now(),
1712
+ });
1713
+ }
1714
+ else if (!outcome.success) {
1715
+ runtimeManager.update(blockId, {
1716
+ state: 'failed',
1717
+ error: {
1718
+ message: outcome.error ?? 'Unknown error',
1719
+ at: Date.now(),
1720
+ },
1721
+ });
1722
+ }
1723
+ // Include the final runtime state so the agent doesn't need a separate read
1724
+ const finalRuntime = runtimeManager.get(blockId);
1725
+ return JSON.stringify({
1726
+ success: outcome.success,
1727
+ blockId,
1728
+ actionType,
1729
+ stage: outcome.stage,
1730
+ message: outcome.success
1731
+ ? `Action ${actionType} completed successfully`
1732
+ : `Action ${actionType} failed at stage: ${outcome.stage}`,
1733
+ ...(outcome.error && { error: outcome.error }),
1734
+ ...(outcome.result && { result: outcome.result }),
1735
+ runtimeState: finalRuntime,
1736
+ });
1737
+ }
1738
+ catch (error) {
1739
+ Logger.error('Error executing action:', error);
1740
+ return JSON.stringify({
1741
+ success: false,
1742
+ blockId,
1743
+ error: error instanceof Error ? error.message : String(error),
1744
+ });
1745
+ }
1746
+ finally {
1747
+ await providerManager.dispose();
1748
+ }
1749
+ }, {
1750
+ name: 'execute_action',
1751
+ description: `Executes an action block through the flow engine pipeline.
1752
+
1753
+ **Flow engine gates:** activation → authorization → execution → runtime state update
1754
+
1755
+ **Supported actions:** http.request, email.send, notification.push, human.checkbox.set, form.submit, protocol.select
1756
+
1757
+ **Usage:**
1758
+ - Pass the blockId of an action block (a block with an \`actionType\` property)
1759
+ - Optionally provide inputOverrides to override/supplement the block's stored inputs
1760
+ - The tool resolves \`{{blockId.prop}}\` references in inputs automatically
1761
+ - Returns the execution outcome including success/failure, stage reached, and result data
1762
+
1763
+ **Example:**
1764
+ \`\`\`json
1765
+ {"blockId": "550e8400-e29b-41d4-a716-446655440000"}
1766
+ \`\`\`
1767
+
1768
+ **With input overrides:**
1769
+ \`\`\`json
1770
+ {"blockId": "550e8400-e29b-41d4-a716-446655440000", "inputOverrides": {"url": "https://api.example.com/data"}}
1771
+ \`\`\`
1772
+
1773
+ **Returns:**
1774
+ \`\`\`json
1775
+ {
1776
+ "success": true,
1777
+ "stage": "execution",
1778
+ "result": {"status": 200, "data": {...}},
1779
+ "blockId": "...",
1780
+ "actionType": "http.request"
1781
+ }
1782
+ \`\`\``,
1783
+ schema: z.object({
1784
+ blockId: z
1785
+ .string()
1786
+ .describe('The exact ID of the action block to execute (must have actionType property)'),
1787
+ inputOverrides: z
1788
+ .record(z.any(), z.any())
1789
+ .optional()
1790
+ .default({})
1791
+ .describe('Optional: override or supplement the block\'s stored inputs. Example: {"url": "https://..."}'),
1792
+ }),
1793
+ });
1794
+ // ============================================================================
1795
+ // Tool 15: Find and Replace
1796
+ // ============================================================================
1797
+ const findAndReplaceTool = tool(async ({ searchText, replaceText, caseSensitive = true, replaceAll = true, }) => {
1798
+ Logger.log(`🔄 find_and_replace tool invoked: "${searchText}" → "${replaceText}"`);
1799
+ const isInRoom = await checkIfInRoomAndJoinPublicRoom(matrixClient, roomId);
1800
+ if (!isInRoom) {
1801
+ return JSON.stringify({
1802
+ success: false,
1803
+ error: `Companion is not in the room ${roomId}, please invite companion to the room. companion user id: ${matrixClient.getUserId()}`,
1804
+ });
1805
+ }
1806
+ const providerManager = new MatrixProviderManager(matrixClient, config);
1807
+ try {
1808
+ const { doc } = await providerManager.init();
1809
+ // Try the original search text first
1810
+ let result = findAndReplaceInDoc(doc, {
1811
+ searchText,
1812
+ replaceText,
1813
+ caseSensitive,
1814
+ replaceAll,
1815
+ docName: 'document',
1816
+ });
1817
+ // If no matches, try the emoji-normalised form (shortcode → emoji or emoji → shortcode)
1818
+ if (!result.success && textContainsEmoji(searchText)) {
1819
+ const emojified = emojify(searchText);
1820
+ const unemojified = unemojify(searchText);
1821
+ const altSearch = emojified !== searchText ? emojified : unemojified;
1822
+ if (altSearch !== searchText) {
1823
+ result = findAndReplaceInDoc(doc, {
1824
+ searchText: altSearch,
1825
+ replaceText,
1826
+ caseSensitive,
1827
+ replaceAll,
1828
+ docName: 'document',
1829
+ });
1830
+ }
1831
+ }
1832
+ return JSON.stringify({
1833
+ success: result.success,
1834
+ message: result.success
1835
+ ? `Replaced ${result.replacementCount} occurrence(s) across ${result.affectedBlockIds.length} block(s)`
1836
+ : `No occurrences of "${searchText}" found`,
1837
+ replacementCount: result.replacementCount,
1838
+ affectedBlockIds: result.affectedBlockIds,
1839
+ });
1840
+ }
1841
+ catch (error) {
1842
+ Logger.error('Error in find and replace:', error);
1843
+ return JSON.stringify({
1844
+ success: false,
1845
+ error: error instanceof Error ? error.message : String(error),
1846
+ });
1847
+ }
1848
+ finally {
1849
+ await providerManager.dispose();
1850
+ }
1851
+ }, {
1852
+ name: 'find_and_replace',
1853
+ description: `Finds and replaces text across all blocks in the document. All replacements happen atomically in a single transaction.
1854
+
1855
+ **Examples:**
1856
+ - Replace all: \`{"searchText": "old text", "replaceText": "new text"}\`
1857
+ - Case-insensitive: \`{"searchText": "OLD", "replaceText": "new", "caseSensitive": false}\`
1858
+ - Replace first only: \`{"searchText": "duplicate", "replaceText": "unique", "replaceAll": false}\`
1859
+
1860
+ **Returns:** Count of replacements and IDs of affected blocks.`,
1861
+ schema: z.object({
1862
+ searchText: z.string().describe('The text to search for'),
1863
+ replaceText: z.string().describe('The text to replace matches with'),
1864
+ caseSensitive: z
1865
+ .boolean()
1866
+ .optional()
1867
+ .default(true)
1868
+ .describe('Whether the search is case-sensitive (default: true)'),
1869
+ replaceAll: z
1870
+ .boolean()
1871
+ .optional()
1872
+ .default(true)
1873
+ .describe('Whether to replace all occurrences or just the first (default: true)'),
1874
+ }),
1875
+ });
1876
+ // ============================================================================
1877
+ // Tool 16: Move Block
1878
+ // ============================================================================
1879
+ const moveBlockTool = tool(async ({ blockId, referenceBlockId, placement }) => {
1880
+ Logger.log(`↕️ move_block tool invoked: move ${blockId} ${placement} ${referenceBlockId}`);
1881
+ const isInRoom = await checkIfInRoomAndJoinPublicRoom(matrixClient, roomId);
1882
+ if (!isInRoom) {
1883
+ return JSON.stringify({
1884
+ success: false,
1885
+ error: `Companion is not in the room ${roomId}, please invite companion to the room. companion user id: ${matrixClient.getUserId()}`,
1886
+ });
1887
+ }
1888
+ const providerManager = new MatrixProviderManager(matrixClient, config);
1889
+ try {
1890
+ const { doc } = await providerManager.init();
1891
+ const snapshot = moveBlock(doc, {
1892
+ blockId,
1893
+ referenceBlockId,
1894
+ placement,
1895
+ docName: 'document',
1896
+ });
1897
+ const movedBlock = getBlockDetail(doc, snapshot.id, true);
1898
+ const simplified = movedBlock
1899
+ ? simplifyBlockForAgent(movedBlock)
1900
+ : null;
1901
+ // Get new position
1902
+ const fragment = doc.getXmlFragment('document');
1903
+ const allBlocks = collectAllBlocks(fragment);
1904
+ const newPosition = allBlocks.findIndex((b) => b.id === blockId);
1905
+ return JSON.stringify({
1906
+ success: true,
1907
+ blockId,
1908
+ blockType: simplified?.type,
1909
+ newPosition: newPosition >= 0 ? newPosition : undefined,
1910
+ message: `Moved ${simplified?.type || 'block'} ${placement} block ${referenceBlockId}`,
1911
+ block: simplified || snapshot,
1912
+ });
1913
+ }
1914
+ catch (error) {
1915
+ Logger.error('Error moving block:', error);
1916
+ return JSON.stringify({
1917
+ success: false,
1918
+ blockId,
1919
+ error: error instanceof Error ? error.message : String(error),
1920
+ });
1921
+ }
1922
+ finally {
1923
+ await providerManager.dispose();
1924
+ }
1925
+ }, {
1926
+ name: 'move_block',
1927
+ description: `Moves a block to a new position relative to another block. Preserves block ID, content, and runtime state.
1928
+
1929
+ **Usage:**
1930
+ 1. Call \`list_blocks\` to get block IDs
1931
+ 2. Specify the block to move and the reference block
1932
+
1933
+ **Example:**
1934
+ \`{"blockId": "uuid-to-move", "referenceBlockId": "uuid-target", "placement": "before"}\``,
1935
+ schema: z.object({
1936
+ blockId: z.string().describe('The ID of the block to move'),
1937
+ referenceBlockId: z
1938
+ .string()
1939
+ .describe('The ID of the block to position relative to'),
1940
+ placement: z
1941
+ .enum(['before', 'after'])
1942
+ .describe('Place the moved block "before" or "after" the reference block'),
1943
+ }),
1944
+ });
1945
+ // ============================================================================
1946
+ // Tool 17: Bulk Edit Blocks
1947
+ // ============================================================================
1948
+ const bulkEditBlocksTool = tool(async ({ edits }) => {
1949
+ Logger.log(`📦 bulk_edit_blocks tool invoked for ${edits.length} edit(s)`);
1950
+ const isInRoom = await checkIfInRoomAndJoinPublicRoom(matrixClient, roomId);
1951
+ if (!isInRoom) {
1952
+ return JSON.stringify({
1953
+ success: false,
1954
+ error: `Companion is not in the room ${roomId}, please invite companion to the room. companion user id: ${matrixClient.getUserId()}`,
1955
+ });
1956
+ }
1957
+ const providerManager = new MatrixProviderManager(matrixClient, config);
1958
+ try {
1959
+ const { doc } = await providerManager.init();
1960
+ const results = [];
1961
+ doc.transact(() => {
1962
+ for (const edit of edits) {
1963
+ try {
1964
+ // Apply property updates
1965
+ if (edit.updates &&
1966
+ Object.keys(edit.updates).length > 0) {
1967
+ const attributes = {
1968
+ props: edit.updates,
1969
+ };
1970
+ editBlock(doc, {
1971
+ blockId: edit.blockId,
1972
+ attributes,
1973
+ docName: 'document',
1974
+ });
1975
+ }
1976
+ // Apply text update
1977
+ if (typeof edit.text === 'string') {
1978
+ editBlock(doc, {
1979
+ blockId: edit.blockId,
1980
+ text: edit.text,
1981
+ docName: 'document',
1982
+ });
1983
+ }
1984
+ // Apply runtime updates
1985
+ if (edit.runtimeUpdates &&
1986
+ Object.keys(edit.runtimeUpdates)
1987
+ .length > 0) {
1988
+ updateRuntimeState(doc, edit.blockId, edit.runtimeUpdates);
1989
+ }
1990
+ // Track what was updated per edit
1991
+ const updatedFields = [];
1992
+ if (edit.updates)
1993
+ updatedFields.push(...Object.keys(edit.updates));
1994
+ if (typeof edit.text === 'string')
1995
+ updatedFields.push('text');
1996
+ if (edit.runtimeUpdates)
1997
+ updatedFields.push(...Object.keys(edit.runtimeUpdates).map((k) => `runtime.${k}`));
1998
+ results.push({
1999
+ blockId: edit.blockId,
2000
+ success: true,
2001
+ updatedFields,
2002
+ });
2003
+ }
2004
+ catch (error) {
2005
+ results.push({
2006
+ blockId: edit.blockId,
2007
+ success: false,
2008
+ updatedFields: [],
2009
+ error: error instanceof Error ? error.message : String(error),
2010
+ });
2011
+ }
2012
+ }
2013
+ }, 'blocknote-crdt-playground');
2014
+ const successCount = results.filter((r) => r.success).length;
2015
+ const failCount = results.filter((r) => !r.success).length;
2016
+ return JSON.stringify({
2017
+ success: failCount === 0,
2018
+ message: `${successCount}/${edits.length} edit(s) succeeded${failCount > 0 ? `, ${failCount} failed` : ''}`,
2019
+ totalEdits: edits.length,
2020
+ successCount,
2021
+ failCount,
2022
+ results,
2023
+ });
2024
+ }
2025
+ catch (error) {
2026
+ Logger.error('Error in bulk edit:', error);
2027
+ return JSON.stringify({
2028
+ success: false,
2029
+ error: error instanceof Error ? error.message : String(error),
2030
+ });
2031
+ }
2032
+ finally {
2033
+ await providerManager.dispose();
2034
+ }
2035
+ }, {
2036
+ name: 'bulk_edit_blocks',
2037
+ description: `Edits multiple blocks in a single atomic transaction. Much more efficient than calling edit_block multiple times — uses one provider init/dispose cycle and one Y.js transaction.
2038
+
2039
+ **Usage:**
2040
+ \`\`\`json
2041
+ {
2042
+ "edits": [
2043
+ {"blockId": "uuid-1", "updates": {"status": "open"}},
2044
+ {"blockId": "uuid-2", "text": "Updated text"},
2045
+ {"blockId": "uuid-3", "updates": {"title": "New"}, "runtimeUpdates": {"state": "completed"}}
2046
+ ]
2047
+ }
2048
+ \`\`\`
2049
+
2050
+ **Features:**
2051
+ - Single transaction for all edits (atomic)
2052
+ - Partial success allowed — individual failures don't block other edits
2053
+ - Each edit can include: \`updates\` (properties), \`text\`, \`runtimeUpdates\`
2054
+
2055
+ **Returns:** Per-edit results with success/failure status.`,
2056
+ schema: z.object({
2057
+ edits: z
2058
+ .array(z.object({
2059
+ blockId: z.string().describe('The ID of the block to edit'),
2060
+ updates: z
2061
+ .record(z.any(), z.any())
2062
+ .optional()
2063
+ .describe('Property updates as key-value pairs'),
2064
+ text: z
2065
+ .string()
2066
+ .optional()
2067
+ .describe('New text content for the block'),
2068
+ runtimeUpdates: z
2069
+ .record(z.any(), z.any())
2070
+ .optional()
2071
+ .describe('Runtime state updates to merge'),
2072
+ }))
2073
+ .describe('Array of block edits to apply'),
2074
+ }),
2075
+ });
2076
+ // ============================================================================
2077
+ // Return tools based on mode
2078
+ // ============================================================================
2079
+ if (readOnly) {
2080
+ return {
2081
+ listBlocksTool,
2082
+ readBlockByIdTool,
2083
+ searchBlocksTool,
2084
+ readFlowContextTool,
2085
+ readFlowStatusTool,
2086
+ readBlockHistoryTool,
2087
+ readPermissionsTool,
2088
+ readSurveyTool,
2089
+ validateSurveyAnswersTool,
2090
+ };
2091
+ }
2092
+ return {
2093
+ listBlocksTool,
2094
+ editBlockTool,
2095
+ createBlockTool,
2096
+ deleteBlockTool,
2097
+ readBlockByIdTool,
2098
+ searchBlocksTool,
2099
+ readFlowContextTool,
2100
+ readFlowStatusTool,
2101
+ readBlockHistoryTool,
2102
+ readPermissionsTool,
2103
+ readSurveyTool,
2104
+ fillSurveyAnswersTool,
2105
+ validateSurveyAnswersTool,
2106
+ executeActionTool,
2107
+ findAndReplaceTool,
2108
+ moveBlockTool,
2109
+ bulkEditBlocksTool,
2110
+ };
2111
+ };
2112
+ const checkIfInRoomAndJoinPublicRoom = async (matrixClient, roomId) => {
2113
+ const joinRuleEvent = await matrixClient.getStateEvent(roomId, 'm.room.join_rules', '');
2114
+ const joinRule = joinRuleEvent.join_rule;
2115
+ const isPublicRoom = joinRule === 'public';
2116
+ const isInRoom = matrixClient.getRoom(roomId)?.getMember(matrixClient.getUserId() ?? '')
2117
+ ?.membership === 'join';
2118
+ if (!isPublicRoom && !isInRoom) {
2119
+ await matrixClient.joinRoom(roomId);
2120
+ Logger.log(`Joined room ${roomId}`);
2121
+ }
2122
+ return isInRoom;
2123
+ };