studiograph 1.3.48-next.26 → 1.3.48-next.261

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 (1640) hide show
  1. package/README.md +27 -17
  2. package/dist/agent/agent-pool.d.ts +120 -4
  3. package/dist/agent/agent-pool.js +358 -18
  4. package/dist/agent/agent-pool.js.map +1 -1
  5. package/dist/agent/context-window.d.ts +113 -0
  6. package/dist/agent/context-window.js +459 -0
  7. package/dist/agent/context-window.js.map +1 -0
  8. package/dist/agent/followups.d.ts +34 -0
  9. package/dist/agent/followups.js +52 -0
  10. package/dist/agent/followups.js.map +1 -0
  11. package/dist/agent/orchestrator.d.ts +110 -11
  12. package/dist/agent/orchestrator.js +547 -75
  13. package/dist/agent/orchestrator.js.map +1 -1
  14. package/dist/agent/prompts/entity-types-section.d.ts +18 -9
  15. package/dist/agent/prompts/entity-types-section.js +42 -18
  16. package/dist/agent/prompts/entity-types-section.js.map +1 -1
  17. package/dist/agent/prompts/system.md +107 -19
  18. package/dist/agent/skill-loader.js +3 -9
  19. package/dist/agent/skill-loader.js.map +1 -1
  20. package/dist/agent/skills/artifact-canvas/skill.md +257 -0
  21. package/dist/agent/skills/create-skill.md +39 -0
  22. package/dist/agent/skills/entity-schema.md +67 -150
  23. package/dist/agent/skills/interview/skill.md +44 -29
  24. package/dist/agent/skills/paged-documents/skill.md +147 -0
  25. package/dist/agent/skills/schema-rules.md +28 -28
  26. package/dist/agent/tools/artifact-tools.d.ts +237 -0
  27. package/dist/agent/tools/artifact-tools.js +407 -0
  28. package/dist/agent/tools/artifact-tools.js.map +1 -0
  29. package/dist/agent/tools/capture-tools.d.ts +31 -0
  30. package/dist/agent/tools/capture-tools.js +40 -0
  31. package/dist/agent/tools/capture-tools.js.map +1 -0
  32. package/dist/agent/tools/folder-tools.d.ts +50 -0
  33. package/dist/agent/tools/folder-tools.js +340 -0
  34. package/dist/agent/tools/folder-tools.js.map +1 -0
  35. package/dist/agent/tools/fs-tools.d.ts +6 -5
  36. package/dist/agent/tools/fs-tools.js +1 -1
  37. package/dist/agent/tools/fs-tools.js.map +1 -1
  38. package/dist/agent/tools/graph-tools.d.ts +45 -73
  39. package/dist/agent/tools/graph-tools.js +765 -239
  40. package/dist/agent/tools/graph-tools.js.map +1 -1
  41. package/dist/agent/tools/history-tools.d.ts +95 -0
  42. package/dist/agent/tools/history-tools.js +465 -0
  43. package/dist/agent/tools/history-tools.js.map +1 -0
  44. package/dist/agent/tools/load-skill.d.ts +4 -3
  45. package/dist/agent/tools/load-skill.js +1 -1
  46. package/dist/agent/tools/load-skill.js.map +1 -1
  47. package/dist/agent/tools/ops-tools.d.ts +2 -1
  48. package/dist/agent/tools/ops-tools.js +125 -25
  49. package/dist/agent/tools/ops-tools.js.map +1 -1
  50. package/dist/agent/tools/permission-tools.d.ts +69 -21
  51. package/dist/agent/tools/permission-tools.js +183 -18
  52. package/dist/agent/tools/permission-tools.js.map +1 -1
  53. package/dist/agent/tools/tool-loader.js +5 -4
  54. package/dist/agent/tools/tool-loader.js.map +1 -1
  55. package/dist/agent/tools/web-tools.js +58 -9
  56. package/dist/agent/tools/web-tools.js.map +1 -1
  57. package/dist/cli/commands/about.d.ts +1 -0
  58. package/dist/cli/commands/about.js +55 -3
  59. package/dist/cli/commands/about.js.map +1 -1
  60. package/dist/cli/commands/admin/audit-workspace.d.ts +23 -0
  61. package/dist/cli/commands/admin/audit-workspace.js +133 -0
  62. package/dist/cli/commands/admin/audit-workspace.js.map +1 -0
  63. package/dist/cli/commands/admin/audit.d.ts +21 -0
  64. package/dist/cli/commands/admin/audit.js +174 -0
  65. package/dist/cli/commands/admin/audit.js.map +1 -0
  66. package/dist/cli/commands/admin/backup.d.ts +54 -0
  67. package/dist/cli/commands/admin/backup.js +207 -0
  68. package/dist/cli/commands/admin/backup.js.map +1 -0
  69. package/dist/cli/commands/admin/folder.d.ts +11 -0
  70. package/dist/cli/commands/admin/folder.js +187 -0
  71. package/dist/cli/commands/admin/folder.js.map +1 -0
  72. package/dist/cli/commands/admin/index.d.ts +16 -0
  73. package/dist/cli/commands/admin/index.js +48 -0
  74. package/dist/cli/commands/admin/index.js.map +1 -0
  75. package/dist/cli/commands/admin/migrate-asset-owners.d.ts +56 -0
  76. package/dist/cli/commands/admin/migrate-asset-owners.js +86 -0
  77. package/dist/cli/commands/admin/migrate-asset-owners.js.map +1 -0
  78. package/dist/cli/commands/admin/migrate-assets.d.ts +62 -0
  79. package/dist/cli/commands/admin/migrate-assets.js +237 -0
  80. package/dist/cli/commands/admin/migrate-assets.js.map +1 -0
  81. package/dist/cli/commands/admin/migrate.d.ts +56 -0
  82. package/dist/cli/commands/admin/migrate.js +263 -0
  83. package/dist/cli/commands/admin/migrate.js.map +1 -0
  84. package/dist/cli/commands/admin/restore.d.ts +24 -0
  85. package/dist/cli/commands/admin/restore.js +228 -0
  86. package/dist/cli/commands/admin/restore.js.map +1 -0
  87. package/dist/cli/commands/admin/secrets.d.ts +23 -0
  88. package/dist/cli/commands/admin/secrets.js +256 -0
  89. package/dist/cli/commands/admin/secrets.js.map +1 -0
  90. package/dist/cli/commands/admin/settings.d.ts +28 -0
  91. package/dist/cli/commands/admin/settings.js +284 -0
  92. package/dist/cli/commands/admin/settings.js.map +1 -0
  93. package/dist/cli/commands/admin/token.d.ts +42 -0
  94. package/dist/cli/commands/admin/token.js +126 -0
  95. package/dist/cli/commands/admin/token.js.map +1 -0
  96. package/dist/cli/commands/admin/transfer-ownership.d.ts +15 -0
  97. package/dist/cli/commands/admin/transfer-ownership.js +106 -0
  98. package/dist/cli/commands/admin/transfer-ownership.js.map +1 -0
  99. package/dist/cli/commands/admin/user.d.ts +11 -0
  100. package/dist/cli/commands/admin/user.js +187 -0
  101. package/dist/cli/commands/admin/user.js.map +1 -0
  102. package/dist/cli/commands/clone.d.ts +23 -3
  103. package/dist/cli/commands/clone.js +140 -36
  104. package/dist/cli/commands/clone.js.map +1 -1
  105. package/dist/cli/commands/config.d.ts +23 -107
  106. package/dist/cli/commands/config.js +52 -260
  107. package/dist/cli/commands/config.js.map +1 -1
  108. package/dist/cli/commands/connector.d.ts +10 -13
  109. package/dist/cli/commands/connector.js +16 -58
  110. package/dist/cli/commands/connector.js.map +1 -1
  111. package/dist/cli/commands/deploy.js +215 -68
  112. package/dist/cli/commands/deploy.js.map +1 -1
  113. package/dist/cli/commands/index.js +7 -4
  114. package/dist/cli/commands/index.js.map +1 -1
  115. package/dist/cli/commands/init.js +10 -30
  116. package/dist/cli/commands/init.js.map +1 -1
  117. package/dist/cli/commands/mcp.js +54 -6
  118. package/dist/cli/commands/mcp.js.map +1 -1
  119. package/dist/cli/commands/r2.d.ts +3 -0
  120. package/dist/cli/commands/r2.js +223 -204
  121. package/dist/cli/commands/r2.js.map +1 -1
  122. package/dist/cli/commands/redeploy.js +65 -12
  123. package/dist/cli/commands/redeploy.js.map +1 -1
  124. package/dist/cli/commands/reset.d.ts +22 -6
  125. package/dist/cli/commands/reset.js +132 -34
  126. package/dist/cli/commands/reset.js.map +1 -1
  127. package/dist/cli/commands/serve.js +242 -24
  128. package/dist/cli/commands/serve.js.map +1 -1
  129. package/dist/cli/commands/sync.d.ts +1 -1
  130. package/dist/cli/commands/sync.js +230 -227
  131. package/dist/cli/commands/sync.js.map +1 -1
  132. package/dist/cli/crypto-bundle.d.ts +39 -0
  133. package/dist/cli/crypto-bundle.js +94 -0
  134. package/dist/cli/crypto-bundle.js.map +1 -0
  135. package/dist/cli/index.js +24 -23
  136. package/dist/cli/index.js.map +1 -1
  137. package/dist/cli/railway-pin.d.ts +68 -0
  138. package/dist/cli/railway-pin.js +96 -0
  139. package/dist/cli/railway-pin.js.map +1 -0
  140. package/dist/cli/railway-provisioning.d.ts +53 -0
  141. package/dist/cli/railway-provisioning.js +85 -0
  142. package/dist/cli/railway-provisioning.js.map +1 -0
  143. package/dist/cli/setup-wizard.d.ts +30 -49
  144. package/dist/cli/setup-wizard.js +39 -40
  145. package/dist/cli/setup-wizard.js.map +1 -1
  146. package/dist/cli/theme.d.ts +1 -1
  147. package/dist/core/accent-palette.d.ts +7 -18
  148. package/dist/core/accent-palette.js +27 -38
  149. package/dist/core/accent-palette.js.map +1 -1
  150. package/dist/core/artifact-asset-resolution.d.ts +90 -0
  151. package/dist/core/artifact-asset-resolution.js +138 -0
  152. package/dist/core/artifact-asset-resolution.js.map +1 -0
  153. package/dist/core/artifact-asset-urls.d.ts +111 -0
  154. package/dist/core/artifact-asset-urls.js +174 -0
  155. package/dist/core/artifact-asset-urls.js.map +1 -0
  156. package/dist/core/artifact-bundle.d.ts +69 -0
  157. package/dist/core/artifact-bundle.js +305 -0
  158. package/dist/core/artifact-bundle.js.map +1 -0
  159. package/dist/core/artifact-escape.d.ts +5 -0
  160. package/dist/core/artifact-escape.js +26 -0
  161. package/dist/core/artifact-escape.js.map +1 -0
  162. package/dist/core/artifact-export.d.ts +83 -0
  163. package/dist/core/artifact-export.js +1567 -0
  164. package/dist/core/artifact-export.js.map +1 -0
  165. package/dist/core/artifact-frame-document.d.ts +105 -0
  166. package/dist/core/artifact-frame-document.js +1427 -0
  167. package/dist/core/artifact-frame-document.js.map +1 -0
  168. package/dist/core/artifact-frame-layout.d.ts +79 -0
  169. package/dist/core/artifact-frame-layout.js +94 -0
  170. package/dist/core/artifact-frame-layout.js.map +1 -0
  171. package/dist/core/artifact-frame-model.d.ts +198 -0
  172. package/dist/core/artifact-frame-model.js +603 -0
  173. package/dist/core/artifact-frame-model.js.map +1 -0
  174. package/dist/core/artifact-frame-selectors.d.ts +42 -0
  175. package/dist/core/artifact-frame-selectors.js +65 -0
  176. package/dist/core/artifact-frame-selectors.js.map +1 -0
  177. package/dist/core/artifact-libraries.d.ts +23 -0
  178. package/dist/core/artifact-libraries.js +229 -0
  179. package/dist/core/artifact-libraries.js.map +1 -0
  180. package/dist/core/artifact-manifest.d.ts +64 -0
  181. package/dist/core/artifact-manifest.js +88 -0
  182. package/dist/core/artifact-manifest.js.map +1 -0
  183. package/dist/core/artifact-paged-css.d.ts +64 -0
  184. package/dist/core/artifact-paged-css.js +253 -0
  185. package/dist/core/artifact-paged-css.js.map +1 -0
  186. package/dist/core/artifact-patch.d.ts +58 -0
  187. package/dist/core/artifact-patch.js +309 -0
  188. package/dist/core/artifact-patch.js.map +1 -0
  189. package/dist/core/artifact-warnings.d.ts +51 -0
  190. package/dist/core/artifact-warnings.js +165 -0
  191. package/dist/core/artifact-warnings.js.map +1 -0
  192. package/dist/core/breakpoints.d.ts +27 -0
  193. package/dist/core/breakpoints.js +28 -0
  194. package/dist/core/breakpoints.js.map +1 -0
  195. package/dist/core/cell-anchor.d.ts +29 -0
  196. package/dist/core/cell-anchor.js +53 -0
  197. package/dist/core/cell-anchor.js.map +1 -0
  198. package/dist/core/comment-anchor.d.ts +41 -0
  199. package/dist/core/comment-anchor.js +147 -0
  200. package/dist/core/comment-anchor.js.map +1 -0
  201. package/dist/core/commit-messages.d.ts +57 -0
  202. package/dist/core/commit-messages.js +85 -0
  203. package/dist/core/commit-messages.js.map +1 -0
  204. package/dist/core/embeds.d.ts +108 -0
  205. package/dist/core/embeds.js +226 -0
  206. package/dist/core/embeds.js.map +1 -0
  207. package/dist/core/entity-body-templates.d.ts +21 -0
  208. package/dist/core/entity-body-templates.js +66 -0
  209. package/dist/core/entity-body-templates.js.map +1 -0
  210. package/dist/core/entity-types-catalog.d.ts +65 -0
  211. package/dist/core/entity-types-catalog.js +142 -0
  212. package/dist/core/entity-types-catalog.js.map +1 -0
  213. package/dist/core/field-library.d.ts +62 -0
  214. package/dist/core/field-library.js +129 -0
  215. package/dist/core/field-library.js.map +1 -0
  216. package/dist/core/folder-paths.d.ts +41 -0
  217. package/dist/core/folder-paths.js +95 -0
  218. package/dist/core/folder-paths.js.map +1 -0
  219. package/dist/core/folder-sidecar.d.ts +36 -0
  220. package/dist/core/folder-sidecar.js +91 -0
  221. package/dist/core/folder-sidecar.js.map +1 -0
  222. package/dist/core/font-family.d.ts +62 -0
  223. package/dist/core/font-family.js +84 -0
  224. package/dist/core/font-family.js.map +1 -0
  225. package/dist/core/format-registry.d.ts +187 -0
  226. package/dist/core/format-registry.js +411 -0
  227. package/dist/core/format-registry.js.map +1 -0
  228. package/dist/core/graph.d.ts +770 -30
  229. package/dist/core/graph.js +2282 -386
  230. package/dist/core/graph.js.map +1 -1
  231. package/dist/core/history-diff.d.ts +35 -0
  232. package/dist/core/history-diff.js +114 -0
  233. package/dist/core/history-diff.js.map +1 -0
  234. package/dist/core/markdown-strip.d.ts +22 -0
  235. package/dist/core/markdown-strip.js +51 -0
  236. package/dist/core/markdown-strip.js.map +1 -0
  237. package/dist/core/refs.d.ts +41 -0
  238. package/dist/core/refs.js +80 -0
  239. package/dist/core/refs.js.map +1 -0
  240. package/dist/core/schema-registry.d.ts +66 -0
  241. package/dist/core/schema-registry.js +198 -37
  242. package/dist/core/schema-registry.js.map +1 -1
  243. package/dist/core/schemas/connector.d.ts +67 -0
  244. package/dist/core/schemas/connector.js +100 -0
  245. package/dist/core/schemas/connector.js.map +1 -0
  246. package/dist/core/schemas/workspace.d.ts +122 -0
  247. package/dist/core/schemas/workspace.js +211 -0
  248. package/dist/core/schemas/workspace.js.map +1 -0
  249. package/dist/core/secrets/SecretStore.d.ts +77 -0
  250. package/dist/core/secrets/SecretStore.js +13 -0
  251. package/dist/core/secrets/SecretStore.js.map +1 -0
  252. package/dist/core/secrets/SettingsResolver.d.ts +54 -0
  253. package/dist/core/secrets/SettingsResolver.js +80 -0
  254. package/dist/core/secrets/SettingsResolver.js.map +1 -0
  255. package/dist/core/secrets/SettingsStore.d.ts +30 -0
  256. package/dist/core/secrets/SettingsStore.js +9 -0
  257. package/dist/core/secrets/SettingsStore.js.map +1 -0
  258. package/dist/core/secrets/SqliteEncryptedStore.d.ts +90 -0
  259. package/dist/core/secrets/SqliteEncryptedStore.js +598 -0
  260. package/dist/core/secrets/SqliteEncryptedStore.js.map +1 -0
  261. package/dist/core/secrets/audit.d.ts +36 -0
  262. package/dist/core/secrets/audit.js +60 -0
  263. package/dist/core/secrets/audit.js.map +1 -0
  264. package/dist/core/secrets/auth-db-migration.d.ts +129 -0
  265. package/dist/core/secrets/auth-db-migration.js +653 -0
  266. package/dist/core/secrets/auth-db-migration.js.map +1 -0
  267. package/dist/core/secrets/bundle.d.ts +141 -0
  268. package/dist/core/secrets/bundle.js +435 -0
  269. package/dist/core/secrets/bundle.js.map +1 -0
  270. package/dist/core/secrets/dual-write.d.ts +81 -0
  271. package/dist/core/secrets/dual-write.js +247 -0
  272. package/dist/core/secrets/dual-write.js.map +1 -0
  273. package/dist/core/secrets/encryption.d.ts +44 -0
  274. package/dist/core/secrets/encryption.js +97 -0
  275. package/dist/core/secrets/encryption.js.map +1 -0
  276. package/dist/core/secrets/index.d.ts +49 -0
  277. package/dist/core/secrets/index.js +74 -0
  278. package/dist/core/secrets/index.js.map +1 -0
  279. package/dist/core/secrets/master-key.d.ts +38 -0
  280. package/dist/core/secrets/master-key.js +122 -0
  281. package/dist/core/secrets/master-key.js.map +1 -0
  282. package/dist/core/secrets/probes/anthropic.d.ts +98 -0
  283. package/dist/core/secrets/probes/anthropic.js +300 -0
  284. package/dist/core/secrets/probes/anthropic.js.map +1 -0
  285. package/dist/core/secrets/probes/brave.d.ts +9 -0
  286. package/dist/core/secrets/probes/brave.js +15 -0
  287. package/dist/core/secrets/probes/brave.js.map +1 -0
  288. package/dist/core/secrets/probes/r2.d.ts +15 -0
  289. package/dist/core/secrets/probes/r2.js +14 -0
  290. package/dist/core/secrets/probes/r2.js.map +1 -0
  291. package/dist/core/secrets/probes/tavily.d.ts +18 -0
  292. package/dist/core/secrets/probes/tavily.js +58 -0
  293. package/dist/core/secrets/probes/tavily.js.map +1 -0
  294. package/dist/core/secrets/probes/voyage.d.ts +13 -0
  295. package/dist/core/secrets/probes/voyage.js +52 -0
  296. package/dist/core/secrets/probes/voyage.js.map +1 -0
  297. package/dist/core/secrets/r2-structural-migration.d.ts +39 -0
  298. package/dist/core/secrets/r2-structural-migration.js +109 -0
  299. package/dist/core/secrets/r2-structural-migration.js.map +1 -0
  300. package/dist/core/secrets/read-flip.d.ts +59 -0
  301. package/dist/core/secrets/read-flip.js +87 -0
  302. package/dist/core/secrets/read-flip.js.map +1 -0
  303. package/dist/core/secrets/registry.d.ts +237 -0
  304. package/dist/core/secrets/registry.js +710 -0
  305. package/dist/core/secrets/registry.js.map +1 -0
  306. package/dist/core/seed-folder.d.ts +59 -0
  307. package/dist/core/seed-folder.js +76 -0
  308. package/dist/core/seed-folder.js.map +1 -0
  309. package/dist/core/types.d.ts +88 -480
  310. package/dist/core/types.js +53 -6
  311. package/dist/core/types.js.map +1 -1
  312. package/dist/core/user-config.d.ts +81 -14
  313. package/dist/core/user-config.js +147 -17
  314. package/dist/core/user-config.js.map +1 -1
  315. package/dist/core/validation.d.ts +828 -1472
  316. package/dist/core/validation.js +475 -163
  317. package/dist/core/validation.js.map +1 -1
  318. package/dist/core/view-scope.d.ts +88 -0
  319. package/dist/core/view-scope.js +71 -0
  320. package/dist/core/view-scope.js.map +1 -0
  321. package/dist/core/workspace-manager.d.ts +156 -11
  322. package/dist/core/workspace-manager.js +364 -92
  323. package/dist/core/workspace-manager.js.map +1 -1
  324. package/dist/core/workspace.d.ts +19 -5
  325. package/dist/core/workspace.js +72 -56
  326. package/dist/core/workspace.js.map +1 -1
  327. package/dist/mcp/comment-virtual-entity.d.ts +36 -0
  328. package/dist/mcp/comment-virtual-entity.js +71 -0
  329. package/dist/mcp/comment-virtual-entity.js.map +1 -0
  330. package/dist/mcp/connector-manager.d.ts +72 -2
  331. package/dist/mcp/connector-manager.js +211 -32
  332. package/dist/mcp/connector-manager.js.map +1 -1
  333. package/dist/mcp/oauth-provider.d.ts +0 -5
  334. package/dist/mcp/oauth-provider.js +31 -25
  335. package/dist/mcp/oauth-provider.js.map +1 -1
  336. package/dist/mcp/oauth-registry.d.ts +46 -8
  337. package/dist/mcp/oauth-registry.js +52 -13
  338. package/dist/mcp/oauth-registry.js.map +1 -1
  339. package/dist/mcp/server-discovery.d.ts +73 -0
  340. package/dist/mcp/server-discovery.js +164 -0
  341. package/dist/mcp/server-discovery.js.map +1 -0
  342. package/dist/mcp/server.d.ts +24 -3
  343. package/dist/mcp/server.js +53 -10
  344. package/dist/mcp/server.js.map +1 -1
  345. package/dist/mcp/state-signer.d.ts +62 -0
  346. package/dist/mcp/state-signer.js +205 -0
  347. package/dist/mcp/state-signer.js.map +1 -0
  348. package/dist/mcp/stdio-proxy.d.ts +67 -0
  349. package/dist/mcp/stdio-proxy.js +149 -0
  350. package/dist/mcp/stdio-proxy.js.map +1 -0
  351. package/dist/mcp/tools.d.ts +73 -2
  352. package/dist/mcp/tools.js +2289 -115
  353. package/dist/mcp/tools.js.map +1 -1
  354. package/dist/server/__tests__/helpers/graph-api.d.ts +17 -0
  355. package/dist/server/__tests__/helpers/graph-api.js +17 -0
  356. package/dist/server/__tests__/helpers/graph-api.js.map +1 -0
  357. package/dist/server/__tests__/helpers/multipart.d.ts +9 -0
  358. package/dist/server/__tests__/helpers/multipart.js +14 -0
  359. package/dist/server/__tests__/helpers/multipart.js.map +1 -0
  360. package/dist/server/artifact-asset-mint.d.ts +59 -0
  361. package/dist/server/artifact-asset-mint.js +127 -0
  362. package/dist/server/artifact-asset-mint.js.map +1 -0
  363. package/dist/server/asset-index-queue.d.ts +98 -0
  364. package/dist/server/asset-index-queue.js +193 -0
  365. package/dist/server/asset-index-queue.js.map +1 -0
  366. package/dist/server/body-write-coordinator.d.ts +161 -0
  367. package/dist/server/body-write-coordinator.js +182 -0
  368. package/dist/server/body-write-coordinator.js.map +1 -0
  369. package/dist/server/cloud-billing-flow.d.ts +32 -0
  370. package/dist/server/cloud-billing-flow.js +75 -0
  371. package/dist/server/cloud-billing-flow.js.map +1 -0
  372. package/dist/server/commit-audit.d.ts +26 -0
  373. package/dist/server/commit-audit.js +30 -0
  374. package/dist/server/commit-audit.js.map +1 -0
  375. package/dist/server/index.d.ts +50 -1
  376. package/dist/server/index.js +811 -229
  377. package/dist/server/index.js.map +1 -1
  378. package/dist/server/middleware/sanitize.d.ts +46 -0
  379. package/dist/server/middleware/sanitize.js +195 -0
  380. package/dist/server/middleware/sanitize.js.map +1 -0
  381. package/dist/server/process-guards.d.ts +37 -0
  382. package/dist/server/process-guards.js +44 -0
  383. package/dist/server/process-guards.js.map +1 -0
  384. package/dist/server/routes/ai-usage-api.d.ts +24 -0
  385. package/dist/server/routes/ai-usage-api.js +68 -0
  386. package/dist/server/routes/ai-usage-api.js.map +1 -0
  387. package/dist/server/routes/artifact-export-api.d.ts +11 -0
  388. package/dist/server/routes/artifact-export-api.js +180 -0
  389. package/dist/server/routes/artifact-export-api.js.map +1 -0
  390. package/dist/server/routes/artifact-patch-api.d.ts +19 -0
  391. package/dist/server/routes/artifact-patch-api.js +114 -0
  392. package/dist/server/routes/artifact-patch-api.js.map +1 -0
  393. package/dist/server/routes/artifact-selection-api.d.ts +15 -0
  394. package/dist/server/routes/artifact-selection-api.js +90 -0
  395. package/dist/server/routes/artifact-selection-api.js.map +1 -0
  396. package/dist/server/routes/asset-api.d.ts +88 -2
  397. package/dist/server/routes/asset-api.js +858 -112
  398. package/dist/server/routes/asset-api.js.map +1 -1
  399. package/dist/server/routes/audit-api.d.ts +24 -0
  400. package/dist/server/routes/audit-api.js +113 -0
  401. package/dist/server/routes/audit-api.js.map +1 -0
  402. package/dist/server/routes/auth-api.js +536 -44
  403. package/dist/server/routes/auth-api.js.map +1 -1
  404. package/dist/server/routes/capture-api.js +81 -9
  405. package/dist/server/routes/capture-api.js.map +1 -1
  406. package/dist/server/routes/chat-sessions-api.d.ts +13 -0
  407. package/dist/server/routes/chat-sessions-api.js +462 -0
  408. package/dist/server/routes/chat-sessions-api.js.map +1 -0
  409. package/dist/server/routes/chat.d.ts +35 -1
  410. package/dist/server/routes/chat.js +858 -68
  411. package/dist/server/routes/chat.js.map +1 -1
  412. package/dist/server/routes/comments-api.d.ts +15 -0
  413. package/dist/server/routes/comments-api.js +444 -0
  414. package/dist/server/routes/comments-api.js.map +1 -0
  415. package/dist/server/routes/config-api.d.ts +3 -2
  416. package/dist/server/routes/config-api.js +183 -57
  417. package/dist/server/routes/config-api.js.map +1 -1
  418. package/dist/server/routes/connectors-api.js +178 -45
  419. package/dist/server/routes/connectors-api.js.map +1 -1
  420. package/dist/server/routes/event-ingest-api.d.ts +15 -0
  421. package/dist/server/routes/event-ingest-api.js +125 -0
  422. package/dist/server/routes/event-ingest-api.js.map +1 -0
  423. package/dist/server/routes/field-library-api.d.ts +28 -0
  424. package/dist/server/routes/field-library-api.js +126 -0
  425. package/dist/server/routes/field-library-api.js.map +1 -0
  426. package/dist/server/routes/git-http.d.ts +3 -3
  427. package/dist/server/routes/git-http.js +90 -30
  428. package/dist/server/routes/git-http.js.map +1 -1
  429. package/dist/server/routes/graph-api-access.d.ts +63 -0
  430. package/dist/server/routes/graph-api-access.js +115 -0
  431. package/dist/server/routes/graph-api-access.js.map +1 -0
  432. package/dist/server/routes/graph-api.d.ts +2 -3
  433. package/dist/server/routes/graph-api.js +1341 -423
  434. package/dist/server/routes/graph-api.js.map +1 -1
  435. package/dist/server/routes/health.d.ts +18 -0
  436. package/dist/server/routes/health.js +116 -0
  437. package/dist/server/routes/health.js.map +1 -0
  438. package/dist/server/routes/import-batch.d.ts +24 -0
  439. package/dist/server/routes/import-batch.js +33 -0
  440. package/dist/server/routes/import-batch.js.map +1 -0
  441. package/dist/server/routes/insights-api.d.ts +9 -2
  442. package/dist/server/routes/insights-api.js +352 -66
  443. package/dist/server/routes/insights-api.js.map +1 -1
  444. package/dist/server/routes/mcp.d.ts +8 -3
  445. package/dist/server/routes/mcp.js +24 -8
  446. package/dist/server/routes/mcp.js.map +1 -1
  447. package/dist/server/routes/oauth-api.d.ts +67 -0
  448. package/dist/server/routes/oauth-api.js +421 -0
  449. package/dist/server/routes/oauth-api.js.map +1 -0
  450. package/dist/server/routes/permissions-api.d.ts +8 -3
  451. package/dist/server/routes/permissions-api.js +43 -12
  452. package/dist/server/routes/permissions-api.js.map +1 -1
  453. package/dist/server/routes/r2-api.d.ts +25 -0
  454. package/dist/server/routes/r2-api.js +273 -0
  455. package/dist/server/routes/r2-api.js.map +1 -0
  456. package/dist/server/routes/secrets-api.d.ts +36 -0
  457. package/dist/server/routes/secrets-api.js +305 -0
  458. package/dist/server/routes/secrets-api.js.map +1 -0
  459. package/dist/server/routes/settings-api.d.ts +29 -0
  460. package/dist/server/routes/settings-api.js +177 -0
  461. package/dist/server/routes/settings-api.js.map +1 -0
  462. package/dist/server/routes/share-api.d.ts +30 -6
  463. package/dist/server/routes/share-api.js +240 -31
  464. package/dist/server/routes/share-api.js.map +1 -1
  465. package/dist/server/routes/views-api.d.ts +11 -2
  466. package/dist/server/routes/views-api.js +123 -22
  467. package/dist/server/routes/views-api.js.map +1 -1
  468. package/dist/server/routes/workspace-api.d.ts +2 -1
  469. package/dist/server/routes/workspace-api.js +150 -23
  470. package/dist/server/routes/workspace-api.js.map +1 -1
  471. package/dist/server/routes/ws.d.ts +2 -1
  472. package/dist/server/routes/ws.js +73 -20
  473. package/dist/server/routes/ws.js.map +1 -1
  474. package/dist/server/session-manager.d.ts +48 -5
  475. package/dist/server/session-manager.js +182 -14
  476. package/dist/server/session-manager.js.map +1 -1
  477. package/dist/server/ws-hub.d.ts +135 -56
  478. package/dist/server/ws-hub.js +0 -0
  479. package/dist/server/ws-hub.js.map +1 -1
  480. package/dist/server/yjs-manager.d.ts +275 -18
  481. package/dist/server/yjs-manager.js +819 -70
  482. package/dist/server/yjs-manager.js.map +1 -1
  483. package/dist/server/yjs-persistence.d.ts +165 -0
  484. package/dist/server/yjs-persistence.js +557 -0
  485. package/dist/server/yjs-persistence.js.map +1 -0
  486. package/dist/server/yjs-text-diff.d.ts +32 -0
  487. package/dist/server/yjs-text-diff.js +61 -0
  488. package/dist/server/yjs-text-diff.js.map +1 -0
  489. package/dist/services/access-control.d.ts +121 -0
  490. package/dist/services/access-control.js +194 -0
  491. package/dist/services/access-control.js.map +1 -0
  492. package/dist/services/ai-usage-ledger.d.ts +75 -0
  493. package/dist/services/ai-usage-ledger.js +250 -0
  494. package/dist/services/ai-usage-ledger.js.map +1 -0
  495. package/dist/services/artifact-asset-token.d.ts +69 -0
  496. package/dist/services/artifact-asset-token.js +94 -0
  497. package/dist/services/artifact-asset-token.js.map +1 -0
  498. package/dist/services/artifact-inspect-metadata.d.ts +21 -0
  499. package/dist/services/artifact-inspect-metadata.js +66 -0
  500. package/dist/services/artifact-inspect-metadata.js.map +1 -0
  501. package/dist/services/artifact-library-sources.d.ts +33 -0
  502. package/dist/services/artifact-library-sources.js +91 -0
  503. package/dist/services/artifact-library-sources.js.map +1 -0
  504. package/dist/services/artifact-patch-runner.d.ts +52 -0
  505. package/dist/services/artifact-patch-runner.js +74 -0
  506. package/dist/services/artifact-patch-runner.js.map +1 -0
  507. package/dist/services/artifact-render-export.d.ts +230 -0
  508. package/dist/services/artifact-render-export.js +1003 -0
  509. package/dist/services/artifact-render-export.js.map +1 -0
  510. package/dist/services/artifact-selection-store.d.ts +68 -0
  511. package/dist/services/artifact-selection-store.js +90 -0
  512. package/dist/services/artifact-selection-store.js.map +1 -0
  513. package/dist/services/artifact-store.d.ts +118 -0
  514. package/dist/services/artifact-store.js +234 -0
  515. package/dist/services/artifact-store.js.map +1 -0
  516. package/dist/services/asset-caption-service.d.ts +103 -0
  517. package/dist/services/asset-caption-service.js +200 -0
  518. package/dist/services/asset-caption-service.js.map +1 -0
  519. package/dist/services/asset-delete-authz.d.ts +32 -0
  520. package/dist/services/asset-delete-authz.js +62 -0
  521. package/dist/services/asset-delete-authz.js.map +1 -0
  522. package/dist/services/asset-delete-impact.d.ts +63 -0
  523. package/dist/services/asset-delete-impact.js +96 -0
  524. package/dist/services/asset-delete-impact.js.map +1 -0
  525. package/dist/services/asset-finalize-service.d.ts +58 -0
  526. package/dist/services/asset-finalize-service.js +216 -0
  527. package/dist/services/asset-finalize-service.js.map +1 -0
  528. package/dist/services/asset-import-service.d.ts +116 -0
  529. package/dist/services/asset-import-service.js +436 -0
  530. package/dist/services/asset-import-service.js.map +1 -0
  531. package/dist/services/asset-index-sweep.d.ts +95 -0
  532. package/dist/services/asset-index-sweep.js +271 -0
  533. package/dist/services/asset-index-sweep.js.map +1 -0
  534. package/dist/services/asset-lifecycle-check.d.ts +22 -0
  535. package/dist/services/asset-lifecycle-check.js +80 -0
  536. package/dist/services/asset-lifecycle-check.js.map +1 -0
  537. package/dist/services/asset-listing.d.ts +63 -0
  538. package/dist/services/asset-listing.js +124 -0
  539. package/dist/services/asset-listing.js.map +1 -0
  540. package/dist/services/asset-move-impact.d.ts +49 -0
  541. package/dist/services/asset-move-impact.js +62 -0
  542. package/dist/services/asset-move-impact.js.map +1 -0
  543. package/dist/services/asset-presign-service.d.ts +62 -0
  544. package/dist/services/asset-presign-service.js +126 -0
  545. package/dist/services/asset-presign-service.js.map +1 -0
  546. package/dist/services/asset-reference-index.d.ts +39 -0
  547. package/dist/services/asset-reference-index.js +33 -0
  548. package/dist/services/asset-reference-index.js.map +1 -0
  549. package/dist/services/asset-reference-scan.d.ts +57 -0
  550. package/dist/services/asset-reference-scan.js +150 -0
  551. package/dist/services/asset-reference-scan.js.map +1 -0
  552. package/dist/services/asset-sidecar-service.d.ts +28 -0
  553. package/dist/services/asset-sidecar-service.js +79 -0
  554. package/dist/services/asset-sidecar-service.js.map +1 -0
  555. package/dist/services/asset-upload-errors.d.ts +41 -0
  556. package/dist/services/asset-upload-errors.js +45 -0
  557. package/dist/services/asset-upload-errors.js.map +1 -0
  558. package/dist/services/asset-upload-service.d.ts +56 -0
  559. package/dist/services/asset-upload-service.js +200 -0
  560. package/dist/services/asset-upload-service.js.map +1 -0
  561. package/dist/services/asset-upload-token.d.ts +58 -0
  562. package/dist/services/asset-upload-token.js +75 -0
  563. package/dist/services/asset-upload-token.js.map +1 -0
  564. package/dist/services/assets/asset-index-service.d.ts +158 -0
  565. package/dist/services/assets/asset-index-service.js +293 -0
  566. package/dist/services/assets/asset-index-service.js.map +1 -0
  567. package/dist/services/assets/base.d.ts +119 -4
  568. package/dist/services/assets/base.js +148 -3
  569. package/dist/services/assets/base.js.map +1 -1
  570. package/dist/services/assets/index.d.ts +90 -0
  571. package/dist/services/assets/index.js +202 -0
  572. package/dist/services/assets/index.js.map +1 -1
  573. package/dist/services/assets/local.d.ts +9 -0
  574. package/dist/services/assets/local.js +30 -4
  575. package/dist/services/assets/local.js.map +1 -1
  576. package/dist/services/assets/r2-sync.d.ts +88 -0
  577. package/dist/services/assets/r2-sync.js +412 -0
  578. package/dist/services/assets/r2-sync.js.map +1 -0
  579. package/dist/services/assets/r2.d.ts +80 -0
  580. package/dist/services/assets/r2.js +159 -4
  581. package/dist/services/assets/r2.js.map +1 -1
  582. package/dist/services/auth-service.d.ts +278 -51
  583. package/dist/services/auth-service.js +995 -272
  584. package/dist/services/auth-service.js.map +1 -1
  585. package/dist/services/chat-session-service.d.ts +189 -0
  586. package/dist/services/chat-session-service.js +544 -0
  587. package/dist/services/chat-session-service.js.map +1 -0
  588. package/dist/services/cloud-inference-billing.d.ts +64 -0
  589. package/dist/services/cloud-inference-billing.js +321 -0
  590. package/dist/services/cloud-inference-billing.js.map +1 -0
  591. package/dist/services/comment-service.d.ts +97 -0
  592. package/dist/services/comment-service.js +341 -0
  593. package/dist/services/comment-service.js.map +1 -0
  594. package/dist/services/comment-virtual-entity.d.ts +36 -0
  595. package/dist/services/comment-virtual-entity.js +71 -0
  596. package/dist/services/comment-virtual-entity.js.map +1 -0
  597. package/dist/services/convert-service.d.ts +64 -0
  598. package/dist/services/convert-service.js +68 -0
  599. package/dist/services/convert-service.js.map +1 -0
  600. package/dist/services/csv-service.d.ts +22 -17
  601. package/dist/services/csv-service.js +55 -92
  602. package/dist/services/csv-service.js.map +1 -1
  603. package/dist/services/entity-mutations.d.ts +313 -0
  604. package/dist/services/entity-mutations.js +531 -0
  605. package/dist/services/entity-mutations.js.map +1 -0
  606. package/dist/services/event-processor.d.ts +104 -0
  607. package/dist/services/event-processor.js +451 -0
  608. package/dist/services/event-processor.js.map +1 -0
  609. package/dist/services/extract/docx.d.ts +1 -0
  610. package/dist/services/extract/docx.js +233 -0
  611. package/dist/services/extract/docx.js.map +1 -0
  612. package/dist/services/extract/index.d.ts +9 -0
  613. package/dist/services/extract/index.js +10 -0
  614. package/dist/services/extract/index.js.map +1 -0
  615. package/dist/services/extract/pdf.d.ts +13 -0
  616. package/dist/services/extract/pdf.js +69 -0
  617. package/dist/services/extract/pdf.js.map +1 -0
  618. package/dist/services/folder-creation.d.ts +33 -0
  619. package/dist/services/folder-creation.js +103 -0
  620. package/dist/services/folder-creation.js.map +1 -0
  621. package/dist/services/fonts/font-metadata.d.ts +42 -0
  622. package/dist/services/fonts/font-metadata.js +126 -0
  623. package/dist/services/fonts/font-metadata.js.map +1 -0
  624. package/dist/services/fonts/font-skill-service.d.ts +66 -0
  625. package/dist/services/fonts/font-skill-service.js +137 -0
  626. package/dist/services/fonts/font-skill-service.js.map +1 -0
  627. package/dist/services/git.d.ts +58 -0
  628. package/dist/services/git.js +194 -55
  629. package/dist/services/git.js.map +1 -1
  630. package/dist/services/graph-maintenance.js +4 -4
  631. package/dist/services/graph-maintenance.js.map +1 -1
  632. package/dist/services/import-service.d.ts +51 -2
  633. package/dist/services/import-service.js +254 -107
  634. package/dist/services/import-service.js.map +1 -1
  635. package/dist/services/lint-service.js +10 -17
  636. package/dist/services/lint-service.js.map +1 -1
  637. package/dist/services/markdown.d.ts +12 -1
  638. package/dist/services/markdown.js +77 -9
  639. package/dist/services/markdown.js.map +1 -1
  640. package/dist/services/mention-detector.d.ts +23 -0
  641. package/dist/services/mention-detector.js +47 -0
  642. package/dist/services/mention-detector.js.map +1 -0
  643. package/dist/services/model-capabilities.d.ts +41 -0
  644. package/dist/services/model-capabilities.js +107 -0
  645. package/dist/services/model-capabilities.js.map +1 -0
  646. package/dist/services/notification-broadcast.d.ts +32 -0
  647. package/dist/services/notification-broadcast.js +38 -0
  648. package/dist/services/notification-broadcast.js.map +1 -0
  649. package/dist/services/notification-service.d.ts +41 -0
  650. package/dist/services/notification-service.js +100 -0
  651. package/dist/services/notification-service.js.map +1 -0
  652. package/dist/services/oauth-server-store.d.ts +147 -0
  653. package/dist/services/oauth-server-store.js +319 -0
  654. package/dist/services/oauth-server-store.js.map +1 -0
  655. package/dist/services/orphan-service.js +2 -2
  656. package/dist/services/orphan-service.js.map +1 -1
  657. package/dist/services/personal-folder.d.ts +49 -0
  658. package/dist/services/personal-folder.js +122 -0
  659. package/dist/services/personal-folder.js.map +1 -0
  660. package/dist/services/playwright-bootstrap.d.ts +78 -0
  661. package/dist/services/playwright-bootstrap.js +219 -0
  662. package/dist/services/playwright-bootstrap.js.map +1 -0
  663. package/dist/services/scratch-asset-promote.d.ts +57 -0
  664. package/dist/services/scratch-asset-promote.js +95 -0
  665. package/dist/services/scratch-asset-promote.js.map +1 -0
  666. package/dist/services/scratch-attachment-service.d.ts +201 -0
  667. package/dist/services/scratch-attachment-service.js +354 -0
  668. package/dist/services/scratch-attachment-service.js.map +1 -0
  669. package/dist/services/share-link-service.d.ts +57 -0
  670. package/dist/services/share-link-service.js +142 -0
  671. package/dist/services/share-link-service.js.map +1 -0
  672. package/dist/services/user-person-bridge.d.ts +136 -0
  673. package/dist/services/user-person-bridge.js +341 -0
  674. package/dist/services/user-person-bridge.js.map +1 -0
  675. package/dist/services/utility-model-resolver.d.ts +37 -0
  676. package/dist/services/utility-model-resolver.js +67 -0
  677. package/dist/services/utility-model-resolver.js.map +1 -0
  678. package/dist/services/vector-service.d.ts +282 -5
  679. package/dist/services/vector-service.js +594 -39
  680. package/dist/services/vector-service.js.map +1 -1
  681. package/dist/services/workspace-access.d.ts +115 -0
  682. package/dist/services/workspace-access.js +154 -0
  683. package/dist/services/workspace-access.js.map +1 -0
  684. package/dist/services/workspace-assets-folder.d.ts +46 -0
  685. package/dist/services/workspace-assets-folder.js +75 -0
  686. package/dist/services/workspace-assets-folder.js.map +1 -0
  687. package/dist/utils/git.d.ts +17 -2
  688. package/dist/utils/git.js +23 -7
  689. package/dist/utils/git.js.map +1 -1
  690. package/dist/utils/log.d.ts +42 -0
  691. package/dist/utils/log.js +137 -0
  692. package/dist/utils/log.js.map +1 -0
  693. package/dist/utils/preflight.js +2 -2
  694. package/dist/utils/preflight.js.map +1 -1
  695. package/dist/utils/version-checker.d.ts +12 -19
  696. package/dist/utils/version-checker.js +14 -104
  697. package/dist/utils/version-checker.js.map +1 -1
  698. package/dist/web/_app/immutable/assets/0.B8rPC4xg.css +2 -0
  699. package/dist/web/_app/immutable/assets/12.DlIf1mKh.css +1 -0
  700. package/dist/web/_app/immutable/assets/13.WUaKJbVc.css +1 -0
  701. package/dist/web/_app/immutable/assets/15.CItnu3nw.css +1 -0
  702. package/dist/web/_app/immutable/assets/16.C3czeWWx.css +1 -0
  703. package/dist/web/_app/immutable/assets/17.CHiwoAMO.css +1 -0
  704. package/dist/web/_app/immutable/assets/18.fUGuXQ0K.css +1 -0
  705. package/dist/web/_app/immutable/assets/19.DcuK5ZVm.css +1 -0
  706. package/dist/web/_app/immutable/assets/2.bJqlVFXX.css +1 -0
  707. package/dist/web/_app/immutable/assets/3.DDfEAhbB.css +1 -0
  708. package/dist/web/_app/immutable/assets/4.Dtodt1Fe.css +1 -0
  709. package/dist/web/_app/immutable/assets/6.JPVpkzLZ.css +1 -0
  710. package/dist/web/_app/immutable/assets/AssetLibraryPicker.BWH260L8.css +1 -0
  711. package/dist/web/_app/immutable/assets/BulkActionsBar.CXSM7tO7.css +1 -0
  712. package/dist/web/_app/immutable/assets/CalendarView.Cch9MMQW.css +1 -0
  713. package/dist/web/_app/immutable/assets/CodeMirrorEditor.D6hf2pNJ.css +1 -0
  714. package/dist/web/_app/immutable/assets/CopyField.DVGZtw4U.css +1 -0
  715. package/dist/web/_app/immutable/assets/FolderMembersPanel.BvV1lBCg.css +1 -0
  716. package/dist/web/_app/immutable/assets/FolderMutationDialogs.De2NQ55E.css +1 -0
  717. package/dist/web/_app/immutable/assets/FolderPicker.Cq1tMnu_.css +1 -0
  718. package/dist/web/_app/immutable/assets/FolderTreeField.DTSzn15s.css +1 -0
  719. package/dist/web/_app/immutable/assets/FrameCanvas.Clh5Xdr7.css +1 -0
  720. package/dist/web/_app/immutable/assets/GalleryView.Cqu3-wtr.css +1 -0
  721. package/dist/web/_app/immutable/assets/GraphView.BJtGL9py.css +1 -0
  722. package/dist/web/_app/immutable/assets/GridCell.czY6eDb_.css +1 -0
  723. package/dist/web/_app/immutable/assets/KanbanView.C5X8TbQk.css +1 -0
  724. package/dist/web/_app/immutable/assets/Link.vV0ne105.css +1 -0
  725. package/dist/web/_app/immutable/assets/Markdown.CSYKSAm0.css +1 -0
  726. package/dist/web/_app/immutable/assets/Rail.Dyxy_E0O.css +1 -0
  727. package/dist/web/_app/immutable/assets/RepoCheckList.BLeJ79Eg.css +1 -0
  728. package/dist/web/_app/immutable/assets/SaveViewDialog.Bb9E81xd.css +1 -0
  729. package/dist/web/_app/immutable/assets/SearchInput.DTB6W35f.css +1 -0
  730. package/dist/web/_app/immutable/assets/SettingsDialog.CHzfQoo_.css +1 -0
  731. package/dist/web/_app/immutable/assets/Spinner.UBfl0pab.css +1 -0
  732. package/dist/web/_app/immutable/assets/StopFilledAlt.CjuwgVxu.css +1 -0
  733. package/dist/web/_app/immutable/assets/TimelineView.u4YqiztR.css +1 -0
  734. package/dist/web/_app/immutable/assets/Version.BQq1Fihu.css +1 -0
  735. package/dist/web/_app/immutable/assets/ViewBanner.DWswnFdi.css +1 -0
  736. package/dist/web/_app/immutable/assets/WorkspaceView.Cd6TS6oo.css +1 -0
  737. package/dist/web/_app/immutable/assets/button.DOg1smbV.css +1 -0
  738. package/dist/web/_app/immutable/assets/checkbox.CAcHq8G6.css +1 -0
  739. package/dist/web/_app/immutable/assets/download-with-progress.BZlFTn09.css +1 -0
  740. package/dist/web/_app/immutable/assets/filters.RgfXKVpA.css +1 -0
  741. package/dist/web/_app/immutable/assets/ibm-plex-mono-latin-400-normal.CvHOgSBP.woff +0 -0
  742. package/dist/web/_app/immutable/assets/ibm-plex-mono-latin-400-normal.DMJ8VG8y.woff2 +0 -0
  743. package/dist/web/_app/immutable/assets/ibm-plex-mono-latin-500-normal.CB9ihrfo.woff +0 -0
  744. package/dist/web/_app/immutable/assets/ibm-plex-mono-latin-500-normal.DSY6xOcd.woff2 +0 -0
  745. package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-400-italic.CZTNEAuW.woff2 +0 -0
  746. package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-400-italic.CsGl1sm0.woff +0 -0
  747. package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-400-normal.CDDApCn2.woff2 +0 -0
  748. package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-400-normal.CYLoc0-x.woff +0 -0
  749. package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-500-italic.BNK2_mGO.woff2 +0 -0
  750. package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-500-italic.DpEwFAQM.woff +0 -0
  751. package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-500-normal.6ng42L7E.woff2 +0 -0
  752. package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-500-normal.BgVn5rGT.woff +0 -0
  753. package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-600-normal.Cu4Hd6ag.woff +0 -0
  754. package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-600-normal.CuJfVYMP.woff2 +0 -0
  755. package/dist/web/_app/immutable/assets/list-columns.BgnafZ4K.css +1 -0
  756. package/dist/web/_app/immutable/chunks/-MYqXlc3.js +185 -0
  757. package/dist/web/_app/immutable/chunks/-Tbr-96c2.js +1 -0
  758. package/dist/web/_app/immutable/chunks/1QRy7-wo.js +1 -0
  759. package/dist/web/_app/immutable/chunks/1_BWKAKA2.js +23 -0
  760. package/dist/web/_app/immutable/chunks/2z2ekE6D2.js +2 -0
  761. package/dist/web/_app/immutable/chunks/6guf_P8c.js +1 -0
  762. package/dist/web/_app/immutable/chunks/7l950wkn.js +1 -0
  763. package/dist/web/_app/immutable/chunks/9Sn2PwSn2.js +1025 -0
  764. package/dist/web/_app/immutable/chunks/AFZSy-Vk.js +2 -0
  765. package/dist/web/_app/immutable/chunks/B06uE1qJ.js +1 -0
  766. package/dist/web/_app/immutable/chunks/B2cjR0rG.js +83 -0
  767. package/dist/web/_app/immutable/chunks/B3u4QMoU.js +1 -0
  768. package/dist/web/_app/immutable/chunks/B4PrlkmE.js +1 -0
  769. package/dist/web/_app/immutable/chunks/B5UH1FNe.js +3 -0
  770. package/dist/web/_app/immutable/chunks/B5sxjq1L2.js +1 -0
  771. package/dist/web/_app/immutable/chunks/B6Lt-qaJ.js +1 -0
  772. package/dist/web/_app/immutable/chunks/B6wo7Stk2.js +1 -0
  773. package/dist/web/_app/immutable/chunks/B9KfEsQw2.js +1 -0
  774. package/dist/web/_app/immutable/chunks/B9MY422N.js +1 -0
  775. package/dist/web/_app/immutable/chunks/B9VQbssY.js +3 -0
  776. package/dist/web/_app/immutable/chunks/BBrP9GiZ.js +1 -0
  777. package/dist/web/_app/immutable/chunks/BEGTD2EL2.js +1 -0
  778. package/dist/web/_app/immutable/chunks/BEyPhM3C.js +1 -0
  779. package/dist/web/_app/immutable/chunks/BIeOkmiV.js +1 -0
  780. package/dist/web/_app/immutable/chunks/BIwe25Lk.js +1 -0
  781. package/dist/web/_app/immutable/chunks/BO6-nRV9.js +1 -0
  782. package/dist/web/_app/immutable/chunks/BPILs-6H.js +1 -0
  783. package/dist/web/_app/immutable/chunks/BPRQzMGs.js +1 -0
  784. package/dist/web/_app/immutable/chunks/BQE7bWgF.js +1 -0
  785. package/dist/web/_app/immutable/chunks/BU5TCj7m.js +12 -0
  786. package/dist/web/_app/immutable/chunks/BZDKT2Qw2.js +2 -0
  787. package/dist/web/_app/immutable/chunks/B_y9A6Kg.js +4 -0
  788. package/dist/web/_app/immutable/chunks/BdR7t2GN.js +3 -0
  789. package/dist/web/_app/immutable/chunks/BnYohOts2.js +1 -0
  790. package/dist/web/_app/immutable/chunks/Bpmx7Umc.js +1 -0
  791. package/dist/web/_app/immutable/chunks/BqButnP4.js +1 -0
  792. package/dist/web/_app/immutable/chunks/BtDSRU36.js +1 -0
  793. package/dist/web/_app/immutable/chunks/Bvr6nSm92.js +1 -0
  794. package/dist/web/_app/immutable/chunks/BvyVm7-j.js +184 -0
  795. package/dist/web/_app/immutable/chunks/Bx6T8G5n2.js +1 -0
  796. package/dist/web/_app/immutable/chunks/BxeOdaUQ.js +1 -0
  797. package/dist/web/_app/immutable/chunks/C1B4xDs0.js +1 -0
  798. package/dist/web/_app/immutable/chunks/C2yz630b.js +2 -0
  799. package/dist/web/_app/immutable/chunks/C5YoDj-e.js +1 -0
  800. package/dist/web/_app/immutable/chunks/C6d9nn3k.js +1 -0
  801. package/dist/web/_app/immutable/chunks/C85NG5lS.js +1 -0
  802. package/dist/web/_app/immutable/chunks/C8ZiSqQm.js +1 -0
  803. package/dist/web/_app/immutable/chunks/CB-t1Bv-.js +1 -0
  804. package/dist/web/_app/immutable/chunks/CG8YKaqc.js +5 -0
  805. package/dist/web/_app/immutable/chunks/CJbr-v9X.js +1 -0
  806. package/dist/web/_app/immutable/chunks/CKGHeJzq.js +1 -0
  807. package/dist/web/_app/immutable/chunks/CKwV6HKc.js +1 -0
  808. package/dist/web/_app/immutable/chunks/CLninaDG2.js +2 -0
  809. package/dist/web/_app/immutable/chunks/CM400nLL.js +7 -0
  810. package/dist/web/_app/immutable/chunks/CPvxN2G62.js +1 -0
  811. package/dist/web/_app/immutable/chunks/CQW66fYJ.js +1 -0
  812. package/dist/web/_app/immutable/chunks/CQl_RUVN2.js +1 -0
  813. package/dist/web/_app/immutable/chunks/CR1JY_cb.js +6 -0
  814. package/dist/web/_app/immutable/chunks/CT35tdLp.js +1 -0
  815. package/dist/web/_app/immutable/chunks/CVKrGJ6C.js +1 -0
  816. package/dist/web/_app/immutable/chunks/CWIHttTH.js +1 -0
  817. package/dist/web/_app/immutable/chunks/CWZSexLW.js +1 -0
  818. package/dist/web/_app/immutable/chunks/CXPMb3zd.js +1 -0
  819. package/dist/web/_app/immutable/chunks/CZBW5wi82.js +1 -0
  820. package/dist/web/_app/immutable/chunks/CaimSvvF.js +5 -0
  821. package/dist/web/_app/immutable/chunks/CcWaWbrI2.js +1 -0
  822. package/dist/web/_app/immutable/chunks/CcgYvk_3.js +1 -0
  823. package/dist/web/_app/immutable/chunks/Cg7m1gqN.js +1 -0
  824. package/dist/web/_app/immutable/chunks/CgCz4Fj6.js +1 -0
  825. package/dist/web/_app/immutable/chunks/CgOgBK5j.js +1 -0
  826. package/dist/web/_app/immutable/chunks/ChS3kWO-.js +2 -0
  827. package/dist/web/_app/immutable/chunks/CkivrILi.js +1 -0
  828. package/dist/web/_app/immutable/chunks/CmWflMFc.js +1 -0
  829. package/dist/web/_app/immutable/chunks/Cm_yhEon2.js +1 -0
  830. package/dist/web/_app/immutable/chunks/CnoUnj6-2.js +1 -0
  831. package/dist/web/_app/immutable/chunks/CqyFdLMa.js +1 -0
  832. package/dist/web/_app/immutable/chunks/CsIWxmio2.js +22 -0
  833. package/dist/web/_app/immutable/chunks/CtgdtZ0G2.js +1 -0
  834. package/dist/web/_app/immutable/chunks/CuYL65Bn.js +2 -0
  835. package/dist/web/_app/immutable/chunks/CuatNdzp.js +2 -0
  836. package/dist/web/_app/immutable/chunks/Cx5IiiDO2.js +2 -0
  837. package/dist/web/_app/immutable/chunks/Cxdio_Al.js +2692 -0
  838. package/dist/web/_app/immutable/chunks/CyTgggiM2.js +1 -0
  839. package/dist/web/_app/immutable/chunks/D-vvXTU3.js +2 -0
  840. package/dist/web/_app/immutable/chunks/D0_Iu3Uj.js +2 -0
  841. package/dist/web/_app/immutable/chunks/D4vfNy_f.js +1 -0
  842. package/dist/web/_app/immutable/chunks/D6toh9Mr.js +1 -0
  843. package/dist/web/_app/immutable/chunks/DAsarOLg2.js +1 -0
  844. package/dist/web/_app/immutable/chunks/DBGaXAP8.js +1 -0
  845. package/dist/web/_app/immutable/chunks/DCgKWtld.js +1 -0
  846. package/dist/web/_app/immutable/chunks/DG7uGoZb2.js +58 -0
  847. package/dist/web/_app/immutable/chunks/DIsSxB_C.js +1 -0
  848. package/dist/web/_app/immutable/chunks/DJTvBXur.js +2 -0
  849. package/dist/web/_app/immutable/chunks/DLuIHCPq.js +1 -0
  850. package/dist/web/_app/immutable/chunks/DMFH9Cnt2.js +1 -0
  851. package/dist/web/_app/immutable/chunks/DNLoQniz.js +1 -0
  852. package/dist/web/_app/immutable/chunks/DPLkAijI2.js +1 -0
  853. package/dist/web/_app/immutable/chunks/DQMWa2t7.js +1 -0
  854. package/dist/web/_app/immutable/chunks/DTOO9nUj.js +1 -0
  855. package/dist/web/_app/immutable/chunks/DaB0hU8_.js +1 -0
  856. package/dist/web/_app/immutable/chunks/Dav8Lgyy2.js +5 -0
  857. package/dist/web/_app/immutable/chunks/Ddgu2Sjp.js +1 -0
  858. package/dist/web/_app/immutable/chunks/DfUXQOZ92.js +1 -0
  859. package/dist/web/_app/immutable/chunks/DgP0Pxes.js +224 -0
  860. package/dist/web/_app/immutable/chunks/DhD1HF7R.js +1 -0
  861. package/dist/web/_app/immutable/chunks/Dhmw5GUH.js +1 -0
  862. package/dist/web/_app/immutable/chunks/DiBVOY752.js +1 -0
  863. package/dist/web/_app/immutable/chunks/DiMsLIh3.js +1 -0
  864. package/dist/web/_app/immutable/chunks/DiiXREv7.js +1 -0
  865. package/dist/web/_app/immutable/chunks/DjXtgWTP.js +1 -0
  866. package/dist/web/_app/immutable/chunks/Dk2-JtmU2.js +1 -0
  867. package/dist/web/_app/immutable/chunks/Dl0bYyV_.js +1 -0
  868. package/dist/web/_app/immutable/chunks/DqKYbIld2.js +1 -0
  869. package/dist/web/_app/immutable/chunks/DtXJCaQc2.js +1 -0
  870. package/dist/web/_app/immutable/chunks/DwuBUEvU.js +1 -0
  871. package/dist/web/_app/immutable/chunks/F5xZdLlB.js +2 -0
  872. package/dist/web/_app/immutable/chunks/F92HmKX8.js +1 -0
  873. package/dist/web/_app/immutable/chunks/FISIvbqq.js +3 -0
  874. package/dist/web/_app/immutable/chunks/GC5Izn9E2.js +1 -0
  875. package/dist/web/_app/immutable/chunks/IMJutqSu2.js +1 -0
  876. package/dist/web/_app/immutable/chunks/K5YHt1WL2.js +1 -0
  877. package/dist/web/_app/immutable/chunks/MAYbL-xn2.js +6 -0
  878. package/dist/web/_app/immutable/chunks/MMtHlrry.js +1 -0
  879. package/dist/web/_app/immutable/chunks/MtN4vJa02.js +1 -0
  880. package/dist/web/_app/immutable/chunks/Oglnp7sm.js +14 -0
  881. package/dist/web/_app/immutable/chunks/PwUOTfSv.js +2 -0
  882. package/dist/web/_app/immutable/chunks/QsO256-z.js +1 -0
  883. package/dist/web/_app/immutable/chunks/RA-JwTzo.js +1 -0
  884. package/dist/web/_app/immutable/chunks/atFSAfcd2.js +2 -0
  885. package/dist/web/_app/immutable/chunks/j_uHTBnD.js +1 -0
  886. package/dist/web/_app/immutable/chunks/joKsoiFC.js +1 -0
  887. package/dist/web/_app/immutable/chunks/kNaey6uv.js +1 -0
  888. package/dist/web/_app/immutable/chunks/nqo2e2Yv.js +1 -0
  889. package/dist/web/_app/immutable/chunks/oIZupUnz.js +1 -0
  890. package/dist/web/_app/immutable/chunks/pPsy4008.js +1 -0
  891. package/dist/web/_app/immutable/chunks/qevfNcid.js +1 -0
  892. package/dist/web/_app/immutable/chunks/rjjyuGhb.js +1 -0
  893. package/dist/web/_app/immutable/chunks/sUAtda6y2.js +2 -0
  894. package/dist/web/_app/immutable/chunks/snCOoD78.js +1 -0
  895. package/dist/web/_app/immutable/chunks/uHaS74te.js +1 -0
  896. package/dist/web/_app/immutable/chunks/w5tsuIav.js +5 -0
  897. package/dist/web/_app/immutable/chunks/xihTtKlq.js +1 -0
  898. package/dist/web/_app/immutable/entry/app.1tcNTKI-.js +2 -0
  899. package/dist/web/_app/immutable/entry/start.BJMsySI1.js +1 -0
  900. package/dist/web/_app/immutable/nodes/0.v0W5DL7W.js +2 -0
  901. package/dist/web/_app/immutable/nodes/1.81Rb3Ith.js +1 -0
  902. package/dist/web/_app/immutable/nodes/10.CAlZNFOq.js +1 -0
  903. package/dist/web/_app/immutable/nodes/11.CCfUginI.js +1 -0
  904. package/dist/web/_app/immutable/nodes/12.BJLboAyk.js +1 -0
  905. package/dist/web/_app/immutable/nodes/13.Cz4UTl2G.js +1 -0
  906. package/dist/web/_app/immutable/nodes/14.DJwakC0o.js +1 -0
  907. package/dist/web/_app/immutable/nodes/15.D2mNE6C-.js +1 -0
  908. package/dist/web/_app/immutable/nodes/16.BjohAUt9.js +1 -0
  909. package/dist/web/_app/immutable/nodes/17.BI2F37bS.js +1 -0
  910. package/dist/web/_app/immutable/nodes/18.DsOT0tDo.js +1 -0
  911. package/dist/web/_app/immutable/nodes/19.BiJWLOUT.js +77 -0
  912. package/dist/web/_app/immutable/nodes/2.Cd72nG2V.js +124 -0
  913. package/dist/web/_app/immutable/nodes/3.BIT9mm9r.js +6 -0
  914. package/dist/web/_app/immutable/nodes/4.Cn7r0Poe.js +16 -0
  915. package/dist/web/_app/immutable/nodes/5.CkJNdCZG.js +1 -0
  916. package/dist/web/_app/immutable/nodes/6.1rbIRQgQ.js +6 -0
  917. package/dist/web/_app/immutable/nodes/7.w17Zl36i.js +1 -0
  918. package/dist/web/_app/immutable/nodes/8.DqTsYuU-.js +1 -0
  919. package/dist/web/_app/immutable/nodes/9.BJhYZPfc.js +1 -0
  920. package/dist/web/_app/version.json +1 -1
  921. package/dist/web/favicon-16.png +0 -0
  922. package/dist/web/favicon-180.png +0 -0
  923. package/dist/web/favicon-32.png +0 -0
  924. package/dist/web/favicon-48.png +0 -0
  925. package/dist/web/favicon-512.png +0 -0
  926. package/dist/web/favicon.ico +0 -0
  927. package/dist/web/favicon.svg +8 -0
  928. package/dist/web/index.html +59 -20
  929. package/dist/web/studiograph-logomark.svg +29 -0
  930. package/package.json +44 -15
  931. package/dist/agent/role-loader.d.ts +0 -24
  932. package/dist/agent/role-loader.js +0 -67
  933. package/dist/agent/role-loader.js.map +0 -1
  934. package/dist/agent/skills/create-deck/content-guide.md +0 -238
  935. package/dist/agent/skills/create-deck/deck-cadence.md +0 -105
  936. package/dist/agent/skills/create-deck/diagrams.md +0 -431
  937. package/dist/agent/skills/create-deck/skill.md +0 -89
  938. package/dist/agent/skills/create-deck/slide-selection.md +0 -115
  939. package/dist/agent/skills/create-deck/slide-types.md +0 -638
  940. package/dist/agent/skills/create-document/skill.md +0 -229
  941. package/dist/agent/skills/enrich-entities.md +0 -136
  942. package/dist/agent/skills/obsidian-source-setup.md +0 -246
  943. package/dist/agent/skills/skill-loader.d.ts +0 -48
  944. package/dist/agent/skills/skill-loader.js +0 -166
  945. package/dist/agent/skills/skill-loader.js.map +0 -1
  946. package/dist/agent/skills/sync-configuration.md +0 -119
  947. package/dist/agent/skills/sync-setup.md +0 -82
  948. package/dist/agent/tools/message-tools.d.ts +0 -42
  949. package/dist/agent/tools/message-tools.js +0 -96
  950. package/dist/agent/tools/message-tools.js.map +0 -1
  951. package/dist/agent/tools/sync-tools.d.ts +0 -35
  952. package/dist/agent/tools/sync-tools.js +0 -992
  953. package/dist/agent/tools/sync-tools.js.map +0 -1
  954. package/dist/auth/github.d.ts +0 -56
  955. package/dist/auth/github.js +0 -169
  956. package/dist/auth/github.js.map +0 -1
  957. package/dist/cli/commands/access.d.ts +0 -11
  958. package/dist/cli/commands/access.js +0 -156
  959. package/dist/cli/commands/access.js.map +0 -1
  960. package/dist/cli/commands/app.d.ts +0 -7
  961. package/dist/cli/commands/app.js +0 -268
  962. package/dist/cli/commands/app.js.map +0 -1
  963. package/dist/cli/commands/auth.d.ts +0 -10
  964. package/dist/cli/commands/auth.js +0 -79
  965. package/dist/cli/commands/auth.js.map +0 -1
  966. package/dist/cli/commands/clear.d.ts +0 -5
  967. package/dist/cli/commands/clear.js +0 -36
  968. package/dist/cli/commands/clear.js.map +0 -1
  969. package/dist/cli/commands/collection.d.ts +0 -10
  970. package/dist/cli/commands/collection.js +0 -185
  971. package/dist/cli/commands/collection.js.map +0 -1
  972. package/dist/cli/commands/enrich.d.ts +0 -11
  973. package/dist/cli/commands/enrich.js +0 -135
  974. package/dist/cli/commands/enrich.js.map +0 -1
  975. package/dist/cli/commands/graphrag.d.ts +0 -12
  976. package/dist/cli/commands/graphrag.js +0 -122
  977. package/dist/cli/commands/graphrag.js.map +0 -1
  978. package/dist/cli/commands/join.d.ts +0 -9
  979. package/dist/cli/commands/join.js +0 -255
  980. package/dist/cli/commands/join.js.map +0 -1
  981. package/dist/cli/commands/members.d.ts +0 -11
  982. package/dist/cli/commands/members.js +0 -230
  983. package/dist/cli/commands/members.js.map +0 -1
  984. package/dist/cli/commands/org.d.ts +0 -10
  985. package/dist/cli/commands/org.js +0 -132
  986. package/dist/cli/commands/org.js.map +0 -1
  987. package/dist/cli/commands/provision.d.ts +0 -8
  988. package/dist/cli/commands/provision.js +0 -116
  989. package/dist/cli/commands/provision.js.map +0 -1
  990. package/dist/cli/commands/resolve.d.ts +0 -8
  991. package/dist/cli/commands/resolve.js +0 -85
  992. package/dist/cli/commands/resolve.js.map +0 -1
  993. package/dist/cli/commands/review.d.ts +0 -19
  994. package/dist/cli/commands/review.js +0 -128
  995. package/dist/cli/commands/review.js.map +0 -1
  996. package/dist/cli/commands/source.d.ts +0 -16
  997. package/dist/cli/commands/source.js +0 -159
  998. package/dist/cli/commands/source.js.map +0 -1
  999. package/dist/cli/commands/start.d.ts +0 -7
  1000. package/dist/cli/commands/start.js +0 -576
  1001. package/dist/cli/commands/start.js.map +0 -1
  1002. package/dist/cli/commands/sync-collection.d.ts +0 -14
  1003. package/dist/cli/commands/sync-collection.js +0 -355
  1004. package/dist/cli/commands/sync-collection.js.map +0 -1
  1005. package/dist/cli/commands/sync-entities.d.ts +0 -13
  1006. package/dist/cli/commands/sync-entities.js +0 -242
  1007. package/dist/cli/commands/sync-entities.js.map +0 -1
  1008. package/dist/cli/commands/team.d.ts +0 -12
  1009. package/dist/cli/commands/team.js +0 -185
  1010. package/dist/cli/commands/team.js.map +0 -1
  1011. package/dist/cli/commands/update.d.ts +0 -8
  1012. package/dist/cli/commands/update.js +0 -155
  1013. package/dist/cli/commands/update.js.map +0 -1
  1014. package/dist/cli/commands/user.d.ts +0 -7
  1015. package/dist/cli/commands/user.js +0 -169
  1016. package/dist/cli/commands/user.js.map +0 -1
  1017. package/dist/cli/scaffolding.d.ts +0 -12
  1018. package/dist/cli/scaffolding.js +0 -397
  1019. package/dist/cli/scaffolding.js.map +0 -1
  1020. package/dist/cli/sync-review-interactive.d.ts +0 -31
  1021. package/dist/cli/sync-review-interactive.js +0 -393
  1022. package/dist/cli/sync-review-interactive.js.map +0 -1
  1023. package/dist/core/feature-flags.d.ts +0 -17
  1024. package/dist/core/feature-flags.js +0 -15
  1025. package/dist/core/feature-flags.js.map +0 -1
  1026. package/dist/core/features.d.ts +0 -16
  1027. package/dist/core/features.js +0 -48
  1028. package/dist/core/features.js.map +0 -1
  1029. package/dist/core/launch-entity-types.d.ts +0 -23
  1030. package/dist/core/launch-entity-types.js +0 -48
  1031. package/dist/core/launch-entity-types.js.map +0 -1
  1032. package/dist/core/migration-runner.d.ts +0 -42
  1033. package/dist/core/migration-runner.js +0 -232
  1034. package/dist/core/migration-runner.js.map +0 -1
  1035. package/dist/core/migration-types.d.ts +0 -101
  1036. package/dist/core/migration-types.js +0 -21
  1037. package/dist/core/migration-types.js.map +0 -1
  1038. package/dist/core/migrations/20260219-formalize-memory-location.d.ts +0 -2
  1039. package/dist/core/migrations/20260219-formalize-memory-location.js +0 -35
  1040. package/dist/core/migrations/20260219-formalize-memory-location.js.map +0 -1
  1041. package/dist/core/migrations/20260220-add-workspace-metadata.d.ts +0 -12
  1042. package/dist/core/migrations/20260220-add-workspace-metadata.js +0 -65
  1043. package/dist/core/migrations/20260220-add-workspace-metadata.js.map +0 -1
  1044. package/dist/core/migrations/20260220-add-workspace-readme.d.ts +0 -11
  1045. package/dist/core/migrations/20260220-add-workspace-readme.js +0 -82
  1046. package/dist/core/migrations/20260220-add-workspace-readme.js.map +0 -1
  1047. package/dist/core/migrations/20260220-migrate-yaml-to-json.d.ts +0 -9
  1048. package/dist/core/migrations/20260220-migrate-yaml-to-json.js +0 -64
  1049. package/dist/core/migrations/20260220-migrate-yaml-to-json.js.map +0 -1
  1050. package/dist/core/migrations/index.d.ts +0 -11
  1051. package/dist/core/migrations/index.js +0 -23
  1052. package/dist/core/migrations/index.js.map +0 -1
  1053. package/dist/integrations/asana.d.ts +0 -26
  1054. package/dist/integrations/asana.js +0 -78
  1055. package/dist/integrations/asana.js.map +0 -1
  1056. package/dist/integrations/figma-local.d.ts +0 -16
  1057. package/dist/integrations/figma-local.js +0 -10
  1058. package/dist/integrations/figma-local.js.map +0 -1
  1059. package/dist/integrations/figma.d.ts +0 -17
  1060. package/dist/integrations/figma.js +0 -17
  1061. package/dist/integrations/figma.js.map +0 -1
  1062. package/dist/integrations/granola.d.ts +0 -24
  1063. package/dist/integrations/granola.js +0 -60
  1064. package/dist/integrations/granola.js.map +0 -1
  1065. package/dist/integrations/linear.d.ts +0 -14
  1066. package/dist/integrations/linear.js +0 -80
  1067. package/dist/integrations/linear.js.map +0 -1
  1068. package/dist/integrations/paper-local.d.ts +0 -14
  1069. package/dist/integrations/paper-local.js +0 -10
  1070. package/dist/integrations/paper-local.js.map +0 -1
  1071. package/dist/integrations/paper.d.ts +0 -2
  1072. package/dist/integrations/paper.js +0 -10
  1073. package/dist/integrations/paper.js.map +0 -1
  1074. package/dist/integrations/pipedrive.d.ts +0 -26
  1075. package/dist/integrations/pipedrive.js +0 -97
  1076. package/dist/integrations/pipedrive.js.map +0 -1
  1077. package/dist/integrations/registry.d.ts +0 -8
  1078. package/dist/integrations/registry.js +0 -7
  1079. package/dist/integrations/registry.js.map +0 -1
  1080. package/dist/integrations/types.d.ts +0 -43
  1081. package/dist/integrations/types.js +0 -5
  1082. package/dist/integrations/types.js.map +0 -1
  1083. package/dist/lib/lib/utils.d.ts +0 -2
  1084. package/dist/lib/lib/utils.js +0 -6
  1085. package/dist/lib/lib/utils.js.map +0 -1
  1086. package/dist/mcp/connectors/asana.d.ts +0 -2
  1087. package/dist/mcp/connectors/asana.js +0 -20
  1088. package/dist/mcp/connectors/asana.js.map +0 -1
  1089. package/dist/mcp/connectors/definitions.d.ts +0 -45
  1090. package/dist/mcp/connectors/definitions.js +0 -32
  1091. package/dist/mcp/connectors/definitions.js.map +0 -1
  1092. package/dist/mcp/connectors/figma.d.ts +0 -5
  1093. package/dist/mcp/connectors/figma.js +0 -21
  1094. package/dist/mcp/connectors/figma.js.map +0 -1
  1095. package/dist/mcp/connectors/gdrive.d.ts +0 -2
  1096. package/dist/mcp/connectors/gdrive.js +0 -20
  1097. package/dist/mcp/connectors/gdrive.js.map +0 -1
  1098. package/dist/mcp/connectors/granola.d.ts +0 -2
  1099. package/dist/mcp/connectors/granola.js +0 -12
  1100. package/dist/mcp/connectors/granola.js.map +0 -1
  1101. package/dist/mcp/connectors/linear.d.ts +0 -2
  1102. package/dist/mcp/connectors/linear.js +0 -19
  1103. package/dist/mcp/connectors/linear.js.map +0 -1
  1104. package/dist/mcp/connectors/obsidian.d.ts +0 -2
  1105. package/dist/mcp/connectors/obsidian.js +0 -19
  1106. package/dist/mcp/connectors/obsidian.js.map +0 -1
  1107. package/dist/mcp/connectors/pipedrive.d.ts +0 -2
  1108. package/dist/mcp/connectors/pipedrive.js +0 -20
  1109. package/dist/mcp/connectors/pipedrive.js.map +0 -1
  1110. package/dist/mcp/connectors/slack.d.ts +0 -2
  1111. package/dist/mcp/connectors/slack.js +0 -21
  1112. package/dist/mcp/connectors/slack.js.map +0 -1
  1113. package/dist/mcp/server-oauth-provider.d.ts +0 -56
  1114. package/dist/mcp/server-oauth-provider.js +0 -142
  1115. package/dist/mcp/server-oauth-provider.js.map +0 -1
  1116. package/dist/renderers/documents/branding.d.ts +0 -2
  1117. package/dist/renderers/documents/branding.js +0 -42
  1118. package/dist/renderers/documents/branding.js.map +0 -1
  1119. package/dist/renderers/documents/entity-types.json +0 -14
  1120. package/dist/renderers/documents/parser/frontmatter.d.ts +0 -5
  1121. package/dist/renderers/documents/parser/frontmatter.js +0 -90
  1122. package/dist/renderers/documents/parser/frontmatter.js.map +0 -1
  1123. package/dist/renderers/documents/parser/index.d.ts +0 -5
  1124. package/dist/renderers/documents/parser/index.js +0 -142
  1125. package/dist/renderers/documents/parser/index.js.map +0 -1
  1126. package/dist/renderers/documents/parser/sections.d.ts +0 -18
  1127. package/dist/renderers/documents/parser/sections.js +0 -131
  1128. package/dist/renderers/documents/parser/sections.js.map +0 -1
  1129. package/dist/renderers/documents/pdf/generator.d.ts +0 -13
  1130. package/dist/renderers/documents/pdf/generator.js +0 -79
  1131. package/dist/renderers/documents/pdf/generator.js.map +0 -1
  1132. package/dist/renderers/documents/plugin.d.ts +0 -2
  1133. package/dist/renderers/documents/plugin.js +0 -63
  1134. package/dist/renderers/documents/plugin.js.map +0 -1
  1135. package/dist/renderers/documents/public/branding/logo.svg +0 -3
  1136. package/dist/renderers/documents/public/editor/chrome.css +0 -461
  1137. package/dist/renderers/documents/public/editor/chrome.js +0 -524
  1138. package/dist/renderers/documents/public/editor/editor.css +0 -751
  1139. package/dist/renderers/documents/public/editor/editor.html +0 -140
  1140. package/dist/renderers/documents/public/editor/editor.js +0 -953
  1141. package/dist/renderers/documents/public/fonts/fonts.css +0 -145
  1142. package/dist/renderers/documents/public/viewer/viewer.css +0 -1
  1143. package/dist/renderers/documents/public/viewer/viewer.js +0 -4
  1144. package/dist/renderers/documents/renderer/back.d.ts +0 -8
  1145. package/dist/renderers/documents/renderer/back.js +0 -28
  1146. package/dist/renderers/documents/renderer/back.js.map +0 -1
  1147. package/dist/renderers/documents/renderer/content.d.ts +0 -7
  1148. package/dist/renderers/documents/renderer/content.js +0 -25
  1149. package/dist/renderers/documents/renderer/content.js.map +0 -1
  1150. package/dist/renderers/documents/renderer/cover.d.ts +0 -15
  1151. package/dist/renderers/documents/renderer/cover.js +0 -43
  1152. package/dist/renderers/documents/renderer/cover.js.map +0 -1
  1153. package/dist/renderers/documents/renderer/helpers.d.ts +0 -1
  1154. package/dist/renderers/documents/renderer/helpers.js +0 -11
  1155. package/dist/renderers/documents/renderer/helpers.js.map +0 -1
  1156. package/dist/renderers/documents/renderer/index.d.ts +0 -32
  1157. package/dist/renderers/documents/renderer/index.js +0 -80
  1158. package/dist/renderers/documents/renderer/index.js.map +0 -1
  1159. package/dist/renderers/documents/renderer/styles.d.ts +0 -16
  1160. package/dist/renderers/documents/renderer/styles.js +0 -546
  1161. package/dist/renderers/documents/renderer/styles.js.map +0 -1
  1162. package/dist/renderers/documents/routes/api.d.ts +0 -2
  1163. package/dist/renderers/documents/routes/api.js +0 -587
  1164. package/dist/renderers/documents/routes/api.js.map +0 -1
  1165. package/dist/renderers/documents/routes/editor.d.ts +0 -2
  1166. package/dist/renderers/documents/routes/editor.js +0 -31
  1167. package/dist/renderers/documents/routes/editor.js.map +0 -1
  1168. package/dist/renderers/documents/routes/viewer.d.ts +0 -2
  1169. package/dist/renderers/documents/routes/viewer.js +0 -67
  1170. package/dist/renderers/documents/routes/viewer.js.map +0 -1
  1171. package/dist/renderers/documents/types.d.ts +0 -61
  1172. package/dist/renderers/documents/types.js +0 -2
  1173. package/dist/renderers/documents/types.js.map +0 -1
  1174. package/dist/renderers/index.d.ts +0 -24
  1175. package/dist/renderers/index.js +0 -66
  1176. package/dist/renderers/index.js.map +0 -1
  1177. package/dist/renderers/slides/design-system/tokens.d.ts +0 -108
  1178. package/dist/renderers/slides/design-system/tokens.js +0 -44
  1179. package/dist/renderers/slides/design-system/tokens.js.map +0 -1
  1180. package/dist/renderers/slides/entity-types.json +0 -14
  1181. package/dist/renderers/slides/parser/comment-parser.d.ts +0 -10
  1182. package/dist/renderers/slides/parser/comment-parser.js +0 -89
  1183. package/dist/renderers/slides/parser/comment-parser.js.map +0 -1
  1184. package/dist/renderers/slides/parser/frontmatter.d.ts +0 -10
  1185. package/dist/renderers/slides/parser/frontmatter.js +0 -93
  1186. package/dist/renderers/slides/parser/frontmatter.js.map +0 -1
  1187. package/dist/renderers/slides/parser/index.d.ts +0 -7
  1188. package/dist/renderers/slides/parser/index.js +0 -569
  1189. package/dist/renderers/slides/parser/index.js.map +0 -1
  1190. package/dist/renderers/slides/parser/slide-splitter.d.ts +0 -6
  1191. package/dist/renderers/slides/parser/slide-splitter.js +0 -54
  1192. package/dist/renderers/slides/parser/slide-splitter.js.map +0 -1
  1193. package/dist/renderers/slides/parser/type-detector.d.ts +0 -6
  1194. package/dist/renderers/slides/parser/type-detector.js +0 -70
  1195. package/dist/renderers/slides/parser/type-detector.js.map +0 -1
  1196. package/dist/renderers/slides/pdf/generator.d.ts +0 -11
  1197. package/dist/renderers/slides/pdf/generator.js +0 -105
  1198. package/dist/renderers/slides/pdf/generator.js.map +0 -1
  1199. package/dist/renderers/slides/plugin.d.ts +0 -2
  1200. package/dist/renderers/slides/plugin.js +0 -70
  1201. package/dist/renderers/slides/plugin.js.map +0 -1
  1202. package/dist/renderers/slides/public/editor/.gitkeep +0 -0
  1203. package/dist/renderers/slides/public/editor/chrome.css +0 -461
  1204. package/dist/renderers/slides/public/editor/chrome.js +0 -519
  1205. package/dist/renderers/slides/public/editor/editor.css +0 -896
  1206. package/dist/renderers/slides/public/editor/editor.html +0 -131
  1207. package/dist/renderers/slides/public/editor/editor.js +0 -1038
  1208. package/dist/renderers/slides/public/fonts/fonts.css +0 -145
  1209. package/dist/renderers/slides/public/viewer/.gitkeep +0 -0
  1210. package/dist/renderers/slides/public/viewer/chrome.css +0 -62
  1211. package/dist/renderers/slides/public/viewer/transitions.css +0 -1
  1212. package/dist/renderers/slides/public/viewer/viewer.css +0 -2145
  1213. package/dist/renderers/slides/public/viewer/viewer.js +0 -303
  1214. package/dist/renderers/slides/renderer/chrome/browser-frame.d.ts +0 -5
  1215. package/dist/renderers/slides/renderer/chrome/browser-frame.js +0 -22
  1216. package/dist/renderers/slides/renderer/chrome/browser-frame.js.map +0 -1
  1217. package/dist/renderers/slides/renderer/chrome/mobile-frame.d.ts +0 -6
  1218. package/dist/renderers/slides/renderer/chrome/mobile-frame.js +0 -18
  1219. package/dist/renderers/slides/renderer/chrome/mobile-frame.js.map +0 -1
  1220. package/dist/renderers/slides/renderer/helpers.d.ts +0 -13
  1221. package/dist/renderers/slides/renderer/helpers.js +0 -33
  1222. package/dist/renderers/slides/renderer/helpers.js.map +0 -1
  1223. package/dist/renderers/slides/renderer/index.d.ts +0 -16
  1224. package/dist/renderers/slides/renderer/index.js +0 -160
  1225. package/dist/renderers/slides/renderer/index.js.map +0 -1
  1226. package/dist/renderers/slides/renderer/layouts/agenda.d.ts +0 -2
  1227. package/dist/renderers/slides/renderer/layouts/agenda.js +0 -18
  1228. package/dist/renderers/slides/renderer/layouts/agenda.js.map +0 -1
  1229. package/dist/renderers/slides/renderer/layouts/blank.d.ts +0 -2
  1230. package/dist/renderers/slides/renderer/layouts/blank.js +0 -4
  1231. package/dist/renderers/slides/renderer/layouts/blank.js.map +0 -1
  1232. package/dist/renderers/slides/renderer/layouts/columns.d.ts +0 -2
  1233. package/dist/renderers/slides/renderer/layouts/columns.js +0 -32
  1234. package/dist/renderers/slides/renderer/layouts/columns.js.map +0 -1
  1235. package/dist/renderers/slides/renderer/layouts/cover.d.ts +0 -2
  1236. package/dist/renderers/slides/renderer/layouts/cover.js +0 -18
  1237. package/dist/renderers/slides/renderer/layouts/cover.js.map +0 -1
  1238. package/dist/renderers/slides/renderer/layouts/diagram-concentric.d.ts +0 -2
  1239. package/dist/renderers/slides/renderer/layouts/diagram-concentric.js +0 -120
  1240. package/dist/renderers/slides/renderer/layouts/diagram-concentric.js.map +0 -1
  1241. package/dist/renderers/slides/renderer/layouts/diagram-matrix.d.ts +0 -2
  1242. package/dist/renderers/slides/renderer/layouts/diagram-matrix.js +0 -158
  1243. package/dist/renderers/slides/renderer/layouts/diagram-matrix.js.map +0 -1
  1244. package/dist/renderers/slides/renderer/layouts/diagram-pyramid.d.ts +0 -2
  1245. package/dist/renderers/slides/renderer/layouts/diagram-pyramid.js +0 -86
  1246. package/dist/renderers/slides/renderer/layouts/diagram-pyramid.js.map +0 -1
  1247. package/dist/renderers/slides/renderer/layouts/diagram-sitemap.d.ts +0 -2
  1248. package/dist/renderers/slides/renderer/layouts/diagram-sitemap.js +0 -146
  1249. package/dist/renderers/slides/renderer/layouts/diagram-sitemap.js.map +0 -1
  1250. package/dist/renderers/slides/renderer/layouts/diagram-venn.d.ts +0 -2
  1251. package/dist/renderers/slides/renderer/layouts/diagram-venn.js +0 -100
  1252. package/dist/renderers/slides/renderer/layouts/diagram-venn.js.map +0 -1
  1253. package/dist/renderers/slides/renderer/layouts/diagram.d.ts +0 -2
  1254. package/dist/renderers/slides/renderer/layouts/diagram.js +0 -21
  1255. package/dist/renderers/slides/renderer/layouts/diagram.js.map +0 -1
  1256. package/dist/renderers/slides/renderer/layouts/divider.d.ts +0 -2
  1257. package/dist/renderers/slides/renderer/layouts/divider.js +0 -4
  1258. package/dist/renderers/slides/renderer/layouts/divider.js.map +0 -1
  1259. package/dist/renderers/slides/renderer/layouts/end.d.ts +0 -2
  1260. package/dist/renderers/slides/renderer/layouts/end.js +0 -8
  1261. package/dist/renderers/slides/renderer/layouts/end.js.map +0 -1
  1262. package/dist/renderers/slides/renderer/layouts/framework.d.ts +0 -2
  1263. package/dist/renderers/slides/renderer/layouts/framework.js +0 -47
  1264. package/dist/renderers/slides/renderer/layouts/framework.js.map +0 -1
  1265. package/dist/renderers/slides/renderer/layouts/image-browser-2up.d.ts +0 -2
  1266. package/dist/renderers/slides/renderer/layouts/image-browser-2up.js +0 -38
  1267. package/dist/renderers/slides/renderer/layouts/image-browser-2up.js.map +0 -1
  1268. package/dist/renderers/slides/renderer/layouts/image-browser-6up.d.ts +0 -2
  1269. package/dist/renderers/slides/renderer/layouts/image-browser-6up.js +0 -30
  1270. package/dist/renderers/slides/renderer/layouts/image-browser-6up.js.map +0 -1
  1271. package/dist/renderers/slides/renderer/layouts/image-browser.d.ts +0 -2
  1272. package/dist/renderers/slides/renderer/layouts/image-browser.js +0 -18
  1273. package/dist/renderers/slides/renderer/layouts/image-browser.js.map +0 -1
  1274. package/dist/renderers/slides/renderer/layouts/image-bullets.d.ts +0 -2
  1275. package/dist/renderers/slides/renderer/layouts/image-bullets.js +0 -26
  1276. package/dist/renderers/slides/renderer/layouts/image-bullets.js.map +0 -1
  1277. package/dist/renderers/slides/renderer/layouts/image-device-pair.d.ts +0 -2
  1278. package/dist/renderers/slides/renderer/layouts/image-device-pair.js +0 -34
  1279. package/dist/renderers/slides/renderer/layouts/image-device-pair.js.map +0 -1
  1280. package/dist/renderers/slides/renderer/layouts/image-full.d.ts +0 -2
  1281. package/dist/renderers/slides/renderer/layouts/image-full.js +0 -36
  1282. package/dist/renderers/slides/renderer/layouts/image-full.js.map +0 -1
  1283. package/dist/renderers/slides/renderer/layouts/image-grid-2x2.d.ts +0 -2
  1284. package/dist/renderers/slides/renderer/layouts/image-grid-2x2.js +0 -23
  1285. package/dist/renderers/slides/renderer/layouts/image-grid-2x2.js.map +0 -1
  1286. package/dist/renderers/slides/renderer/layouts/image-keywords.d.ts +0 -2
  1287. package/dist/renderers/slides/renderer/layouts/image-keywords.js +0 -31
  1288. package/dist/renderers/slides/renderer/layouts/image-keywords.js.map +0 -1
  1289. package/dist/renderers/slides/renderer/layouts/image-mobile-3up.d.ts +0 -2
  1290. package/dist/renderers/slides/renderer/layouts/image-mobile-3up.js +0 -22
  1291. package/dist/renderers/slides/renderer/layouts/image-mobile-3up.js.map +0 -1
  1292. package/dist/renderers/slides/renderer/layouts/image-mobile.d.ts +0 -2
  1293. package/dist/renderers/slides/renderer/layouts/image-mobile.js +0 -17
  1294. package/dist/renderers/slides/renderer/layouts/image-mobile.js.map +0 -1
  1295. package/dist/renderers/slides/renderer/layouts/image-split-right.d.ts +0 -2
  1296. package/dist/renderers/slides/renderer/layouts/image-split-right.js +0 -19
  1297. package/dist/renderers/slides/renderer/layouts/image-split-right.js.map +0 -1
  1298. package/dist/renderers/slides/renderer/layouts/image-split.d.ts +0 -2
  1299. package/dist/renderers/slides/renderer/layouts/image-split.js +0 -19
  1300. package/dist/renderers/slides/renderer/layouts/image-split.js.map +0 -1
  1301. package/dist/renderers/slides/renderer/layouts/introductions.d.ts +0 -2
  1302. package/dist/renderers/slides/renderer/layouts/introductions.js +0 -41
  1303. package/dist/renderers/slides/renderer/layouts/introductions.js.map +0 -1
  1304. package/dist/renderers/slides/renderer/layouts/moodboard.d.ts +0 -15
  1305. package/dist/renderers/slides/renderer/layouts/moodboard.js +0 -63
  1306. package/dist/renderers/slides/renderer/layouts/moodboard.js.map +0 -1
  1307. package/dist/renderers/slides/renderer/layouts/next-steps.d.ts +0 -2
  1308. package/dist/renderers/slides/renderer/layouts/next-steps.js +0 -13
  1309. package/dist/renderers/slides/renderer/layouts/next-steps.js.map +0 -1
  1310. package/dist/renderers/slides/renderer/layouts/pullquote.d.ts +0 -2
  1311. package/dist/renderers/slides/renderer/layouts/pullquote.js +0 -17
  1312. package/dist/renderers/slides/renderer/layouts/pullquote.js.map +0 -1
  1313. package/dist/renderers/slides/renderer/layouts/schedule.d.ts +0 -2
  1314. package/dist/renderers/slides/renderer/layouts/schedule.js +0 -58
  1315. package/dist/renderers/slides/renderer/layouts/schedule.js.map +0 -1
  1316. package/dist/renderers/slides/renderer/layouts/section.d.ts +0 -2
  1317. package/dist/renderers/slides/renderer/layouts/section.js +0 -18
  1318. package/dist/renderers/slides/renderer/layouts/section.js.map +0 -1
  1319. package/dist/renderers/slides/renderer/layouts/statement.d.ts +0 -2
  1320. package/dist/renderers/slides/renderer/layouts/statement.js +0 -13
  1321. package/dist/renderers/slides/renderer/layouts/statement.js.map +0 -1
  1322. package/dist/renderers/slides/renderer/layouts/stats.d.ts +0 -2
  1323. package/dist/renderers/slides/renderer/layouts/stats.js +0 -26
  1324. package/dist/renderers/slides/renderer/layouts/stats.js.map +0 -1
  1325. package/dist/renderers/slides/renderer/layouts/table.d.ts +0 -2
  1326. package/dist/renderers/slides/renderer/layouts/table.js +0 -23
  1327. package/dist/renderers/slides/renderer/layouts/table.js.map +0 -1
  1328. package/dist/renderers/slides/renderer/layouts/text-body.d.ts +0 -2
  1329. package/dist/renderers/slides/renderer/layouts/text-body.js +0 -14
  1330. package/dist/renderers/slides/renderer/layouts/text-body.js.map +0 -1
  1331. package/dist/renderers/slides/renderer/placeholder.d.ts +0 -5
  1332. package/dist/renderers/slides/renderer/placeholder.js +0 -12
  1333. package/dist/renderers/slides/renderer/placeholder.js.map +0 -1
  1334. package/dist/renderers/slides/routes/api.d.ts +0 -2
  1335. package/dist/renderers/slides/routes/api.js +0 -568
  1336. package/dist/renderers/slides/routes/api.js.map +0 -1
  1337. package/dist/renderers/slides/routes/editor.d.ts +0 -2
  1338. package/dist/renderers/slides/routes/editor.js +0 -32
  1339. package/dist/renderers/slides/routes/editor.js.map +0 -1
  1340. package/dist/renderers/slides/routes/viewer.d.ts +0 -2
  1341. package/dist/renderers/slides/routes/viewer.js +0 -67
  1342. package/dist/renderers/slides/routes/viewer.js.map +0 -1
  1343. package/dist/renderers/slides/types.d.ts +0 -111
  1344. package/dist/renderers/slides/types.js +0 -2
  1345. package/dist/renderers/slides/types.js.map +0 -1
  1346. package/dist/server/agents/agent-loader.d.ts +0 -16
  1347. package/dist/server/agents/agent-loader.js +0 -175
  1348. package/dist/server/agents/agent-loader.js.map +0 -1
  1349. package/dist/server/agents/agent-runner.d.ts +0 -33
  1350. package/dist/server/agents/agent-runner.js +0 -187
  1351. package/dist/server/agents/agent-runner.js.map +0 -1
  1352. package/dist/server/agents/agent-runtime.d.ts +0 -39
  1353. package/dist/server/agents/agent-runtime.js +0 -214
  1354. package/dist/server/agents/agent-runtime.js.map +0 -1
  1355. package/dist/server/agents/base-agent.d.ts +0 -30
  1356. package/dist/server/agents/base-agent.js +0 -88
  1357. package/dist/server/agents/base-agent.js.map +0 -1
  1358. package/dist/server/agents/executive-digest.d.ts +0 -16
  1359. package/dist/server/agents/executive-digest.js +0 -115
  1360. package/dist/server/agents/executive-digest.js.map +0 -1
  1361. package/dist/server/agents/graph-steward.d.ts +0 -16
  1362. package/dist/server/agents/graph-steward.js +0 -59
  1363. package/dist/server/agents/graph-steward.js.map +0 -1
  1364. package/dist/server/agents/meeting-followup.d.ts +0 -16
  1365. package/dist/server/agents/meeting-followup.js +0 -88
  1366. package/dist/server/agents/meeting-followup.js.map +0 -1
  1367. package/dist/server/agents/pipeline-monitor.d.ts +0 -15
  1368. package/dist/server/agents/pipeline-monitor.js +0 -86
  1369. package/dist/server/agents/pipeline-monitor.js.map +0 -1
  1370. package/dist/server/agents/types.d.ts +0 -81
  1371. package/dist/server/agents/types.js +0 -41
  1372. package/dist/server/agents/types.js.map +0 -1
  1373. package/dist/server/chrome/chrome.css +0 -701
  1374. package/dist/server/chrome/chrome.js +0 -374
  1375. package/dist/server/collab-authority.d.ts +0 -70
  1376. package/dist/server/collab-authority.js +0 -218
  1377. package/dist/server/collab-authority.js.map +0 -1
  1378. package/dist/server/collab.d.ts +0 -29
  1379. package/dist/server/collab.js +0 -195
  1380. package/dist/server/collab.js.map +0 -1
  1381. package/dist/server/commit-scheduler.d.ts +0 -39
  1382. package/dist/server/commit-scheduler.js +0 -113
  1383. package/dist/server/commit-scheduler.js.map +0 -1
  1384. package/dist/server/plugin-loader.d.ts +0 -53
  1385. package/dist/server/plugin-loader.js +0 -155
  1386. package/dist/server/plugin-loader.js.map +0 -1
  1387. package/dist/server/routes/agents-api.d.ts +0 -10
  1388. package/dist/server/routes/agents-api.js +0 -25
  1389. package/dist/server/routes/agents-api.js.map +0 -1
  1390. package/dist/server/routes/collab.d.ts +0 -6
  1391. package/dist/server/routes/collab.js +0 -10
  1392. package/dist/server/routes/collab.js.map +0 -1
  1393. package/dist/server/routes/features-api.d.ts +0 -10
  1394. package/dist/server/routes/features-api.js +0 -15
  1395. package/dist/server/routes/features-api.js.map +0 -1
  1396. package/dist/server/routes/git-api.d.ts +0 -9
  1397. package/dist/server/routes/git-api.js +0 -82
  1398. package/dist/server/routes/git-api.js.map +0 -1
  1399. package/dist/server/routes/meeting-ingest-api.d.ts +0 -14
  1400. package/dist/server/routes/meeting-ingest-api.js +0 -117
  1401. package/dist/server/routes/meeting-ingest-api.js.map +0 -1
  1402. package/dist/server/routes/messages-api.d.ts +0 -15
  1403. package/dist/server/routes/messages-api.js +0 -569
  1404. package/dist/server/routes/messages-api.js.map +0 -1
  1405. package/dist/server/routes/sync-api.d.ts +0 -26
  1406. package/dist/server/routes/sync-api.js +0 -848
  1407. package/dist/server/routes/sync-api.js.map +0 -1
  1408. package/dist/server/routes/view-public.d.ts +0 -10
  1409. package/dist/server/routes/view-public.js +0 -107
  1410. package/dist/server/routes/view-public.js.map +0 -1
  1411. package/dist/server/routes/webhook.d.ts +0 -14
  1412. package/dist/server/routes/webhook.js +0 -102
  1413. package/dist/server/routes/webhook.js.map +0 -1
  1414. package/dist/services/batch-processor.d.ts +0 -49
  1415. package/dist/services/batch-processor.js +0 -166
  1416. package/dist/services/batch-processor.js.map +0 -1
  1417. package/dist/services/briefing.d.ts +0 -26
  1418. package/dist/services/briefing.js +0 -206
  1419. package/dist/services/briefing.js.map +0 -1
  1420. package/dist/services/github-provisioner.d.ts +0 -36
  1421. package/dist/services/github-provisioner.js +0 -126
  1422. package/dist/services/github-provisioner.js.map +0 -1
  1423. package/dist/services/github-service.d.ts +0 -99
  1424. package/dist/services/github-service.js +0 -310
  1425. package/dist/services/github-service.js.map +0 -1
  1426. package/dist/services/heartbeat.d.ts +0 -35
  1427. package/dist/services/heartbeat.js +0 -236
  1428. package/dist/services/heartbeat.js.map +0 -1
  1429. package/dist/services/image-import-service.d.ts +0 -24
  1430. package/dist/services/image-import-service.js +0 -108
  1431. package/dist/services/image-import-service.js.map +0 -1
  1432. package/dist/services/insights.d.ts +0 -38
  1433. package/dist/services/insights.js +0 -269
  1434. package/dist/services/insights.js.map +0 -1
  1435. package/dist/services/meeting-processor.d.ts +0 -97
  1436. package/dist/services/meeting-processor.js +0 -382
  1437. package/dist/services/meeting-processor.js.map +0 -1
  1438. package/dist/services/message-service.d.ts +0 -117
  1439. package/dist/services/message-service.js +0 -414
  1440. package/dist/services/message-service.js.map +0 -1
  1441. package/dist/services/sync/collection-sync.d.ts +0 -60
  1442. package/dist/services/sync/collection-sync.js +0 -509
  1443. package/dist/services/sync/collection-sync.js.map +0 -1
  1444. package/dist/services/sync/commit.d.ts +0 -60
  1445. package/dist/services/sync/commit.js +0 -354
  1446. package/dist/services/sync/commit.js.map +0 -1
  1447. package/dist/services/sync/context-index.d.ts +0 -69
  1448. package/dist/services/sync/context-index.js +0 -280
  1449. package/dist/services/sync/context-index.js.map +0 -1
  1450. package/dist/services/sync/data-fetcher.d.ts +0 -31
  1451. package/dist/services/sync/data-fetcher.js +0 -12
  1452. package/dist/services/sync/data-fetcher.js.map +0 -1
  1453. package/dist/services/sync/derive.d.ts +0 -34
  1454. package/dist/services/sync/derive.js +0 -164
  1455. package/dist/services/sync/derive.js.map +0 -1
  1456. package/dist/services/sync/enrichment-state.d.ts +0 -31
  1457. package/dist/services/sync/enrichment-state.js +0 -63
  1458. package/dist/services/sync/enrichment-state.js.map +0 -1
  1459. package/dist/services/sync/enrichment.d.ts +0 -25
  1460. package/dist/services/sync/enrichment.js +0 -121
  1461. package/dist/services/sync/enrichment.js.map +0 -1
  1462. package/dist/services/sync/entity-refresh.d.ts +0 -30
  1463. package/dist/services/sync/entity-refresh.js +0 -275
  1464. package/dist/services/sync/entity-refresh.js.map +0 -1
  1465. package/dist/services/sync/frontmatter-extractor.d.ts +0 -40
  1466. package/dist/services/sync/frontmatter-extractor.js +0 -279
  1467. package/dist/services/sync/frontmatter-extractor.js.map +0 -1
  1468. package/dist/services/sync/graph-match-state.d.ts +0 -33
  1469. package/dist/services/sync/graph-match-state.js +0 -61
  1470. package/dist/services/sync/graph-match-state.js.map +0 -1
  1471. package/dist/services/sync/graph-match.d.ts +0 -53
  1472. package/dist/services/sync/graph-match.js +0 -316
  1473. package/dist/services/sync/graph-match.js.map +0 -1
  1474. package/dist/services/sync/graphrag-client.d.ts +0 -43
  1475. package/dist/services/sync/graphrag-client.js +0 -94
  1476. package/dist/services/sync/graphrag-client.js.map +0 -1
  1477. package/dist/services/sync/graphrag-config.d.ts +0 -16
  1478. package/dist/services/sync/graphrag-config.js +0 -39
  1479. package/dist/services/sync/graphrag-config.js.map +0 -1
  1480. package/dist/services/sync/graphrag-context.d.ts +0 -14
  1481. package/dist/services/sync/graphrag-context.js +0 -109
  1482. package/dist/services/sync/graphrag-context.js.map +0 -1
  1483. package/dist/services/sync/graphrag-indexer.d.ts +0 -30
  1484. package/dist/services/sync/graphrag-indexer.js +0 -358
  1485. package/dist/services/sync/graphrag-indexer.js.map +0 -1
  1486. package/dist/services/sync/llm.d.ts +0 -32
  1487. package/dist/services/sync/llm.js +0 -115
  1488. package/dist/services/sync/llm.js.map +0 -1
  1489. package/dist/services/sync/mcp-client.d.ts +0 -71
  1490. package/dist/services/sync/mcp-client.js +0 -299
  1491. package/dist/services/sync/mcp-client.js.map +0 -1
  1492. package/dist/services/sync/model-factory.d.ts +0 -13
  1493. package/dist/services/sync/model-factory.js +0 -38
  1494. package/dist/services/sync/model-factory.js.map +0 -1
  1495. package/dist/services/sync/name-quality.d.ts +0 -31
  1496. package/dist/services/sync/name-quality.js +0 -60
  1497. package/dist/services/sync/name-quality.js.map +0 -1
  1498. package/dist/services/sync/output-schemas.d.ts +0 -92
  1499. package/dist/services/sync/output-schemas.js +0 -43
  1500. package/dist/services/sync/output-schemas.js.map +0 -1
  1501. package/dist/services/sync/prompts.d.ts +0 -19
  1502. package/dist/services/sync/prompts.js +0 -128
  1503. package/dist/services/sync/prompts.js.map +0 -1
  1504. package/dist/services/sync/reconciler.d.ts +0 -48
  1505. package/dist/services/sync/reconciler.js +0 -294
  1506. package/dist/services/sync/reconciler.js.map +0 -1
  1507. package/dist/services/sync/rest-client.d.ts +0 -40
  1508. package/dist/services/sync/rest-client.js +0 -100
  1509. package/dist/services/sync/rest-client.js.map +0 -1
  1510. package/dist/services/sync/source-config.d.ts +0 -67
  1511. package/dist/services/sync/source-config.js +0 -304
  1512. package/dist/services/sync/source-config.js.map +0 -1
  1513. package/dist/services/sync/source-definitions/asana.d.ts +0 -14
  1514. package/dist/services/sync/source-definitions/asana.js +0 -42
  1515. package/dist/services/sync/source-definitions/asana.js.map +0 -1
  1516. package/dist/services/sync/source-definitions/definitions.d.ts +0 -8
  1517. package/dist/services/sync/source-definitions/definitions.js +0 -8
  1518. package/dist/services/sync/source-definitions/definitions.js.map +0 -1
  1519. package/dist/services/sync/source-definitions/gdrive.d.ts +0 -16
  1520. package/dist/services/sync/source-definitions/gdrive.js +0 -68
  1521. package/dist/services/sync/source-definitions/gdrive.js.map +0 -1
  1522. package/dist/services/sync/source-definitions/granola.d.ts +0 -2
  1523. package/dist/services/sync/source-definitions/granola.js +0 -21
  1524. package/dist/services/sync/source-definitions/granola.js.map +0 -1
  1525. package/dist/services/sync/source-definitions/linear.d.ts +0 -2
  1526. package/dist/services/sync/source-definitions/linear.js +0 -62
  1527. package/dist/services/sync/source-definitions/linear.js.map +0 -1
  1528. package/dist/services/sync/source-definitions/obsidian.d.ts +0 -2
  1529. package/dist/services/sync/source-definitions/obsidian.js +0 -55
  1530. package/dist/services/sync/source-definitions/obsidian.js.map +0 -1
  1531. package/dist/services/sync/source-definitions/pipedrive.d.ts +0 -2
  1532. package/dist/services/sync/source-definitions/pipedrive.js +0 -43
  1533. package/dist/services/sync/source-definitions/pipedrive.js.map +0 -1
  1534. package/dist/services/sync/staging.d.ts +0 -53
  1535. package/dist/services/sync/staging.js +0 -130
  1536. package/dist/services/sync/staging.js.map +0 -1
  1537. package/dist/services/sync/structured-extractor.d.ts +0 -57
  1538. package/dist/services/sync/structured-extractor.js +0 -584
  1539. package/dist/services/sync/structured-extractor.js.map +0 -1
  1540. package/dist/services/sync/sync-runner.d.ts +0 -32
  1541. package/dist/services/sync/sync-runner.js +0 -195
  1542. package/dist/services/sync/sync-runner.js.map +0 -1
  1543. package/dist/services/sync/sync-state.d.ts +0 -43
  1544. package/dist/services/sync/sync-state.js +0 -154
  1545. package/dist/services/sync/sync-state.js.map +0 -1
  1546. package/dist/services/sync/types.d.ts +0 -381
  1547. package/dist/services/sync/types.js +0 -8
  1548. package/dist/services/sync/types.js.map +0 -1
  1549. package/dist/services/sync/unstructured-extractor.d.ts +0 -27
  1550. package/dist/services/sync/unstructured-extractor.js +0 -185
  1551. package/dist/services/sync/unstructured-extractor.js.map +0 -1
  1552. package/dist/utils/merge-resolver.d.ts +0 -59
  1553. package/dist/utils/merge-resolver.js +0 -303
  1554. package/dist/utils/merge-resolver.js.map +0 -1
  1555. package/dist/utils/workspace-config.d.ts +0 -8
  1556. package/dist/utils/workspace-config.js +0 -22
  1557. package/dist/utils/workspace-config.js.map +0 -1
  1558. package/dist/web/_app/immutable/assets/0.B3eM5J6m.css +0 -1
  1559. package/dist/web/_app/immutable/assets/10.2jzI3cZY.css +0 -1
  1560. package/dist/web/_app/immutable/assets/11.BbBNo8jg.css +0 -1
  1561. package/dist/web/_app/immutable/assets/12.vV-8Nqud.css +0 -1
  1562. package/dist/web/_app/immutable/assets/2.CDsrnCq3.css +0 -1
  1563. package/dist/web/_app/immutable/assets/3.Ih7su2rl.css +0 -1
  1564. package/dist/web/_app/immutable/assets/4.CwXIQz3p.css +0 -1
  1565. package/dist/web/_app/immutable/assets/ChevronLeft.BwM9mwS4.css +0 -1
  1566. package/dist/web/_app/immutable/assets/GraphView.CRV5cekT.css +0 -1
  1567. package/dist/web/_app/immutable/assets/Toaster.rN8r_Hzv.css +0 -1
  1568. package/dist/web/_app/immutable/assets/ViewToolbar.CsWTe0yn.css +0 -1
  1569. package/dist/web/_app/immutable/assets/WorkspaceView.iWtzksrr.css +0 -1
  1570. package/dist/web/_app/immutable/chunks/0kuZ2gtb.js +0 -1
  1571. package/dist/web/_app/immutable/chunks/1Ea1UXwM.js +0 -2
  1572. package/dist/web/_app/immutable/chunks/284Zhbif.js +0 -1
  1573. package/dist/web/_app/immutable/chunks/7S2LGqoP.js +0 -2
  1574. package/dist/web/_app/immutable/chunks/7WZENMwt.js +0 -1
  1575. package/dist/web/_app/immutable/chunks/B0qHb2ee.js +0 -1
  1576. package/dist/web/_app/immutable/chunks/B45od9dM.js +0 -2
  1577. package/dist/web/_app/immutable/chunks/B4hXkypI.js +0 -1
  1578. package/dist/web/_app/immutable/chunks/BB_5th5W.js +0 -3383
  1579. package/dist/web/_app/immutable/chunks/BDvg8O5k.js +0 -5
  1580. package/dist/web/_app/immutable/chunks/BGiCmAMQ.js +0 -4
  1581. package/dist/web/_app/immutable/chunks/BHgT2Ede.js +0 -1
  1582. package/dist/web/_app/immutable/chunks/BQ1nK9ru.js +0 -1
  1583. package/dist/web/_app/immutable/chunks/BS0-13V2.js +0 -1
  1584. package/dist/web/_app/immutable/chunks/BSiC7hSS.js +0 -1
  1585. package/dist/web/_app/immutable/chunks/BkxW0BJZ.js +0 -1
  1586. package/dist/web/_app/immutable/chunks/Bp7FKkGt.js +0 -1
  1587. package/dist/web/_app/immutable/chunks/BrebtJNG.js +0 -18
  1588. package/dist/web/_app/immutable/chunks/Bz0ZjVQE.js +0 -2
  1589. package/dist/web/_app/immutable/chunks/C0LVZhvn.js +0 -1
  1590. package/dist/web/_app/immutable/chunks/C0Zx4tMe.js +0 -1
  1591. package/dist/web/_app/immutable/chunks/CHJcPZZK.js +0 -2
  1592. package/dist/web/_app/immutable/chunks/CRpK3grw.js +0 -2
  1593. package/dist/web/_app/immutable/chunks/CSVbGg_b.js +0 -5
  1594. package/dist/web/_app/immutable/chunks/CX_61fY8.js +0 -5
  1595. package/dist/web/_app/immutable/chunks/CYrVHOHA.js +0 -1
  1596. package/dist/web/_app/immutable/chunks/CdIKiXW6.js +0 -1
  1597. package/dist/web/_app/immutable/chunks/CqkleIqs.js +0 -1
  1598. package/dist/web/_app/immutable/chunks/D14M5KlG.js +0 -1
  1599. package/dist/web/_app/immutable/chunks/D71c9o7J.js +0 -2
  1600. package/dist/web/_app/immutable/chunks/DFYOFE3o.js +0 -1
  1601. package/dist/web/_app/immutable/chunks/DHJAqtPb.js +0 -1
  1602. package/dist/web/_app/immutable/chunks/DOqcDZIJ.js +0 -1
  1603. package/dist/web/_app/immutable/chunks/DQNtFKv6.js +0 -1
  1604. package/dist/web/_app/immutable/chunks/DZHWps9n.js +0 -1
  1605. package/dist/web/_app/immutable/chunks/DjLrjK2H.js +0 -1
  1606. package/dist/web/_app/immutable/chunks/DjvJRtva.js +0 -1
  1607. package/dist/web/_app/immutable/chunks/DnL9f0lc.js +0 -7
  1608. package/dist/web/_app/immutable/chunks/Dqq8gyOz.js +0 -1
  1609. package/dist/web/_app/immutable/chunks/DsnmJJEf.js +0 -1
  1610. package/dist/web/_app/immutable/chunks/DtG1tx2A.js +0 -1
  1611. package/dist/web/_app/immutable/chunks/Dxmmz-Kc.js +0 -1
  1612. package/dist/web/_app/immutable/chunks/DzVOENkg.js +0 -1
  1613. package/dist/web/_app/immutable/chunks/Ee5s0qSS.js +0 -5
  1614. package/dist/web/_app/immutable/chunks/KlRV1tKP.js +0 -1
  1615. package/dist/web/_app/immutable/chunks/L7dfS1-w.js +0 -1
  1616. package/dist/web/_app/immutable/chunks/MaQouOBH.js +0 -2
  1617. package/dist/web/_app/immutable/chunks/PPVm8Dsz.js +0 -1
  1618. package/dist/web/_app/immutable/chunks/WSUKABI_.js +0 -1
  1619. package/dist/web/_app/immutable/chunks/_7ZfIgbM.js +0 -1
  1620. package/dist/web/_app/immutable/chunks/pNJmbo_B.js +0 -1
  1621. package/dist/web/_app/immutable/chunks/q4I7EnCp.js +0 -1
  1622. package/dist/web/_app/immutable/chunks/qE6Hm01v.js +0 -1
  1623. package/dist/web/_app/immutable/chunks/vxEtkL8z.js +0 -1
  1624. package/dist/web/_app/immutable/chunks/wDIGUaoU.js +0 -3
  1625. package/dist/web/_app/immutable/chunks/wZWzupWT.js +0 -1
  1626. package/dist/web/_app/immutable/entry/app.Dc6h105F.js +0 -2
  1627. package/dist/web/_app/immutable/entry/start.D4codAU1.js +0 -1
  1628. package/dist/web/_app/immutable/nodes/0.BM3j5xaK.js +0 -2
  1629. package/dist/web/_app/immutable/nodes/1.CZ2dT63B.js +0 -1
  1630. package/dist/web/_app/immutable/nodes/10.AJJtsnPU.js +0 -1
  1631. package/dist/web/_app/immutable/nodes/11.B7SEgGLF.js +0 -1
  1632. package/dist/web/_app/immutable/nodes/12.BcJKl4UQ.js +0 -1
  1633. package/dist/web/_app/immutable/nodes/2.jp27tyTq.js +0 -269
  1634. package/dist/web/_app/immutable/nodes/3.I7SH_06w.js +0 -5
  1635. package/dist/web/_app/immutable/nodes/4.Bdphc-UJ.js +0 -11
  1636. package/dist/web/_app/immutable/nodes/5.CXpOErnP.js +0 -1
  1637. package/dist/web/_app/immutable/nodes/6.1wcSxmEA.js +0 -1
  1638. package/dist/web/_app/immutable/nodes/7.D0b7WYTp.js +0 -1
  1639. package/dist/web/_app/immutable/nodes/8.D0Gxvy3F.js +0 -1
  1640. package/dist/web/_app/immutable/nodes/9.B5Nr92OR.js +0 -1
package/dist/mcp/tools.js CHANGED
@@ -5,14 +5,96 @@
5
5
  * methods — no new business logic. Results are returned as JSON text so the
6
6
  * MCP client (Claude Desktop) can process structured data.
7
7
  *
8
+ * Every tool enforces per-user access control via `services/access-control.ts`.
9
+ * Callers (the HTTP /mcp route, the stdio MCP server) must resolve a user
10
+ * identity before registering tools — `registerTools` requires a user in its
11
+ * `McpAccessContext` argument so there is no anonymous MCP path.
12
+ *
8
13
  * Note: `inputSchema` is cast to `any` at each call site because the MCP SDK's
9
14
  * ZodRawShapeCompat type does not accept ZodObject in TypeScript's strict mode.
10
15
  * Runtime schema validation is handled correctly by the SDK regardless.
11
16
  */
12
17
  import { z } from 'zod';
13
18
  import { ENTITY_SCHEMAS } from '../core/validation.js';
14
- import { isLaunchVisibleEntityType } from '../core/launch-entity-types.js';
19
+ import { isWorkspaceOwner } from '../services/auth-service.js';
20
+ import { mintArtifactAssetTokenForBundle } from '../server/artifact-asset-mint.js';
21
+ import { getAccessibleRepos, hasRepoAccess, canWriteRepo, isFolderAdminOrOwner, getAccessibleEntityIds, filterWikilinks, } from '../services/access-control.js';
22
+ import { assetDeleteDenial } from '../services/asset-delete-authz.js';
23
+ import { isArchivedRepo } from '../core/types.js';
24
+ import { createSharedFolder, FolderCreationError } from '../services/folder-creation.js';
25
+ import { validateFolderPath } from '../core/folder-paths.js';
26
+ import { existsSync } from 'fs';
27
+ import { join } from 'path';
28
+ import { isCuratedEntityType } from '../core/entity-types-catalog.js';
29
+ import { describeZodObject } from '../core/schema-registry.js';
15
30
  import { getAgentAllowedEntityTypes } from '../agent/prompts/entity-types-section.js';
31
+ import * as commentVirtualEntity from '../services/comment-virtual-entity.js';
32
+ import { buildActor, prepareCreatePayload, prepareUpdatePayload, diagnosedUpdate, DroppedAssetError, assertEntityRefs, applyPatchEntity, } from '../services/entity-mutations.js';
33
+ import { ArtifactStore, ArtifactWriteError } from '../services/artifact-store.js';
34
+ import { patchPersistedArtifact, ArtifactPatchBaseHashError } from '../services/artifact-patch-runner.js';
35
+ import { hashArtifactBody } from '../agent/tools/artifact-tools.js';
36
+ import { buildArtifactManifest } from '../core/artifact-manifest.js';
37
+ import { summarizeArtifactWrite } from '../core/artifact-warnings.js';
38
+ import { BridgeError, validateUserHandleInPayload, assertCanChangeUserHandle } from '../services/user-person-bridge.js';
39
+ import { extractAssetEmbeds, stripFrontmatter, assetEmbed } from '../core/embeds.js';
40
+ import { listRepoAssets, listWorkspaceAssets } from '../services/asset-listing.js';
41
+ import { presignAssetUpload } from '../services/asset-presign-service.js';
42
+ import { finalizeAssetUpload } from '../services/asset-finalize-service.js';
43
+ import { importAssetAsEntity } from '../services/asset-import-service.js';
44
+ import { AssetUploadError } from '../services/asset-upload-errors.js';
45
+ import { presignMimeAllowlist, inlineUploadMimeAllowlist, resolveFormat, isInlineUploadAllowed } from '../core/format-registry.js';
46
+ import { resolveWorkspaceAssetsRepo } from '../services/workspace-assets-folder.js';
47
+ import { Workspace } from '../core/workspace.js';
48
+ import { validateArtifactBundle } from '../core/artifact-frame-model.js';
49
+ import { ArtifactPatchError } from '../core/artifact-patch.js';
50
+ import { buildFrameDocument, bundleNeedsPagination } from '../core/artifact-frame-document.js';
51
+ import { ArtifactRenderExportUnavailableError, ArtifactRenderInstallingError, artifactExportFilename, artifactPngArchiveFilename, inspectArtifactRender, inspectionToModelPayload, nameArtifactPngFiles, renderArtifactPdf, renderArtifactPngFiles, zipArtifactPngFiles, } from '../services/artifact-render-export.js';
52
+ import { artifactLibrarySourcesForBundle, artifactPagedSource } from '../services/artifact-library-sources.js';
53
+ /**
54
+ * Server identity passed to `new McpServer(...)` on BOTH transports (stdio
55
+ * `src/mcp/server.ts` + HTTP `src/server/routes/mcp.ts`). Centralized so the
56
+ * two surfaces can never drift on name/version.
57
+ */
58
+ export const MCP_SERVER_INFO = { name: 'studiograph', version: '1.0.0' };
59
+ /**
60
+ * Server-level `instructions` (MCP `ServerOptions.instructions`, surfaced in
61
+ * the `initialize` response). With Claude Code's tool-search/deferral on, this
62
+ * is what tells the client WHEN to search for Studiograph's tools — it loads
63
+ * even when individual tool schemas are deferred. Must be passed in McpServer's
64
+ * SECOND constructor arg (`new McpServer(MCP_SERVER_INFO, { instructions })`) —
65
+ * putting it in the serverInfo object silently drops it.
66
+ *
67
+ * Budget: Claude Code truncates server instructions at 2KB and front-loads
68
+ * matter most — keep this short and lead with category + when-to-reach-for-it.
69
+ */
70
+ export const MCP_INSTRUCTIONS = 'Studiograph is the team\'s knowledge workspace: a Git-backed graph of entries ' +
71
+ '(people, organizations, projects, tasks, deals, events, notes, documents, ' +
72
+ 'deliverables, skills, and uploaded assets) stored as markdown with YAML ' +
73
+ 'frontmatter and connected by [[wikilinks]].\n\n' +
74
+ 'Reach for these tools whenever the user wants to CAPTURE, FIND, READ, or ' +
75
+ 'UPDATE workspace knowledge — search before concluding something is unavailable. ' +
76
+ 'Common entry points:\n' +
77
+ '- Find / read: search_entities, list_entities, get_entity, find_related\n' +
78
+ '- Create / edit: create_entity, update_entity, patch_entity (small edits)\n' +
79
+ '- Organize: list_repos, create_repo, create_subfolder, move_entity\n' +
80
+ '- Assets (images/files): list_assets, attach_asset_to_entity, upload_asset_inline\n' +
81
+ '- Artifacts: create_artifact, inspect_artifact, export_artifact\n' +
82
+ '- History / undo: list_file_history, list_recent_deletions, restore_file\n' +
83
+ '- Saved workflows: list_skills, get_skill\n\n' +
84
+ 'When the user refers to "this", "the selected frame/slide/element", or otherwise ' +
85
+ 'points at something in an artifact they are viewing, call get_current_selection ' +
86
+ '(repo + entity_id of that artifact) FIRST to learn which frame they mean, then target it.';
87
+ /**
88
+ * Tools pinned against Claude Code's tool-search deferral via
89
+ * `_meta["anthropic/alwaysLoad"]=true` (see PIN_META). These are the handful an
90
+ * agent needs on essentially every turn, so they stay loaded even when the rest
91
+ * of the surface is deferred (verified: code.claude.com/docs/en/mcp.md, CC
92
+ * v2.1.121+). Keep this set SMALL — each pinned tool consumes upfront context.
93
+ * Exported so tests can assert the pin lands on exactly these tools.
94
+ */
95
+ export const MCP_PINNED_TOOLS = ['search_entities', 'list_entities', 'get_entity', 'create_entity'];
96
+ const PINNED_SET = new Set(MCP_PINNED_TOOLS);
97
+ const PIN_META = { _meta: { 'anthropic/alwaysLoad': true } };
16
98
  // ── helpers ───────────────────────────────────────────────────────────────────
17
99
  function ok(data) {
18
100
  return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
@@ -21,38 +103,308 @@ function fail(err) {
21
103
  const msg = err instanceof Error ? err.message : String(err);
22
104
  return { content: [{ type: 'text', text: msg }], isError: true };
23
105
  }
24
- // Strip frontmatter fields that duplicate the top-level id/type keys
106
+ /** Deny-without-leaking helper same wording as a missing repo/entity. */
107
+ function notFound(repo) {
108
+ return fail(new Error(`Folder "${repo}" not found`));
109
+ }
110
+ /**
111
+ * Map an AssetUploadError (or any Error with a stable `code` discriminator)
112
+ * onto the MCP `fail()` envelope, embedding the code so the client can
113
+ * branch programmatically. Wraps `fail()` rather than replacing it so any
114
+ * non-typed error falls through to the existing "unknown" path.
115
+ *
116
+ * The code makes its way into the text payload as a JSON object — MCP
117
+ * clients that parse the text get a structured shape; pure-text consumers
118
+ * still get a readable line.
119
+ */
120
+ function errorToMcpResult(err) {
121
+ if (err instanceof AssetUploadError) {
122
+ return {
123
+ content: [{
124
+ type: 'text',
125
+ text: JSON.stringify({ error: err.message, code: err.code }, null, 2),
126
+ }],
127
+ isError: true,
128
+ };
129
+ }
130
+ return fail(err);
131
+ }
132
+ // Shape a graph entity into the MCP result. Strip the frontmatter fields that
133
+ // duplicate the top-level keys: `id`, `entity_type`, AND `type`.
134
+ //
135
+ // `type` is the load-bearing one: it is NOT a duplicate of `entity_type` — it's
136
+ // a real field on `event` entities (the sub-type: meeting/call/workshop). Left
137
+ // in the spread it would land AFTER `type: entityType` and shadow the canonical
138
+ // type, so search/list reported `type: "meeting"` for an event and the caller
139
+ // couldn't round-trip that back into get_entity (`Entity not found:
140
+ // meeting/<id>`). We re-expose it as `subtype` so the top-level `type` is always
141
+ // the canonical entity type, which is what get_entity resolves against.
25
142
  function shape(id, entityType, data, extra) {
26
- const { id: _id, entity_type: _et, ...rest } = data;
27
- return { id, type: entityType, ...rest, ...extra };
143
+ const { id: _id, entity_type: _et, type: subtype, ...rest } = data;
144
+ return {
145
+ id,
146
+ type: entityType,
147
+ ...(subtype !== undefined ? { subtype } : {}),
148
+ ...rest,
149
+ ...extra,
150
+ };
151
+ }
152
+ /**
153
+ * Default subfolders for entity types when the caller omits `folder_path`.
154
+ * Existing entries with no folder live at the repo root; new entries of these
155
+ * types route into a typed subfolder so the repo stays organised by default.
156
+ * An explicit `folder_path: ""` (or any explicit value) overrides this map.
157
+ */
158
+ const DEFAULT_FOLDER_BY_TYPE = {
159
+ person: 'People',
160
+ organization: 'Organizations',
161
+ };
162
+ /** Convert an absolute entity path to a repo-relative POSIX path. */
163
+ function relPathOf(absPath, repoRoot) {
164
+ return absPath.startsWith(repoRoot + '/') ? absPath.slice(repoRoot.length + 1) : absPath;
165
+ }
166
+ /**
167
+ * Robustly resolve an entity from a search/list result so any hit re-opens
168
+ * without the caller guessing the physical type: explicit `path` wins; else try
169
+ * the supplied `entity_type`; on a miss (wrong/legacy type — e.g. an `event`
170
+ * whose `type` field is "meeting") fall back to id-only resolution. Shared by
171
+ * get_entity and delete_entity so the resolution can't drift between read and
172
+ * delete. Throws when nothing resolves (caller wraps via fail()).
173
+ */
174
+ function resolveEntityRef(graph, ref) {
175
+ if (ref.path)
176
+ return graph.getByPath(ref.path);
177
+ if (ref.entity_type) {
178
+ try {
179
+ return graph.get(ref.entity_type, ref.entity_id);
180
+ }
181
+ catch {
182
+ return graph.getById(ref.entity_id);
183
+ }
184
+ }
185
+ return graph.getById(ref.entity_id);
186
+ }
187
+ /**
188
+ * MCP clients sometimes serialize an object/array tool argument to a JSON
189
+ * STRING. For the artifact args that carry structured payloads — an
190
+ * ArtifactBundle or a patch-ops array — a perfectly valid payload then arrives
191
+ * as a string. Used as a `z.preprocess` step on the REGISTERED schema (NOT only
192
+ * in the handler): the MCP SDK validates `arguments` against the registered
193
+ * inputSchema BEFORE the handler runs (server/mcp.js `safeParseAsync`), so a
194
+ * `z.array(z.unknown())` ops field would reject a stringified array before the
195
+ * handler could coerce it. Preprocessing on the schema fixes both the SDK
196
+ * pre-validation and the handler's own re-parse from ONE place.
197
+ *
198
+ * Parse a string value back to JSON so the inner schema sees the structured
199
+ * value; a non-string value (already an object/array) and a string that is NOT
200
+ * valid JSON both pass through unchanged so the inner schema / downstream
201
+ * validator still emits the real error. Deliberately scoped to these artifact
202
+ * args — NOT create_entity/update_entity.data, where `z.record` already rejects
203
+ * a string and blanket coercion could mangle an object whose own field value is
204
+ * a JSON-looking string.
205
+ */
206
+ function coerceJsonArg(value) {
207
+ if (typeof value !== 'string')
208
+ return value;
209
+ try {
210
+ return JSON.parse(value);
211
+ }
212
+ catch {
213
+ return value;
214
+ }
28
215
  }
216
+ function parseArtifactBundleFromBody(content, label) {
217
+ let rawBundle;
218
+ try {
219
+ rawBundle = JSON.parse(content);
220
+ }
221
+ catch {
222
+ throw new Error(`${label} body is not a valid JSON bundle.`);
223
+ }
224
+ const validation = validateArtifactBundle(rawBundle);
225
+ if (!validation.ok || !validation.bundle) {
226
+ throw new Error(`Invalid artifact bundle: ${validation.errors.join('; ')}`);
227
+ }
228
+ return validation.bundle;
229
+ }
230
+ function exportedData(format, bytes, mimeType) {
231
+ if (format === 'html') {
232
+ return {
233
+ mime_type: 'text/html; charset=utf-8',
234
+ content: bytes.toString(),
235
+ };
236
+ }
237
+ const buffer = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes);
238
+ return {
239
+ mime_type: mimeType ?? (format === 'png' ? 'image/png' : 'application/pdf'),
240
+ data_base64: buffer.toString('base64'),
241
+ byteLength: buffer.length,
242
+ };
243
+ }
244
+ /**
245
+ * Presign MIME enum for `create_asset_upload`, derived from the format registry
246
+ * (P4-V1) so the tool's accepted set can't drift from the presign service's gate.
247
+ * z.enum needs a non-empty tuple; the registry always yields ≥1 image MIME, but
248
+ * fall back to png/jpeg defensively so a registry edit can never produce an
249
+ * empty enum that throws at module load.
250
+ */
251
+ const PRESIGN_MIME_TUPLE = (() => {
252
+ const mimes = presignMimeAllowlist();
253
+ return mimes.length > 0 ? mimes : ['image/png', 'image/jpeg'];
254
+ })();
255
+ /** Inline-upload (upload_asset_inline) caps + accepted-MIME display list,
256
+ * computed once at module load from the registry (not per /mcp request). */
257
+ const INLINE_MAX_BYTES = 6 * 1024 * 1024; // ~4.5MB pre-base64; keeps the model payload sane.
258
+ const INLINE_UPLOAD_MIME_LIST = inlineUploadMimeAllowlist().sort();
29
259
  // ── tool registration ─────────────────────────────────────────────────────────
30
- export function registerTools(server, wm, authService) {
31
- // Entity types the MCP surface may create. Matches the agent's allowlist so a
32
- // workspace looks identical across the main app and MCP clients. See
33
- // src/core/launch-entity-types.ts for policy rationale.
260
+ export function registerTools(server, wm, authService, ctx) {
261
+ const { user, wsHub, apiTokenName, commentService, selectionStore } = ctx;
262
+ // Register through this wrapper instead of server.registerTool directly so
263
+ // the always-load pin (PIN_META) is injected from a SINGLE source
264
+ // (PINNED_SET / MCP_PINNED_TOOLS) and can't drift per-call. Non-pinned tools
265
+ // pass through untouched.
266
+ const baseRegisterTool = server.registerTool.bind(server);
267
+ const reg = ((name, def, handler) => baseRegisterTool(name, PINNED_SET.has(name) ? { ...def, ...PIN_META } : def, handler));
268
+ // External-agent identity: "<owner first name>'s <token name>", or
269
+ // "<owner first name>'s agent" as a fallback when the token has no name
270
+ // (older tokens or contexts that didn't thread the token through). First name
271
+ // only keeps the chip short while preserving cross-user disambiguation; the
272
+ // `userId` lets the client paint the chip with the owning user's avatar accent
273
+ // (so the full identity is still recoverable). Falls back to the whole
274
+ // displayName when there's no space to split on (handles, single names).
275
+ const ownerFirstName = user.displayName.trim().split(/\s+/)[0] || user.displayName;
276
+ const agentLabel = apiTokenName
277
+ ? `${ownerFirstName}'s ${apiTokenName}`
278
+ : `${ownerFirstName}'s agent`;
279
+ const agentIdentity = {
280
+ kind: 'external',
281
+ label: agentLabel,
282
+ userId: String(user.id),
283
+ };
284
+ // Match the broadcast shape used by src/server/routes/graph-api.ts so the
285
+ // realtime listener in web/src/lib/stores/realtime.svelte.ts treats MCP
286
+ // mutations identically to web-UI mutations. The `agent` tag lets clients
287
+ // distinguish agent-driven mutations from human-driven ones.
288
+ function emitEntityChange(action, repo, entityType, entityId) {
289
+ wsHub?.broadcast({
290
+ type: 'entity_change',
291
+ action,
292
+ repo,
293
+ entityType,
294
+ entityId,
295
+ actor: user.displayName,
296
+ source: 'api',
297
+ agent: agentIdentity,
298
+ timestamp: new Date().toISOString(),
299
+ });
300
+ }
301
+ /**
302
+ * Wrap a mutation tool call so the sidebar can show a live "X's agent is
303
+ * acting" chip in the affected folder. `enter` fires before the work,
304
+ * `leave` after — wsHub TTL-cleans on crash. `verb` shapes the chip copy.
305
+ */
306
+ async function withAgentActivity(repo, verb, fn) {
307
+ wsHub?.handleAgentActivity('enter', repo, agentIdentity, verb);
308
+ try {
309
+ return await fn();
310
+ }
311
+ finally {
312
+ wsHub?.handleAgentActivity('leave', repo, agentIdentity, verb);
313
+ }
314
+ }
315
+ // Entity types the MCP surface may create. Matches the agent's allowlist so
316
+ // a workspace looks identical across the main app and MCP clients: the full
317
+ // canonical catalog plus workspace custom types. See
318
+ // src/core/entity-types-catalog.ts.
34
319
  const builtinTypeKeys = new Set(Object.keys(ENTITY_SCHEMAS));
35
320
  const customWorkspaceTypes = wm.getSchemaRegistry().getAllTypes()
36
321
  .filter(t => !builtinTypeKeys.has(t));
37
322
  const mcpAllowedCreateTypes = getAgentAllowedEntityTypes(customWorkspaceTypes);
38
323
  const mcpAllowedSet = new Set(mcpAllowedCreateTypes);
39
324
  // ── list_repos ────────────────────────────────────────────────────────────
40
- server.registerTool('list_repos', { description: 'List all folders in the Studiograph workspace' }, async () => ok(wm.getAllRepoConfigs()));
325
+ reg('list_repos', { description: 'List folders in the Studiograph workspace that you have access to' },
326
+ // Archived folders are hidden from the inventory (parity with the sidebar +
327
+ // agent folder list); they stay reachable by name for unarchive.
328
+ async () => ok(getAccessibleRepos(user, wm, authService).filter(r => !isArchivedRepo(r))));
329
+ // ── create_repo ───────────────────────────────────────────────────────────
330
+ //
331
+ // Creates a new shared folder in the workspace and grants the
332
+ // caller folder-admin access. Shares its implementation with the web route
333
+ // POST /api/repos via services/folder-creation.ts so both surfaces behave
334
+ // identically. After creation, use create_entity with the new folder name
335
+ // to add entries. Folder names are immutable slugs; the display name is
336
+ // editable later via the web UI.
337
+ const createRepoSchema = z.object({
338
+ name: z.string().describe('Folder slug — lowercase kebab-case (letters, digits, hyphens; must start with a letter or digit). ' +
339
+ 'This becomes the folder id and directory name; it cannot be changed without a rename.'),
340
+ display_name: z.string().optional().describe('Human-readable name shown in the UI (e.g. "Client Projects"). Defaults to the slug.'),
341
+ description: z.string().optional().describe('Short description of what this folder is for.'),
342
+ });
343
+ reg('create_repo', {
344
+ description: 'Create a new shared folder in the workspace. Use this when an agent or ' +
345
+ 'user needs a fresh container for a new project, client area, or topic and no existing ' +
346
+ 'folder fits. The caller is automatically granted folder-admin access. ' +
347
+ 'Follow up with create_entity (passing the new folder name as `repo`) to seed entries. ' +
348
+ 'Folder names must be kebab-case and unique; the private per-user "entries-<id>" ' +
349
+ 'namespace is reserved and will be rejected.',
350
+ inputSchema: createRepoSchema,
351
+ }, async (args) => {
352
+ const { name, display_name, description } = createRepoSchema.parse(args);
353
+ try {
354
+ const repo = await createSharedFolder(wm, authService, { name, display_name, description }, user, wsHub);
355
+ return ok({ success: true, ...repo });
356
+ }
357
+ catch (err) {
358
+ if (err instanceof FolderCreationError) {
359
+ return fail(new Error(err.message));
360
+ }
361
+ return fail(err);
362
+ }
363
+ });
41
364
  // ── list_entities ─────────────────────────────────────────────────────────
42
365
  const listEntitiesSchema = z.object({
43
366
  repo: z.string().optional().describe('Folder name — omit to list across all folders'),
44
- entity_type: z.string().optional().describe('Entity type filter (project, client, person, meeting…)'),
367
+ entity_type: z.string().optional().describe('Entity type filter (project, client, person, event…)'),
45
368
  limit: z.number().int().positive().optional().describe('Maximum number of results'),
46
369
  });
47
- server.registerTool('list_entities', { description: 'List entities in a folder (or all folders). Optionally filter by entity type.', inputSchema: listEntitiesSchema }, async (args) => {
370
+ reg('list_entities', { description: 'List entries in a folder (or all folders). Optionally filter by entity type. Each result carries the canonical `type` and a repo-relative `path` — pass either back to get_entity to open it.', inputSchema: listEntitiesSchema }, async (args) => {
48
371
  const { repo, entity_type, limit } = listEntitiesSchema.parse(args);
49
372
  try {
373
+ // Comments live in SQLite, not the filesystem graph — dispatch through
374
+ // the virtual-entity adapter when the caller asks for them explicitly.
375
+ if (entity_type === 'comment' && commentService) {
376
+ if (repo && !hasRepoAccess(user, repo, wm, authService))
377
+ return notFound(repo);
378
+ const accessibleNames = new Set(getAccessibleRepos(user, wm, authService).map(r => r.name));
379
+ const envelopes = commentVirtualEntity.listComments(commentService, { repo, limit });
380
+ const filtered = envelopes.filter(e => accessibleNames.has(e.data.repo));
381
+ return ok(filtered.map(e => shape(e.id, e.entityType, e.data, { repo: e.data.repo })));
382
+ }
50
383
  if (repo) {
51
- const entities = wm.getGraph(repo).list(entity_type, limit);
52
- return ok(entities.map(e => shape(e.id, e.entityType, e.data)));
384
+ if (!hasRepoAccess(user, repo, wm, authService))
385
+ return notFound(repo);
386
+ const graph = wm.getGraph(repo);
387
+ const repoRoot = graph.getRepoPath();
388
+ const entities = graph.list(entity_type, limit);
389
+ return ok(entities.map(e => shape(e.id, e.entityType, e.data, { path: relPathOf(e.path, repoRoot) })));
390
+ }
391
+ // No repo specified → iterate only over the user's accessible folders.
392
+ const accessible = getAccessibleRepos(user, wm, authService);
393
+ const results = [];
394
+ for (const r of accessible) {
395
+ try {
396
+ const graph = wm.getGraph(r.name);
397
+ const repoRoot = graph.getRepoPath();
398
+ const list = graph.list(entity_type);
399
+ for (const e of list) {
400
+ results.push(shape(e.id, e.entityType, e.data, { repo: r.name, path: relPathOf(e.path, repoRoot) }));
401
+ if (limit && results.length >= limit)
402
+ return ok(results);
403
+ }
404
+ }
405
+ catch { /* skip unmounted repos */ }
53
406
  }
54
- const entities = wm.list(entity_type, limit);
55
- return ok(entities.map(e => shape(e.id, e.entityType, e.data, { repo: e.repoName })));
407
+ return ok(results);
56
408
  }
57
409
  catch (err) {
58
410
  return fail(err);
@@ -61,14 +413,88 @@ export function registerTools(server, wm, authService) {
61
413
  // ── get_entity ────────────────────────────────────────────────────────────
62
414
  const getEntitySchema = z.object({
63
415
  repo: z.string().describe('Folder name'),
64
- entity_type: z.string().describe('Entity type (project, client, person…)'),
65
- entity_id: z.string().describe('Entity ID (kebab-case slug)'),
416
+ entity_type: z.string().optional().describe('Canonical entity type as returned in a search/list result\'s `type` (e.g. "event" — NOT a category/subtype value like "meeting"). Optional: when omitted or wrong, the entity is resolved by id.'),
417
+ entity_id: z.string().optional().describe('Entity ID (kebab-case slug). Provide entity_id or path.'),
418
+ path: z.string().optional().describe('Repo-relative file path from a search/list result (`path` field). When given, resolves directly and ignores entity_type.'),
419
+ });
420
+ reg('get_entity', {
421
+ description: 'Get a specific entry — full markdown content and frontmatter. Robust resolution: pass the `type` and `id` ' +
422
+ '(or the `path`) exactly as returned by search_entities/list_entities. If the type is wrong or omitted, the ' +
423
+ 'entry is still resolved by id, so a search hit never fails to open.',
424
+ inputSchema: getEntitySchema,
425
+ }, async (args) => {
426
+ const { repo, entity_type, entity_id, path } = getEntitySchema.parse(args);
427
+ if (!hasRepoAccess(user, repo, wm, authService))
428
+ return notFound(repo);
429
+ if (!entity_id && !path)
430
+ return fail(new Error('get_entity requires entity_id or path'));
431
+ try {
432
+ if (entity_type === 'comment' && commentService) {
433
+ const envelope = commentVirtualEntity.getComment(commentService, entity_id);
434
+ if (!envelope)
435
+ return fail(new Error(`Comment "${entity_id}" not found`));
436
+ if (envelope.data.repo !== repo)
437
+ return notFound(repo);
438
+ return ok({ id: envelope.id, type: envelope.entityType, data: envelope.data, content: envelope.document.content });
439
+ }
440
+ const graph = wm.getGraph(repo);
441
+ // Resolve robustly so any search/list result re-opens without the
442
+ // caller guessing the physical type (shared with delete_entity).
443
+ const entity = resolveEntityRef(graph, { entity_type, entity_id, path });
444
+ // Prefer the live editor body when a seeded Y.Doc is open: graph reads
445
+ // disk, which lags the editor by the autosave debounce, so a user's
446
+ // freshly-placed image/asset embed may not be on disk yet. Use the
447
+ // RESOLVED type/id — getById/getByPath may differ from what was passed.
448
+ const liveBody = graph.getLiveBody(entity.entityType, entity.id);
449
+ const content = liveBody ?? entity.document.content;
450
+ return ok({
451
+ id: entity.id,
452
+ type: entity.entityType,
453
+ path: relPathOf(entity.path, graph.getRepoPath()),
454
+ data: entity.data,
455
+ content,
456
+ // Artifact entries: the staleness token for edits. Pass back to
457
+ // create_artifact as base_hash so an edit derived from this read
458
+ // fails loudly (instead of silently clobbering) if the artifact
459
+ // changed in between.
460
+ ...(entity.entityType === 'artifact' ? { artifact_hash: hashArtifactBody(content) } : {}),
461
+ });
462
+ }
463
+ catch (err) {
464
+ return fail(err);
465
+ }
66
466
  });
67
- server.registerTool('get_entity', { description: 'Get a specific entity — includes full markdown content and frontmatter', inputSchema: getEntitySchema }, async (args) => {
68
- const { repo, entity_type, entity_id } = getEntitySchema.parse(args);
467
+ // ── get_entity_schema ─────────────────────────────────────────────────────
468
+ const getEntitySchemaSchema = z.object({
469
+ entity_type: z.string().describe('Entity type (canonical or workspace custom)'),
470
+ });
471
+ reg('get_entity_schema', {
472
+ description: 'Get the authoritative schema for an entry type before creating or editing one. Returns each ' +
473
+ 'frontmatter field with its type, whether it is optional, the Zod description, the full enum value ' +
474
+ 'list when applicable, and any default — call this before writing enum-shaped fields (stage, status, ' +
475
+ 'category, priority, etc.) since the validator only accepts the listed `enum` values. Also returns ' +
476
+ '`bodyTemplate`: a suggested markdown section scaffold for the entry body (e.g. a deal\'s ' +
477
+ '"## Opportunity / ## Scope / ## Next Actions / ## Notes"). The body template is a GUIDELINE, not a ' +
478
+ 'requirement — use it, adapt it, add or drop sections, or ignore it entirely based on what the ' +
479
+ 'specific entry needs.',
480
+ inputSchema: getEntitySchemaSchema,
481
+ }, async (args) => {
482
+ const { entity_type } = getEntitySchemaSchema.parse(args);
69
483
  try {
70
- const entity = wm.getGraph(repo).get(entity_type, entity_id);
71
- return ok({ id: entity.id, type: entity.entityType, data: entity.data, content: entity.document.content });
484
+ const registry = wm.getSchemaRegistry();
485
+ const knownTypes = new Set(registry.getAllTypes());
486
+ if (!knownTypes.has(entity_type)) {
487
+ return fail(new Error(`Unknown entity type: ${entity_type}. Known types: ${[...knownTypes].sort().join(', ')}`));
488
+ }
489
+ const fields = describeZodObject(registry.getSchema(entity_type));
490
+ // Suggested body scaffold (guideline). null for types with no template
491
+ // (e.g. note, dataset) — omit the key rather than emit null.
492
+ const bodyTemplate = registry.getBodyTemplate(entity_type);
493
+ return ok({
494
+ entityType: entity_type,
495
+ fields,
496
+ ...(bodyTemplate != null ? { bodyTemplate } : {}),
497
+ });
72
498
  }
73
499
  catch (err) {
74
500
  return fail(err);
@@ -81,11 +507,20 @@ export function registerTools(server, wm, authService) {
81
507
  repos: z.array(z.string()).optional().describe('Limit to specific folder names'),
82
508
  limit: z.number().int().positive().optional().describe('Maximum number of results'),
83
509
  });
84
- server.registerTool('search_entities', { description: 'Full-text search for entities across all folders', inputSchema: searchSchema }, async (args) => {
510
+ reg('search_entities', { description: 'Full-text search for entries across folders you can access. Each result carries the canonical `type` and a repo-relative `path` — pass either back to get_entity to open it.', inputSchema: searchSchema }, async (args) => {
85
511
  const { query, entity_type, repos, limit } = searchSchema.parse(args);
86
512
  try {
87
- const results = await wm.search({ query, entityType: entity_type, repoNames: repos, limit });
88
- return ok(results.map(e => shape(e.id, e.entityType, e.data, { repo: e.repoName })));
513
+ const accessibleNames = new Set(getAccessibleRepos(user, wm, authService).map(r => r.name));
514
+ const scoped = repos
515
+ ? repos.filter(r => accessibleNames.has(r))
516
+ : [...accessibleNames];
517
+ if (scoped.length === 0)
518
+ return ok([]);
519
+ const results = await wm.search({ query, entityType: entity_type, repoNames: scoped, limit });
520
+ return ok(results.map(e => shape(e.id, e.entityType, e.data, {
521
+ repo: e.repoName,
522
+ path: relPathOf(e.path, wm.getGraph(e.repoName).getRepoPath()),
523
+ })));
89
524
  }
90
525
  catch (err) {
91
526
  return fail(err);
@@ -102,49 +537,1476 @@ export function registerTools(server, wm, authService) {
102
537
  repo: z.string().describe('Folder name'),
103
538
  entity_type: createEntityTypeSchema.describe(`Entity type. Allowed: ${mcpAllowedCreateTypes.join(', ')}.`),
104
539
  entity_id: z.string().describe('Entity ID (kebab-case slug)'),
105
- data: z.record(z.unknown()).describe('Frontmatter fields as a JSON object'),
106
- content: z.string().optional().describe('Markdown body content'),
540
+ data: z.record(z.string(), z.unknown()).describe('Type-specific frontmatter fields (e.g. for person: name, email, organization, role). ' +
541
+ 'EVERY type requires `name` — the display title of the entry. For content types ' +
542
+ '(note, document, deck, event) `name` IS the title; pass it as `name` (`title` is also ' +
543
+ 'accepted and normalized to `name`). ' +
544
+ 'Base fields (entity_id, entity_type, created_at, updated_at, created_by, updated_by) ' +
545
+ 'are stamped automatically — do not include them.'),
546
+ content: z.string().optional().describe('Markdown body content. Do NOT start the body with an H1 (`# Title`) or use `#` anywhere — the entry already has a title (from frontmatter) rendered above the body; a `#` heading just duplicates it. Any headings begin at `##` (H2). Write one line per paragraph — use newlines only for real paragraph/list breaks, not to wrap prose at a column width (entries render source line breaks literally).'),
547
+ folder_path: z.string().optional().describe('Repo-relative subfolder to place the entry in (e.g. "People", "clients/acme"). ' +
548
+ 'Omit to use the per-type default (person → People, organization → Organizations); ' +
549
+ 'pass an empty string to force the repo root. Parent folders are auto-created.'),
107
550
  });
108
- server.registerTool('create_entity', { description: 'Create a new entity in a folder. Commits to git automatically.', inputSchema: createSchema }, async (args) => {
109
- const { repo, entity_type, entity_id, data, content } = createSchema.parse(args);
551
+ reg('create_entity', {
552
+ description: 'Create a new entity (entry) in a folder — person, organization, project, task, note, document, etc. ' +
553
+ 'Commits to git automatically. Base frontmatter (entity_id, timestamps, author) is stamped ' +
554
+ 'automatically; `data` carries only type-specific fields. For person entries, `status` synonyms ' +
555
+ '(active/prospective/past/…) normalize to `current` | `former`. See the `folder_path` field for ' +
556
+ 'placement defaults. Response includes the final repo-relative `path`. ' +
557
+ 'Do NOT use this tool to make an `artifact` (Artifact Canvas: slide decks, documents, graphics, ' +
558
+ 'prototypes, diagrams) — use create_artifact instead. An `artifact` body must be ArtifactBundle JSON, ' +
559
+ 'never markdown; create_artifact assembles and validates it for you. For raw markdown/prose, use a ' +
560
+ '`document` entry.',
561
+ inputSchema: createSchema,
562
+ }, async (args) => {
563
+ const { repo, entity_type, entity_id, data, content, folder_path } = createSchema.parse(args);
564
+ if (!hasRepoAccess(user, repo, wm, authService))
565
+ return notFound(repo);
566
+ if (!canWriteRepo(user, repo, wm, authService)) {
567
+ return fail(new Error(`You do not have write access to folder "${repo}"`));
568
+ }
110
569
  // Defense in depth: MCP SDK may not enforce zod enum strictly on all clients.
111
570
  if (!mcpAllowedSet.has(entity_type)) {
112
- const hint = isLaunchVisibleEntityType(entity_type)
113
- ? ''
571
+ const hint = isCuratedEntityType(entity_type)
572
+ ? '' // shouldn't happen for any other curated type — they're all allowed
114
573
  : ` Allowed: ${mcpAllowedCreateTypes.join(', ')}.`;
115
- return fail(new Error(`Entity type "${entity_type}" is not available for creation.${hint}`));
574
+ return fail(new Error(`Entity type "${entity_type}" is not a known canonical or workspace custom type.${hint}`));
575
+ }
576
+ // Explicit folder_path (including "") wins; otherwise apply the per-type
577
+ // default so the agent doesn't have to know the workspace convention.
578
+ const effectiveFolder = folder_path !== undefined
579
+ ? folder_path
580
+ : (DEFAULT_FOLDER_BY_TYPE[entity_type] ?? '');
581
+ // Enforce the user↔person bridge invariant on person creates, matching
582
+ // graph-api + graph-tools: at most one person entity per auth handle
583
+ // workspace-wide (validateUserHandleInPayload), and only a workspace
584
+ // owner may stamp a handle (assertCanChangeUserHandle). Without this,
585
+ // create_entity over MCP was the one write path that could plant a
586
+ // SECOND linked record — violating "multiple person entries, but only
587
+ // one linked".
588
+ if (entity_type === 'person') {
589
+ try {
590
+ const incoming = (data ?? {});
591
+ validateUserHandleInPayload(wm, incoming);
592
+ if (Object.prototype.hasOwnProperty.call(incoming, 'user_handle')) {
593
+ assertCanChangeUserHandle(user, '', incoming.user_handle);
594
+ }
595
+ }
596
+ catch (err) {
597
+ if (err instanceof BridgeError)
598
+ return fail(err);
599
+ throw err;
600
+ }
116
601
  }
602
+ // Scope ref fields (today: task.assignee → person). Reject a wikilink
603
+ // that resolves to a non-person; forward references are allowed.
117
604
  try {
118
- const entity = await wm.getGraph(repo).create({
605
+ assertEntityRefs(wm, entity_type, data, { userExists: (id) => !!authService.findUserById(Number(id)) });
606
+ }
607
+ catch (err) {
608
+ return fail(err);
609
+ }
610
+ const actor = buildActor(user);
611
+ let options;
612
+ try {
613
+ // Rejects a markdown/malformed body on an `artifact` entry (ArtifactBodyError)
614
+ // with guidance to use create_artifact — see prepareCreatePayload.
615
+ options = prepareCreatePayload({
119
616
  entityType: entity_type,
120
617
  entityId: entity_id,
121
618
  data: data,
122
619
  content,
123
- });
124
- return ok({ success: true, id: entity.id, type: entity.entityType });
620
+ folderPath: effectiveFolder || undefined,
621
+ }, actor);
125
622
  }
126
623
  catch (err) {
127
624
  return fail(err);
128
625
  }
626
+ return withAgentActivity(repo, 'creating', async () => {
627
+ try {
628
+ const graph = wm.getGraph(repo);
629
+ // Detect implicit subfolder creation: if the entity lands in a folder
630
+ // that didn't exist yet, the entity_change below only refreshes the
631
+ // entry list — clients need a _folder event to refetch the folder
632
+ // tree (sidebar + list/gallery folder rows). Normalize the same way
633
+ // graph.create does so the existence check matches the real path.
634
+ const normFolder = effectiveFolder ? validateFolderPath(effectiveFolder) : '';
635
+ const folderExisted = !normFolder || existsSync(join(graph.getRepoPath(), normFolder));
636
+ const entity = await graph.create(options);
637
+ emitEntityChange('created', repo, entity.entityType, entity.id);
638
+ if (!folderExisted)
639
+ emitEntityChange('created', repo, '_folder', normFolder);
640
+ return ok({
641
+ success: true,
642
+ id: entity.id,
643
+ type: entity.entityType,
644
+ repo,
645
+ path: relPathOf(entity.path, graph.getRepoPath()),
646
+ folder_path: entity.folder_path,
647
+ });
648
+ }
649
+ catch (err) {
650
+ return fail(err);
651
+ }
652
+ });
129
653
  });
130
654
  // ── update_entity ─────────────────────────────────────────────────────────
655
+ //
656
+ // Bare success envelopes were misleading agents: callers couldn't tell
657
+ // whether their write landed, whether validation silently dropped a field,
658
+ // or whether they'd just rewritten the file with identical bytes. The
659
+ // response now includes path, commit, before/after hashes, per-section
660
+ // changed flags, and the post-write entity snapshot so agents can verify
661
+ // their intent matches reality without a follow-up get_entity round-trip.
662
+ //
663
+ // No-op detection compares semantic frontmatter (excluding auto-stamped
664
+ // `updated_at` / `updated_by`) and raw content. A no-op is reported as
665
+ // success with `noop: true` — not an error — so idempotent re-runs are
666
+ // explicit. If the caller passed data or content but the on-disk file is
667
+ // byte-identical after the write, that's a silent-drop signal: the call
668
+ // fails loudly with a message that names which inputs were ignored.
131
669
  const updateSchema = z.object({
132
670
  repo: z.string().describe('Folder name'),
133
671
  entity_type: z.string().describe('Entity type'),
134
672
  entity_id: z.string().describe('Entity ID'),
135
- data: z.record(z.unknown()).optional().describe('Frontmatter fields to update (merged with existing)'),
136
- content: z.string().optional().describe('New markdown body content'),
673
+ data: z.record(z.string(), z.unknown()).optional().describe('Frontmatter fields to update (merged with existing). Set a field to null to DELETE it (omitting a key leaves the existing value untouched).'),
674
+ content: z.string().optional().describe('New markdown body content (replaces the entire body). Do NOT start the body with an H1 (`# Title`) or use `#` anywhere — the entry already has a title (from frontmatter) rendered above the body; a `#` heading just duplicates it. Any headings begin at `##` (H2). Write one line per paragraph — use newlines only for real paragraph/list breaks, not to wrap prose at a column width (entries render source line breaks literally).'),
675
+ allow_image_removal: z.boolean().optional().describe('Whole-body writes are rejected with code ASSET_REMOVAL_BLOCKED when the submitted content would drop ' +
676
+ 'image/attachment embeds that the current document still has (a common silent-data-loss bug when an agent ' +
677
+ 'edits stale content). Set true ONLY when you intend to delete those embeds. Prefer patch_entity for small edits.'),
137
678
  });
138
- server.registerTool('update_entity', { description: 'Update an existing entity. Data fields are merged with existing frontmatter.', inputSchema: updateSchema }, async (args) => {
139
- const { repo, entity_type, entity_id, data, content } = updateSchema.parse(args);
679
+ reg('update_entity', {
680
+ description: 'Update (edit) an existing entity. `data` fields merge with existing frontmatter; `content` replaces the ' +
681
+ 'whole markdown body. Immutable base fields are preserved from disk; `updated_at`/`updated_by` are stamped. ' +
682
+ 'For small body edits prefer patch_entity (it can\'t drop image embeds). Returns the post-write snapshot plus ' +
683
+ 'diagnostic flags (frontmatter_changed, content_changed, noop, commit) so you can verify the write landed ' +
684
+ 'without a follow-up get_entity. ' +
685
+ 'To edit an `artifact` entry, do NOT write its body here (it is ArtifactBundle JSON, not markdown) — ' +
686
+ 'call read_artifact then create_artifact. Frontmatter-only `data` edits to an artifact are fine.',
687
+ inputSchema: updateSchema,
688
+ }, async (args) => {
689
+ const { repo, entity_type, entity_id, data, content, allow_image_removal } = updateSchema.parse(args);
690
+ if (!hasRepoAccess(user, repo, wm, authService))
691
+ return notFound(repo);
692
+ if (!canWriteRepo(user, repo, wm, authService)) {
693
+ return fail(new Error(`You do not have write access to folder "${repo}"`));
694
+ }
695
+ if (data === undefined && content === undefined) {
696
+ return fail(new Error('update_entity requires at least one of `data` or `content`'));
697
+ }
698
+ // Enforce the user↔person bridge invariant on person updates, matching
699
+ // graph-api + graph-tools: workspace-wide handle uniqueness (skipping the
700
+ // entity being written) + owner-only authz to change `user_handle`. Keeps
701
+ // update_entity from becoming a back door around "only one linked record".
702
+ if (entity_type === 'person' && data) {
703
+ try {
704
+ const incoming = data;
705
+ validateUserHandleInPayload(wm, incoming, { repo, entityId: entity_id });
706
+ if (Object.prototype.hasOwnProperty.call(incoming, 'user_handle')) {
707
+ let currentHandle = '';
708
+ try {
709
+ currentHandle = (wm.getGraph(repo).get('person', entity_id)?.data?.user_handle ?? '');
710
+ }
711
+ catch { /* new/missing entity — treat as empty */ }
712
+ assertCanChangeUserHandle(user, currentHandle, incoming.user_handle);
713
+ }
714
+ }
715
+ catch (err) {
716
+ if (err instanceof BridgeError)
717
+ return fail(err);
718
+ throw err;
719
+ }
720
+ }
721
+ // Scope ref fields (today: task.assignee → person). Reject a wikilink
722
+ // that resolves to a non-person; forward references are allowed.
723
+ try {
724
+ assertEntityRefs(wm, entity_type, data, { userExists: (id) => !!authService.findUserById(Number(id)) });
725
+ }
726
+ catch (err) {
727
+ return fail(err);
728
+ }
729
+ const actor = buildActor(user);
730
+ let options;
140
731
  try {
141
- const entity = await wm.getGraph(repo).update({
732
+ // Rejects a markdown/malformed body on an `artifact` entry (ArtifactBodyError)
733
+ // with guidance to use create_artifact — see prepareUpdatePayload.
734
+ options = prepareUpdatePayload({
142
735
  entityType: entity_type,
143
736
  entityId: entity_id,
144
737
  data: data,
145
738
  content,
739
+ }, actor);
740
+ }
741
+ catch (err) {
742
+ return fail(err);
743
+ }
744
+ return withAgentActivity(repo, 'editing', async () => {
745
+ try {
746
+ const graph = wm.getGraph(repo);
747
+ // Diagnosed update (shared with the agent surface): snapshots
748
+ // before, runs the server-write path (disk + commit serialize via
749
+ // the per-doc lock; live clients get the post-commit broadcast),
750
+ // and throws SilentDropError when the caller intended a change
751
+ // that produced no on-disk diff. The outer catch maps that to fail.
752
+ const { entity, frontmatter_changed, content_changed, noop, broadcast } = await diagnosedUpdate(graph, options, { data, content }, { allowAssetRemoval: allow_image_removal });
753
+ emitEntityChange('updated', repo, entity.entityType, entity.id);
754
+ // Repo-relative path so external agents don't see the workspace
755
+ // absolute path. The HEAD commit is captured after graph.update
756
+ // commits the transaction — null when noGit is on or no commit
757
+ // was produced (e.g. transaction self-cleared because there was
758
+ // no staged diff).
759
+ const repoRoot = graph.getRepoPath();
760
+ const relPath = entity.path.startsWith(repoRoot + '/')
761
+ ? entity.path.slice(repoRoot.length + 1)
762
+ : entity.path;
763
+ const commit = graph.getGitService()?.getHeadCommit() ?? null;
764
+ return ok({
765
+ success: true,
766
+ id: entity.id,
767
+ type: entity.entityType,
768
+ repo,
769
+ path: relPath,
770
+ commit,
771
+ frontmatter_changed,
772
+ content_changed,
773
+ noop,
774
+ broadcast,
775
+ updated_at: entity.data.updated_at ?? null,
776
+ entity: {
777
+ id: entity.id,
778
+ type: entity.entityType,
779
+ data: entity.data,
780
+ content: entity.document.content,
781
+ },
782
+ });
783
+ }
784
+ catch (err) {
785
+ if (err instanceof DroppedAssetError) {
786
+ return ok({
787
+ success: false,
788
+ code: 'ASSET_REMOVAL_BLOCKED',
789
+ droppedAssets: err.dropped,
790
+ message: err.message,
791
+ });
792
+ }
793
+ return fail(err);
794
+ }
795
+ });
796
+ });
797
+ // ── Artifact Canvas tools ────────────────────────────────────────────────
798
+ //
799
+ // MCP parity for the in-app artifact tools. External agents should not need
800
+ // to hand-roll graph entity JSON for artifacts or discover the HTTP export
801
+ // routes. These wrappers keep the same ArtifactBundle validation and commit
802
+ // semantics as the web/chat flow, while visual inspection reuses the shared
803
+ // Playwright renderer used by PDF/PNG export.
804
+ const createArtifactSchema = z.object({
805
+ repo: z.string().describe('Folder name where the artifact entry should be created or updated.'),
806
+ bundle: z.preprocess(coerceJsonArg, z.unknown()).nonoptional().describe('ArtifactBundle object with artifactId, title, and frames: an ordered, non-empty array of { id, title?, layout (slides|pages|artboard|app|whiteboard), geometry ({ width, height }), content (the frame\'s HTML fragment), background? (neutral|transparent|a safe CSS color or gradient — set it to the design\'s real surface so a dark/colored frame does not render white) }. A "pages" (document) frame also accepts furniture ({ header?: {first?,middle?,last?}, footer?: {first?,middle?,last?} } — per-page running header/footer for letterhead) and pageMargins ({ top?, right?, bottom?, left? } in px). Optional bundle-level: shared ({ files, head }), tokens (CSS design variables), libraries (three, d3, motion).'),
807
+ entity_id: z.string().optional().describe('Existing artifact entity id to update, or desired id for a new artifact. Defaults to a slug from bundle.artifactId/title.'),
808
+ folder_path: z.string().optional().describe('Repo-relative subfolder for a newly created artifact. Ignored when updating an existing artifact.'),
809
+ base_hash: z.string().optional().describe('REQUIRED when updating an artifact that already has content: the artifact_hash returned by get_entity for that entry. It detects that the artifact changed since you read it (your edit would silently overwrite someone else\'s) and fails with guidance instead of clobbering. Optional only when filling a blank artifact.'),
810
+ replace: z.boolean().optional().describe('Set true to intentionally overwrite a non-blank artifact with a blank bundle (clearing it). Without it, blanking a populated artifact is refused. base_hash from get_entity is still required.'),
811
+ });
812
+ reg('create_artifact', {
813
+ description: 'Create or update a persisted sandboxed Artifact Canvas entry from an HTML/CSS/JS ArtifactBundle. ' +
814
+ 'Use this for DESIGNED, laid-out, visual outputs: slide decks, social graphics, prototypes, diagrams, ' +
815
+ 'small interactive tools, artifacts using curated local libraries (three, d3, motion), and documents the ' +
816
+ 'user wants DESIGNED on letterhead / as a formatted PDF. A plain prose draft of a proposal, contract, memo, ' +
817
+ 'or report is a "document" ENTRY (create_entity), NOT an artifact — build a document artifact only when the ' +
818
+ 'user signals a designed/print output; when ambiguous, prefer the document entry or ask. ' +
819
+ 'Match the artifact TYPE to its frame structure: a slide DECK is N "slides" frames, one per slide ' +
820
+ '(e.g. 1280×720); a designed DOCUMENT — a letter, contract, proposal, or report laid out on letterhead — ' +
821
+ 'is ONE "pages" frame whose content is the WHOLE document body at a fixed page size (US-Letter 816×1056; ' +
822
+ 'also Legal 816×1344, Tabloid 1056×1632, A4 794×1123, A3 1123×1587, A5 559×794): the platform flows the ' +
823
+ 'text across as many pages as needed, numbers them, and exports a real multi-page PDF — so do NOT set a ' +
824
+ 'document\'s height to "auto", do NOT split it into one frame per page, and do NOT build a page grid ' +
825
+ 'inside an "app"/"artboard" frame. Style it as rich HTML/CSS; for repeating letterhead/headers/footers use ' +
826
+ 'standard CSS Paged Media in a shared CSS file (@page margin boxes + position:running(); a full-bleed ' +
827
+ 'branded band runs edge-to-edge with side page margins 0 + a padded body), which overrides the platform ' +
828
+ 'defaults. (The "furniture"/"pageMargins" frame fields are an optional shortcut for a plain inset ' +
829
+ 'header/footer.) A GRAPHIC, poster, or diagram is ' +
830
+ '"artboard" frame(s) at a fixed size; an APP or prototype is a single "app" frame with fill geometry; a ' +
831
+ 'WHITEBOARD is a single "whiteboard" frame. Never hand-roll a stepper, filmstrip, pagination, or page ' +
832
+ 'grid inside one frame. ' +
833
+ 'Validates the bundle, writes an artifact graph entry, commits to git, and ' +
834
+ 'broadcasts the same live entity_change event as the web agent. ' +
835
+ 'To EDIT an existing artifact: call get_entity first, modify the full bundle from its content, and pass ' +
836
+ 'its artifact_hash back as base_hash (REQUIRED when the artifact already has content) — a concurrent ' +
837
+ 'change is then detected instead of silently overwritten. ' +
838
+ 'The result reports the resolved frame structure (manifest) and any warnings — check the frame count and ' +
839
+ 'per-frame layouts against what the user asked for (a deck is N "slides" frames, not one "app" frame) and ' +
840
+ 'fix it before responding.',
841
+ inputSchema: createArtifactSchema,
842
+ }, async (args) => {
843
+ const { repo, bundle: rawBundle, entity_id, folder_path, base_hash, replace } = createArtifactSchema.parse(args);
844
+ if (!hasRepoAccess(user, repo, wm, authService))
845
+ return notFound(repo);
846
+ if (!canWriteRepo(user, repo, wm, authService)) {
847
+ return fail(new Error(`You do not have write access to folder "${repo}"`));
848
+ }
849
+ // base_hash asserts "this is an edit of an entry I read" — without
850
+ // entity_id the id-collision loop below would mint a `-N` DUPLICATE and
851
+ // silently ignore the hash, forking the artifact instead of updating it.
852
+ if (base_hash && !entity_id) {
853
+ return fail(new Error('base_hash implies updating an existing artifact, but entity_id is missing — pass the entity_id you read with get_entity, or omit base_hash to create a new artifact.'));
854
+ }
855
+ const graph = wm.getGraph(repo);
856
+ const actor = buildActor(user);
857
+ return withAgentActivity(repo, entity_id ? 'editing' : 'creating', async () => {
858
+ try {
859
+ // The ArtifactStore owns the whole write lifecycle (validate, identity,
860
+ // blank-stub adoption, content-equality idempotency, the OCC rules, the
861
+ // diagnosed write). The MCP surface stays thin: access (above), the
862
+ // response envelope, and the change broadcast.
863
+ const result = await new ArtifactStore(graph).write({
864
+ bundle: rawBundle,
865
+ actor,
866
+ // MCP entity_id is create-or-update-AT-id: a caller picks the id and
867
+ // expects it created if absent (unless base_hash asserts an edit).
868
+ target: entity_id ? { entityId: entity_id, createIfMissing: true } : null,
869
+ baseHash: base_hash,
870
+ replace,
871
+ folderPath: folder_path || undefined,
872
+ });
873
+ const entity = result.entity;
874
+ // Broadcast only when something actually changed. `noop` covers BOTH the
875
+ // content-equality short-circuit (no write) AND a diagnosedUpdate that
876
+ // ran but changed nothing — neither should emit a phantom change. Matches
877
+ // the chat surface's `!location.noop` gate.
878
+ if (!result.noop)
879
+ emitEntityChange(result.action, repo, entity.entityType, entity.id);
880
+ return ok({
881
+ success: true,
882
+ action: result.action,
883
+ id: result.entityId,
884
+ type: entity.entityType,
885
+ repo,
886
+ path: relPathOf(entity.path, graph.getRepoPath()),
887
+ commit: graph.getGitService()?.getHeadCommit() ?? null,
888
+ noop: result.noop,
889
+ // Return-rich (P4.3): the resolved frame structure + warnings, built
890
+ // from the bundle ArtifactStore actually persisted, so the agent can
891
+ // self-verify (e.g. a deck that collapsed to one frame) without a
892
+ // render. Structure-only (no per-frame source) → persists safely.
893
+ ...summarizeArtifactWrite(result.bundle),
894
+ ...(result.diagnostics ? {
895
+ frontmatter_changed: result.diagnostics.frontmatterChanged,
896
+ content_changed: result.diagnostics.contentChanged,
897
+ broadcast: result.diagnostics.broadcast,
898
+ } : {}),
899
+ });
900
+ }
901
+ catch (err) {
902
+ if (err instanceof ArtifactWriteError) {
903
+ switch (err.code) {
904
+ case 'needs-base-hash':
905
+ return fail(new Error('This artifact already has content — call get_entity first and pass its artifact_hash as base_hash, so a concurrent change is detected instead of silently overwritten. (Intentionally clearing it requires base_hash plus replace: true.)'));
906
+ case 'stale':
907
+ return fail(new Error('Artifact changed since you read it (base_hash mismatch). Call get_entity again, re-apply your change to the current source, then retry with the new artifact_hash.'));
908
+ case 'blank-overwrite':
909
+ return fail(new Error('The new bundle is blank but this artifact has content. Pass replace: true only if you intend to clear it.'));
910
+ case 'deleted':
911
+ return fail(new Error(`Artifact "${entity_id}" no longer exists in "${repo}" (deleted or moved since you read it). Re-check with get_entity or search_entities before retrying — refusing to recreate it from a possibly stale copy.`));
912
+ default:
913
+ return fail(new Error(err.message));
914
+ }
915
+ }
916
+ return fail(err);
917
+ }
918
+ });
919
+ });
920
+ const patchArtifactSchema = z.object({
921
+ repo: z.string().describe('Folder containing the persisted artifact entry to patch.'),
922
+ entity_id: z.string().describe('Persisted artifact entity id to patch.'),
923
+ base_hash: z.string().describe('REQUIRED: the artifact_hash returned by get_entity for the version your ops target. The patch is rejected (re-read and retry) if the artifact changed since that read.'),
924
+ ops: z.preprocess(coerceJsonArg, z.array(z.unknown())).nonoptional().describe('Ordered, non-empty list of patch operations applied in order to the current bundle. Each is an object with an `op` field: '
925
+ + 'set_frame_content { frameId, content } — replace a frame\'s HTML; '
926
+ + 'patch_frame_content { frameId, find, replace, expectedCount? } — replace every occurrence of an exact substring within ONE frame (cannot touch other frames); '
927
+ + 'set_frame_meta { frameId, title?, layout?, geometry?, background? } — empty title/background clears it; '
928
+ + 'add_frame { frame, atIndex? } — insert a new frame ({ id, layout, geometry, content, ... }); '
929
+ + 'remove_frame { frameId }; '
930
+ + 'reorder_frames { order } — order is a permutation of all current frame ids; '
931
+ + 'set_shared_file { path, content } / remove_shared_file { path }; '
932
+ + 'set_tokens { tokens } — replace the design-variable map.'),
933
+ });
934
+ reg('patch_artifact', {
935
+ description: 'Edit ONE frame (or a shared file / the token map) of an existing artifact WITHOUT re-emitting the whole bundle — the efficient way to revise a large artifact. To find which frame to edit cheaply, call get_artifact_manifest (frame ids + keyText + the artifact_hash, no full source). Pass that hash as base_hash plus an ordered list of ops; the ops apply in order, the result is validated, and it is saved as one revision with the same concurrency safety as create_artifact. Any frame you add must follow the same per-type structure as create_artifact (a document is ONE "pages" frame the platform flows across pages — never one frame per page, never a page grid inside an "app"/"artboard" frame). For a whole-bundle rewrite or a new artifact, use create_artifact. The result reports the resolved frame structure (manifest) and any warnings — check them against the user\'s intent and fix before responding.',
936
+ inputSchema: patchArtifactSchema,
937
+ }, async (args) => {
938
+ const { repo, entity_id, base_hash, ops } = patchArtifactSchema.parse(args);
939
+ if (!hasRepoAccess(user, repo, wm, authService))
940
+ return notFound(repo);
941
+ if (!canWriteRepo(user, repo, wm, authService)) {
942
+ return fail(new Error(`You do not have write access to folder "${repo}"`));
943
+ }
944
+ const graph = wm.getGraph(repo);
945
+ const actor = buildActor(user);
946
+ return withAgentActivity(repo, 'editing', async () => {
947
+ try {
948
+ // Friendly not-found before the shared runner (which throws a generic
949
+ // error from graph.get). The runner then loads the live body, verifies
950
+ // base_hash, applies the ops, and persists via ArtifactStore.write —
951
+ // the SAME path the web canvas's REST patch endpoint uses (P5).
952
+ try {
953
+ graph.get('artifact', entity_id);
954
+ }
955
+ catch {
956
+ return fail(new Error(`Artifact "${entity_id}" not found in "${repo}". Check with get_entity or search_entities first.`));
957
+ }
958
+ let applied;
959
+ let result;
960
+ try {
961
+ ({ outcome: result, applied } = await patchPersistedArtifact({
962
+ graph,
963
+ actor,
964
+ entityId: entity_id,
965
+ baseHash: base_hash,
966
+ ops,
967
+ label: `Artifact ${repo}/${entity_id}`,
968
+ }));
969
+ }
970
+ catch (err) {
971
+ if (err instanceof ArtifactPatchBaseHashError) {
972
+ return fail(new Error('Artifact changed since you read it (base_hash mismatch). Call get_entity again, recompute your ops against the current source, then retry with the new artifact_hash.'));
973
+ }
974
+ if (err instanceof ArtifactPatchError)
975
+ return fail(err);
976
+ throw err;
977
+ }
978
+ const written = result.entity;
979
+ if (!result.noop)
980
+ emitEntityChange(result.action, repo, written.entityType, written.id);
981
+ return ok({
982
+ success: true,
983
+ action: result.action,
984
+ id: result.entityId,
985
+ type: written.entityType,
986
+ repo,
987
+ path: relPathOf(written.path, graph.getRepoPath()),
988
+ commit: graph.getGitService()?.getHeadCommit() ?? null,
989
+ noop: result.noop,
990
+ changed_frame_ids: applied.changedFrameIds,
991
+ removed_frame_ids: applied.removedFrameIds,
992
+ reordered: applied.reordered,
993
+ // Return-rich (P4.3): the change summary PLUS the full resolved
994
+ // structure (manifest, with frameCount) + warnings, so the agent
995
+ // verifies the patched result matches intent in the same turn.
996
+ ...summarizeArtifactWrite(result.bundle),
997
+ });
998
+ }
999
+ catch (err) {
1000
+ if (err instanceof ArtifactWriteError) {
1001
+ switch (err.code) {
1002
+ case 'needs-base-hash':
1003
+ return fail(new Error('This artifact already has content — call get_entity first and pass its artifact_hash as base_hash so a concurrent change is detected instead of silently overwritten.'));
1004
+ case 'stale':
1005
+ return fail(new Error('Artifact changed since you read it (base_hash mismatch). Call get_entity again, recompute your ops against the current source, then retry with the new artifact_hash.'));
1006
+ case 'blank-overwrite':
1007
+ return fail(new Error('These ops would blank the artifact. patch_artifact will not clear all content; rewrite it with create_artifact (replace: true) if that is intended.'));
1008
+ case 'deleted':
1009
+ return fail(new Error(`Artifact "${entity_id}" no longer exists in "${repo}" (deleted or moved since you read it).`));
1010
+ default:
1011
+ return fail(new Error(err.message));
1012
+ }
1013
+ }
1014
+ return fail(err);
1015
+ }
1016
+ });
1017
+ });
1018
+ const getArtifactManifestSchema = z.object({
1019
+ repo: z.string().describe('Folder containing the persisted artifact entry.'),
1020
+ entity_id: z.string().describe('Persisted artifact entity id.'),
1021
+ frame_id: z.string().optional().describe('If set, also include this one frame\'s full HTML content (for a targeted patch_artifact). Errors if no frame has this id.'),
1022
+ });
1023
+ reg('get_artifact_manifest', {
1024
+ description: 'Read an artifact\'s STRUCTURE cheaply, without pulling its full source — the fast way to find which frame to edit before patch_artifact. Returns each frame\'s index, id, title, layout, geometry, a short keyText snippet, contentChars, plus the artifact_hash (pass it to patch_artifact as base_hash). No browser, always available. Pass frame_id to also get that one frame\'s full content. For the whole bundle use get_entity; for a visual render use inspect_artifact.',
1025
+ inputSchema: getArtifactManifestSchema,
1026
+ }, async (args) => {
1027
+ const { repo, entity_id, frame_id } = getArtifactManifestSchema.parse(args);
1028
+ if (!hasRepoAccess(user, repo, wm, authService))
1029
+ return notFound(repo);
1030
+ try {
1031
+ const graph = wm.getGraph(repo);
1032
+ let entity;
1033
+ try {
1034
+ entity = graph.get('artifact', entity_id);
1035
+ }
1036
+ catch {
1037
+ return fail(new Error(`Artifact "${entity_id}" not found in "${repo}".`));
1038
+ }
1039
+ // Live editor body preferred over disk (matches get_entity / patch_artifact),
1040
+ // so the manifest + hash reflect what the user currently sees.
1041
+ const body = graph.getLiveBody('artifact', entity_id) ?? entity.document.content;
1042
+ const bundle = parseArtifactBundleFromBody(body, `Artifact ${repo}/${entity_id}`);
1043
+ const manifest = buildArtifactManifest(bundle, { frameId: frame_id });
1044
+ return ok({ success: true, artifact_hash: hashArtifactBody(body), ...manifest });
1045
+ }
1046
+ catch (err) {
1047
+ // Unknown frame_id or unparseable body → clear recoverable error.
1048
+ return fail(err);
1049
+ }
1050
+ });
1051
+ const getCurrentSelectionSchema = z.object({
1052
+ repo: z.string().describe('Folder containing the artifact the user is viewing.'),
1053
+ entity_id: z.string().describe('Persisted artifact entity id the user is viewing.'),
1054
+ });
1055
+ reg('get_current_selection', {
1056
+ description: 'Return the frame/element the user currently has SELECTED in the artifact canvas (for this user + artifact), so you can act on "this"/"the selected frame" without them describing it. '
1057
+ + 'Returns selection.targets[]: each has a `frameId` (the frame to target with patch_artifact — e.g. "cover") plus selector/text/tagName to recognize the specific element within it. '
1058
+ + '(`primaryTargetId` is an element-level id like "cover::h1", NOT a frame id — use `targets[].frameId` for patch_artifact.) '
1059
+ + 'Returns selection: null when nothing is selected (or the selection has gone stale). '
1060
+ + 'Only the live web app pushes selections, so this is empty unless the user has an artifact open and has clicked something.',
1061
+ inputSchema: getCurrentSelectionSchema,
1062
+ }, async (args) => {
1063
+ const { repo, entity_id } = getCurrentSelectionSchema.parse(args);
1064
+ if (!hasRepoAccess(user, repo, wm, authService))
1065
+ return notFound(repo);
1066
+ // No store on this transport (standalone stdio process) → no shared UI
1067
+ // selection to read; report cleanly rather than erroring.
1068
+ const selection = selectionStore?.get(String(user.id), repo, entity_id) ?? null;
1069
+ return ok({ success: true, repo, entity_id, selection });
1070
+ });
1071
+ const inspectArtifactSchema = z.object({
1072
+ repo: z.string().optional().describe('Folder containing the persisted artifact entry. Required when bundle is omitted.'),
1073
+ entity_id: z.string().optional().describe('Persisted artifact entity id. Required when bundle is omitted.'),
1074
+ bundle: z.preprocess(coerceJsonArg, z.unknown()).optional().describe('ArtifactBundle object to inspect without saving. If omitted, repo + entity_id are loaded from the graph.'),
1075
+ context: z.enum(['editor', 'present']).optional().describe('"editor" lays every frame out in the canvas grid (all frames visible — the default); "present" renders the present stage (the first frame full-bleed).'),
1076
+ frame_indexes: z.array(z.number()).optional().describe('Specific zero-based frame/page indexes to screenshot.'),
1077
+ max_frames: z.number().optional().describe('Maximum number of individual frames/pages to screenshot. Defaults to 6, max 24.'),
1078
+ include_page_screenshot: z.boolean().optional().describe('Whether to include a screenshot of the whole rendered page. Defaults to true.'),
1079
+ include_frame_screenshots: z.boolean().optional().describe('Whether to include screenshots for selected frames/pages/slides. Defaults to true.'),
1080
+ screenshot_format: z.enum(['jpeg', 'png']).optional().describe('Screenshot encoding. Defaults to jpeg to keep tool results compact.'),
1081
+ base_url: z.string().optional().describe('Optional app origin such as http://localhost:3000, used only to allow same-origin /api/assets/* font/image loads during render.'),
1082
+ });
1083
+ reg('inspect_artifact', {
1084
+ description: 'Use Playwright to visually inspect an ArtifactBundle or persisted artifact entry. Returns a page screenshot, ' +
1085
+ 'frame/page/slide metadata (including measured rendered height), optional individual frame screenshots, console messages, page errors, and failed requests. ' +
1086
+ 'Also reports framesOverflowing — frames whose content is taller than their declared height and is therefore CROPPED from view (set geometry.height to "auto" or split the content to fix; a "pages" document frame is never listed — it flows/paginates instead of cropping, so never set a document to height "auto"). ' +
1087
+ 'Use this after creating or revising artifacts — especially large or content-driven ones — to verify layout, catch cropped frames, and check individual slides/pages.',
1088
+ inputSchema: inspectArtifactSchema,
1089
+ }, async (args) => {
1090
+ const parsed = inspectArtifactSchema.parse(args);
1091
+ let bundle;
1092
+ try {
1093
+ if (parsed.bundle !== undefined) {
1094
+ const validation = validateArtifactBundle(parsed.bundle);
1095
+ if (!validation.ok || !validation.bundle) {
1096
+ return fail(new Error(`Invalid artifact bundle: ${validation.errors.join('; ')}`));
1097
+ }
1098
+ bundle = validation.bundle;
1099
+ }
1100
+ else {
1101
+ if (!parsed.repo || !parsed.entity_id) {
1102
+ return fail(new Error('inspect_artifact requires either bundle or both repo and entity_id.'));
1103
+ }
1104
+ if (!hasRepoAccess(user, parsed.repo, wm, authService))
1105
+ return notFound(parsed.repo);
1106
+ const entity = wm.getGraph(parsed.repo).get('artifact', parsed.entity_id);
1107
+ bundle = parseArtifactBundleFromBody(entity.document.content, `Artifact ${parsed.repo}/${parsed.entity_id}`);
1108
+ }
1109
+ // With a base_url + a persisted target, mint a bundle-scoped asset token
1110
+ // so the headless render's `/api/assets/...` image loads resolve
1111
+ // (token-mode auth) instead of aborting to the broken-image placeholder.
1112
+ const assetToken = (parsed.base_url && parsed.repo && parsed.entity_id)
1113
+ ? mintArtifactAssetTokenForBundle(wm, authService, parsed.repo, parsed.entity_id, bundle, String(user.id)).token
1114
+ : undefined;
1115
+ const options = {
1116
+ context: parsed.context,
1117
+ frameIndexes: parsed.frame_indexes,
1118
+ maxFrames: parsed.max_frames,
1119
+ includePageScreenshot: parsed.include_page_screenshot,
1120
+ includeFrameScreenshots: parsed.include_frame_screenshots,
1121
+ screenshotFormat: parsed.screenshot_format,
1122
+ baseUrl: parsed.base_url,
1123
+ assetToken,
1124
+ };
1125
+ // Emit screenshots as real MCP image content blocks (vision) + a
1126
+ // base64-free metadata text block, mirroring the in-app agent tool: the
1127
+ // client model sees the frames for ~1.6k tokens each instead of ~300k of
1128
+ // undecoded base64 text.
1129
+ const { metadata, images } = inspectionToModelPayload(await inspectArtifactRender(bundle, options));
1130
+ return {
1131
+ content: [
1132
+ { type: 'text', text: JSON.stringify({ success: true, inspection: metadata }, null, 2) },
1133
+ ...images.map((img) => ({ type: 'image', data: img.data, mimeType: img.mimeType })),
1134
+ ],
1135
+ };
1136
+ }
1137
+ catch (err) {
1138
+ // Browser unavailable (still installing on a fresh deploy, or not
1139
+ // installed): degrade to a browser-free structural summary straight from
1140
+ // the frame model so the agent keeps a feedback loop.
1141
+ if (err instanceof ArtifactRenderExportUnavailableError && bundle) {
1142
+ return {
1143
+ content: [
1144
+ { type: 'text', text: JSON.stringify({
1145
+ success: true,
1146
+ degraded: true,
1147
+ inspection: buildArtifactManifest(bundle),
1148
+ note: `VISUAL INSPECTION UNAVAILABLE — the browser render runtime could not run (${err.message}). This is a STRUCTURAL-ONLY summary from the frame model: it CANNOT measure rendered height, detect cropped/overflowing frames, or verify visual layout. You are effectively blind to how this renders — do NOT report the artifact as visually verified or claim its layout/pagination is correct.`,
1149
+ }, null, 2) },
1150
+ ],
1151
+ };
1152
+ }
1153
+ return fail(err);
1154
+ }
1155
+ });
1156
+ const exportArtifactSchema = z.object({
1157
+ repo: z.string().describe('Folder containing the artifact entry.'),
1158
+ entity_id: z.string().describe('Persisted artifact entity id.'),
1159
+ format: z.enum(['html', 'png', 'pdf']).describe('Export format to produce. HTML returns text content; PNG/PDF return base64 bytes.'),
1160
+ base_url: z.string().optional().describe('Optional app origin such as http://localhost:3000, used only to allow same-origin /api/assets/* font/image loads during PNG/PDF render.'),
1161
+ });
1162
+ reg('export_artifact', {
1163
+ description: 'Export a persisted Artifact Canvas entry as HTML, PNG, or PDF. HTML returns text content; workspace images ' +
1164
+ 'render as labeled placeholders in it (the text export cannot carry asset files — use PNG/PDF for faithful ' +
1165
+ 'images, or the app’s HTML export for a self-contained zip). ' +
1166
+ 'PNG/PDF are rendered with Playwright and returned as base64 bytes so MCP clients can save them.',
1167
+ inputSchema: exportArtifactSchema,
1168
+ }, async (args) => {
1169
+ const { repo, entity_id, format, base_url } = exportArtifactSchema.parse(args);
1170
+ if (!hasRepoAccess(user, repo, wm, authService))
1171
+ return notFound(repo);
1172
+ try {
1173
+ const entity = wm.getGraph(repo).get('artifact', entity_id);
1174
+ const bundle = parseArtifactBundleFromBody(entity.document.content, `Artifact ${repo}/${entity_id}`);
1175
+ const typedFormat = format;
1176
+ let bytes;
1177
+ let filename = artifactExportFilename(bundle, typedFormat);
1178
+ let mimeType;
1179
+ if (typedFormat === 'html') {
1180
+ // MCP html export returns TEXT — it can't carry the asset zip the web
1181
+ // export route ships — so workspace-asset refs render as labeled
1182
+ // placeholders under the export's no-network CSP (D11b: a downloaded
1183
+ // file is never absolutized back to the workspace and never phones
1184
+ // home). Pixel-faithful output with images = png/pdf below; the
1185
+ // self-contained zip = the web export endpoint.
1186
+ bytes = buildFrameDocument(bundle, {
1187
+ host: 'none',
1188
+ interactive: true,
1189
+ librarySources: artifactLibrarySourcesForBundle(bundle),
1190
+ ...(bundleNeedsPagination(bundle) ? { pagedSource: artifactPagedSource() } : {}),
1191
+ assets: { mode: 'relative', bundledKeys: new Set() },
1192
+ });
1193
+ }
1194
+ else if (typedFormat === 'png') {
1195
+ const pngFiles = await renderArtifactPngFiles(bundle, { baseUrl: base_url });
1196
+ if (pngFiles.length > 1) {
1197
+ const namedFiles = nameArtifactPngFiles(bundle, pngFiles);
1198
+ bytes = await zipArtifactPngFiles(namedFiles);
1199
+ filename = artifactPngArchiveFilename(bundle);
1200
+ mimeType = 'application/zip';
1201
+ }
1202
+ else {
1203
+ bytes = pngFiles[0]?.bytes ?? Buffer.alloc(0);
1204
+ }
1205
+ }
1206
+ else {
1207
+ bytes = await renderArtifactPdf(bundle, { baseUrl: base_url });
1208
+ }
1209
+ return ok({
1210
+ success: true,
1211
+ repo,
1212
+ entity_id,
1213
+ format: typedFormat,
1214
+ filename,
1215
+ ...exportedData(typedFormat, bytes, mimeType),
1216
+ });
1217
+ }
1218
+ catch (err) {
1219
+ if (err instanceof ArtifactRenderInstallingError) {
1220
+ return fail(err); // transient: message already says it's installing + retry
1221
+ }
1222
+ if (err instanceof ArtifactRenderExportUnavailableError) {
1223
+ return fail(new Error(`${err.message} Install with: npx playwright install chromium`));
1224
+ }
1225
+ return fail(err);
1226
+ }
1227
+ });
1228
+ // ── patch_entity ──────────────────────────────────────────────────────────
1229
+ //
1230
+ // Surgical body edit: replace an exact substring without resending the
1231
+ // whole body. The motivating failure was an agent fetching content,
1232
+ // renaming a heading, and resending the entire body — which dropped
1233
+ // editor-placed image embeds it never saw. patch_entity can't drop what it
1234
+ // doesn't send: it reads the CURRENT body (live editor when open, else
1235
+ // disk), applies the literal find→replace, and writes the result through
1236
+ // the same guarded server-write path. The asset-drop guard still runs, so
1237
+ // a patch that happens to delete an embed is rejected unless the substring
1238
+ // is itself the embed being intentionally removed.
1239
+ const patchEntitySchema = z.object({
1240
+ repo: z.string().describe('Folder name'),
1241
+ entity_type: z.string().describe('Entity type'),
1242
+ entity_id: z.string().describe('Entity ID'),
1243
+ find: z.string().describe('Exact substring to locate in the current body (verbatim, including whitespace).'),
1244
+ replace: z.string().describe('Replacement text. Empty string deletes the matched substring.'),
1245
+ expected_count: z.number().int().positive().optional().describe('Optional safety check: require `find` to occur exactly this many times. The patch is rejected with ' +
1246
+ 'NO_MATCH / AMBIGUOUS_MATCH when the actual count differs, so you never edit the wrong occurrence.'),
1247
+ });
1248
+ reg('patch_entity', {
1249
+ description: 'Surgically edit an entry body by replacing an exact substring — without rewriting the whole body. ' +
1250
+ 'Prefer this over update_entity for small edits (renaming a heading, fixing a sentence): it operates on ' +
1251
+ 'the live editor body and cannot drop image/asset embeds it did not touch. Returns the same diagnostic ' +
1252
+ 'flags as update_entity. Use `expected_count` to guard against ambiguous matches. ' +
1253
+ 'Multiple edits to the SAME entry must be SEQUENTIAL — each patch rewrites the body, so parallel calls ' +
1254
+ 'to one entry conflict by design (optimistic concurrency); issue them one at a time, each after the ' +
1255
+ 'previous succeeds. Patches to different entries may run in parallel. ' +
1256
+ 'Do NOT patch an `artifact` body — it is ArtifactBundle JSON; a substring edit corrupts it. ' +
1257
+ 'Edit one frame via patch_artifact (get_entity first for its artifact_hash), or rewrite the whole bundle with create_artifact.',
1258
+ inputSchema: patchEntitySchema,
1259
+ }, async (args) => {
1260
+ const { repo, entity_type, entity_id, find, replace, expected_count } = patchEntitySchema.parse(args);
1261
+ if (!hasRepoAccess(user, repo, wm, authService))
1262
+ return notFound(repo);
1263
+ if (!canWriteRepo(user, repo, wm, authService)) {
1264
+ return fail(new Error(`You do not have write access to folder "${repo}"`));
1265
+ }
1266
+ if (entity_type === 'artifact') {
1267
+ // An artifact body is ArtifactBundle JSON; a substring find/replace that
1268
+ // happens to keep the JSON valid would still be the wrong edit route (the
1269
+ // write-time validity guard only catches a corrupt result). Reject up
1270
+ // front and steer to the frame-addressable path.
1271
+ return fail(new Error('Do not patch an `artifact` body with patch_entity — it is ArtifactBundle JSON, not markdown. Read it with get_entity (for its artifact_hash) and edit one frame via patch_artifact, or rewrite the whole bundle with create_artifact.'));
1272
+ }
1273
+ if (find === '')
1274
+ return fail(new Error('patch_entity requires a non-empty `find` string'));
1275
+ if (find === replace)
1276
+ return fail(new Error('patch_entity `find` and `replace` are identical — nothing to do'));
1277
+ return withAgentActivity(repo, 'editing', async () => {
1278
+ try {
1279
+ const graph = wm.getGraph(repo);
1280
+ const actor = buildActor(user);
1281
+ // Shared read-modify-write-retry (optimistic concurrency, disk-hash
1282
+ // baseRevision, parallel-patch compose) — see applyPatchEntity.
1283
+ const outcome = await applyPatchEntity(graph, actor, {
1284
+ entityType: entity_type,
1285
+ entityId: entity_id,
1286
+ find,
1287
+ replace,
1288
+ expectedCount: expected_count,
1289
+ });
1290
+ if (outcome.status === 'no_match') {
1291
+ return ok({ success: false, code: 'NO_MATCH', message: `\`find\` substring not present in ${entity_type}/${entity_id}.` });
1292
+ }
1293
+ if (outcome.status === 'ambiguous') {
1294
+ return ok({ success: false, code: 'AMBIGUOUS_MATCH', occurrences: outcome.occurrences, message: `Expected ${expected_count} occurrence(s) of \`find\`, found ${outcome.occurrences}.` });
1295
+ }
1296
+ if (outcome.status === 'conflict') {
1297
+ return ok({ success: false, code: 'WRITE_CONFLICT', message: `Could not apply patch to ${entity_type}/${entity_id}: the entry kept changing under concurrent writes after ${outcome.attempts} attempts. Retry, or avoid editing the same entry in parallel.` });
1298
+ }
1299
+ const updated = outcome.entity;
1300
+ emitEntityChange('updated', repo, updated.entityType, updated.id);
1301
+ const repoRoot = graph.getRepoPath();
1302
+ const relPath = updated.path.startsWith(repoRoot + '/') ? updated.path.slice(repoRoot.length + 1) : updated.path;
1303
+ return ok({
1304
+ success: true,
1305
+ id: updated.id, type: updated.entityType, repo, path: relPath,
1306
+ commit: graph.getGitService()?.getHeadCommit() ?? null,
1307
+ occurrences: outcome.occurrences, frontmatter_changed: outcome.frontmatter_changed, content_changed: outcome.content_changed, noop: outcome.noop, broadcast: outcome.broadcast,
1308
+ });
1309
+ }
1310
+ catch (err) {
1311
+ if (err instanceof DroppedAssetError) {
1312
+ return ok({ success: false, code: 'ASSET_REMOVAL_BLOCKED', droppedAssets: err.dropped, message: err.message });
1313
+ }
1314
+ return fail(err);
1315
+ }
1316
+ });
1317
+ });
1318
+ // ── list_assets ─────────────────────────────────────────────────────────
1319
+ //
1320
+ // Read-only discovery of assets (images, PDFs, files) already uploaded to a
1321
+ // folder. Shares listRepoAssets() with the HTTP asset endpoints, so the
1322
+ // shape — including the render-safe `embed` string — is identical. Assets
1323
+ // are created only through the upload routes / workspace UI; this tool never
1324
+ // creates one, it only surfaces existing ones so an agent can reference them
1325
+ // in an entry body without guessing a URL or embed syntax.
1326
+ const listAssetsSchema = z.object({
1327
+ repo: z.string().optional().describe('Folder name. OMIT to list assets across ALL folders you can access, including the workspace-wide shared "Assets" store (where Studio-level shared images like logos live).'),
1328
+ name: z.string().optional().describe('Filter to assets whose filename contains this text (case-insensitive). Use with no `repo` to find a shared asset by name across the workspace.'),
1329
+ entity_type: z.string().optional().describe("Filter by owner kind. Assets are folder-homed (owner is 'folder'/'workspace'), so per-ENTRY filtering only matches legacy pre-migration rows — to find what an entry embeds, read its body instead."),
1330
+ entity_id: z.string().optional().describe("Filter by owner id (the folder name for folder-homed assets; an entry id only for legacy pre-migration rows)"),
1331
+ });
1332
+ reg('list_assets', {
1333
+ description: 'List assets (images, PDFs, files) already uploaded. Pass `repo` for one folder, or OMIT `repo` to list ' +
1334
+ 'across every folder you can access — INCLUDING the workspace-wide shared "Assets" store (the only way to ' +
1335
+ 'discover Studio-level shared images). Each result includes a ready-to-paste `embed` — standard markdown ' +
1336
+ '`![name](url)` — that renders in the editor, plus the owning `repo`. To display an asset in an entry, ' +
1337
+ 'place that exact `embed` string via attach_asset_to_entity (or patch_entity for append/insert). ' +
1338
+ 'NEVER write `![[filename]]` transclusion syntax: it does not resolve to a stored asset and shows as dead ' +
1339
+ 'text. Assets are uploaded via the workspace UI — this tool only references existing ones. Optionally ' +
1340
+ 'filter by `name` (filename contains), `entity_type`, and/or `entity_id`.',
1341
+ inputSchema: listAssetsSchema,
1342
+ }, async (args) => {
1343
+ const { repo, name, entity_type, entity_id } = listAssetsSchema.parse(args);
1344
+ const nameNeedle = name?.trim().toLowerCase();
1345
+ // Workspace scope: omit `repo` → every accessible folder, including the
1346
+ // workspace-wide shared store (getAccessibleRepos returns workspace_wide
1347
+ // repos for any member). Reuses the same listWorkspaceAssets primitive the
1348
+ // /api/workspace/assets/manager endpoint uses, so shapes can't drift.
1349
+ let assets;
1350
+ if (repo === undefined) {
1351
+ const accessible = getAccessibleRepos(user, wm, authService).map(r => r.name);
1352
+ assets = await listWorkspaceAssets(accessible, wm);
1353
+ }
1354
+ else {
1355
+ if (!hasRepoAccess(user, repo, wm, authService))
1356
+ return notFound(repo);
1357
+ try {
1358
+ wm.getGraph(repo);
1359
+ }
1360
+ catch {
1361
+ return notFound(repo);
1362
+ }
1363
+ // Tag with `repo` so single- and workspace-scope results share one shape.
1364
+ assets = (await listRepoAssets(repo, wm)).map(a => ({ ...a, repo }));
1365
+ }
1366
+ if (entity_type)
1367
+ assets = assets.filter(a => a.entityType === entity_type);
1368
+ if (entity_id)
1369
+ assets = assets.filter(a => a.entityId === entity_id);
1370
+ if (nameNeedle)
1371
+ assets = assets.filter(a => a.filename.toLowerCase().includes(nameNeedle));
1372
+ return ok({
1373
+ repo: repo ?? null,
1374
+ scope: repo === undefined ? 'workspace' : 'repo',
1375
+ count: assets.length,
1376
+ assets: assets.map(a => ({
1377
+ asset_id: a.assetId,
1378
+ filename: a.filename,
1379
+ mime_type: a.contentType,
1380
+ repo: a.repo,
1381
+ entity_type: a.entityType,
1382
+ entity_id: a.entityId,
1383
+ entity_name: a.entityName,
1384
+ url: a.url,
1385
+ embed: a.embed,
1386
+ })),
1387
+ });
1388
+ });
1389
+ // ── attach_asset_to_entity ──────────────────────────────────────────────
1390
+ //
1391
+ // Place an already-uploaded asset (from list_assets) into an entry body
1392
+ // without rewriting the whole body. Thin convenience over the patch_entity
1393
+ // machinery: it builds the render-safe `![alt](url)` embed and either
1394
+ // appends it or swaps a placeholder for it, then writes through the same
1395
+ // guarded server-write path.
1396
+ //
1397
+ // `replace_placeholder` is the sanctioned way to fix a dead `![[Foo.png]]`
1398
+ // placeholder. Note: that swap necessarily *drops* the transclusion target
1399
+ // (`Foo.png`) the asset-drop guard recognizes, so a plain patch_entity of
1400
+ // the same span would be rejected with ASSET_REMOVAL_BLOCKED. Here we pass
1401
+ // allowAssetRemoval for replace_placeholder only — the agent is explicitly
1402
+ // targeting that placeholder and we're re-adding the image as a working
1403
+ // embed, so no real asset is lost. `append` keeps the guard on.
1404
+ const attachAssetSchema = z.object({
1405
+ repo: z.string().describe('Folder name'),
1406
+ entity_type: z.string().describe('Entity type of the target entry'),
1407
+ entity_id: z.string().describe('Entity ID of the target entry'),
1408
+ url: z.string().describe('Asset url, taken verbatim from list_assets (e.g. /api/assets/<id>/<file>)'),
1409
+ alt: z.string().optional().describe('Alt text / caption for the embed. Defaults to the asset filename.'),
1410
+ mode: z.enum(['append', 'replace_placeholder']).default('append').describe('"append" adds the embed at the end of the body. "replace_placeholder" swaps `placeholder` text ' +
1411
+ '(e.g. a dead `![[Foo.png]]` written earlier) for the working embed.'),
1412
+ placeholder: z.string().optional().describe('Required when mode="replace_placeholder": exact text to replace.'),
1413
+ expected_count: z.number().int().positive().optional().describe('For replace_placeholder: require `placeholder` to occur exactly this many times, else reject (AMBIGUOUS_MATCH).'),
1414
+ });
1415
+ reg('attach_asset_to_entity', {
1416
+ description: 'Embed an already-uploaded asset (from list_assets) into an entry body using its render-safe markdown, ' +
1417
+ 'without rewriting the whole body. mode="append" adds it at the end; mode="replace_placeholder" swaps dead ' +
1418
+ 'placeholder text (e.g. an `![[Foo.png]]` an agent wrote earlier) for the working embed. Drop-guarded on ' +
1419
+ 'append: rejects with ASSET_REMOVAL_BLOCKED if it would remove other image embeds.',
1420
+ inputSchema: attachAssetSchema,
1421
+ }, async (args) => {
1422
+ const { repo, entity_type, entity_id, url, alt, mode, placeholder, expected_count } = attachAssetSchema.parse(args);
1423
+ if (!hasRepoAccess(user, repo, wm, authService))
1424
+ return notFound(repo);
1425
+ if (!canWriteRepo(user, repo, wm, authService)) {
1426
+ return fail(new Error(`You do not have write access to folder "${repo}"`));
1427
+ }
1428
+ if (!url.trim())
1429
+ return fail(new Error('attach_asset_to_entity requires a non-empty `url`'));
1430
+ if (mode === 'replace_placeholder' && !placeholder) {
1431
+ return fail(new Error('mode="replace_placeholder" requires a non-empty `placeholder`'));
1432
+ }
1433
+ // The URL's last segment is percent-encoded (encodeAssetFilename); decode
1434
+ // it for the human-facing alt text (the embed keeps the original encoded
1435
+ // url). Malformed `%` → keep raw rather than throw.
1436
+ const altSegment = (url.split('/').pop() || 'asset').split('?')[0];
1437
+ let derivedAlt = altSegment;
1438
+ try {
1439
+ derivedAlt = decodeURIComponent(altSegment);
1440
+ }
1441
+ catch { /* keep raw */ }
1442
+ const filename = alt ?? derivedAlt;
1443
+ const embed = assetEmbed(filename, url);
1444
+ return withAgentActivity(repo, 'editing', async () => {
1445
+ try {
1446
+ const graph = wm.getGraph(repo);
1447
+ const entity = graph.get(entity_type, entity_id);
1448
+ const currentBody = graph.getLiveBody(entity_type, entity_id) ?? entity.document.content;
1449
+ let newBody;
1450
+ let occurrences;
1451
+ let allowAssetRemoval = false;
1452
+ if (mode === 'replace_placeholder') {
1453
+ const ph = placeholder;
1454
+ let count = 0;
1455
+ for (let i = currentBody.indexOf(ph); i !== -1; i = currentBody.indexOf(ph, i + ph.length))
1456
+ count++;
1457
+ if (count === 0) {
1458
+ return ok({ success: false, code: 'NO_MATCH', message: `placeholder not present in ${entity_type}/${entity_id}.` });
1459
+ }
1460
+ if (expected_count !== undefined && count !== expected_count) {
1461
+ return ok({ success: false, code: 'AMBIGUOUS_MATCH', occurrences: count, message: `Expected ${expected_count} occurrence(s) of placeholder, found ${count}.` });
1462
+ }
1463
+ occurrences = count;
1464
+ newBody = currentBody.split(ph).join(embed);
1465
+ // The placeholder we're removing is itself a recognized embed
1466
+ // target; swapping it for the working embed is the whole point.
1467
+ allowAssetRemoval = true;
1468
+ }
1469
+ else {
1470
+ if (currentBody.includes(embed)) {
1471
+ return ok({ success: false, code: 'ALREADY_PRESENT', message: `Asset embed already present in ${entity_type}/${entity_id}.` });
1472
+ }
1473
+ newBody = currentBody.trimEnd() + '\n\n' + embed + '\n';
1474
+ }
1475
+ const options = prepareUpdatePayload({ entityType: entity_type, entityId: entity_id, content: newBody }, buildActor(user));
1476
+ const { entity: updated, frontmatter_changed, content_changed, noop, broadcast } = await diagnosedUpdate(graph, options, { content: newBody }, { allowAssetRemoval });
1477
+ emitEntityChange('updated', repo, updated.entityType, updated.id);
1478
+ const repoRoot = graph.getRepoPath();
1479
+ const relPath = updated.path.startsWith(repoRoot + '/') ? updated.path.slice(repoRoot.length + 1) : updated.path;
1480
+ return ok({
1481
+ success: true,
1482
+ id: updated.id, type: updated.entityType, repo, path: relPath,
1483
+ embed, mode, occurrences,
1484
+ commit: graph.getGitService()?.getHeadCommit() ?? null,
1485
+ frontmatter_changed, content_changed, noop, broadcast,
1486
+ });
1487
+ }
1488
+ catch (err) {
1489
+ if (err instanceof DroppedAssetError) {
1490
+ return ok({ success: false, code: 'ASSET_REMOVAL_BLOCKED', droppedAssets: err.dropped, message: err.message });
1491
+ }
1492
+ return fail(err);
1493
+ }
1494
+ });
1495
+ });
1496
+ // ── create_asset_upload ─────────────────────────────────────────────────
1497
+ //
1498
+ // Step 1 of 3 for the pre-signed asset upload flow. Validates request,
1499
+ // resolves target folder (defaults to the workspace_wide Assets store),
1500
+ // mints a pre-signed PUT URL the agent uploads to directly, and returns
1501
+ // an HMAC token to pass back to finalize_asset_upload. Bytes never travel
1502
+ // through this MCP server — only authz + indexing do.
1503
+ //
1504
+ // The tool description is load-bearing: it's what the agent reads to
1505
+ // plan the three-step flow. Be explicit about each step and the exact
1506
+ // shape of the PUT.
1507
+ const createAssetUploadSchema = z.object({
1508
+ repo: z.string().optional().describe('Folder name. Omit to upload into the shared "Assets" store (created on first use).'),
1509
+ filename: z.string().describe('Filename including extension (e.g. "logo.png"). Sanitized server-side; no path segments.'),
1510
+ // Registry-driven (P4-V1): the presign path only accepts formats safe as
1511
+ // raw bytes (the app never holds them, so it can't sanitize). SVG +
1512
+ // documents are excluded here and go through upload_asset_inline instead.
1513
+ mime_type: z.enum(PRESIGN_MIME_TUPLE).describe(`Image MIME accepted by the presign flow: ${PRESIGN_MIME_TUPLE.join(', ')}. ` +
1514
+ `SVG and documents are not presignable (use upload_asset_inline).`),
1515
+ });
1516
+ reg('create_asset_upload', {
1517
+ description: 'Upload a large image (PNG/JPEG, max 20MB) via pre-signed PUT — STEP 1 of 3. ' +
1518
+ 'Returns { assetId, uploadUrl, headers, token, expiresAt, repo }.\n\n' +
1519
+ 'STEP 2 (not an MCP tool): HTTP PUT the raw image bytes to `uploadUrl` with the returned `headers` ' +
1520
+ 'verbatim (notably `Content-Type`). The URL expires in 15 minutes.\n\n' +
1521
+ 'STEP 3: call `finalize_asset_upload({ token })` to register the asset and get a permanent URL, then ' +
1522
+ 'optionally `attach_asset_to_entity({ url, ... })` to embed it in an entry body.\n\n' +
1523
+ 'For small files, prefer the one-call upload_asset_inline. ' +
1524
+ 'Errors with code "local_backend" when the workspace uses local filesystem storage (R2 only).',
1525
+ inputSchema: createAssetUploadSchema,
1526
+ }, async (args) => {
1527
+ const parsed = createAssetUploadSchema.parse(args);
1528
+ try {
1529
+ // Asset-as-embed (B2): the upload always produces a FREE-STANDING,
1530
+ // folder-owned asset. There is no entity-ownership handoff — embed it
1531
+ // into an entry afterwards via attach_asset_to_entity.
1532
+ const result = await presignAssetUpload({
1533
+ workspaceManager: wm,
1534
+ authService,
1535
+ user,
1536
+ repo: parsed.repo,
1537
+ filename: parsed.filename,
1538
+ mimeType: parsed.mime_type,
1539
+ });
1540
+ return ok({
1541
+ assetId: result.assetId,
1542
+ uploadUrl: result.uploadUrl,
1543
+ headers: result.headers,
1544
+ token: result.token,
1545
+ expiresAt: result.expiresAt,
1546
+ repo: result.repo,
1547
+ });
1548
+ }
1549
+ catch (err) {
1550
+ return errorToMcpResult(err);
1551
+ }
1552
+ });
1553
+ // ── finalize_asset_upload ───────────────────────────────────────────────
1554
+ //
1555
+ // Step 3 of 3. Verifies the upload landed at the pre-signed URL,
1556
+ // validates content-type + size at the wire, promotes the blob from
1557
+ // `pending/` into the canonical keyspace, and registers a FREE-STANDING,
1558
+ // folder-owned asset (no entity). Idempotent on token replay:
1559
+ // a second call returns the existing assetId + URL.
1560
+ //
1561
+ // Identity-bound (R1): the user calling finalize must be the same user
1562
+ // who minted the token — leaked tokens cannot be replayed by another
1563
+ // folder-writer. Workspace owners are NOT exempt.
1564
+ const finalizeAssetUploadSchema = z.object({
1565
+ token: z.string().describe('HMAC token returned by create_asset_upload.'),
1566
+ name: z.string().optional().describe('Optional display name for the asset entry. Defaults to a title-cased version of the filename stem.'),
1567
+ });
1568
+ reg('finalize_asset_upload', {
1569
+ description: 'STEP 3 of 3. Call this after PUTting bytes to the uploadUrl returned by create_asset_upload. ' +
1570
+ 'Verifies the upload landed, validates content type and size, registers a free-standing asset, and returns ' +
1571
+ '{ assetId, url, ownerType, ownerId, repo }. Embed it into an entry afterwards via attach_asset_to_entity. ' +
1572
+ 'Idempotent: calling with the same token twice returns the existing asset.\n\n' +
1573
+ 'Errors with code "upload_not_received" if the PUT never happened (or the 15-min window expired); ' +
1574
+ '"upload_validation" if content type or size doesn\'t match the presign contract; "token_invalid" if ' +
1575
+ 'the token is tampered or expired; "forbidden" if the caller is not the original presigner or has lost ' +
1576
+ 'folder access in the interim.',
1577
+ inputSchema: finalizeAssetUploadSchema,
1578
+ }, async (args) => {
1579
+ const parsed = finalizeAssetUploadSchema.parse(args);
1580
+ try {
1581
+ const result = await finalizeAssetUpload({
1582
+ workspaceManager: wm,
1583
+ authService,
1584
+ user,
1585
+ token: parsed.token,
1586
+ name: parsed.name,
1587
+ });
1588
+ // Asset-as-embed (B2): finalize registers a FREE-STANDING asset, not an
1589
+ // entity, so there's no entity_change to broadcast — it surfaces in the
1590
+ // asset manager on its next load. Its image caption/OCR IS derived for
1591
+ // search: finalize seeds an assetId-keyed index-state row + notifies the
1592
+ // live caption drain, and the caption folds into any entry that embeds
1593
+ // the asset.
1594
+ return ok({
1595
+ assetId: result.assetId,
1596
+ url: result.url,
1597
+ ownerType: result.ownerType,
1598
+ ownerId: result.ownerId,
1599
+ repo: result.repo,
1600
+ });
1601
+ }
1602
+ catch (err) {
1603
+ return errorToMcpResult(err);
1604
+ }
1605
+ });
1606
+ // ── upload_asset_inline ─────────────────────────────────────────────────────
1607
+ //
1608
+ // V3: single-call asset upload for SMALL files where the bytes can ride
1609
+ // inline (base64). Unlike create_asset_upload (R2-only presign, 3 steps,
1610
+ // bytes never touch the app), this works on local OR R2 backends and is one
1611
+ // round-trip — the tradeoff is a tight size cap (base64 inflates the model
1612
+ // context ~33%, and the bytes traverse this server). For large files or when
1613
+ // an R2 backend is available, prefer the presign flow.
1614
+ //
1615
+ // Indexing reaches web parity: after the asset entity is committed we enqueue
1616
+ // the live caption/extract drain (same as a web upload), so an inline-
1617
+ // uploaded image is captioned while the server runs.
1618
+ const uploadAssetInlineSchema = z.object({
1619
+ repo: z.string().optional().describe('Folder name. Omit to upload into the shared "Assets" store (created on first use).'),
1620
+ filename: z.string().describe('Filename including extension (e.g. "diagram.png"). Sanitized server-side; no path segments.'),
1621
+ data_base64: z.string().describe('The file bytes, base64-encoded. Keep under ~4.5MB raw (6MB base64) — larger files must use create_asset_upload.'),
1622
+ name: z.string().optional().describe('Optional display name for the asset entry. Defaults to a title-cased filename stem.'),
1623
+ folder_path: z.string().optional().describe('Optional repo-relative subfolder for the entry .md (e.g. "Diagrams"). Defaults to repo root.'),
1624
+ });
1625
+ reg('upload_asset_inline', {
1626
+ description: 'Ingest a SMALL file (image or document) as a typed entry in ONE call by passing its bytes ' +
1627
+ 'base64-encoded. The file is stored as a free-standing asset and a typed entry that embeds it ' +
1628
+ 'is created — image→material, doc/pdf→document (extracted text in the body), xlsx/csv→dataset — ' +
1629
+ 'exactly as a web import would. Returns { assetId, url, repo, entities: [{ entityType, entityId }] }.\n\n' +
1630
+ 'Size limit ~4.5MB raw (6MB base64) — for larger files use create_asset_upload (R2 presign, 3 steps). ' +
1631
+ 'Accepted types are the workspace upload set (images + documents); SVG is not yet accepted here. ' +
1632
+ 'Images are captioned for search automatically in the background.',
1633
+ inputSchema: uploadAssetInlineSchema,
1634
+ }, async (args) => {
1635
+ const parsed = uploadAssetInlineSchema.parse(args);
1636
+ // 1. Format gate: resolve the filename to a registry format and check it
1637
+ // directly via isInlineUploadAllowed — NOT via detectContentType, whose
1638
+ // CONTENT_TYPES map returns application/octet-stream for several
1639
+ // registry-accepted assets (.tsv/.psd/.ai/.aac/...), which would wrongly
1640
+ // reject them. Both the gate and the allowlist now derive from the same
1641
+ // registry predicate, so they can't disagree. Rejects SVG (transitional
1642
+ // until sanitize-html), unaccepted docs, and unknown types (fallback).
1643
+ const format = resolveFormat(parsed.filename);
1644
+ if (!isInlineUploadAllowed(format)) {
1645
+ return fail(new Error(`"${parsed.filename}" is not accepted by upload_asset_inline. ` +
1646
+ `Accepted: ${INLINE_UPLOAD_MIME_LIST.join(', ')}.`));
1647
+ }
1648
+ // 2. Decode + size gate. Node's Buffer.from(..,'base64') silently DROPS
1649
+ // invalid characters instead of throwing, so a garbage string would
1650
+ // "decode" to junk bytes. Validate the shape first (strip one optional
1651
+ // data: prefix + whitespace, then assert the strict base64 charset),
1652
+ // and re-encode-compare to catch truncation.
1653
+ const raw = parsed.data_base64.replace(/^data:[^;]+;base64,/, '').replace(/\s+/g, '');
1654
+ if (raw.length === 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(raw) || raw.length % 4 !== 0) {
1655
+ return fail(new Error('data_base64 is not valid base64.'));
1656
+ }
1657
+ const buffer = Buffer.from(raw, 'base64');
1658
+ if (buffer.length === 0 || buffer.toString('base64') !== raw) {
1659
+ return fail(new Error('data_base64 is not valid base64.'));
1660
+ }
1661
+ if (buffer.length > INLINE_MAX_BYTES) {
1662
+ return fail(new Error(`Decoded size ${buffer.length} bytes exceeds the inline ${INLINE_MAX_BYTES}-byte cap. Use create_asset_upload for larger files.`));
1663
+ }
1664
+ // 3. Resolve the target repo (default → shared Assets store) + authz.
1665
+ let repo;
1666
+ try {
1667
+ repo = parsed.repo
1668
+ ?? await resolveWorkspaceAssetsRepo(new Workspace(wm.getWorkspacePath()), wm);
1669
+ }
1670
+ catch (err) {
1671
+ return errorToMcpResult(err);
1672
+ }
1673
+ if (!canWriteRepo(user, repo, wm, authService)) {
1674
+ return fail(`You don't have permission to write to "${repo}". Editor or admin role required.`);
1675
+ }
1676
+ let graph;
1677
+ try {
1678
+ graph = wm.getGraph(repo);
1679
+ }
1680
+ catch (err) {
1681
+ return errorToMcpResult(err);
1682
+ }
1683
+ // 4. Ingest via the asset-as-embed import: store the bytes as a
1684
+ // free-standing folder-owned asset + create the typed entry(ies) whose
1685
+ // body embeds it (image→material, doc→document, xlsx→dataset×N).
1686
+ // Auto-commits. Same path as web import/drag-drop.
1687
+ try {
1688
+ const result = await withAgentActivity(repo, 'creating', () => importAssetAsEntity({
1689
+ graph,
1690
+ filename: parsed.filename,
1691
+ buffer,
1692
+ folderPath: parsed.folder_path || undefined,
1693
+ name: parsed.name,
1694
+ user: buildActor(user),
1695
+ uploaderUserId: String(user.id),
1696
+ }));
1697
+ for (const e of result.entities)
1698
+ emitEntityChange('created', repo, e.entityType, e.entityId);
1699
+ // V3 indexing parity: the created entries' bodies are vector-indexed
1700
+ // natively; the embedded image asset's caption drains in the background.
1701
+ return ok({
1702
+ assetId: result.assetId,
1703
+ url: result.asset_url,
1704
+ repo,
1705
+ entities: result.entities.map(e => ({ entityType: e.entityType, entityId: e.entityId })),
1706
+ });
1707
+ }
1708
+ catch (err) {
1709
+ return errorToMcpResult(err);
1710
+ }
1711
+ });
1712
+ // ── move_entity ───────────────────────────────────────────────────────────
1713
+ //
1714
+ // Relocate an entity to a different folder within the same repo. Wraps
1715
+ // graph.move() which renames the file on disk and commits the rename so
1716
+ // git's similarity detection preserves history. The (type, id) identity
1717
+ // is unchanged — get_entity, search, list, validate_wikilinks, and the
1718
+ // history tools all walk the entire repo tree and continue to find the
1719
+ // entry at its new path. For cross-repo transfers, the web UI's bulk-move
1720
+ // dialog or POST /api/repos/:repo/transfer is the right surface.
1721
+ const moveEntitySchema = z.object({
1722
+ repo: z.string().describe('Folder name containing the entry'),
1723
+ entity_type: z.string().describe('Entity type'),
1724
+ entity_id: z.string().describe('Entity ID'),
1725
+ folder_path: z.string().describe('Target repo-relative folder path (e.g. "People", "Clients/Acme"). ' +
1726
+ 'Empty string moves the entry to the repo root. Parent folders are auto-created.'),
1727
+ overwrite: z.boolean().optional().describe('When true, replace an existing entry at the destination. The occupant ' +
1728
+ 'must itself be a parseable entity — README/.gitkeep/etc. are never ' +
1729
+ 'clobbered. Defaults to false; the call errors with "Path already ' +
1730
+ 'occupied" when the destination is taken.'),
1731
+ });
1732
+ reg('move_entity', {
1733
+ description: 'Move an existing entry to a different folder within the same folder/repo. ' +
1734
+ 'Preserves frontmatter, markdown content, entity ID, and git history (file is renamed, not rewritten). ' +
1735
+ 'Use this to organise entries into subfolders after creation — e.g. moving a person into People/. ' +
1736
+ 'By default, errors if the destination already has an entry; pass `overwrite: true` to replace.',
1737
+ inputSchema: moveEntitySchema,
1738
+ }, async (args) => {
1739
+ const { repo, entity_type, entity_id, folder_path, overwrite } = moveEntitySchema.parse(args);
1740
+ if (!hasRepoAccess(user, repo, wm, authService))
1741
+ return notFound(repo);
1742
+ if (!canWriteRepo(user, repo, wm, authService)) {
1743
+ return fail(new Error(`You do not have write access to folder "${repo}"`));
1744
+ }
1745
+ const actor = buildActor(user);
1746
+ return withAgentActivity(repo, 'editing', async () => {
1747
+ try {
1748
+ const graph = wm.getGraph(repo);
1749
+ // If the destination folder doesn't exist yet, the move creates it —
1750
+ // emit a _folder event (alongside the entry update) so clients
1751
+ // refetch the folder tree, not just the entry list.
1752
+ const normTarget = folder_path ? validateFolderPath(folder_path) : '';
1753
+ const targetExisted = !normTarget || existsSync(join(graph.getRepoPath(), normTarget));
1754
+ const moved = await graph.move(entity_type, entity_id, folder_path, undefined, actor, overwrite);
1755
+ emitEntityChange('updated', repo, moved.entityType, moved.id);
1756
+ if (!targetExisted)
1757
+ emitEntityChange('created', repo, '_folder', normTarget);
1758
+ return ok({
1759
+ success: true,
1760
+ id: moved.id,
1761
+ type: moved.entityType,
1762
+ repo,
1763
+ path: relPathOf(moved.path, graph.getRepoPath()),
1764
+ folder_path: moved.folder_path,
1765
+ });
1766
+ }
1767
+ catch (err) {
1768
+ return fail(err);
1769
+ }
1770
+ });
1771
+ });
1772
+ // ── create_subfolder ──────────────────────────────────────────────────────
1773
+ //
1774
+ // Create a nested subfolder inside an existing folder/collection. Wraps
1775
+ // graph.createFolder(). Distinct from create_repo, which registers a new
1776
+ // top-level collection. Emits the _folder sentinel so live clients refetch
1777
+ // the folder tree (sidebar + list/gallery folder rows) without a reload.
1778
+ const createSubfolderSchema = z.object({
1779
+ repo: z.string().describe('Folder name (collection) to create the subfolder inside'),
1780
+ path: z.string().describe('Repo-relative POSIX path of the new subfolder (e.g. "Clients/Acme"). Parent segments are auto-created.'),
1781
+ display_name: z.string().optional().describe('Optional human-readable label. Omit when the title-cased folder name is the right display ' +
1782
+ '(pass it for casing the slug can\'t express, e.g. "HR" or "Schema_OS").'),
1783
+ });
1784
+ reg('create_subfolder', {
1785
+ description: 'Create a new subfolder inside an existing folder/collection. Path is repo-relative POSIX (e.g. "Clients/Acme"); parent segments are auto-created. ' +
1786
+ 'Use it to organise entries, then place entries inside via create_entity (folder_path) or move_entity. ' +
1787
+ 'For a brand-new top-level collection, use create_repo instead.',
1788
+ inputSchema: createSubfolderSchema,
1789
+ }, async (args) => {
1790
+ const { repo, path, display_name } = createSubfolderSchema.parse(args);
1791
+ if (!hasRepoAccess(user, repo, wm, authService))
1792
+ return notFound(repo);
1793
+ if (!canWriteRepo(user, repo, wm, authService)) {
1794
+ return fail(new Error(`You do not have write access to folder "${repo}"`));
1795
+ }
1796
+ const actor = buildActor(user);
1797
+ return withAgentActivity(repo, 'creating', async () => {
1798
+ try {
1799
+ const graph = wm.getGraph(repo);
1800
+ // createFolder is an idempotent ensure — report when the path already
1801
+ // existed so the caller doesn't think it minted a new folder (and
1802
+ // silently merge entries into the existing one).
1803
+ const { alreadyExisted } = await graph.createFolder(path, undefined, actor, { displayName: display_name });
1804
+ emitEntityChange('created', repo, '_folder', path);
1805
+ return ok({
1806
+ success: true,
1807
+ alreadyExisted,
1808
+ repo,
1809
+ path,
1810
+ folder_path: path,
1811
+ message: alreadyExisted
1812
+ ? `Folder "${repo}/${path}" already exists — reusing it (nothing created).`
1813
+ : `Created folder "${repo}/${path}".`,
1814
+ });
1815
+ }
1816
+ catch (err) {
1817
+ return fail(err);
1818
+ }
1819
+ });
1820
+ });
1821
+ // ── delete_entity ─────────────────────────────────────────────────────────
1822
+ //
1823
+ // Delete an entry. Git-committed and recoverable from history
1824
+ // (list_recent_deletions / restore_file). Embedded assets are KEPT — assets
1825
+ // are folder-owned library citizens that entries only reference
1826
+ // (assets-referenced-not-owned P1), so deleting an entry never destroys
1827
+ // binaries. In the workspace_wide Assets store the per-asset uploader/owner
1828
+ // delete gate still applies (D2). Robust resolution mirrors get_entity.
1829
+ const deleteEntitySchema = z.object({
1830
+ repo: z.string().describe('Folder name'),
1831
+ entity_type: z.string().optional().describe('Canonical entity type (as returned by search/list `type`). Optional: when omitted or wrong, the entry is resolved by id.'),
1832
+ entity_id: z.string().optional().describe('Entity ID (kebab-case slug). Provide entity_id or path.'),
1833
+ path: z.string().optional().describe('Repo-relative file path from a search/list result. When given, resolves directly and ignores entity_type.'),
1834
+ });
1835
+ reg('delete_entity', {
1836
+ description: 'Delete an entry. Git-committed and recoverable from history via list_recent_deletions / restore_file. ' +
1837
+ 'Embedded assets are NOT deleted — assets are folder-owned and only referenced by entries. ' +
1838
+ 'Robust resolution: pass type+id or path exactly as returned by search_entities/list_entities; a wrong or omitted type still resolves by id. ' +
1839
+ 'Destructive — only call on explicit user request.',
1840
+ inputSchema: deleteEntitySchema,
1841
+ }, async (args) => {
1842
+ const { repo, entity_type, entity_id, path } = deleteEntitySchema.parse(args);
1843
+ if (!hasRepoAccess(user, repo, wm, authService))
1844
+ return notFound(repo);
1845
+ if (!canWriteRepo(user, repo, wm, authService)) {
1846
+ return fail(new Error(`You do not have write access to folder "${repo}"`));
1847
+ }
1848
+ if (!entity_id && !path)
1849
+ return fail(new Error('delete_entity requires entity_id or path'));
1850
+ const actor = buildActor(user);
1851
+ return withAgentActivity(repo, 'deleting', async () => {
1852
+ try {
1853
+ const graph = wm.getGraph(repo);
1854
+ // Resolve robustly (shared with get_entity): explicit path wins; else
1855
+ // try the supplied type; on a miss fall back to id-only resolution.
1856
+ const entity = resolveEntityRef(graph, { entity_type, entity_id, path });
1857
+ const resolvedType = entity.entityType;
1858
+ const resolvedId = entity.id;
1859
+ // Workspace-Assets store gate (D2): in a workspace_wide folder only the
1860
+ // uploader or a workspace owner may delete — folder-write is not enough.
1861
+ const denial = assetDeleteDenial(wm.getAssetIndex(), wm.getRepoConfig(repo), user, repo, resolvedType, resolvedId);
1862
+ if (denial)
1863
+ return fail(new Error(denial));
1864
+ // Soft bridge-awareness: deleting a person linked to an auth account
1865
+ // orphans that user. Don't block — surface a warning so the agent can
1866
+ // tell the user (the Profile panel catches the orphan after).
1867
+ let bridgeWarning;
1868
+ if (resolvedType === 'person') {
1869
+ const handle = entity.data?.user_handle;
1870
+ if (typeof handle === 'string' && handle.length > 0) {
1871
+ bridgeWarning = `This record was linked to the "${handle}" account. Deleting it unlinks that user — their "My Tasks", @mentions, and assignments stop resolving until they link a new record.`;
1872
+ }
1873
+ }
1874
+ await graph.delete(resolvedType, resolvedId, undefined, actor);
1875
+ emitEntityChange('deleted', repo, resolvedType, resolvedId);
1876
+ return ok({
1877
+ success: true,
1878
+ repo,
1879
+ type: resolvedType,
1880
+ id: resolvedId,
1881
+ ...(bridgeWarning ? { warning: bridgeWarning } : {}),
1882
+ });
1883
+ }
1884
+ catch (err) {
1885
+ return fail(err);
1886
+ }
1887
+ });
1888
+ });
1889
+ // ── delete_subfolder ──────────────────────────────────────────────────────
1890
+ //
1891
+ // Delete a subfolder inside a folder/collection. Empty → delete; non-empty +
1892
+ // recursive:false → refuse with a blast-radius payload; recursive:true →
1893
+ // delete the folder + its entries. Deleted entries are git-recoverable; their
1894
+ // referenced assets are kept (folder-owned). Distinct from archive_repo, which
1895
+ // reversibly hides an entire top-level folder.
1896
+ const deleteSubfolderSchema = z.object({
1897
+ repo: z.string().describe('Folder name (collection) containing the subfolder'),
1898
+ path: z.string().describe('Repo-relative POSIX path of the subfolder to delete (e.g. "Clients/Acme")'),
1899
+ recursive: z.boolean().optional().describe('Delete the folder AND every entry/subfolder inside it. Defaults to false: a non-empty folder is REFUSED with a blast-radius payload (entriesDeleted, subfoldersDeleted) so you can confirm with the user first. NEVER pass true on the first attempt.'),
1900
+ });
1901
+ reg('delete_subfolder', {
1902
+ description: 'Delete a subfolder inside a folder. Empty subfolders delete immediately. A non-empty subfolder is REFUSED unless recursive:true — the refusal returns the blast radius (entries + subfolders that would be removed) so you can confirm with the user before re-invoking. Deleted ENTRIES are git-recoverable; their referenced assets are kept. To remove a whole top-level folder, prefer archive_repo (reversible) — permanent folder deletion is web/human-only.',
1903
+ inputSchema: deleteSubfolderSchema,
1904
+ }, async (args) => {
1905
+ const { repo, path, recursive } = deleteSubfolderSchema.parse(args);
1906
+ if (!hasRepoAccess(user, repo, wm, authService))
1907
+ return notFound(repo);
1908
+ if (!canWriteRepo(user, repo, wm, authService)) {
1909
+ return fail(new Error(`You do not have write access to folder "${repo}"`));
1910
+ }
1911
+ // Workspace-Assets store gate (mirror the web route): a recursive delete in
1912
+ // a workspace_wide store destroys every asset under the folder, bypassing
1913
+ // the per-asset uploader/owner rule — restrict it to a workspace owner.
1914
+ if (recursive === true && wm.getRepoConfig(repo)?.workspace_wide && !isWorkspaceOwner(user.role)) {
1915
+ return fail(new Error('Only a workspace owner can recursively delete folders in the shared Assets store.'));
1916
+ }
1917
+ const actor = buildActor(user);
1918
+ return withAgentActivity(repo, 'deleting', async () => {
1919
+ try {
1920
+ // Validate + normalize the path BEFORE the destructive call (don't
1921
+ // rely on deleteFolder's internal check as the only guard against
1922
+ // traversal/root/malformed paths). validateFolderPath throws
1923
+ // InvalidFolderPathError for traversal and returns '' for the repo
1924
+ // root, which deleteFolder also rejects. The throw lands in the catch
1925
+ // below and surfaces as a fail() result. Pass the normalized path.
1926
+ const normPath = validateFolderPath(path);
1927
+ const graph = wm.getGraph(repo);
1928
+ const result = await graph.deleteFolder(normPath, { recursive: recursive === true, user: actor });
1929
+ emitEntityChange('deleted', repo, '_folder', normPath);
1930
+ return ok({
1931
+ success: true,
1932
+ repo,
1933
+ path: normPath,
1934
+ entriesDeleted: result.entriesDeleted,
1935
+ subfoldersDeleted: result.subfoldersDeleted,
1936
+ });
1937
+ }
1938
+ catch (err) {
1939
+ // Surface the blast radius so the caller can confirm before retrying
1940
+ // recursively (structured, like the web route's 409 body).
1941
+ if (err?.code === 'FOLDER_NOT_EMPTY' && err.blastRadius) {
1942
+ return {
1943
+ content: [{
1944
+ type: 'text',
1945
+ text: JSON.stringify({
1946
+ error: err.message,
1947
+ code: 'FOLDER_NOT_EMPTY',
1948
+ blast_radius: err.blastRadius,
1949
+ }, null, 2),
1950
+ }],
1951
+ isError: true,
1952
+ };
1953
+ }
1954
+ return fail(err);
1955
+ }
1956
+ });
1957
+ });
1958
+ // ── archive_repo / unarchive_repo ─────────────────────────────────────────
1959
+ //
1960
+ // Archive is a reversible, lossless, identity-preserving repo-level hide (a
1961
+ // flag on the config, NOT a rename). An archived folder drops out of the
1962
+ // sidebar, search, graph, and the agent inventory but keeps its entries,
1963
+ // wikilinks, and assets. Agents/MCP may archive but may NEVER permanently
1964
+ // delete a repo — permanent deletion stays a deliberate human action in the
1965
+ // web UI. Folder admin or workspace owner only.
1966
+ const archiveRepoSchema = z.object({
1967
+ repo: z.string().describe('Folder name to archive'),
1968
+ });
1969
+ reg('archive_repo', {
1970
+ description: 'Archive a folder — a reversible, lossless hide. The folder drops out of the sidebar, search, graph, and the agent folder inventory, but nothing is deleted (entries, wikilinks, and assets are preserved) and it can be restored with unarchive_repo. Use this instead of deleting a folder: permanent folder deletion is a human-only action in the web UI and is never available to agents. Requires folder admin or workspace owner.',
1971
+ inputSchema: archiveRepoSchema,
1972
+ }, async (args) => {
1973
+ const { repo } = archiveRepoSchema.parse(args);
1974
+ if (!hasRepoAccess(user, repo, wm, authService))
1975
+ return notFound(repo);
1976
+ if (!isFolderAdminOrOwner(user, repo, wm, authService)) {
1977
+ return fail(new Error(`Archiving folder "${repo}" requires folder admin or workspace owner.`));
1978
+ }
1979
+ try {
1980
+ const updated = await wm.setRepoArchived(repo, true, user.handle);
1981
+ return ok({
1982
+ success: true,
1983
+ repo,
1984
+ archived_at: updated.archived_at ?? null,
1985
+ message: `Archived folder "${repo}". It's hidden from the workspace but fully recoverable — call unarchive_repo to restore it.`,
1986
+ });
1987
+ }
1988
+ catch (err) {
1989
+ return fail(err);
1990
+ }
1991
+ });
1992
+ reg('unarchive_repo', {
1993
+ description: 'Restore a previously archived folder so it reappears in the sidebar, search, graph, and the agent folder inventory. Requires folder admin or workspace owner.',
1994
+ inputSchema: archiveRepoSchema,
1995
+ }, async (args) => {
1996
+ const { repo } = archiveRepoSchema.parse(args);
1997
+ if (!hasRepoAccess(user, repo, wm, authService))
1998
+ return notFound(repo);
1999
+ if (!isFolderAdminOrOwner(user, repo, wm, authService)) {
2000
+ return fail(new Error(`Unarchiving folder "${repo}" requires folder admin or workspace owner.`));
2001
+ }
2002
+ try {
2003
+ const updated = await wm.setRepoArchived(repo, false, user.handle);
2004
+ return ok({
2005
+ success: true,
2006
+ repo,
2007
+ archived: isArchivedRepo(updated),
2008
+ message: `Restored folder "${repo}".`,
146
2009
  });
147
- return ok({ success: true, id: entity.id, type: entity.entityType });
148
2010
  }
149
2011
  catch (err) {
150
2012
  return fail(err);
@@ -156,11 +2018,19 @@ export function registerTools(server, wm, authService) {
156
2018
  entity_type: z.string().describe('Entity type'),
157
2019
  entity_id: z.string().describe('Entity ID'),
158
2020
  });
159
- server.registerTool('validate_wikilinks', { description: 'Check which wikilinks in an entity resolve to real entities and which are broken', inputSchema: validateWikilinksSchema }, async (args) => {
2021
+ reg('validate_wikilinks', { description: 'Check which wikilinks in an entity resolve to real entities and which are broken', inputSchema: validateWikilinksSchema }, async (args) => {
160
2022
  const { repo, entity_type, entity_id } = validateWikilinksSchema.parse(args);
2023
+ if (!hasRepoAccess(user, repo, wm, authService))
2024
+ return notFound(repo);
161
2025
  try {
162
2026
  const result = wm.getGraph(repo).validateWikilinks(entity_type, entity_id);
163
- return ok({ entity_id, valid: result.valid, broken: result.broken, total: result.valid.length + result.broken.length });
2027
+ const accessibleIds = getAccessibleEntityIds(user, wm, authService);
2028
+ const valid = filterWikilinks(result.valid, accessibleIds);
2029
+ // Anything the user can't see is reported as broken rather than valid —
2030
+ // it shouldn't leak the target's existence.
2031
+ const hiddenFromValid = result.valid.filter(id => !valid.includes(id));
2032
+ const broken = [...result.broken, ...hiddenFromValid];
2033
+ return ok({ entity_id, valid, broken, total: valid.length + broken.length });
164
2034
  }
165
2035
  catch (err) {
166
2036
  return fail(err);
@@ -174,17 +2044,35 @@ export function registerTools(server, wm, authService) {
174
2044
  direction: z.enum(['outgoing', 'incoming', 'both']).default('both')
175
2045
  .describe('outgoing = links from this entity, incoming = links to this entity'),
176
2046
  });
177
- server.registerTool('find_related', { description: 'Find entities related via wikilinks (outgoing) or backlinks (incoming)', inputSchema: relatedSchema }, async (args) => {
2047
+ reg('find_related', { description: 'Find entities related via wikilinks (outgoing) or backlinks (incoming)', inputSchema: relatedSchema }, async (args) => {
178
2048
  const { repo, entity_type, entity_id, direction } = relatedSchema.parse(args);
2049
+ if (!hasRepoAccess(user, repo, wm, authService))
2050
+ return notFound(repo);
179
2051
  try {
2052
+ const accessibleIds = getAccessibleEntityIds(user, wm, authService);
180
2053
  const results = [];
181
2054
  if (direction === 'outgoing' || direction === 'both') {
182
2055
  const related = wm.getGraph(repo).getRelated(entity_type, entity_id);
183
- results.push(...related.map(e => shape(e.id, e.entityType, e.data, { direction: 'outgoing' })));
2056
+ for (const e of related) {
2057
+ if (accessibleIds && !accessibleIds.has(e.id))
2058
+ continue;
2059
+ results.push(shape(e.id, e.entityType, e.data, { direction: 'outgoing' }));
2060
+ }
184
2061
  }
185
2062
  if (direction === 'incoming' || direction === 'both') {
2063
+ // Comments anchored to this entry surface as incoming backlinks.
2064
+ if (commentService) {
2065
+ const anchored = commentVirtualEntity.findRelatedComments(commentService, repo, entity_type, entity_id);
2066
+ for (const e of anchored) {
2067
+ results.push(shape(e.id, e.entityType, e.data, { direction: 'incoming', repo }));
2068
+ }
2069
+ }
186
2070
  const backlinks = wm.getBacklinks(repo, entity_type, entity_id);
187
- results.push(...backlinks.map(e => shape(e.id, e.entityType, e.data, { direction: 'incoming', repo: e.repoName })));
2071
+ for (const e of backlinks) {
2072
+ if (!hasRepoAccess(user, e.repoName, wm, authService))
2073
+ continue;
2074
+ results.push(shape(e.id, e.entityType, e.data, { direction: 'incoming', repo: e.repoName }));
2075
+ }
188
2076
  }
189
2077
  return ok(results);
190
2078
  }
@@ -192,84 +2080,370 @@ export function registerTools(server, wm, authService) {
192
2080
  return fail(err);
193
2081
  }
194
2082
  });
195
- // ── View management tools ────────────────────────────────────────────────
196
- if (authService) {
197
- server.registerTool('list_views', { description: 'List the current user\'s saved views.', inputSchema: z.object({}) }, async () => {
198
- try {
199
- return ok(authService.getUserViews(0));
2083
+ // ── list_skills ──────────────────────────────────────────────────────────
2084
+ //
2085
+ // Surfaces user-authored `skill` entities to external MCP clients so
2086
+ // connected agents (Claude Desktop, Cursor, etc.) can discover and run
2087
+ // the workspace's codified workflows. Only graph skills (entities) are
2088
+ // exposed; bundled filesystem skills (onboarding/interview/enrichment
2089
+ // prompts) stay internal to Studiograph's own agent.
2090
+ const listSkillsSchema = z.object({
2091
+ repo: z.string().optional().describe('Folder name — omit to list across all folders you have access to'),
2092
+ });
2093
+ function projectSkill(entity, repo) {
2094
+ const data = entity.data;
2095
+ return {
2096
+ id: entity.id,
2097
+ name: data.name || entity.id,
2098
+ description: data.description ?? '',
2099
+ repo,
2100
+ loading: data.loading === 'eager' ? 'eager' : 'on-demand',
2101
+ };
2102
+ }
2103
+ // `graph.list('skill')` is lenient — root-level .md files without a
2104
+ // declared `entity_type` (READMEs, CHANGELOGs) get tagged as whatever the
2105
+ // caller asks for. For MCP we want only real skills, so require the
2106
+ // frontmatter to explicitly say `entity_type: skill`.
2107
+ function isRealSkill(entity) {
2108
+ return entity.data.entity_type === 'skill';
2109
+ }
2110
+ reg('list_skills', {
2111
+ description: 'List skills (reusable agent instructions) available in the Studiograph workspace. ' +
2112
+ 'Skills are user-authored markdown — call get_skill to read the full body and follow ' +
2113
+ 'its instructions. Optionally filter by folder.',
2114
+ inputSchema: listSkillsSchema,
2115
+ }, async (args) => {
2116
+ const { repo } = listSkillsSchema.parse(args);
2117
+ try {
2118
+ if (repo) {
2119
+ if (!hasRepoAccess(user, repo, wm, authService))
2120
+ return notFound(repo);
2121
+ const skills = wm.getGraph(repo).list('skill');
2122
+ return ok(skills.filter(isRealSkill).map(e => projectSkill(e, repo)));
200
2123
  }
201
- catch (err) {
202
- return fail(err);
2124
+ const accessible = getAccessibleRepos(user, wm, authService);
2125
+ const results = [];
2126
+ for (const r of accessible) {
2127
+ try {
2128
+ const skills = wm.getGraph(r.name).list('skill');
2129
+ for (const e of skills) {
2130
+ if (isRealSkill(e))
2131
+ results.push(projectSkill(e, r.name));
2132
+ }
2133
+ }
2134
+ catch { /* skip unmounted repos */ }
203
2135
  }
204
- });
205
- const createViewSchema = z.object({
206
- name: z.string().describe('View name'),
207
- selected_type: z.string().optional().describe('Entity type filter (e.g. "task")'),
208
- group_by: z.string().optional().describe('Column to group by (e.g. "status")'),
209
- });
210
- server.registerTool('create_view', { description: 'Create a new saved view. Views appear as tabs in the web UI toolbar.', inputSchema: createViewSchema }, async (args) => {
211
- const { name, selected_type, group_by } = createViewSchema.parse(args);
212
- try {
213
- const config = {
214
- selectedType: selected_type ?? null,
215
- groupBy: group_by ?? null,
216
- sortKey: 'name',
217
- sortAsc: true,
218
- filters: { text: {}, enum: {}, date: {} },
219
- };
220
- const view = authService.createUserView(0, name, 'list', config);
221
- return ok({ success: true, view: { id: view.id, name: view.name, config: view.config } });
2136
+ return ok(results);
2137
+ }
2138
+ catch (err) {
2139
+ return fail(err);
2140
+ }
2141
+ });
2142
+ // ── get_skill ────────────────────────────────────────────────────────────
2143
+ //
2144
+ // Returns the full markdown body of a skill alongside its metadata. The
2145
+ // body is instructions to follow — the MCP client is the runtime. No
2146
+ // server-side agent loop, no parameter substitution in v1.
2147
+ const getSkillSchema = z.object({
2148
+ name: z.string().describe('Skill id (the entity_id of the skill entity)'),
2149
+ repo: z.string().optional().describe('Folder to look in. Omit to search across all accessible folders.'),
2150
+ });
2151
+ reg('get_skill', {
2152
+ description: 'Fetch the full content of a skill. Returns the skill\'s markdown body along with its ' +
2153
+ 'metadata. The body contains instructions to follow read it and apply it to the current task.',
2154
+ inputSchema: getSkillSchema,
2155
+ }, async (args) => {
2156
+ const { name, repo } = getSkillSchema.parse(args);
2157
+ try {
2158
+ if (repo) {
2159
+ if (!hasRepoAccess(user, repo, wm, authService))
2160
+ return notFound(repo);
2161
+ try {
2162
+ const entity = wm.getGraph(repo).get('skill', name);
2163
+ if (!isRealSkill({ data: entity.data })) {
2164
+ return fail(new Error(`Skill "${name}" not found`));
2165
+ }
2166
+ return ok({
2167
+ ...projectSkill({ id: entity.id, data: entity.data }, repo),
2168
+ content: entity.document.content,
2169
+ });
2170
+ }
2171
+ catch {
2172
+ return fail(new Error(`Skill "${name}" not found`));
2173
+ }
222
2174
  }
223
- catch (err) {
224
- return fail(err);
2175
+ // No repo — search accessible folders for the skill by id.
2176
+ const accessible = getAccessibleRepos(user, wm, authService);
2177
+ const matches = [];
2178
+ for (const r of accessible) {
2179
+ try {
2180
+ const entity = wm.getGraph(r.name).get('skill', name);
2181
+ if (!isRealSkill({ data: entity.data }))
2182
+ continue;
2183
+ matches.push({ repo: r.name, entity });
2184
+ }
2185
+ catch { /* not in this repo */ }
225
2186
  }
226
- });
227
- const updateViewSchema = z.object({
228
- id: z.number().describe('View ID'),
229
- name: z.string().optional().describe('New view name'),
230
- selected_type: z.string().optional().describe('Entity type filter (use "all" to clear)'),
231
- group_by: z.string().optional().describe('Column to group by (use "none" to clear)'),
232
- });
233
- server.registerTool('update_view', { description: 'Update a saved view\'s name or configuration.', inputSchema: updateViewSchema }, async (args) => {
234
- const { id, name, selected_type, group_by } = updateViewSchema.parse(args);
2187
+ if (matches.length === 0)
2188
+ return fail(new Error(`Skill "${name}" not found`));
2189
+ if (matches.length > 1) {
2190
+ const folders = matches.map(m => m.repo).join(', ');
2191
+ return fail(new Error(`Skill "${name}" exists in multiple folders (${folders}). Specify repo.`));
2192
+ }
2193
+ const { repo: matchRepo, entity } = matches[0];
2194
+ return ok({
2195
+ ...projectSkill({ id: entity.id, data: entity.data }, matchRepo),
2196
+ content: entity.document.content,
2197
+ });
2198
+ }
2199
+ catch (err) {
2200
+ return fail(err);
2201
+ }
2202
+ });
2203
+ // ── Git history tools ──────────────────────────────────────────────────────
2204
+ //
2205
+ // Mirrors the agent's history-tools.ts surface so external MCP clients get
2206
+ // the same primitives. Reads gated on hasRepoAccess; restore_file gated on
2207
+ // canWriteRepo. Single-entity scope only — multi-entity rollback is
2208
+ // intentionally absent (deferred to a future revert_commit).
2209
+ const listFileHistorySchema = z.object({
2210
+ repo: z.string().describe('Folder name'),
2211
+ entity_type: z.string().optional().describe('Entity type as returned by search/list. Optional — resolved by id if omitted or wrong.'),
2212
+ entity_id: z.string().optional().describe('Entry ID. Provide entity_id or path.'),
2213
+ path: z.string().optional().describe('Repo-relative file path from a search/list result. When given, resolves directly.'),
2214
+ limit: z.number().optional().describe('Max commits (default 20, max 100)'),
2215
+ });
2216
+ reg('list_file_history', { description: 'List commits that touched a single entry, newest first. Resolve by type+id or by path; a wrong/omitted type still resolves by id.', inputSchema: listFileHistorySchema }, async (args) => {
2217
+ const { repo, entity_type, entity_id, path, limit } = listFileHistorySchema.parse(args);
2218
+ try {
2219
+ if (!hasRepoAccess(user, repo, wm, authService))
2220
+ return fail(`Folder "${repo}" not found`);
2221
+ if (!entity_id && !path)
2222
+ return fail('list_file_history requires entity_id or path');
2223
+ const graph = wm.getGraph(repo);
2224
+ const gitService = graph.getGitService();
2225
+ if (!gitService)
2226
+ return fail('Folder is not git-backed');
2227
+ let entityPath;
235
2228
  try {
236
- const existing = authService.getUserView(id, 0);
237
- if (!existing)
238
- return fail(`View ${id} not found`);
239
- const fields = {};
240
- if (name)
241
- fields.name = name;
242
- if (selected_type !== undefined || group_by !== undefined) {
243
- const config = { ...existing.config };
244
- if (selected_type !== undefined)
245
- config.selectedType = selected_type === 'all' ? null : selected_type;
246
- if (group_by !== undefined)
247
- config.groupBy = group_by === 'none' ? null : group_by;
248
- fields.config = config;
249
- }
250
- authService.updateUserView(id, 0, fields);
251
- return ok({ success: true });
2229
+ // Same robust resolution as get_entity: path wins; else try the
2230
+ // supplied type; on a miss fall back to id-only.
2231
+ let entity;
2232
+ if (path)
2233
+ entity = graph.getByPath(path);
2234
+ else if (entity_type) {
2235
+ try {
2236
+ entity = graph.get(entity_type, entity_id);
2237
+ }
2238
+ catch {
2239
+ entity = graph.getById(entity_id);
2240
+ }
2241
+ }
2242
+ else
2243
+ entity = graph.getById(entity_id);
2244
+ entityPath = relPathOf(entity.path, graph.getRepoPath());
252
2245
  }
253
- catch (err) {
254
- return fail(err);
2246
+ catch {
2247
+ // Live resolution failed (entry may be deleted) → search recent
2248
+ // deletions by filename.
2249
+ const lookupId = entity_id ?? (path ? path.split('/').pop().replace(/\.md$/, '') : '');
2250
+ const idLower = `${lookupId}.md`.toLowerCase();
2251
+ const match = gitService.listRecentDeletions(30).find((d) => d.filePath.toLowerCase().endsWith(idLower));
2252
+ if (!match)
2253
+ return fail(`Entry ${entity_type ?? entity_id ?? path} not found in "${repo}".`);
2254
+ entityPath = match.filePath;
255
2255
  }
256
- });
257
- const deleteViewSchema = z.object({
258
- id: z.number().describe('View ID to delete'),
259
- });
260
- server.registerTool('delete_view', { description: 'Delete a saved view.', inputSchema: deleteViewSchema }, async (args) => {
261
- const { id } = deleteViewSchema.parse(args);
262
- try {
263
- const existing = authService.getUserView(id, 0);
264
- if (!existing)
265
- return fail(`View ${id} not found`);
266
- authService.deleteUserView(id, 0);
267
- return ok({ success: true, message: `Deleted view "${existing.name}"` });
2256
+ const cap = Math.min(100, Math.max(1, limit ?? 20));
2257
+ const commits = gitService.getFileHistory(entityPath, cap).map((c) => ({
2258
+ hash: c.hash.slice(0, 8),
2259
+ fullHash: c.hash,
2260
+ author: c.author,
2261
+ timestamp: c.timestamp,
2262
+ message: c.message,
2263
+ }));
2264
+ return ok({ repo, entityType: entity_type, entityId: entity_id, commits });
2265
+ }
2266
+ catch (err) {
2267
+ return fail(err);
2268
+ }
2269
+ });
2270
+ const listFolderHistorySchema = z.object({
2271
+ repo: z.string().describe('Folder name'),
2272
+ limit: z.number().optional().describe('Max commits (default 30, max 200)'),
2273
+ });
2274
+ reg('list_folder_history', { description: 'List recent commits across an entire folder.', inputSchema: listFolderHistorySchema }, async (args) => {
2275
+ const { repo, limit } = listFolderHistorySchema.parse(args);
2276
+ try {
2277
+ if (!hasRepoAccess(user, repo, wm, authService))
2278
+ return fail(`Folder "${repo}" not found`);
2279
+ const gitService = wm.getGraph(repo).getGitService();
2280
+ if (!gitService)
2281
+ return fail('Folder is not git-backed');
2282
+ const cap = Math.min(200, Math.max(1, limit ?? 30));
2283
+ const commits = gitService.getHistory(cap).map((c) => ({
2284
+ hash: c.hash.slice(0, 8),
2285
+ fullHash: c.hash,
2286
+ author: c.author,
2287
+ timestamp: c.timestamp,
2288
+ message: c.message,
2289
+ }));
2290
+ return ok({ repo, commits });
2291
+ }
2292
+ catch (err) {
2293
+ return fail(err);
2294
+ }
2295
+ });
2296
+ const listRecentDeletionsSchema = z.object({
2297
+ repo: z.string().describe('Folder name'),
2298
+ within_days: z.number().optional().describe('Lookback window (default 7, max 90)'),
2299
+ });
2300
+ reg('list_recent_deletions', { description: 'List entries deleted within the last N days. Use before restore_file to find a target.', inputSchema: listRecentDeletionsSchema }, async (args) => {
2301
+ const { repo, within_days } = listRecentDeletionsSchema.parse(args);
2302
+ try {
2303
+ if (!hasRepoAccess(user, repo, wm, authService))
2304
+ return fail(`Folder "${repo}" not found`);
2305
+ const gitService = wm.getGraph(repo).getGitService();
2306
+ if (!gitService)
2307
+ return fail('Folder is not git-backed');
2308
+ const days = Math.min(90, Math.max(1, within_days ?? 7));
2309
+ const raw = gitService.listRecentDeletions(days);
2310
+ // Naive coords: use filename basename for entity_id; entity_type
2311
+ // sniffed from frontmatter at lastVersionCommit.
2312
+ const deletions = raw.map((d) => {
2313
+ const idMatch = d.filePath.match(/([^/]+)\.md$/i);
2314
+ const entityId = idMatch ? idMatch[1] : null;
2315
+ const content = entityId ? gitService.getFileAtCommit(d.filePath, d.lastVersionCommit) : null;
2316
+ let entityType = null;
2317
+ if (content) {
2318
+ const fm = content.match(/^---\s*\n([\s\S]*?)\n---/);
2319
+ const tm = fm?.[1].match(/^entity_type:\s*['"]?([\w-]+)['"]?\s*$/m);
2320
+ if (tm)
2321
+ entityType = tm[1];
2322
+ }
2323
+ if (!entityId || !entityType)
2324
+ return null;
2325
+ return {
2326
+ entityType,
2327
+ entityId,
2328
+ deletedAt: d.deletedAt,
2329
+ deletedBy: d.deletedBy.name,
2330
+ deletedInCommit: d.deletedAtCommit.slice(0, 8),
2331
+ lastVersionCommit: d.lastVersionCommit,
2332
+ commitMessage: d.commitMessage,
2333
+ path: d.filePath,
2334
+ };
2335
+ }).filter((x) => x !== null);
2336
+ return ok({ repo, withinDays: days, deletions });
2337
+ }
2338
+ catch (err) {
2339
+ return fail(err);
2340
+ }
2341
+ });
2342
+ const readFileAtCommitSchema = z.object({
2343
+ repo: z.string().describe('Folder name'),
2344
+ entity_type: z.string().describe('Entity type'),
2345
+ entity_id: z.string().describe('Entry ID'),
2346
+ commit_hash: z.string().describe('Commit hash (short or full)'),
2347
+ });
2348
+ reg('read_file_at_commit', { description: 'Read an entry\'s full content at a specific commit.', inputSchema: readFileAtCommitSchema }, async (args) => {
2349
+ const { repo, entity_type, entity_id, commit_hash } = readFileAtCommitSchema.parse(args);
2350
+ try {
2351
+ if (!hasRepoAccess(user, repo, wm, authService))
2352
+ return fail(`Folder "${repo}" not found`);
2353
+ const graph = wm.getGraph(repo);
2354
+ const found = graph.getEntityAtCommit(entity_type, entity_id, commit_hash);
2355
+ if (!found)
2356
+ return fail(`Entry ${entity_type}/${entity_id} not found at commit ${commit_hash}`);
2357
+ // Surface the image/asset embeds present in the body at this commit so
2358
+ // a caller can verify whether images existed here without diffing the
2359
+ // raw markdown by eye. Asset *binaries* aren't in git — these are the
2360
+ // body references, the only image evidence git retains.
2361
+ const embeds = extractAssetEmbeds(stripFrontmatter(found.content));
2362
+ return ok({ repo, entityType: entity_type, entityId: entity_id, commitHash: commit_hash, path: found.path, content: found.content, embeds, embedCount: embeds.length });
2363
+ }
2364
+ catch (err) {
2365
+ return fail(err);
2366
+ }
2367
+ });
2368
+ const restoreFileSchema = z.object({
2369
+ repo: z.string().describe('Folder name'),
2370
+ entity_type: z.string().describe('Entity type'),
2371
+ entity_id: z.string().describe('Entry ID'),
2372
+ commit_hash: z.string().describe('Commit hash to restore from'),
2373
+ confirm: z.boolean().optional().describe('Set to true ONLY after the user has approved the preview. Defaults to false.'),
2374
+ });
2375
+ reg('restore_file', { description: 'Restore an entry to its content at a specific commit. Preview-then-confirm: first call returns the preview payload, re-invoke with confirm: true to execute.', inputSchema: restoreFileSchema }, async (args) => {
2376
+ const { repo, entity_type, entity_id, commit_hash, confirm } = restoreFileSchema.parse(args);
2377
+ try {
2378
+ if (!hasRepoAccess(user, repo, wm, authService))
2379
+ return fail(`Folder "${repo}" not found`);
2380
+ if (!canWriteRepo(user, repo, wm, authService))
2381
+ return fail(`You don't have permission to write to "${repo}". Editor or admin role required.`);
2382
+ const graph = wm.getGraph(repo);
2383
+ if (confirm !== true) {
2384
+ const found = graph.getEntityAtCommit(entity_type, entity_id, commit_hash);
2385
+ if (!found)
2386
+ return fail(`Entry ${entity_type}/${entity_id} not found at commit ${commit_hash}`);
2387
+ let action = 'recreate';
2388
+ try {
2389
+ graph.get(entity_type, entity_id);
2390
+ action = 'overwrite';
2391
+ }
2392
+ catch { }
2393
+ const sizeBytes = Buffer.byteLength(found.content, 'utf-8');
2394
+ const shortHash = commit_hash.length > 8 ? commit_hash.slice(0, 8) : commit_hash;
2395
+ // Embeds the restored body will re-link. Restore rewrites the .md
2396
+ // body only; asset binaries live outside git and are NOT touched by
2397
+ // restore — they remain wherever they are (intact unless separately
2398
+ // deleted), so restoring the body re-links any that still exist.
2399
+ const embeds = extractAssetEmbeds(stripFrontmatter(found.content));
2400
+ const assetNote = embeds.length
2401
+ ? `Restores the body and re-links ${embeds.length} embedded asset reference(s): ${embeds.join(', ')}. ` +
2402
+ `Asset binaries are stored outside git and are not modified by restore (they remain intact unless separately deleted). `
2403
+ : '';
2404
+ return ok({
2405
+ success: false,
2406
+ code: 'PREVIEW_REQUIRED',
2407
+ preview: { action, targetPath: found.path, sizeBytes, commitHash: shortHash, embeds, embedCount: embeds.length },
2408
+ message: `Will ${action} ${entity_type}/${entity_id} from ${shortHash} (${sizeBytes} bytes). ${assetNote}Re-invoke with confirm: true after approval.`,
2409
+ });
268
2410
  }
269
- catch (err) {
270
- return fail(err);
2411
+ const result = await withAgentActivity(repo, 'editing', async () => {
2412
+ const { PathConflictError } = await import('../core/graph.js');
2413
+ try {
2414
+ return await graph.restoreFile(entity_type, entity_id, commit_hash);
2415
+ }
2416
+ catch (err) {
2417
+ if (err instanceof PathConflictError) {
2418
+ return { conflict: { path: err.path, occupiedBy: err.occupiedBy } };
2419
+ }
2420
+ throw err;
2421
+ }
2422
+ });
2423
+ if ('conflict' in result) {
2424
+ return ok({
2425
+ success: false,
2426
+ code: 'PATH_CONFLICT',
2427
+ conflict: result.conflict,
2428
+ message: `Cannot restore — ${result.conflict.path} is occupied by ${result.conflict.occupiedBy.entityType}/${result.conflict.occupiedBy.entityId}.`,
2429
+ });
271
2430
  }
272
- });
273
- }
2431
+ emitEntityChange(result.action === 'recreate' ? 'created' : 'updated', repo, entity_type, entity_id);
2432
+ const shortHash = commit_hash.length > 8 ? commit_hash.slice(0, 8) : commit_hash;
2433
+ return ok({
2434
+ success: true,
2435
+ repo,
2436
+ entityType: entity_type,
2437
+ entityId: entity_id,
2438
+ action: result.action,
2439
+ path: result.path,
2440
+ commitHash: shortHash,
2441
+ message: `Restored ${entity_type}/${entity_id} from ${shortHash} (${result.action}).`,
2442
+ });
2443
+ }
2444
+ catch (err) {
2445
+ return fail(err);
2446
+ }
2447
+ });
274
2448
  }
275
2449
  //# sourceMappingURL=tools.js.map