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
@@ -2,132 +2,65 @@
2
2
  * Graph API routes
3
3
  *
4
4
  * REST façade over WorkspaceManager.
5
- * Enforces per-collection access when users are authenticated via JWT.
5
+ * Enforces per-folder access when users are authenticated via JWT.
6
6
  * API key / open mode → full access (no filtering).
7
7
  */
8
+ import { isArchivedRepo } from '../../core/types.js';
8
9
  import { isWorkspaceOwner } from '../../services/auth-service.js';
10
+ import { WorkspaceAccessService } from '../../services/workspace-access.js';
9
11
  import { Workspace } from '../../core/workspace.js';
10
- import { existsSync } from 'fs';
11
- import { join } from 'path';
12
- /** Convert an AuthUser from the request to a GitUser for commit attribution. */
13
- function reqGitUser(req) {
14
- const user = req.user;
15
- if (!user)
16
- return undefined;
17
- return { id: String(user.id), name: user.displayName, email: user.email };
18
- }
19
- /** Get the list of repos the current user can access (content visibility). */
20
- function getAccessibleRepos(req, workspaceManager, authService) {
21
- const allRepos = workspaceManager.getAllRepoConfigs();
22
- const user = req.user;
23
- if (!user)
24
- return allRepos; // API key / open mode → full access
25
- // Folder membership is the sole source of truth for content visibility.
26
- const allowed = new Set(authService.getUserFolders(user.id));
27
- return allRepos.filter(r => allowed.has(r.name));
28
- }
29
- /** Check if the current user can access a specific repo's contents. */
30
- function hasRepoAccess(req, repoName, workspaceManager, authService) {
31
- const user = req.user;
32
- if (!user)
33
- return true; // API key / open mode
34
- return authService.getUserFolders(user.id).includes(repoName);
35
- }
36
- /** Check if the current user is a folder admin of the given repo, or the workspace owner. */
37
- function isFolderAdminOrOwner(req, repoName, authService) {
38
- const user = req.user;
39
- if (!user)
40
- return true; // API key / open mode → admin-equivalent
41
- if (authService.isFolderAdmin(user.id, repoName))
42
- return true;
43
- return isWorkspaceOwner(user.role);
44
- }
45
- /**
46
- * Build a set of all entity IDs visible to the current user.
47
- * Used to filter wikilinks so users don't see links to entities
48
- * in collections they can't access.
49
- */
50
- function getAccessibleEntityIds(req, workspaceManager, authService) {
51
- const user = req.user;
52
- if (!user)
53
- return null; // API key / open mode → no filtering
54
- const repos = getAccessibleRepos(req, workspaceManager, authService);
55
- const ids = new Set();
56
- for (const repo of repos) {
57
- try {
58
- const graph = workspaceManager.getGraph(repo.name);
59
- for (const e of graph.search({})) {
60
- ids.add(e.id);
61
- }
62
- }
63
- catch { /* skip */ }
64
- }
65
- return ids;
66
- }
67
- /** Filter wikilinks to only those pointing to accessible entities. */
68
- function filterWikilinks(wikilinks, accessibleIds) {
69
- if (!accessibleIds)
70
- return wikilinks;
71
- return wikilinks.filter(id => accessibleIds.has(id));
72
- }
73
- /**
74
- * Guard against non-admin members creating eager skills in shared folders —
75
- * they would bloat the system prompt for everyone with folder access.
76
- * Returns an error string or null if the operation is allowed.
77
- */
78
- function checkEagerSkillPermission(req, entityType, data, repoName, workspaceManager, authService) {
79
- if (entityType !== 'skill')
80
- return null;
81
- if (!data || data.loading !== 'eager')
82
- return null;
83
- const user = req.user;
84
- if (!user)
85
- return null; // API key / open mode
86
- const privateRepo = workspaceManager.getUserPrivateRepo(user.id);
87
- if (privateRepo && privateRepo.name === repoName)
88
- return null;
89
- if (isFolderAdminOrOwner(req, repoName, authService))
90
- return null;
91
- return 'Creating an eager skill in a shared folder requires folder admin or workspace owner. Save this skill to your private folder or set loading to "on-demand".';
92
- }
93
- export async function registerGraphApiRoutes(fastify, workspaceManager, authService, wsHub, sessionManager, yjsManager) {
12
+ import { seedFolderName, seedFolderUserId, resolveFolderDisplayName } from '../../core/seed-folder.js';
13
+ import { createSharedFolder, FolderCreationError } from '../../services/folder-creation.js';
14
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs';
15
+ import { basename, dirname, join } from 'path';
16
+ import { RESERVED_ENTITY_TYPES } from '../../core/validation.js';
17
+ import { validateFolderPath } from '../../core/folder-paths.js';
18
+ import { deriveAssetDisplayName, stripFrontmatter } from '../../core/embeds.js';
19
+ import { parseFrontmatterCacheFree } from '../../core/history-diff.js';
20
+ import { StaleBaseRevisionError } from '../../core/graph.js';
21
+ import { checkEagerSkillPermission, filterWikilinks, getAccessibleEntityIds, getAccessibleRepos, getContentRepos, graphForAdmin as resolveGraphForAdmin, graphForRead as resolveGraphForRead, graphForWrite as resolveGraphForWrite, hasRepoAccess, isFolderAdminOrOwner, reqGitUser, reqUser, } from './graph-api-access.js';
22
+ import { buildFontSkill, isFontAssetRow } from '../../services/fonts/font-skill-service.js';
23
+ import { BridgeError, assertCanChangeUserHandle, validateUserHandleInPayload } from '../../services/user-person-bridge.js';
24
+ import { assertEntityRefs, EntityRefError, UnknownUserRefError } from '../../services/entity-mutations.js';
25
+ import { assetDeleteDenial } from '../../services/asset-delete-authz.js';
26
+ import { log } from '../../utils/log.js';
27
+ export async function registerGraphApiRoutes(fastify, workspaceManager, authService, wsHub, sessionManager) {
28
+ // ARCHITECTURAL BOUNDARY: every per-route graph resolution flows through
29
+ // this service so the act of getting a graph IS the access check. Do not
30
+ // call `workspaceManager.getGraph(repo)` from a request-scoped handler —
31
+ // use `accessService.graphFor*(reqUser(req), repo)` instead. A security
32
+ // audit (2026-05) flagged the previous opt-in pattern (route had to
33
+ // remember `hasRepoAccess()` before `getGraph()`) as an architectural
34
+ // risk; if you find yourself reaching for the raw WM here, that's the
35
+ // smell the audit was about.
36
+ const accessService = new WorkspaceAccessService(workspaceManager, authService);
37
+ const graphForRead = (req, reply, repoName) => resolveGraphForRead(accessService, req, reply, repoName);
38
+ const graphForWrite = (req, reply, repoName) => resolveGraphForWrite(accessService, req, reply, repoName);
39
+ const graphForAdmin = (req, reply, repoName) => resolveGraphForAdmin(accessService, req, reply, repoName);
94
40
  // POST /api/repos — any authenticated user can create a folder.
95
41
  // The creator is granted folder-admin access automatically.
42
+ // Shared logic lives in services/folder-creation.ts so the MCP tool
43
+ // create_repo behaves identically.
96
44
  fastify.post('/api/repos', async (req, reply) => {
97
45
  const user = req.user;
98
46
  const { name, display_name, description } = req.body || {};
99
- if (!name || !/^[a-z0-9][a-z0-9-]*$/.test(name)) {
100
- return reply.status(400).send({ error: 'Folder name must be kebab-case' });
101
- }
102
47
  try {
103
- const workspace = new Workspace(workspaceManager['workspacePath']);
104
- // Folders created here are shared team folders. Every user already
105
- // has one auto-provisioned private "My Entries" folder (see
106
- // ensurePrivateCollection in auth-api.ts) — passing private=true
107
- // here would collide with the one-private-per-user rule.
108
- const repo = await workspace.registerRepo({
109
- name,
110
- display_name: display_name || undefined,
111
- description,
112
- owner_id: user?.id,
113
- });
114
- workspaceManager.reloadConfig();
115
- if (user)
116
- authService.grantFolderAccess(user.id, name, 'admin');
117
- wsHub?.broadcast({
118
- type: 'collection_created', repo: name,
119
- actor: user?.displayName ?? 'API',
120
- timestamp: new Date().toISOString(),
121
- });
48
+ const repo = await createSharedFolder(workspaceManager, authService, { name, display_name, description }, user, wsHub);
122
49
  return reply.status(201).send(repo);
123
50
  }
124
51
  catch (err) {
125
- return reply.status(409).send({ error: err.message });
52
+ if (err instanceof FolderCreationError) {
53
+ const status = err.code === 'invalid_name' ? 400
54
+ : err.code === 'duplicate' ? 409
55
+ : 500;
56
+ return reply.status(status).send({ error: err.message });
57
+ }
58
+ return reply.status(500).send({ error: err?.message ?? String(err) });
126
59
  }
127
60
  });
128
61
  // DELETE /api/repos/:repo — folder admin or workspace owner only.
129
62
  fastify.delete('/api/repos/:repo', async (req, reply) => {
130
- if (!isFolderAdminOrOwner(req, req.params.repo, authService)) {
63
+ if (!isFolderAdminOrOwner(req, req.params.repo, workspaceManager, authService)) {
131
64
  return reply.status(403).send({ error: 'Folder admin access required' });
132
65
  }
133
66
  const { repo } = req.params;
@@ -139,11 +72,63 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
139
72
  return reply.status(404).send({ error: `Folder \u201C${repo}\u201D not found` });
140
73
  }
141
74
  const repoConfig = config.repos[repoIndex];
75
+ // System folders (the reserved Workspace Assets store) are an app
76
+ // invariant — undeletable, so nothing needs to recreate them on boot.
77
+ if (repoConfig.system) {
78
+ return reply.status(403).send({ error: 'This is a system folder and cannot be deleted.' });
79
+ }
142
80
  const repoPath = join(workspaceManager['workspacePath'], repoConfig.path);
81
+ // Reclaim the repo's assets (assets.db rows + blobs) BEFORE destroying
82
+ // the repo dir. Assets are workspace-global — the index lives in
83
+ // .studiograph/assets.db and blobs live outside the repo dir (R2 bucket,
84
+ // or .studiograph/assets/{id}/ locally) — so the rmSync below does NOT
85
+ // reclaim them. Skipping this leaves orphan assets.db rows with a dead
86
+ // repo reference (the Meld-class bug) and orphan blobs. deleteAssetById
87
+ // deletes the blob + index row + caption state and is self-isolating
88
+ // (never throws), so a failed blob delete still drops the index row.
89
+ const assetIndex = workspaceManager.getAssetIndex();
90
+ try {
91
+ const graph = workspaceManager.getGraph(repo);
92
+ for (const row of assetIndex.listByRepo(repo)) {
93
+ // Per-row isolation: one bad blob delete must not abort the sweep
94
+ // and strand the rest. (deleteAssetById is already self-isolating,
95
+ // but a rejected await would otherwise break the loop.)
96
+ try {
97
+ await graph.deleteAssetById(row.assetId, row.filename);
98
+ }
99
+ catch (err) {
100
+ log.warn(`[repo-delete] blob ${row.assetId} for ${repo} failed: ${err?.message ?? err}`);
101
+ }
102
+ }
103
+ }
104
+ catch (err) {
105
+ log.warn(`[repo-delete] asset blob cleanup for ${repo} failed: ${err?.message ?? err}`);
106
+ }
107
+ // Drop the index rows UNCONDITIONALLY — even if the blob loop above
108
+ // threw (e.g. getGraph failed, or no asset backend is configured so
109
+ // deleteAssetById short-circuits), orphan index rows with a dead repo
110
+ // reference are exactly the Meld-class bug we're closing. Idempotent
111
+ // when the loop already removed them.
112
+ try {
113
+ assetIndex.deleteByRepo(repo);
114
+ }
115
+ catch (err) {
116
+ log.warn(`[repo-delete] asset index cleanup for ${repo} failed: ${err?.message ?? err}`);
117
+ }
143
118
  // Remove from config
144
119
  config.repos.splice(repoIndex, 1);
145
120
  workspace.writeConfig(config);
146
- workspace.pushWorkspaceConfig(`Remove collection: ${repo}`);
121
+ workspace.pushWorkspaceConfig(`Remove folder: ${repo}`);
122
+ // Durable seed deletion: if a user deletes THEIR OWN seed "My Folder",
123
+ // record a tombstone so the boot sweep + login provisioning don't
124
+ // resurrect it. Only the seed's owner deleting it counts — when a folder
125
+ // admin/workspace owner deletes someone else's (e.g. shared) seed, we do
126
+ // NOT tombstone, so that user's home re-provisions on their next login
127
+ // rather than being permanently removed by another person's action.
128
+ const seedUserId = seedFolderUserId(repo);
129
+ if (seedUserId != null && seedUserId === reqUser(req)?.id) {
130
+ authService.setSeedFolderDeleted(seedUserId, true);
131
+ }
147
132
  // Delete files
148
133
  const { rmSync } = await import('fs');
149
134
  if (existsSync(repoPath)) {
@@ -151,7 +136,7 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
151
136
  }
152
137
  workspaceManager.reloadConfig();
153
138
  wsHub?.broadcast({
154
- type: 'collection_deleted', repo,
139
+ type: 'folder_deleted', repo,
155
140
  actor: req.user?.displayName ?? 'API',
156
141
  timestamp: new Date().toISOString(),
157
142
  });
@@ -161,17 +146,82 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
161
146
  return reply.status(500).send({ error: err.message });
162
147
  }
163
148
  });
149
+ // POST /api/repos/:repo/archive — reversible repo-level hide. Folder admin or
150
+ // workspace owner only. Unlike DELETE (permanent, human/web-only), archive is
151
+ // lossless + identity-preserving and is also exposed to agents/MCP. The
152
+ // archived folder drops out of the sidebar + knowledge surfaces on the next
153
+ // /api/repos fetch; the live-refresh + "Archived folders" management panel are
154
+ // a separate Tier-2 web PR.
155
+ fastify.post('/api/repos/:repo/archive', async (req, reply) => {
156
+ if (!isFolderAdminOrOwner(req, req.params.repo, workspaceManager, authService)) {
157
+ return reply.status(403).send({ error: 'Folder admin access required' });
158
+ }
159
+ try {
160
+ const updated = await workspaceManager.setRepoArchived(req.params.repo, true, reqUser(req)?.handle);
161
+ return reply.send({ success: true, repo: updated });
162
+ }
163
+ catch (err) {
164
+ const status = err?.message?.includes('not found') ? 404
165
+ : err?.message?.includes('System folders') ? 403
166
+ : 500;
167
+ return reply.status(status).send({ error: err.message });
168
+ }
169
+ });
170
+ // POST /api/repos/:repo/unarchive — restore an archived folder. Same authz as
171
+ // archive (archived folders keep their folder_access rows, so folder-admin
172
+ // still resolves).
173
+ fastify.post('/api/repos/:repo/unarchive', async (req, reply) => {
174
+ if (!isFolderAdminOrOwner(req, req.params.repo, workspaceManager, authService)) {
175
+ return reply.status(403).send({ error: 'Folder admin access required' });
176
+ }
177
+ try {
178
+ const updated = await workspaceManager.setRepoArchived(req.params.repo, false, reqUser(req)?.handle);
179
+ return reply.send({ success: true, repo: updated });
180
+ }
181
+ catch (err) {
182
+ // Map the system-folder rejection to 403 consistently with /archive
183
+ // (setRepoArchived guards `system` before the idempotency no-op, so even
184
+ // an unarchive on a system folder surfaces it).
185
+ const status = err?.message?.includes('not found') ? 404
186
+ : err?.message?.includes('System folders') ? 403
187
+ : 500;
188
+ return reply.status(status).send({ error: err.message });
189
+ }
190
+ });
191
+ // GET /api/repos/archived — list archived folders the caller can manage.
192
+ // Archived folders are excluded from /api/repos (getContentRepos), so this is
193
+ // the ONLY way the web Settings UI can see them to restore / permanently
194
+ // delete. Visibility mirrors who can unarchive: folder-admin or workspace
195
+ // owner on each folder (isFolderAdminOrOwner) — not just any member. Returns
196
+ // the archive metadata + member count + caller role for the Settings rows.
197
+ fastify.get('/api/repos/archived', async (req, reply) => {
198
+ workspaceManager.reloadConfig();
199
+ const user = reqUser(req);
200
+ const archived = workspaceManager.getAllRepoConfigs()
201
+ .filter(r => isArchivedRepo(r) && isFolderAdminOrOwner(req, r.name, workspaceManager, authService));
202
+ const enriched = archived.map(r => ({
203
+ name: r.name,
204
+ // Null when there's no explicit display name — the client applies the
205
+ // same title-cased-slug fallback the active folder cards use.
206
+ display_name: r.display_name ?? null,
207
+ description: r.description,
208
+ archived_at: r.archived_at ?? null,
209
+ archived_by: r.archived_by ?? null,
210
+ member_count: authService.getFolderMembers(r.name).length,
211
+ caller_role: user ? authService.getFolderRole(user.id, r.name) : null,
212
+ }));
213
+ return reply.send(enriched);
214
+ });
164
215
  // PATCH /api/repos/:repo — rename/update sync rules. Folder admin or workspace owner only.
165
216
  fastify.patch('/api/repos/:repo', async (req, reply) => {
166
- if (!isFolderAdminOrOwner(req, req.params.repo, authService)) {
217
+ if (!isFolderAdminOrOwner(req, req.params.repo, workspaceManager, authService)) {
167
218
  return reply.status(403).send({ error: 'Folder admin access required' });
168
219
  }
169
220
  const { repo } = req.params;
170
221
  const body = req.body || {};
171
- const { name: newName, display_name: newDisplayName, sync: newSync, ...rest } = body;
172
- if ('private' in rest) {
173
- return reply.status(400).send({ error: 'The private flag is immutable after creation' });
174
- }
222
+ // Only name/display_name/sync are applied; any other field (incl. the
223
+ // deprecated/inert `personal`) is ignored.
224
+ const { name: newName, display_name: newDisplayName, sync: newSync } = body;
175
225
  if (newName === undefined && newSync === undefined && newDisplayName === undefined) {
176
226
  return reply.status(400).send({ error: 'Nothing to update' });
177
227
  }
@@ -185,6 +235,11 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
185
235
  if (!repoConfig) {
186
236
  return reply.status(404).send({ error: `Folder \u201C${repo}\u201D not found` });
187
237
  }
238
+ // System folders (the reserved Workspace Assets store) have a reserved
239
+ // name + fixed display; renaming/reconfiguring them is not allowed.
240
+ if (repoConfig.system) {
241
+ return reply.status(403).send({ error: 'This is a system folder and cannot be renamed or reconfigured.' });
242
+ }
188
243
  // Handle rename
189
244
  if (newName !== undefined) {
190
245
  if (config.repos.some(r => r.name === newName)) {
@@ -207,7 +262,7 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
207
262
  if (existsSync(markerPath)) {
208
263
  writeFS(markerPath, JSON.stringify({ repo_name: newName }, null, 2), 'utf-8');
209
264
  }
210
- // Update collection_access to follow the rename
265
+ // Update folder_access to follow the rename
211
266
  authService.renameFolder(repo, newName);
212
267
  }
213
268
  // Handle display name update (without rename)
@@ -219,7 +274,7 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
219
274
  repoConfig.sync = newSync.length > 0 ? newSync : undefined;
220
275
  }
221
276
  workspace.writeConfig(config);
222
- workspace.pushWorkspaceConfig(newName !== undefined ? `Rename collection: ${repo} → ${newName}` : `Update collection: ${repo}`);
277
+ workspace.pushWorkspaceConfig(newName !== undefined ? `Rename folder: ${repo} → ${newName}` : `Update folder: ${repo}`);
223
278
  workspaceManager.reloadConfig();
224
279
  return reply.send(repoConfig);
225
280
  }
@@ -229,22 +284,57 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
229
284
  });
230
285
  // GET /api/repos — list accessible repos (re-reads config to pick up CLI changes).
231
286
  // Augments each repo with the caller's folder role ('admin' | 'editor'),
232
- // member_count, and entity_types (distinct types present in the folder —
233
- // derived from directory names, no file parsing). The entity_types list
234
- // lets the sidebar render Views without a per-repo entity fetch on boot.
287
+ // member_count, entity_types (distinct types present), and folders (the
288
+ // flat list of nested-folder paths in the repo, used by the sidebar to
289
+ // render the active-path expansion).
235
290
  fastify.get('/api/repos', async (req, reply) => {
236
291
  workspaceManager.reloadConfig();
237
- const repos = getAccessibleRepos(req, workspaceManager, authService);
292
+ // System folders (the reserved Workspace Assets store) are accessible but
293
+ // excluded from the sidebar — they're surfaced via the asset manager, not
294
+ // the folder list. They stay in getAccessibleRepos so authz + the asset
295
+ // manager (/api/workspace/assets) still see them.
296
+ const repos = getContentRepos(req, workspaceManager, authService);
238
297
  const user = req.user;
298
+ const users = authService.listUsers();
239
299
  const enriched = repos.map(r => {
240
300
  const memberCount = authService.getFolderMembers(r.name).length;
241
301
  const callerRole = user ? authService.getFolderRole(user.id, r.name) : null;
242
302
  let entityTypes = [];
303
+ // `folders` carries the per-folder display_name override (when one
304
+ // exists in the folder's .studiograph-folder.json sidecar); the client
305
+ // falls back to toDisplayName(slug) when display_name is absent.
306
+ let folders = [];
243
307
  try {
244
- entityTypes = workspaceManager.getGraph(r.name).listEntityTypes();
308
+ // Direct WM access OK here — `repos` came from getAccessibleRepos
309
+ // above, so every iteration is over a folder the caller can read.
310
+ const graph = workspaceManager.getGraph(r.name);
311
+ entityTypes = graph.listEntityTypes();
312
+ folders = graph.listFolders().map(f => {
313
+ const entry = { path: f.path };
314
+ if (f.display_name)
315
+ entry.display_name = f.display_name;
316
+ if (f.id)
317
+ entry.id = f.id;
318
+ return entry;
319
+ });
245
320
  }
246
321
  catch { /* repo not mounted */ }
247
- return { ...r, caller_role: callerRole, member_count: memberCount, entity_types: entityTypes };
322
+ return {
323
+ ...r,
324
+ // Seed folders read as their owner's "{name}'s Folder" for everyone
325
+ // (incl. the owner); every other folder keeps its stored display_name.
326
+ display_name: resolveFolderDisplayName(r, users),
327
+ caller_role: callerRole,
328
+ member_count: memberCount,
329
+ // True only for THE CALLER's own seed "My Folder" (resolvable as
330
+ // repo:"private") — not just any entries-<id> folder, so a peer's seed
331
+ // shared with the caller is never mistaken for the caller's home (the
332
+ // capture default keys off this). "Solo/private" is derived separately
333
+ // on the client from member_count === 1.
334
+ is_seed: !!user && r.name === seedFolderName(user.id),
335
+ entity_types: entityTypes,
336
+ folders,
337
+ };
248
338
  });
249
339
  return reply.send(enriched);
250
340
  });
@@ -258,6 +348,7 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
258
348
  }
259
349
  workspaceManager.reloadConfig();
260
350
  const all = workspaceManager.getAllRepoConfigs();
351
+ const users = authService.listUsers();
261
352
  const result = all.map(r => {
262
353
  const members = authService.getFolderMembers(r.name);
263
354
  const admins = members.filter(m => m.role === 'admin');
@@ -265,13 +356,16 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
265
356
  ? members.find(m => m.user.id === r.owner_id)?.user
266
357
  : admins[0]?.user;
267
358
  let entryCount = 0;
359
+ // Direct WM access OK — this endpoint is owner-only (gated above) and
360
+ // returns metadata for every folder the workspace owner needs to see
361
+ // for admin purposes. No content is leaked: only entry counts.
268
362
  try {
269
363
  entryCount = workspaceManager.getGraph(r.name).search({}).length;
270
364
  }
271
365
  catch { /* skip */ }
272
366
  return {
273
367
  name: r.name,
274
- display_name: r.display_name ?? r.name,
368
+ display_name: resolveFolderDisplayName(r, users) ?? r.name,
275
369
  description: r.description,
276
370
  member_count: members.length,
277
371
  admin_count: admins.length,
@@ -282,44 +376,45 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
282
376
  });
283
377
  return reply.send(result);
284
378
  });
379
+ // Build the lightweight summary objects used by /entities?summary=true.
380
+ // Extracted so the per-repo and bulk endpoints share the same shape.
381
+ const EXCLUDED_SUMMARY_FIELDS = new Set([
382
+ 'entity_type', 'entity_id', 'created_by', 'updated_by',
383
+ 'description', 'content', 'context', 'rationale', 'alternatives', 'consequences',
384
+ 'steps', 'guidelines', 'bio', 'agenda', 'summary', 'decision', 'action_items',
385
+ ]);
386
+ function buildSummaries(entities, accessibleIds) {
387
+ return entities.map(e => {
388
+ const extra = Object.fromEntries(Object.entries(e.data ?? {}).filter(([k, v]) => !EXCLUDED_SUMMARY_FIELDS.has(k) && k !== 'name' && k !== 'created_at' && k !== 'updated_at' &&
389
+ (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || Array.isArray(v) || v instanceof Date)));
390
+ return {
391
+ id: e.id,
392
+ entityType: e.entityType,
393
+ folder_path: e.folder_path ?? '',
394
+ data: {
395
+ name: e.data.name,
396
+ created_at: e.data.created_at,
397
+ updated_at: e.data.updated_at,
398
+ ...extra,
399
+ },
400
+ document: { wikilinks: filterWikilinks(e.document?.wikilinks ?? [], accessibleIds) },
401
+ };
402
+ });
403
+ }
285
404
  // GET /api/repos/:repo/entities — list all entities in a repo
286
405
  // ?summary=true returns lightweight objects (no content, minimal data) for sidebar/graph
287
406
  fastify.get('/api/repos/:repo/entities', async (req, reply) => {
288
407
  const { repo } = req.params;
289
- if (!hasRepoAccess(req, repo, workspaceManager, authService)) {
290
- return reply.status(404).send({ error: `Folder \u201C${repo}\u201D not found` });
291
- }
292
- let graph;
293
- try {
294
- graph = workspaceManager.getGraph(repo);
295
- }
296
- catch (err) {
297
- return reply.status(404).send({ error: err.message });
298
- }
408
+ // Access enforced by graphForRead \u2014 do not call workspaceManager.getGraph
409
+ // directly here. The audit (2026-05) flagged direct WM access from routes
410
+ // as the primary architectural risk; the service is the gate.
411
+ const graph = graphForRead(req, reply, repo);
412
+ if (!graph)
413
+ return;
299
414
  const entities = graph.search({});
300
415
  const accessibleIds = getAccessibleEntityIds(req, workspaceManager, authService);
301
416
  if (req.query.summary === 'true') {
302
- const EXCLUDED_SUMMARY_FIELDS = new Set([
303
- 'entity_type', 'entity_id', 'created_by', 'updated_by',
304
- 'description', 'content', 'context', 'rationale', 'alternatives', 'consequences',
305
- 'steps', 'guidelines', 'bio', 'agenda', 'summary', 'decision', 'action_items',
306
- ]);
307
- const summaries = entities.map(e => {
308
- const extra = Object.fromEntries(Object.entries(e.data ?? {}).filter(([k, v]) => !EXCLUDED_SUMMARY_FIELDS.has(k) && k !== 'name' && k !== 'created_at' && k !== 'updated_at' &&
309
- (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || Array.isArray(v) || v instanceof Date)));
310
- return {
311
- id: e.id,
312
- entityType: e.entityType,
313
- data: {
314
- name: e.data.name,
315
- created_at: e.data.created_at,
316
- updated_at: e.data.updated_at,
317
- ...extra,
318
- },
319
- document: { wikilinks: filterWikilinks(e.document?.wikilinks ?? [], accessibleIds) },
320
- };
321
- });
322
- return reply.send(summaries);
417
+ return reply.send(buildSummaries(entities, accessibleIds));
323
418
  }
324
419
  // Full response — filter wikilinks in document
325
420
  const filtered = entities.map(e => {
@@ -329,19 +424,57 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
329
424
  });
330
425
  return reply.send(filtered);
331
426
  });
427
+ // GET /api/workspace/entities?summary=true — bulk entity summaries for ALL
428
+ // accessible repos in a single round-trip. Replaces 15+ parallel
429
+ // /api/repos/:repo/entities?summary=true calls. Important on dev where each
430
+ // request has Vite middleware overhead and the browser's HTTP/1.1 6-slot
431
+ // limit serializes them, blocking other navigation fetches.
432
+ // Response shape: `{ [repoName]: EntitySummary[] }`.
433
+ fastify.get('/api/workspace/entities', async (req, reply) => {
434
+ // Mirror the per-repo endpoint's contract: only summary=true is supported
435
+ // here. Returning full bodies for the whole workspace would be huge and
436
+ // isn't a use case we have today.
437
+ if (req.query.summary !== 'true') {
438
+ return reply.status(400).send({ error: 'summary=true is required for the bulk entities endpoint' });
439
+ }
440
+ const repos = getContentRepos(req, workspaceManager, authService);
441
+ // Single-pass: collect the per-repo entity arrays AND the accessible-ids
442
+ // set in one walk. The previous shape called graph.search({}) twice per
443
+ // repo (once via getAccessibleEntityIds, once in this loop), doubling
444
+ // disk I/O on the most-trafficked endpoint.
445
+ const user = reqUser(req);
446
+ const collectIds = user !== undefined; // unauthenticated → no wikilink filtering
447
+ const accessibleIds = collectIds ? new Set() : null;
448
+ const perRepoEntities = [];
449
+ for (const r of repos) {
450
+ let graph;
451
+ // Direct WM access OK — `repos` is the access-filtered list above.
452
+ try {
453
+ graph = workspaceManager.getGraph(r.name);
454
+ }
455
+ catch {
456
+ continue;
457
+ } // skip unmounted repos rather than failing the whole request
458
+ const entities = graph.search({});
459
+ perRepoEntities.push({ name: r.name, entities });
460
+ if (accessibleIds) {
461
+ for (const e of entities)
462
+ accessibleIds.add(e.id);
463
+ }
464
+ }
465
+ const result = {};
466
+ for (const { name, entities } of perRepoEntities) {
467
+ result[name] = buildSummaries(entities, accessibleIds);
468
+ }
469
+ return reply.send(result);
470
+ });
332
471
  // GET /api/repos/:repo/entities/:type — list entities of a type
333
472
  fastify.get('/api/repos/:repo/entities/:type', async (req, reply) => {
334
473
  const { repo, type } = req.params;
335
- if (!hasRepoAccess(req, repo, workspaceManager, authService)) {
336
- return reply.status(404).send({ error: `Folder \u201C${repo}\u201D not found` });
337
- }
338
- let graph;
339
- try {
340
- graph = workspaceManager.getGraph(repo);
341
- }
342
- catch (err) {
343
- return reply.status(404).send({ error: err.message });
344
- }
474
+ // Access enforced by graphForRead (WorkspaceAccessService).
475
+ const graph = graphForRead(req, reply, repo);
476
+ if (!graph)
477
+ return;
345
478
  const entities = graph.search({ entityType: type });
346
479
  const accessibleIds = getAccessibleEntityIds(req, workspaceManager, authService);
347
480
  const filtered = entities.map(e => {
@@ -354,16 +487,10 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
354
487
  // GET /api/repos/:repo/entities/:type/:id — get single entity
355
488
  fastify.get('/api/repos/:repo/entities/:type/:id', async (req, reply) => {
356
489
  const { repo, type, id } = req.params;
357
- if (!hasRepoAccess(req, repo, workspaceManager, authService)) {
358
- return reply.status(404).send({ error: `Folder \u201C${repo}\u201D not found` });
359
- }
360
- let graph;
361
- try {
362
- graph = workspaceManager.getGraph(repo);
363
- }
364
- catch (err) {
365
- return reply.status(404).send({ error: err.message });
366
- }
490
+ // Access enforced by graphForRead (WorkspaceAccessService).
491
+ const graph = graphForRead(req, reply, repo);
492
+ if (!graph)
493
+ return;
367
494
  let entity;
368
495
  try {
369
496
  entity = graph.get(type, id);
@@ -371,6 +498,12 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
371
498
  catch (err) {
372
499
  return reply.status(404).send({ error: err.message ?? `Entity not found: ${type}/${id}` });
373
500
  }
501
+ // graph.get may also return null for a missing entity without throwing
502
+ // (depends on the backing graph implementation). Treat that as 404 so
503
+ // we don't blow up on the wikilinks access below.
504
+ if (!entity) {
505
+ return reply.status(404).send({ error: `Entity not found: ${type}/${id}` });
506
+ }
374
507
  if (entity.document?.wikilinks?.length) {
375
508
  const accessibleIds = getAccessibleEntityIds(req, workspaceManager, authService);
376
509
  return reply.send({
@@ -380,36 +513,185 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
380
513
  }
381
514
  return reply.send(entity);
382
515
  });
516
+ // GET /api/repos/:repo/entities/:type/:id/source — download the raw
517
+ // markdown file (frontmatter + body verbatim, exactly as on disk). For
518
+ // artifacts the body IS the embedded bundle JSON, so the download is a
519
+ // portable single-file copy of the whole entry. Read-gated like every
520
+ // other entity read; serialization fidelity comes from serving the disk
521
+ // bytes rather than re-emitting parsed frontmatter.
522
+ fastify.get('/api/repos/:repo/entities/:type/:id/source', async (req, reply) => {
523
+ const { repo, type, id } = req.params;
524
+ // Access enforced by graphForRead (WorkspaceAccessService).
525
+ const graph = graphForRead(req, reply, repo);
526
+ if (!graph)
527
+ return;
528
+ const filePath = graph.getEntityFilePath(type, id);
529
+ if (!filePath || !existsSync(filePath)) {
530
+ return reply.status(404).send({ error: `Entity not found: ${type}/${id}` });
531
+ }
532
+ const raw = readFileSync(filePath);
533
+ // Name the download from the resolved on-disk basename, sanitized to a
534
+ // safe quoted-string token. Created/renamed ids are kebab-case slugs, but
535
+ // this route resolves EXISTING files via the index — a hand-authored/
536
+ // legacy/imported basename can carry characters that corrupt the header
537
+ // or, with CR/LF, allow header injection. Strip ALL ASCII control chars
538
+ // (Node's validateHeaderValue rejects NUL/VT/FF/DEL etc. → ERR_INVALID_CHAR
539
+ // at send time, not just CR/LF) plus the quoted-string delimiters
540
+ // (" \ ;). Fall back to a safe default if nothing usable remains.
541
+ const safeName = (basename(filePath) || `${id}.md`).replace(/[\x00-\x1f\x7f";\\]/g, '').trim() || `${id}.md`;
542
+ reply.header('Content-Disposition', `attachment; filename="${safeName}"`);
543
+ reply.header('Content-Type', 'text/markdown; charset=utf-8');
544
+ reply.header('X-Content-Type-Options', 'nosniff');
545
+ return reply.send(raw);
546
+ });
383
547
  // GET /api/repos/:repo/entities/:type/:id/history — entity commit history
384
548
  fastify.get('/api/repos/:repo/entities/:type/:id/history', async (req, reply) => {
385
549
  const { repo, type, id } = req.params;
386
- if (!hasRepoAccess(req, repo, workspaceManager, authService)) {
387
- return reply.status(404).send({ error: `Folder \u201C${repo}\u201D not found` });
550
+ // Access enforced by graphForRead (WorkspaceAccessService).
551
+ const graph = graphForRead(req, reply, repo);
552
+ if (!graph)
553
+ return;
554
+ const gitService = graph.getGitService();
555
+ if (!gitService) {
556
+ return reply.send([]);
388
557
  }
389
- let graph;
558
+ const limit = Math.min(parseInt(req.query.limit ?? '20', 10) || 20, 50);
559
+ // Resolve the actual on-disk path via graph.get() — it walks nested
560
+ // folders to find the file. The previous hardcoded `${plural}/${id}.md`
561
+ // missed any entry not at the legacy flat path (entries created in a
562
+ // sub-folder, MCP entries that landed elsewhere, etc.) and silently
563
+ // returned an empty history. Convert absolute path → repo-relative
564
+ // for git log.
565
+ let filePath;
390
566
  try {
391
- graph = workspaceManager.getGraph(repo);
567
+ const entity = graph.get(type, id);
568
+ const repoRoot = graph.getRepoPath();
569
+ filePath = entity.path.startsWith(repoRoot + '/')
570
+ ? entity.path.slice(repoRoot.length + 1)
571
+ : entity.path;
392
572
  }
393
573
  catch (err) {
394
574
  return reply.status(404).send({ error: err.message });
395
575
  }
396
- const gitService = graph.getGitService();
397
- if (!gitService) {
398
- return reply.send([]);
399
- }
400
- const limit = Math.min(parseInt(req.query.limit ?? '20', 10) || 20, 50);
401
- const { PLURAL_TO_SINGULAR } = await import('../../core/validation.js');
402
- // Reverse lookup: singular type → plural folder name
403
- const pluralDir = Object.entries(PLURAL_TO_SINGULAR).find(([, v]) => v === type)?.[0] ?? `${type}s`;
404
- const filePath = `${pluralDir}/${id}.md`;
405
576
  try {
406
577
  const history = gitService.getFileHistory(filePath, limit);
578
+ // Opt-in per-commit change summary for the History panel. Off by
579
+ // default so the eager gating fetch on every entity-page load stays
580
+ // cheap; the panel requests it only when expanded.
581
+ if (req.query.changes === 'true') {
582
+ return reply.send(history.map((c) => ({ ...c, changes: graph.getCommitChange(filePath, c.hash) })));
583
+ }
407
584
  return reply.send(history);
408
585
  }
409
586
  catch {
410
587
  return reply.send([]);
411
588
  }
412
589
  });
590
+ // GET /api/repos/:repo/entities/:type/:id/at-commit/:hash — full body content
591
+ // of an entry as it was at a past commit, for the History "version preview"
592
+ // (read-only Live Preview render). Same read gate as /history.
593
+ //
594
+ // Reuses graph.getEntityAtCommit, which walks the commit tree by IDENTITY
595
+ // (entityType, entityId) — so a rename/move between then and now resolves to
596
+ // the right historical file without the route duplicating path-slicing, and a
597
+ // commit where the entry didn't yet exist at this identity resolves to null.
598
+ // null → typed 404 VERSION_NOT_AT_PATH so the UI can show an honest "this
599
+ // version isn't viewable here" message instead of a misleading generic error.
600
+ fastify.get('/api/repos/:repo/entities/:type/:id/at-commit/:hash', async (req, reply) => {
601
+ const { repo, type, id, hash } = req.params;
602
+ // Defense-in-depth: the hash always comes from our own history list, but
603
+ // validate the shape before it reaches git.
604
+ if (!/^[0-9a-f]{7,40}$/.test(hash)) {
605
+ return reply.status(400).send({ error: 'Invalid commit hash' });
606
+ }
607
+ // Access enforced by graphForRead (WorkspaceAccessService).
608
+ const graph = graphForRead(req, reply, repo);
609
+ if (!graph)
610
+ return;
611
+ const hit = graph.getEntityAtCommit(type, id, hash);
612
+ if (hit === null) {
613
+ return reply.status(404).send({ error: 'VERSION_NOT_AT_PATH' });
614
+ }
615
+ // Editor content is body-only (frontmatter stripped) — match it so the
616
+ // preview renders exactly what the live editor would. The parsed
617
+ // frontmatter rides along so the preview can also show the title + fields
618
+ // as they were at this commit. parseFrontmatterCacheFree (NOT the shared
619
+ // gray-matter MarkdownService) is required for historical content — the
620
+ // shared parser's module-level cache returns stale values for past commits.
621
+ return reply.send({
622
+ body: stripFrontmatter(hit.content),
623
+ frontmatter: parseFrontmatterCacheFree(hit.content),
624
+ });
625
+ });
626
+ // POST /api/repos/:repo/entities/:type/:id/restore — restore an entry to
627
+ // its content at a past commit. Single-entity scope. Editor-or-admin role
628
+ // required (canWriteRepo). Returns 200 with the restore result, 404 if
629
+ // the entry doesn't exist at that commit, 409 PATH_CONFLICT if a different
630
+ // entry now occupies the target path, 403 if the caller lacks write access.
631
+ fastify.post('/api/repos/:repo/entities/:type/:id/restore', async (req, reply) => {
632
+ const { repo, type, id } = req.params;
633
+ const { commitHash } = req.body ?? {};
634
+ if (!commitHash || typeof commitHash !== 'string') {
635
+ return reply.status(400).send({ error: 'commitHash is required' });
636
+ }
637
+ // Access enforced by graphForWrite: 404 if no read access, 403 if no write.
638
+ const graph = graphForWrite(req, reply, repo);
639
+ if (!graph)
640
+ return;
641
+ // Flush any deferred autosave for this repo first — otherwise a restore
642
+ // could overwrite a pending (uncommitted) edit on disk and commit only
643
+ // the restored version, silently discarding the in-flight work. Matches
644
+ // rename/move/change-type, which all flush before mutating.
645
+ if (sessionManager)
646
+ await sessionManager.flush(repo);
647
+ try {
648
+ // restoreFile reconciles any live editor Y.Doc against the restored disk
649
+ // content internally (via the BodyWriteCoordinator's per-doc lock), so an
650
+ // open editor shows the restored version instead of syncing stale content
651
+ // back over it. That lives in graph.restoreFile so every restore caller
652
+ // (this route, the agent tool, the MCP tool) gets it.
653
+ const result = await graph.restoreFile(type, id, commitHash, reqGitUser(req));
654
+ wsHub?.broadcast({
655
+ type: 'entity_change',
656
+ action: result.action === 'recreate' ? 'created' : 'updated',
657
+ repo,
658
+ entityType: type,
659
+ entityId: id,
660
+ actor: reqUser(req)?.displayName ?? 'API',
661
+ source: 'api',
662
+ timestamp: new Date().toISOString(),
663
+ });
664
+ return reply.send({
665
+ success: true,
666
+ action: result.action,
667
+ path: result.path,
668
+ });
669
+ }
670
+ catch (err) {
671
+ // PathConflictError carries an error code distinct from generic
672
+ // failures so the UI can render an actionable conflict message.
673
+ if (err?.code === 'PATH_CONFLICT' && err?.occupiedBy) {
674
+ return reply.status(409).send({
675
+ error: 'PATH_CONFLICT',
676
+ path: err.path,
677
+ occupiedBy: err.occupiedBy,
678
+ });
679
+ }
680
+ const message = err instanceof Error ? err.message : 'Restore failed';
681
+ // "not found at commit" / "did not exist at commit" → 404 so the
682
+ // client can show "that version isn't available" instead of a
683
+ // generic 500.
684
+ if (/not found at commit|did not exist at commit/i.test(message)) {
685
+ return reply.status(404).send({ error: message });
686
+ }
687
+ // Type-mismatch refusal (restoring would silently change entity_type)
688
+ // is a client-actionable conflict, not a server fault → 409.
689
+ if (/would change its type|was type '/i.test(message)) {
690
+ return reply.status(409).send({ error: message });
691
+ }
692
+ return reply.status(500).send({ error: message });
693
+ }
694
+ });
413
695
  // GET /api/schema/:entityType/fields — field metadata for an entity type
414
696
  fastify.get('/api/schema/:entityType/fields', async (req, reply) => {
415
697
  const { entityType } = req.params;
@@ -417,20 +699,25 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
417
699
  const fields = registry.getFieldMetadata(entityType);
418
700
  return reply.send(fields);
419
701
  });
702
+ // GET /api/formats — the format registry, serialized for the client.
703
+ //
704
+ // Single source of truth for ingestion: the web client builds its accept
705
+ // lists, disposition matrix, and supported-types copy from this instead of
706
+ // maintaining parallel (and drifting) constants. Mirrors the
707
+ // GET /api/schema/:type/fields pattern. Workspace-invariant + auth-gated
708
+ // by the global middleware; no per-repo access needed.
709
+ fastify.get('/api/formats', async (_req, reply) => {
710
+ const { serializeForClient } = await import('../../core/format-registry.js');
711
+ return reply.send(serializeForClient());
712
+ });
420
713
  // GET /api/repos/:repo/search?q=&type=&tags= — search within a repo
421
714
  fastify.get('/api/repos/:repo/search', async (req, reply) => {
422
715
  const { repo } = req.params;
423
- if (!hasRepoAccess(req, repo, workspaceManager, authService)) {
424
- return reply.status(404).send({ error: `Folder \u201C${repo}\u201D not found` });
425
- }
716
+ // Access enforced by graphForRead (WorkspaceAccessService).
717
+ const graph = graphForRead(req, reply, repo);
718
+ if (!graph)
719
+ return;
426
720
  const { q, type, tags } = req.query;
427
- let graph;
428
- try {
429
- graph = workspaceManager.getGraph(repo);
430
- }
431
- catch (err) {
432
- return reply.status(404).send({ error: err.message });
433
- }
434
721
  const results = graph.search({
435
722
  query: q,
436
723
  entityType: type,
@@ -446,19 +733,18 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
446
733
  });
447
734
  // GET /api/graph — full workspace graph topology (nodes + edges)
448
735
  fastify.get('/api/graph', async (req, reply) => {
449
- const repos = getAccessibleRepos(req, workspaceManager, authService);
736
+ const repos = getContentRepos(req, workspaceManager, authService);
450
737
  const nodes = [];
451
738
  const edgeSet = new Set();
452
739
  const edges = [];
453
740
  const entityIds = new Set();
454
741
  for (const repoConfig of repos) {
455
742
  try {
743
+ // Direct WM access OK — `repos` came from getAccessibleRepos above.
456
744
  const graph = workspaceManager.getGraph(repoConfig.name);
457
745
  const entities = graph.search({});
458
746
  for (const entity of entities) {
459
747
  const nodeId = entity.id;
460
- if (nodeId.toLowerCase().startsWith('readme'))
461
- continue;
462
748
  if (!entityIds.has(nodeId)) {
463
749
  entityIds.add(nodeId);
464
750
  nodes.push({
@@ -486,10 +772,10 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
486
772
  const validEdges = edges.filter(e => entityIds.has(e.source) && entityIds.has(e.target));
487
773
  return reply.send({ nodes, edges: validEdges });
488
774
  });
489
- // GET /api/search?q=&type=&tags=&repos= — cross-repo search
775
+ // GET /api/search?q=&type=&tags=&repos=&mode= — cross-repo search
490
776
  fastify.get('/api/search', async (req, reply) => {
491
- const { q, type, tags, repos } = req.query;
492
- const accessibleNames = new Set(getAccessibleRepos(req, workspaceManager, authService).map(r => r.name));
777
+ const { q, type, tags, repos, mode } = req.query;
778
+ const accessibleNames = new Set(getContentRepos(req, workspaceManager, authService).map(r => r.name));
493
779
  // Filter requested repos to only accessible ones
494
780
  let repoNames = repos ? repos.split(',').map(r => r.trim()) : undefined;
495
781
  if (repoNames) {
@@ -498,12 +784,35 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
498
784
  else {
499
785
  repoNames = [...accessibleNames];
500
786
  }
787
+ const keywordMode = mode === 'keyword';
501
788
  const results = await workspaceManager.search({
502
789
  query: q,
503
790
  entityType: type,
504
791
  tags: tags ? tags.split(',').map(t => t.trim()) : undefined,
505
792
  repoNames,
793
+ mode: keywordMode ? 'keyword' : undefined,
794
+ // Autosuggest dropdown caps at 20 visible rows; capping server-side
795
+ // avoids serializing a long tail the client throws away.
796
+ limit: keywordMode ? 20 : undefined,
506
797
  });
798
+ // Autosuggest doesn't need the markdown body or wikilinks — the
799
+ // dropdown renders {name, type, repo} only. Returning a slim shape
800
+ // cuts response size from ~hundreds of KB to a few KB on big
801
+ // workspaces, and skips the per-entity wikilink visibility filter.
802
+ if (keywordMode) {
803
+ const slim = results.map(e => ({
804
+ id: e.id,
805
+ path: e.path,
806
+ folder_path: e.folder_path,
807
+ entityType: e.entityType,
808
+ repoName: e.repoName,
809
+ data: {
810
+ name: e.data?.name,
811
+ updated_at: e.data?.updated_at,
812
+ },
813
+ }));
814
+ return reply.send(slim);
815
+ }
507
816
  const accessibleIds = getAccessibleEntityIds(req, workspaceManager, authService);
508
817
  const filtered = results.map((e) => {
509
818
  if (!e.document?.wikilinks?.length)
@@ -515,23 +824,29 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
515
824
  // PUT /api/repos/:repo/entities/:type/:id — update entity (data and/or content)
516
825
  fastify.put('/api/repos/:repo/entities/:type/:id', async (req, reply) => {
517
826
  const { repo, type, id } = req.params;
518
- if (!hasRepoAccess(req, repo, workspaceManager, authService)) {
519
- return reply.status(404).send({ error: `Folder \u201C${repo}\u201D not found` });
520
- }
827
+ // Access enforced by graphForWrite \u2014 PUT updates an entity, write required.
828
+ const graph = graphForWrite(req, reply, repo);
829
+ if (!graph)
830
+ return;
521
831
  const eagerError = checkEagerSkillPermission(req, type, req.body.data, repo, workspaceManager, authService);
522
832
  if (eagerError)
523
833
  return reply.status(403).send({ error: eagerError });
524
- let graph;
525
834
  try {
526
- graph = workspaceManager.getGraph(repo);
527
- }
528
- catch (err) {
529
- return reply.status(404).send({ error: err.message });
530
- }
531
- try {
532
- // Optimistic concurrency: reject if entity was modified after client last fetched it
835
+ // Optimistic concurrency. Two preconditions:
836
+ // - `X-Studiograph-Base-Body-Hash` (Phase 3a): sha256 hex of the
837
+ // body the client last loaded. Enforced INSIDE the per-doc lock
838
+ // by graph.update via `baseRevision`, so it composes with
839
+ // serialization — two body-writing PUTs from the same baseline
840
+ // can't both pass and back-stomp each other.
841
+ // - `If-Unmodified-Since` (legacy): kept for non-web clients;
842
+ // RFC-7231 second-precision so aliases sub-millisecond writes,
843
+ // which is why the body-hash form is preferred.
844
+ // Body-hash header is IGNORED on metadata-only PUTs — a stale body
845
+ // baseline is irrelevant when the PUT doesn't touch the body, and
846
+ // enforcing it would cause spurious 412s after an agent body-write.
847
+ const baseBodyHash = req.headers['x-studiograph-base-body-hash'];
533
848
  const ifUnmodifiedSince = req.headers['if-unmodified-since'];
534
- if (ifUnmodifiedSince) {
849
+ if (ifUnmodifiedSince && !baseBodyHash) {
535
850
  const current = graph.get(type, id);
536
851
  const entityUpdatedAt = current?.data?.updated_at;
537
852
  if (entityUpdatedAt && new Date(entityUpdatedAt) > new Date(ifUnmodifiedSince)) {
@@ -539,6 +854,40 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
539
854
  }
540
855
  }
541
856
  const gitUser = reqGitUser(req);
857
+ // Workspace-wide uniqueness check for `user_handle` on person entities.
858
+ // Skip the entity being updated so we don't reject it for clashing
859
+ // with itself. Also gate WHO can change the bridge: only workspace
860
+ // owners may stamp/clear/rewrite user_handle. Other folder-writers
861
+ // see the field but can't edit it (defense against impersonation
862
+ // through the agent's Current User prompt).
863
+ if (type === 'person' && req.body.data) {
864
+ try {
865
+ validateUserHandleInPayload(workspaceManager, req.body.data, { repo, entityId: id });
866
+ if (Object.prototype.hasOwnProperty.call(req.body.data, 'user_handle')) {
867
+ const current = graph.get('person', id);
868
+ const currentHandle = (current?.data?.user_handle ?? '');
869
+ assertCanChangeUserHandle(reqUser(req), currentHandle, req.body.data.user_handle);
870
+ }
871
+ }
872
+ catch (err) {
873
+ if (err instanceof BridgeError) {
874
+ const status = err.code === 'OWNER_REQUIRED' ? 403 : 409;
875
+ return reply.status(status).send({ error: err.message, code: err.code, ...err.details });
876
+ }
877
+ throw err;
878
+ }
879
+ }
880
+ // Scope ref fields (today: task.assignee → person). Rejects a wikilink
881
+ // that resolves to a non-person; forward references are allowed.
882
+ try {
883
+ assertEntityRefs(workspaceManager, type, req.body.data, { userExists: (id) => !!authService.findUserById(Number(id)) });
884
+ }
885
+ catch (err) {
886
+ if (err instanceof EntityRefError || err instanceof UnknownUserRefError) {
887
+ return reply.status(400).send({ error: err.message, code: err.code, field: err.field });
888
+ }
889
+ throw err;
890
+ }
542
891
  const updated = await graph.update({
543
892
  entityType: type,
544
893
  entityId: id,
@@ -547,16 +896,16 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
547
896
  commitMessage: `Update ${type}/${id} from web UI`,
548
897
  user: gitUser,
549
898
  skipCommit: !!sessionManager, // defer commit if scheduler is active
899
+ source: 'autosave',
900
+ // Forward the body-hash precondition into graph.update so the
901
+ // check runs INSIDE the per-doc lock — see Phase 3 P1 fix.
902
+ // Only meaningful for body writes; ignored when content is
903
+ // undefined (graph.update gates on content !== undefined too).
904
+ baseRevision: req.body.content !== undefined ? baseBodyHash : undefined,
550
905
  });
551
906
  if (sessionManager && gitUser) {
552
907
  sessionManager.markDirty(repo, gitUser, type, id, updated.path);
553
908
  }
554
- // Push the new content into any active Y.Doc so other open tabs /
555
- // browsers for the same entity see the update and the CRDT doesn't
556
- // blend stale state on the next resync. No-op when no Y.Doc is live.
557
- if (req.body.content !== undefined) {
558
- yjsManager?.syncDocFromDisk(`${repo}/${type}/${id}`);
559
- }
560
909
  wsHub?.broadcast({
561
910
  type: 'entity_change', action: 'updated', repo, entityType: type, entityId: id,
562
911
  actor: req.user?.displayName ?? 'API',
@@ -565,26 +914,36 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
565
914
  return reply.send(updated);
566
915
  }
567
916
  catch (err) {
917
+ // Translate the locked-section baseRevision mismatch into the
918
+ // same 412 shape the route used to emit pre-lock. Preserves the
919
+ // client-side contract (code: STALE_BODY_HASH + currentBodyHash
920
+ // + entity).
921
+ if (err instanceof StaleBaseRevisionError) {
922
+ const current = graph.get(type, id);
923
+ return reply.status(412).send({
924
+ error: 'Precondition Failed: body was modified',
925
+ code: 'STALE_BODY_HASH',
926
+ currentBodyHash: err.actual,
927
+ entity: current,
928
+ });
929
+ }
568
930
  return reply.status(400).send({ error: err.message });
569
931
  }
570
932
  });
571
- // POST /api/repos/:repo/change-type — change entity type (moves file between type dirs)
933
+ // POST /api/repos/:repo/change-type — change entity type. Rewrites the
934
+ // `entity_type` frontmatter + swaps type-specific fields IN PLACE (the file
935
+ // stays in its current folder; type lives in frontmatter, not the path). The
936
+ // (type,id) identity still moves, so the client renavigates to the new-type URL.
572
937
  fastify.post('/api/repos/:repo/change-type', async (req, reply) => {
573
938
  const { repo } = req.params;
574
939
  const { entityType: type, entityId: id, newType } = req.body ?? {};
575
940
  if (!type || !id || !newType) {
576
941
  return reply.status(400).send({ error: 'entityType, entityId, and newType are required' });
577
942
  }
578
- if (!hasRepoAccess(req, repo, workspaceManager, authService)) {
579
- return reply.status(404).send({ error: `Folder \u201C${repo}\u201D not found` });
580
- }
581
- let graph;
582
- try {
583
- graph = workspaceManager.getGraph(repo);
584
- }
585
- catch (err) {
586
- return reply.status(404).send({ error: err.message });
587
- }
943
+ // Access enforced by graphForRead (WorkspaceAccessService).
944
+ const graph = graphForRead(req, reply, repo);
945
+ if (!graph)
946
+ return;
588
947
  try {
589
948
  // Flush any pending session saves before moving the file
590
949
  if (sessionManager) {
@@ -622,21 +981,15 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
622
981
  if (id === newId) {
623
982
  return reply.status(400).send({ error: 'New ID must differ from the current ID' });
624
983
  }
625
- if (!hasRepoAccess(req, repo, workspaceManager, authService)) {
626
- return reply.status(404).send({ error: `Folder \u201C${repo}\u201D not found` });
627
- }
984
+ // Access enforced by graphForWrite (WorkspaceAccessService).
985
+ const graph = graphForWrite(req, reply, repo);
986
+ if (!graph)
987
+ return;
628
988
  // Workspace-wide uniqueness — ids must be unique across all folders
629
989
  const conflict = workspaceManager.findEntityById(newId);
630
990
  if (conflict) {
631
991
  return reply.status(409).send({ error: `An entry with id "${newId}" already exists in folder "${conflict.repoName}".` });
632
992
  }
633
- let graph;
634
- try {
635
- graph = workspaceManager.getGraph(repo);
636
- }
637
- catch (err) {
638
- return reply.status(404).send({ error: err.message });
639
- }
640
993
  try {
641
994
  // Flush pending session saves so we don't PUT to the old path after rename
642
995
  if (sessionManager) {
@@ -650,6 +1003,7 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
650
1003
  const refactorMsg = `Refactor wikilinks: ${id} \u2192 ${newId}`;
651
1004
  for (const other of getAccessibleRepos(req, workspaceManager, authService)) {
652
1005
  try {
1006
+ // Direct WM access OK — iterating the access-filtered list.
653
1007
  const otherGraph = workspaceManager.getGraph(other.name);
654
1008
  await otherGraph.refactorWikilink(id, newId, refactorMsg);
655
1009
  }
@@ -672,45 +1026,63 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
672
1026
  return reply.status(status).send({ error: err.message });
673
1027
  }
674
1028
  });
675
- // POST /api/repos/:repo/transfer — move or copy entity to another collection
1029
+ // POST /api/repos/:repo/transfer — move or copy entity to another folder
676
1030
  fastify.post('/api/repos/:repo/transfer', async (req, reply) => {
677
1031
  const sourceRepo = req.params.repo;
678
- const { entityType, entityId, targetRepo, mode } = req.body ?? {};
1032
+ const { entityType, entityId, targetRepo, mode, targetFolderPath } = req.body ?? {};
679
1033
  if (!entityType || !entityId || !targetRepo || !mode) {
680
1034
  return reply.status(400).send({ error: 'entityType, entityId, targetRepo, and mode are required' });
681
1035
  }
682
1036
  if (sourceRepo === targetRepo) {
683
1037
  return reply.status(400).send({ error: 'Source and target folders must be different' });
684
1038
  }
685
- if (!hasRepoAccess(req, sourceRepo, workspaceManager, authService)) {
686
- return reply.status(404).send({ error: `Folder \u201C${sourceRepo}\u201D not found` });
687
- }
688
- if (!hasRepoAccess(req, targetRepo, workspaceManager, authService)) {
689
- return reply.status(404).send({ error: `Folder \u201C${targetRepo}\u201D not found` });
690
- }
691
- let sourceGraph, targetGraph;
1039
+ // Workspace Assets store ownership gate (codex P1 / D2). Moving an `asset`
1040
+ // OUT of a workspace_wide store reassigns its index repo, after which the
1041
+ // uploader/owner delete rule no longer applies — so the move itself must
1042
+ // honor that rule. assetDeleteDenial is a no-op for non-workspace_wide
1043
+ // sources, so normal folder→folder moves are unaffected.
1044
+ if (entityType === 'asset') {
1045
+ const denial = assetDeleteDenial(workspaceManager.getAssetIndex?.(), workspaceManager.getRepoConfig(sourceRepo), reqUser(req), sourceRepo, entityType, entityId);
1046
+ if (denial)
1047
+ return reply.status(403).send({ error: denial });
1048
+ }
1049
+ // Access enforced by graphForWrite — both source (delete on move)
1050
+ // and target (create) require write access. Resolved up-front so a
1051
+ // target-side denial never surfaces only after source has been
1052
+ // mutated. Audit (2026-05) flagged the previous opt-in pattern.
1053
+ const sourceGraph = graphForWrite(req, reply, sourceRepo);
1054
+ if (!sourceGraph)
1055
+ return;
1056
+ const targetGraph = graphForWrite(req, reply, targetRepo);
1057
+ if (!targetGraph)
1058
+ return;
692
1059
  try {
693
- sourceGraph = workspaceManager.getGraph(sourceRepo);
694
- targetGraph = workspaceManager.getGraph(targetRepo);
695
- }
696
- catch (err) {
697
- return reply.status(404).send({ error: err.message });
698
- }
699
- try {
700
- // Flush pending saves
701
- if (sessionManager)
1060
+ // Phase 0 — prevalidate. Audit (2026-05) required this: previously
1061
+ // the route created in target, broadcast `created`, and only then
1062
+ // tried to delete from source. A delete failure left a duplicate
1063
+ // entry the client had already been told about.
1064
+ if (sessionManager) {
702
1065
  await sessionManager.flush(sourceRepo);
703
- // Read source entity
1066
+ if (targetRepo !== sourceRepo)
1067
+ await sessionManager.flush(targetRepo);
1068
+ }
1069
+ // Source must exist; target must not collide. Both checks run BEFORE
1070
+ // any mutation so we never enter phase 2 unless phase 1 will succeed.
704
1071
  const source = sourceGraph.get(entityType, entityId);
705
- // Check for conflicts in target
1072
+ const sourcePath = source.path;
1073
+ const sourceRawMarkdown = readFileSync(sourcePath, 'utf-8');
706
1074
  try {
707
1075
  targetGraph.get(entityType, entityId);
708
1076
  return reply.status(409).send({ error: `"${entityId}" already exists in ${targetRepo}` });
709
1077
  }
710
1078
  catch { /* not found = good */ }
711
- // Create in target
712
1079
  const gitUser = reqGitUser(req);
713
1080
  const { entity_type, entity_id, ...dataFields } = source.data;
1081
+ // Phase 1 — create in target. No broadcast yet: a half-completed
1082
+ // move (created in target, source still present) must NOT be
1083
+ // observable to clients, or they will display two copies until the
1084
+ // next refresh. `targetFolderPath` lets the caller drop the entry
1085
+ // straight into a subfolder of the target repo.
714
1086
  const created = await targetGraph.create({
715
1087
  entityType: entityType,
716
1088
  entityId,
@@ -718,21 +1090,120 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
718
1090
  content: source.document?.content ?? '',
719
1091
  commitMessage: `${mode === 'move' ? 'Move' : 'Copy'} ${entityType}: ${entityId} from ${sourceRepo}`,
720
1092
  user: gitUser,
1093
+ folderPath: targetFolderPath,
721
1094
  });
1095
+ // Phase 2 — for `move` only: delete from source. If this fails, run
1096
+ // a compensating delete on target so we don't leave a duplicate.
1097
+ // The compensating delete is best-effort; if it also fails, surface
1098
+ // a 500 with both error messages so the residue is visible (the
1099
+ // previous behavior silently succeeded and left orphans).
1100
+ if (mode === 'move') {
1101
+ try {
1102
+ // delete() never touches assets (referenced, not owned), so the
1103
+ // moved entity's blobs are safe through the source-side delete.
1104
+ await sourceGraph.delete(entityType, entityId, undefined, gitUser);
1105
+ // Re-point ownership to the target repo ONLY after the source
1106
+ // delete succeeds. Doing it after means a failed source delete
1107
+ // (handled below) leaves the index pointing at the source repo,
1108
+ // which is correct for the entity we're about to restore there.
1109
+ // Blobs/URLs are unchanged (immutable keys); only the index's
1110
+ // repo column moves, which is what serve-time authz reads.
1111
+ workspaceManager.getAssetIndex?.()?.reassignOwner(sourceRepo, entityType, entityId, { repo: targetRepo });
1112
+ }
1113
+ catch (deleteErr) {
1114
+ const deleteMsg = deleteErr instanceof Error ? deleteErr.message : String(deleteErr);
1115
+ let recoveryMsg = null;
1116
+ try {
1117
+ let restoreSource = !existsSync(sourcePath);
1118
+ if (!restoreSource) {
1119
+ try {
1120
+ restoreSource = readFileSync(sourcePath, 'utf-8') !== sourceRawMarkdown;
1121
+ }
1122
+ catch {
1123
+ restoreSource = true;
1124
+ }
1125
+ }
1126
+ if (restoreSource) {
1127
+ mkdirSync(dirname(sourcePath), { recursive: true });
1128
+ writeFileSync(sourcePath, sourceRawMarkdown, 'utf-8');
1129
+ const recoveryPaths = [sourcePath];
1130
+ const folderMarkerPath = join(dirname(sourcePath), '.gitkeep');
1131
+ if (existsSync(folderMarkerPath)) {
1132
+ rmSync(folderMarkerPath, { force: true });
1133
+ recoveryPaths.push(folderMarkerPath);
1134
+ }
1135
+ const sourceAny = sourceGraph;
1136
+ const recoveryGit = sourceAny.gitService;
1137
+ const recoveryUser = gitUser ?? sourceAny.gitUser;
1138
+ if (!recoveryGit || !recoveryUser) {
1139
+ throw new Error('source graph git service or git user unavailable');
1140
+ }
1141
+ recoveryGit.commitFiles(recoveryPaths, `Recover ${entityType}: ${entityId} after failed move to ${targetRepo}`, recoveryUser);
1142
+ }
1143
+ }
1144
+ catch (recoveryErr) {
1145
+ recoveryMsg = recoveryErr instanceof Error ? recoveryErr.message : String(recoveryErr);
1146
+ log.error('[critical] cross-repo move source recovery failed', {
1147
+ sourceRepo,
1148
+ targetRepo,
1149
+ entityType,
1150
+ entityId,
1151
+ sourcePath,
1152
+ deleteError: deleteMsg,
1153
+ recoveryError: recoveryMsg,
1154
+ capturedContent: sourceRawMarkdown,
1155
+ });
1156
+ }
1157
+ try {
1158
+ // Compensating delete never touches assets either — the
1159
+ // recovered source entity keeps pointing at live URLs.
1160
+ await targetGraph.delete(entityType, entityId, undefined, gitUser);
1161
+ if (recoveryMsg) {
1162
+ return reply.status(500).send({
1163
+ error: `Move failed: source delete of ${entityType}/${entityId} failed (${deleteMsg}). Target rolled back, but source recovery commit failed (${recoveryMsg}); manual source repo recovery is required.`,
1164
+ code: 'MOVE_SOURCE_RECOVERY_FAILED',
1165
+ sourceRepo,
1166
+ targetRepo,
1167
+ });
1168
+ }
1169
+ return reply.status(500).send({
1170
+ error: `Move failed: source delete of ${entityType}/${entityId} failed (${deleteMsg}). Target rolled back; folders are back to their pre-move state.`,
1171
+ });
1172
+ }
1173
+ catch (compErr) {
1174
+ const compMsg = compErr instanceof Error ? compErr.message : String(compErr);
1175
+ if (recoveryMsg) {
1176
+ return reply.status(500).send({
1177
+ error: `Move failed and could not be rolled back. Source delete failed (${deleteMsg}); source recovery commit failed (${recoveryMsg}); compensating target delete also failed (${compMsg}). Captured source content was logged for manual recovery.`,
1178
+ code: 'MOVE_RECOVERY_AND_ROLLBACK_FAILED',
1179
+ sourceRepo,
1180
+ targetRepo,
1181
+ });
1182
+ }
1183
+ return reply.status(500).send({
1184
+ error: `Move failed and could not be rolled back. Source delete failed (${deleteMsg}); compensating target delete also failed (${compMsg}). Entry now exists in BOTH ${sourceRepo} and ${targetRepo} — manual cleanup required.`,
1185
+ code: 'MOVE_DUPLICATE',
1186
+ sourceRepo,
1187
+ targetRepo,
1188
+ });
1189
+ }
1190
+ }
1191
+ }
1192
+ // Phase 3 — both phases succeeded; only now broadcast. Order:
1193
+ // source-delete first so subscribers don't briefly observe the
1194
+ // entity in both folders during the network race.
722
1195
  const actor = req.user?.displayName ?? 'API';
723
1196
  const ts = new Date().toISOString();
724
- wsHub?.broadcast({
725
- type: 'entity_change', action: 'created', repo: targetRepo, entityType, entityId,
726
- actor, source: 'api', timestamp: ts,
727
- });
728
- // Delete from source if moving
729
1197
  if (mode === 'move') {
730
- await sourceGraph.delete(entityType, entityId, undefined, gitUser);
731
1198
  wsHub?.broadcast({
732
1199
  type: 'entity_change', action: 'deleted', repo: sourceRepo, entityType, entityId,
733
1200
  actor, source: 'api', timestamp: ts,
734
1201
  });
735
1202
  }
1203
+ wsHub?.broadcast({
1204
+ type: 'entity_change', action: 'created', repo: targetRepo, entityType, entityId,
1205
+ actor, source: 'api', timestamp: ts,
1206
+ });
736
1207
  return reply.send({
737
1208
  success: true,
738
1209
  entity: created,
@@ -743,27 +1214,211 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
743
1214
  return reply.status(400).send({ error: err.message });
744
1215
  }
745
1216
  });
746
- // DELETE /api/repos/:repo/entities/:type/:iddelete an entity
747
- fastify.delete('/api/repos/:repo/entities/:type/:id', async (req, reply) => {
748
- const { repo, type, id } = req.params;
749
- if (!hasRepoAccess(req, repo, workspaceManager, authService)) {
750
- return reply.status(404).send({ error: `Folder \u201C${repo}\u201D not found` });
1217
+ // POST /api/repos/:repo/transfer-entities — bulk move a list of entries
1218
+ // into a single target folder (in this or another repo). The web UI's
1219
+ // multi-select Move flow used to loop and POST per entry; this collapses
1220
+ // to one round-trip per source repo and one commit per repo touched.
1221
+ fastify.post('/api/repos/:repo/transfer-entities', async (req, reply) => {
1222
+ const sourceRepo = req.params.repo;
1223
+ const { entries, targetRepo, targetFolder } = req.body ?? {};
1224
+ if (!Array.isArray(entries) || !targetRepo || targetFolder === undefined) {
1225
+ return reply.status(400).send({ error: 'entries, targetRepo, and targetFolder are required' });
1226
+ }
1227
+ // Workspace Assets store ownership gate (codex P1 / D2): same rule as the
1228
+ // single /transfer route, applied per asset entry. No-op when the source
1229
+ // isn't workspace_wide.
1230
+ for (const e of entries) {
1231
+ if (e.entityType !== 'asset')
1232
+ continue;
1233
+ const denial = assetDeleteDenial(workspaceManager.getAssetIndex?.(), workspaceManager.getRepoConfig(sourceRepo), reqUser(req), sourceRepo, e.entityType, e.entityId);
1234
+ if (denial)
1235
+ return reply.status(403).send({ error: denial });
1236
+ }
1237
+ // Access enforced by graphForWrite — both source (mutate) and
1238
+ // target (write) require write access. Resolved up-front so a
1239
+ // target-side denial never surfaces only after source mutation.
1240
+ const sourceGraph = graphForWrite(req, reply, sourceRepo);
1241
+ if (!sourceGraph)
1242
+ return;
1243
+ const targetGraph = graphForWrite(req, reply, targetRepo);
1244
+ if (!targetGraph)
1245
+ return;
1246
+ try {
1247
+ if (sessionManager) {
1248
+ await sessionManager.flush(sourceRepo);
1249
+ if (targetRepo !== sourceRepo)
1250
+ await sessionManager.flush(targetRepo);
1251
+ }
1252
+ const result = await sourceGraph.moveEntitiesToRepo(entries.map(e => ({ entityType: e.entityType, entityId: e.entityId })), targetGraph, targetFolder, { user: reqGitUser(req) });
1253
+ const actor = req.user?.displayName ?? 'API';
1254
+ const ts = new Date().toISOString();
1255
+ const summary = `moved ${entries.length} ${entries.length === 1 ? 'entry' : 'entries'}`;
1256
+ wsHub?.broadcast({ type: 'repo_sync', repo: sourceRepo, source: 'api', summary, workspaceWide: workspaceManager.getRepoConfig(sourceRepo)?.workspace_wide === true, actor, timestamp: ts });
1257
+ if (targetRepo !== sourceRepo) {
1258
+ wsHub?.broadcast({ type: 'repo_sync', repo: targetRepo, source: 'api', summary, workspaceWide: workspaceManager.getRepoConfig(targetRepo)?.workspace_wide === true, actor, timestamp: ts });
1259
+ }
1260
+ return reply.send({ success: true, ...result });
1261
+ }
1262
+ catch (err) {
1263
+ const status = err?.name === 'InvalidFolderPathError' ? 400
1264
+ : err.message?.includes('not found') ? 404
1265
+ : err.message?.includes('already occupied') ? 409
1266
+ : 400;
1267
+ return reply.status(status).send({ error: err.message });
751
1268
  }
752
- let graph;
1269
+ });
1270
+ // POST /api/repos/:repo/transfer-folder — move an entire folder (with all
1271
+ // descendants) into another repo in one shot. Replaces the per-entry loop
1272
+ // the web UI used to run, which produced 2 git commits per entry; this
1273
+ // route performs a single FS-level rename and one commit per repo.
1274
+ fastify.post('/api/repos/:repo/transfer-folder', async (req, reply) => {
1275
+ const sourceRepo = req.params.repo;
1276
+ const { sourceFolder, targetRepo, targetFolder } = req.body ?? {};
1277
+ if (!sourceFolder || !targetRepo || targetFolder === undefined) {
1278
+ return reply.status(400).send({ error: 'sourceFolder, targetRepo, and targetFolder are required' });
1279
+ }
1280
+ if (sourceRepo === targetRepo && sourceFolder === targetFolder) {
1281
+ return reply.status(400).send({ error: 'Source and target are the same' });
1282
+ }
1283
+ // Workspace Assets store gate (codex P1 / D2). Moving a folder OUT of a
1284
+ // workspace_wide store relocates+reassigns every asset under it, escaping
1285
+ // the per-asset uploader/owner delete rule. A folder-level bulk move in
1286
+ // the shared store is an admin action — restrict it to a workspace owner.
1287
+ // (No-op for normal folders; the manager never folder-moves the store.)
1288
+ if (workspaceManager.getRepoConfig(sourceRepo)?.workspace_wide && !isWorkspaceOwner(reqUser(req)?.role ?? '')) {
1289
+ return reply.status(403).send({ error: 'Only a workspace owner can move folders out of the shared Assets store.' });
1290
+ }
1291
+ // Access enforced by graphForWrite — both source (mutate) and
1292
+ // target (write) require write access. Resolved up-front so a
1293
+ // target-side denial never surfaces only after source mutation.
1294
+ const sourceGraph = graphForWrite(req, reply, sourceRepo);
1295
+ if (!sourceGraph)
1296
+ return;
1297
+ const targetGraph = graphForWrite(req, reply, targetRepo);
1298
+ if (!targetGraph)
1299
+ return;
753
1300
  try {
754
- graph = workspaceManager.getGraph(repo);
1301
+ // Flush any deferred saves on either side so we don't strand a
1302
+ // pending edit in the source folder mid-move.
1303
+ if (sessionManager) {
1304
+ await sessionManager.flush(sourceRepo);
1305
+ if (targetRepo !== sourceRepo)
1306
+ await sessionManager.flush(targetRepo);
1307
+ }
1308
+ const stats = await sourceGraph.moveFolderToRepo(sourceFolder, targetGraph, targetFolder, { user: reqGitUser(req) });
1309
+ const actor = req.user?.displayName ?? 'API';
1310
+ const ts = new Date().toISOString();
1311
+ // One refresh-trigger per repo. The receiving and sending lists
1312
+ // both need to update; using `repo_sync` keeps the realtime
1313
+ // handler simple — it already calls workspace.refreshEntities
1314
+ // on this event type. Avoids emitting 2× entries-moved entity_change
1315
+ // broadcasts (one created, one deleted) per file.
1316
+ wsHub?.broadcast({ type: 'repo_sync', repo: sourceRepo, source: 'api', summary: 'moved a folder', workspaceWide: workspaceManager.getRepoConfig(sourceRepo)?.workspace_wide === true, actor, timestamp: ts });
1317
+ if (targetRepo !== sourceRepo) {
1318
+ wsHub?.broadcast({ type: 'repo_sync', repo: targetRepo, source: 'api', summary: 'moved a folder', workspaceWide: workspaceManager.getRepoConfig(targetRepo)?.workspace_wide === true, actor, timestamp: ts });
1319
+ }
1320
+ return reply.send({ success: true, ...stats });
755
1321
  }
756
1322
  catch (err) {
757
- return reply.status(404).send({ error: err.message });
1323
+ const status = err?.name === 'InvalidFolderPathError' ? 400
1324
+ : err.message?.includes('not found') ? 404
1325
+ : err.message?.includes('not empty') ? 409
1326
+ : 400;
1327
+ return reply.status(status).send({ error: err.message });
758
1328
  }
1329
+ });
1330
+ // Workspace Assets store delete gate (codex P1 / D2) — uploader/owner only.
1331
+ // Pure decision lives in services/asset-delete-authz.ts (unit-tested); this
1332
+ // closure binds it to the route's workspaceManager.
1333
+ const denyAssetDelete = (user, repo, entityType, entityId) => assetDeleteDenial(workspaceManager.getAssetIndex?.(), workspaceManager.getRepoConfig(repo), user, repo, entityType, entityId);
1334
+ // DELETE /api/repos/:repo/entities/:type/:id — delete an entity
1335
+ fastify.delete('/api/repos/:repo/entities/:type/:id', async (req, reply) => {
1336
+ const { repo, type, id } = req.params;
1337
+ const denial = denyAssetDelete(reqUser(req), repo, type, id);
1338
+ if (denial)
1339
+ return reply.status(403).send({ error: denial });
1340
+ // Access enforced by graphForRead (WorkspaceAccessService).
1341
+ const graph = graphForRead(req, reply, repo);
1342
+ if (!graph)
1343
+ return;
759
1344
  try {
1345
+ // Soft bridge-awareness: deleting a person that's linked to an auth
1346
+ // account orphans that user (their "My Tasks"/@mentions stop
1347
+ // resolving). We don't BLOCK the delete — the link is a soft
1348
+ // relationship — but we surface a warning so the caller knows, and the
1349
+ // orphan is then caught by the Profile panel's unlinked affordance.
1350
+ let bridgeWarning;
1351
+ if (type === 'person') {
1352
+ try {
1353
+ const handle = graph.get('person', id)?.data?.user_handle;
1354
+ if (typeof handle === 'string' && handle.length > 0) {
1355
+ bridgeWarning = `This record was linked to the "${handle}" account. Deleting it unlinks that user — their "My Tasks", @mentions, and task assignments stop resolving until they link a new record (Settings → Profile → "Create & link").`;
1356
+ log.warn(`[bridge] deleting linked person ${repo}/${id} (was user ${handle})`);
1357
+ }
1358
+ }
1359
+ catch { /* not readable / already gone — let graph.delete surface it */ }
1360
+ }
760
1361
  await graph.delete(type, id, `Delete ${type}/${id} from web UI`, reqGitUser(req));
1362
+ // Entity deletion KEEPS assets (assets-referenced-not-owned P1) — a
1363
+ // former `asset` entity's index row persists as a library asset
1364
+ // (re-homed to its folder by `admin migrate-asset-owners`).
761
1365
  wsHub?.broadcast({
762
1366
  type: 'entity_change', action: 'deleted', repo, entityType: type, entityId: id,
763
1367
  actor: req.user?.displayName ?? 'API',
764
1368
  source: 'api', timestamp: new Date().toISOString(),
765
1369
  });
766
- return reply.send({ ok: true });
1370
+ return reply.send({ ok: true, ...(bridgeWarning ? { warning: bridgeWarning } : {}) });
1371
+ }
1372
+ catch (err) {
1373
+ return reply.status(400).send({ error: err.message });
1374
+ }
1375
+ });
1376
+ // POST /api/repos/:repo/entities/bulk-delete — delete many entities in
1377
+ // one git transaction + one WS broadcast. Replaces N parallel
1378
+ // DELETE /entities/:type/:id calls, which on a 500-entry bulk delete
1379
+ // produced 500 git commits + 500 entity_change broadcasts and froze
1380
+ // the UI for minutes.
1381
+ fastify.post('/api/repos/:repo/entities/bulk-delete', async (req, reply) => {
1382
+ const { repo } = req.params;
1383
+ // Access enforced by graphForWrite (WorkspaceAccessService).
1384
+ const graph = graphForWrite(req, reply, repo);
1385
+ if (!graph)
1386
+ return;
1387
+ const body = req.body;
1388
+ if (!body?.entities || !Array.isArray(body.entities)) {
1389
+ return reply.status(400).send({ error: 'Expected { entities: [{ entityType, entityId }] }' });
1390
+ }
1391
+ // Apply the workspace-Assets delete gate per entity (codex P1 / D2):
1392
+ // uploader/owner-only. Denied entries are reported, not silently dropped.
1393
+ const deleteUser = reqUser(req);
1394
+ const denied = [];
1395
+ const allowed = body.entities.filter(e => {
1396
+ const d = denyAssetDelete(deleteUser, repo, e.entityType, e.entityId);
1397
+ if (d) {
1398
+ denied.push({ entityType: e.entityType, entityId: e.entityId, error: d });
1399
+ return false;
1400
+ }
1401
+ return true;
1402
+ });
1403
+ try {
1404
+ const result = await graph.bulkDelete(allowed.map(e => ({ entityType: e.entityType, entityId: e.entityId })), {
1405
+ user: reqGitUser(req),
1406
+ commitMessage: `Bulk delete ${allowed.length} ${allowed.length === 1 ? 'entry' : 'entries'} from web UI`,
1407
+ });
1408
+ if (result.deleted.length > 0) {
1409
+ wsHub?.broadcast({
1410
+ type: 'repo_sync', repo,
1411
+ actor: req.user?.displayName ?? 'API',
1412
+ source: 'api',
1413
+ summary: `deleted ${result.deleted.length} ${result.deleted.length === 1 ? 'entry' : 'entries'}`,
1414
+ workspaceWide: workspaceManager.getRepoConfig(repo)?.workspace_wide === true,
1415
+ timestamp: new Date().toISOString(),
1416
+ });
1417
+ }
1418
+ return reply.send({
1419
+ deleted: result.deleted.length,
1420
+ errors: [...result.errors, ...denied],
1421
+ });
767
1422
  }
768
1423
  catch (err) {
769
1424
  return reply.status(400).send({ error: err.message });
@@ -772,203 +1427,466 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
772
1427
  // POST /api/repos/:repo/entities — create a new entity
773
1428
  fastify.post('/api/repos/:repo/entities', async (req, reply) => {
774
1429
  const { repo } = req.params;
775
- if (!hasRepoAccess(req, repo, workspaceManager, authService)) {
776
- return reply.status(404).send({ error: `Folder \u201C${repo}\u201D not found` });
777
- }
778
- const { entityType, entityId, data = {}, content = '' } = req.body;
1430
+ // Access enforced by graphForWrite (WorkspaceAccessService).
1431
+ const graph = graphForWrite(req, reply, repo);
1432
+ if (!graph)
1433
+ return;
1434
+ const { entityType, entityId, data = {}, content = '', folder_path } = req.body;
779
1435
  if (!entityType || !entityId) {
780
1436
  return reply.status(400).send({ error: 'entityType and entityId are required' });
781
1437
  }
1438
+ // Refuse to re-mint a retired type (`asset`, Phase F). Otherwise an
1439
+ // unknown entityType falls back to EntityBaseSchema and writes a
1440
+ // base-only stub of exactly the type asset-as-embed removed.
1441
+ if (RESERVED_ENTITY_TYPES.has(entityType)) {
1442
+ return reply.status(400).send({ error: `Entity type '${entityType}' was retired and can no longer be created.` });
1443
+ }
782
1444
  const eagerError = checkEagerSkillPermission(req, entityType, data, repo, workspaceManager, authService);
783
1445
  if (eagerError)
784
1446
  return reply.status(403).send({ error: eagerError });
785
- let graph;
1447
+ // Person entities can carry a `user_handle` pointer to an auth account;
1448
+ // enforce workspace-wide uniqueness AND owner-only authz before
1449
+ // delegating to the per-repo create path. Stamping a handle on a new
1450
+ // entity is the same trust action as stamping it on an existing one,
1451
+ // so non-owners can't create a pre-linked person entity either.
1452
+ if (entityType === 'person') {
1453
+ try {
1454
+ validateUserHandleInPayload(workspaceManager, data);
1455
+ if (Object.prototype.hasOwnProperty.call(data, 'user_handle')) {
1456
+ assertCanChangeUserHandle(reqUser(req), '', data.user_handle);
1457
+ }
1458
+ }
1459
+ catch (err) {
1460
+ if (err instanceof BridgeError) {
1461
+ const status = err.code === 'OWNER_REQUIRED' ? 403 : 409;
1462
+ return reply.status(status).send({ error: err.message, code: err.code, ...err.details });
1463
+ }
1464
+ throw err;
1465
+ }
1466
+ }
1467
+ // Scope ref fields (today: task.assignee → person). Rejects a wikilink
1468
+ // that resolves to a non-person; forward references are allowed.
786
1469
  try {
787
- graph = workspaceManager.getGraph(repo);
1470
+ assertEntityRefs(workspaceManager, entityType, data, { userExists: (id) => !!authService.findUserById(Number(id)) });
788
1471
  }
789
1472
  catch (err) {
790
- return reply.status(404).send({ error: err.message });
1473
+ if (err instanceof EntityRefError || err instanceof UnknownUserRefError) {
1474
+ return reply.status(400).send({ error: err.message, code: err.code, field: err.field });
1475
+ }
1476
+ throw err;
791
1477
  }
792
1478
  try {
1479
+ // Detect implicit subfolder creation: if the entry lands in a folder
1480
+ // that didn't exist yet, the entity_change below only refreshes the
1481
+ // entry list — clients need a _folder event to refetch the folder tree
1482
+ // (sidebar + list/gallery folder rows). Mirrors the MCP create_entity
1483
+ // path; normalize the same way graph.create does so the existence
1484
+ // check matches the real on-disk path.
1485
+ const normFolder = folder_path ? validateFolderPath(folder_path) : '';
1486
+ const folderExisted = !normFolder || existsSync(join(graph.getRepoPath(), normFolder));
1487
+ const now = new Date().toISOString();
1488
+ const actorId = reqGitUser(req)?.id ?? 'web';
1489
+ const stampedData = {
1490
+ ...data,
1491
+ entity_type: entityType,
1492
+ entity_id: entityId,
1493
+ created_at: data.created_at ?? now,
1494
+ updated_at: data.updated_at ?? now,
1495
+ created_by: data.created_by ?? actorId,
1496
+ updated_by: data.updated_by ?? actorId,
1497
+ };
793
1498
  const entity = await graph.create({
794
1499
  entityType: entityType,
795
1500
  entityId,
796
- data: { entity_type: entityType, entity_id: entityId, ...data },
1501
+ data: stampedData,
797
1502
  content,
1503
+ folderPath: folder_path,
798
1504
  commitMessage: `Create ${entityType}/${entityId} from web UI`,
799
1505
  user: reqGitUser(req),
800
1506
  });
1507
+ const actor = req.user?.displayName ?? 'API';
1508
+ const ts = new Date().toISOString();
801
1509
  wsHub?.broadcast({
802
1510
  type: 'entity_change', action: 'created', repo, entityType, entityId,
803
- actor: req.user?.displayName ?? 'API',
804
- source: 'api', timestamp: new Date().toISOString(),
1511
+ actor, source: 'api', timestamp: ts,
805
1512
  });
1513
+ if (!folderExisted) {
1514
+ wsHub?.broadcast({
1515
+ type: 'entity_change', action: 'created', repo, entityType: '_folder', entityId: normFolder,
1516
+ actor, source: 'api', timestamp: ts,
1517
+ });
1518
+ }
806
1519
  return reply.status(201).send(entity);
807
1520
  }
808
1521
  catch (err) {
809
1522
  return reply.status(400).send({ error: err.message });
810
1523
  }
811
1524
  });
812
- // POST /api/repos/:repo/commitflush deferred commits (navigate-away signal)
813
- fastify.post('/api/repos/:repo/commit', async (req, reply) => {
814
- if (!sessionManager) {
815
- return reply.send({ committed: false });
816
- }
1525
+ // POST /api/repos/:repo/font-skillspackage selected font assets into a
1526
+ // `skill` entity. The server reads each file's REAL metadata (typographic
1527
+ // family / weight / italic via fontkit), groups faces into families, and
1528
+ // writes a skill whose markdown body carries an orienting note, the font
1529
+ // files as chips, and the derived @font-face block. The skill is then
1530
+ // auto-discovered by the agent (skill index) and used to make on-brand
1531
+ // artifacts. `:repo` is the DESTINATION folder; each asset is downloaded from
1532
+ // its own owning repo.
1533
+ fastify.post('/api/repos/:repo/font-skills', async (req, reply) => {
817
1534
  const { repo } = req.params;
818
- if (!hasRepoAccess(req, repo, workspaceManager, authService)) {
819
- return reply.status(404).send({ error: `Folder \u201C${repo}\u201D not found` });
1535
+ const graph = graphForWrite(req, reply, repo); // dest-folder write access
1536
+ if (!graph)
1537
+ return;
1538
+ const { name, folder_path } = req.body ?? {};
1539
+ const requested = Array.isArray(req.body?.assets) ? req.body.assets : [];
1540
+ if (requested.length === 0) {
1541
+ return reply.status(400).send({ error: 'No font assets supplied.' });
1542
+ }
1543
+ // SECURITY: never trust the client-supplied `repo`. Asset blobs are keyed
1544
+ // by an immutable workspace-global key, so downloadAssetById reads them
1545
+ // regardless of which repo's graph you call — authorization MUST come from
1546
+ // the asset INDEX (its real owning repo + filename), exactly like the
1547
+ // exporter. Resolve every input here; unknown/filename-mismatch refs are
1548
+ // dropped (reported as skipped), and the resolved owning-repo is what the
1549
+ // per-asset read check + download then use.
1550
+ const assetIndex = workspaceManager.getAssetIndex();
1551
+ const indexSkipped = [];
1552
+ const resolvedAssets = [];
1553
+ for (const a of requested) {
1554
+ const row = a?.assetId ? assetIndex.get(a.assetId) : undefined;
1555
+ if (!row || row.filename !== a.filename) {
1556
+ indexSkipped.push({ assetId: a?.assetId ?? '', filename: a?.filename ?? '', reason: 'unknown' });
1557
+ continue;
1558
+ }
1559
+ // Reject non-font rows up front — don't download + fontkit-parse an
1560
+ // arbitrary (possibly large) blob just to discard it as unparseable.
1561
+ if (!isFontAssetRow(row)) {
1562
+ indexSkipped.push({ assetId: a.assetId, filename: a.filename, reason: 'not-a-font' });
1563
+ continue;
1564
+ }
1565
+ resolvedAssets.push({ assetId: a.assetId, filename: a.filename, repo: row.repo });
820
1566
  }
821
- const committed = await sessionManager.flush(repo);
822
- return reply.send({ committed });
823
- });
824
- // POST /api/repos/:repo/import — import files via agent processing
825
- fastify.post('/api/repos/:repo/import', async (req, reply) => {
826
- const { repo } = req.params;
827
- if (!hasRepoAccess(req, repo, workspaceManager, authService)) {
828
- return reply.status(403).send({ error: 'Access denied' });
1567
+ let result;
1568
+ try {
1569
+ result = await buildFontSkill({
1570
+ assets: resolvedAssets,
1571
+ nameOverride: name,
1572
+ // Read authz against the asset's REAL owning repo (from the index),
1573
+ // not the caller-supplied one. download() only runs after canRead.
1574
+ canRead: (assetRepo) => hasRepoAccess(req, assetRepo, workspaceManager, authService),
1575
+ download: (assetRepo, assetId, filename) => workspaceManager.getGraph(assetRepo).downloadAssetById(assetId, filename),
1576
+ });
829
1577
  }
830
- const body = req.body;
831
- if (!body?.files || !Array.isArray(body.files)) {
832
- return reply.status(400).send({ error: 'Expected { files: [{ filename, content }] }' });
1578
+ catch (err) {
1579
+ return reply.status(400).send({ error: err?.message ?? String(err) });
1580
+ }
1581
+ const skipped = [...indexSkipped, ...result.skipped];
1582
+ if (!result.draft) {
1583
+ return reply.status(422).send({ error: 'No readable font files in the selection.', skipped });
1584
+ }
1585
+ const draft = result.draft;
1586
+ const normFolder = folder_path ? validateFolderPath(folder_path) : '';
1587
+ const folderExisted = !normFolder || existsSync(join(graph.getRepoPath(), normFolder));
1588
+ const actor = req.user?.displayName ?? 'API';
1589
+ // Avoid clobbering an existing skill at the slug — append -2, -3, ….
1590
+ let entityId = draft.entityId;
1591
+ for (let i = 2; i < 1000; i++) {
1592
+ try {
1593
+ graph.get('skill', entityId);
1594
+ entityId = `${draft.entityId}-${i}`;
1595
+ }
1596
+ catch {
1597
+ break;
1598
+ }
833
1599
  }
834
- const captureRepo = body.agentDecides ? undefined : repo;
1600
+ const now = new Date().toISOString();
1601
+ const actorId = reqGitUser(req)?.id ?? 'web';
835
1602
  try {
836
- const { extractText, isSupportedFile } = await import('../../services/import-service.js');
837
- const results = [];
838
- const CONCURRENCY = 3;
839
- async function processFile(file) {
840
- if (!file.filename || !isSupportedFile(file.filename)) {
841
- return { filename: file.filename, status: 'error', error: `Unsupported file: ${file.filename}` };
842
- }
843
- try {
844
- const text = await extractText(file.filename, file.content);
845
- if (!text) {
846
- console.warn(`[import] No text extracted from ${file.filename}`);
847
- return { filename: file.filename, status: 'error', error: 'No text content extracted' };
848
- }
849
- console.log(`[import] Extracted ${text.length} chars from ${file.filename}`);
850
- const captureRes = await fastify.inject({
851
- method: 'POST',
852
- url: '/api/capture',
853
- headers: {
854
- 'content-type': 'application/json',
855
- ...(req.headers.authorization ? { authorization: req.headers.authorization } : {}),
856
- ...(req.headers.cookie ? { cookie: req.headers.cookie } : {}),
857
- },
858
- payload: JSON.stringify({ text: `[Imported from ${file.filename}]\n\n${text}`, ...(captureRepo ? { repo: captureRepo } : {}) }),
859
- });
860
- const captureBody = JSON.parse(captureRes.body);
861
- if (captureRes.statusCode !== 200 || !captureBody.success) {
862
- console.warn(`[import] Capture failed for ${file.filename}: ${captureRes.statusCode} ${captureBody.error ?? JSON.stringify(captureBody).slice(0, 200)}`);
863
- return { filename: file.filename, status: 'error', error: captureBody.error ?? 'Agent processing failed' };
864
- }
865
- return { filename: file.filename, status: 'created', entities: captureBody.entities };
866
- }
867
- catch (err) {
868
- return { filename: file.filename, status: 'error', error: err.message ?? 'Processing failed' };
869
- }
870
- }
871
- // Process files in batches of CONCURRENCY
872
- for (let i = 0; i < body.files.length; i += CONCURRENCY) {
873
- const batch = body.files.slice(i, i + CONCURRENCY);
874
- const batchResults = await Promise.all(batch.map(processFile));
875
- results.push(...batchResults);
1603
+ await graph.create({
1604
+ entityType: 'skill',
1605
+ entityId,
1606
+ data: {
1607
+ entity_type: 'skill',
1608
+ entity_id: entityId,
1609
+ name: draft.name,
1610
+ description: draft.description,
1611
+ applies_to: ['artifact', 'deck', 'document'],
1612
+ loading: 'on-demand',
1613
+ tags: ['fonts'],
1614
+ created_at: now, updated_at: now, created_by: actorId, updated_by: actorId,
1615
+ },
1616
+ content: draft.content,
1617
+ folderPath: folder_path,
1618
+ commitMessage: `Create font skill ${draft.name} from web UI`,
1619
+ user: reqGitUser(req),
1620
+ });
1621
+ }
1622
+ catch (err) {
1623
+ return reply.status(400).send({ error: err?.message ?? String(err), skipped });
1624
+ }
1625
+ wsHub?.broadcast({ type: 'entity_change', action: 'created', repo, entityType: 'skill', entityId, actor, source: 'api', timestamp: now });
1626
+ if (!folderExisted) {
1627
+ wsHub?.broadcast({ type: 'entity_change', action: 'created', repo, entityType: '_folder', entityId: normFolder, actor, source: 'api', timestamp: new Date().toISOString() });
1628
+ }
1629
+ return reply.status(201).send({
1630
+ created: { repo, entityId, name: draft.name, families: draft.familyNames, faces: draft.faceCount },
1631
+ skipped,
1632
+ });
1633
+ });
1634
+ // POST /api/repos/:repo/move — move entry to a different folder within the
1635
+ // same repo. Distinct from /transfer (cross-repo) and /rename (id change).
1636
+ // The entry's type and id are unchanged; only its folder_path moves.
1637
+ fastify.post('/api/repos/:repo/move', async (req, reply) => {
1638
+ const { repo } = req.params;
1639
+ const { entityType, entityId, folder_path } = req.body ?? {};
1640
+ if (!entityType || !entityId || folder_path === undefined) {
1641
+ return reply.status(400).send({ error: 'entityType, entityId, and folder_path are required' });
1642
+ }
1643
+ // Access enforced by graphForWrite (WorkspaceAccessService).
1644
+ const graph = graphForWrite(req, reply, repo);
1645
+ if (!graph)
1646
+ return;
1647
+ try {
1648
+ if (sessionManager)
1649
+ await sessionManager.flush(repo);
1650
+ // If the destination folder doesn't exist yet, the move creates it —
1651
+ // emit a _folder event (alongside the entry update) so clients refetch
1652
+ // the folder tree, not just the entry list. Mirrors MCP move_entity.
1653
+ const normTarget = folder_path ? validateFolderPath(folder_path) : '';
1654
+ const targetExisted = !normTarget || existsSync(join(graph.getRepoPath(), normTarget));
1655
+ const moved = await graph.move(entityType, entityId, folder_path, undefined, reqGitUser(req));
1656
+ const actor = req.user?.displayName ?? 'API';
1657
+ const ts = new Date().toISOString();
1658
+ wsHub?.broadcast({
1659
+ type: 'entity_change', action: 'updated', repo, entityType, entityId,
1660
+ actor, source: 'api', timestamp: ts,
1661
+ });
1662
+ if (!targetExisted) {
1663
+ wsHub?.broadcast({
1664
+ type: 'entity_change', action: 'created', repo, entityType: '_folder', entityId: normTarget,
1665
+ actor, source: 'api', timestamp: ts,
1666
+ });
876
1667
  }
877
- const created = results.filter(r => r.status === 'created').length;
878
- const entityCount = results.reduce((sum, r) => sum + (r.entities?.length ?? 0), 0);
879
- return {
880
- success: true,
881
- imported: created,
882
- entityCount,
883
- skipped: results.filter(r => r.status === 'error').length,
884
- errors: results.filter(r => r.status === 'error').map(r => r.error),
885
- results,
886
- };
1668
+ return reply.send({ success: true, entity: moved });
887
1669
  }
888
1670
  catch (err) {
889
- return reply.status(400).send({
890
- success: false,
891
- error: err instanceof Error ? err.message : 'Unknown error',
1671
+ const status = err?.name === 'InvalidFolderPathError' ? 400
1672
+ : err.message?.includes('already occupied') ? 409
1673
+ : err.message?.includes('not found') ? 404
1674
+ : 400;
1675
+ return reply.status(status).send({ error: err.message });
1676
+ }
1677
+ });
1678
+ // POST /api/repos/:repo/folders — create a folder (with .gitkeep). Auto-creates parents.
1679
+ // Optional display_name is written to the folder's .studiograph-folder.json
1680
+ // sidecar when set; otherwise the client falls back to toDisplayName(slug).
1681
+ fastify.post('/api/repos/:repo/folders', async (req, reply) => {
1682
+ const { repo } = req.params;
1683
+ const { path, display_name } = req.body ?? {};
1684
+ if (!path || typeof path !== 'string') {
1685
+ return reply.status(400).send({ error: 'path is required' });
1686
+ }
1687
+ // Access enforced by graphForWrite (WorkspaceAccessService).
1688
+ const graph = graphForWrite(req, reply, repo);
1689
+ if (!graph)
1690
+ return;
1691
+ try {
1692
+ const { alreadyExisted } = await graph.createFolder(path, undefined, reqGitUser(req), {
1693
+ displayName: display_name,
892
1694
  });
1695
+ // Emit the _folder sentinel so OTHER connected clients refetch the
1696
+ // folder tree (sidebar + list/gallery folder rows). The local creator
1697
+ // already updated optimistically via workspace.addFolder.
1698
+ wsHub?.broadcast({
1699
+ type: 'entity_change', action: 'created', repo, entityType: '_folder', entityId: validateFolderPath(path),
1700
+ actor: req.user?.displayName ?? 'API',
1701
+ source: 'api', timestamp: new Date().toISOString(),
1702
+ });
1703
+ // createFolder is an idempotent ensure; report reuse so the caller isn't
1704
+ // misled into thinking a duplicate name minted a new folder.
1705
+ return reply.status(201).send({ success: true, path, alreadyExisted });
1706
+ }
1707
+ catch (err) {
1708
+ return reply.status(400).send({ error: err.message });
893
1709
  }
894
1710
  });
895
- // POST /api/repos/:repo/import-bookmarksimport browser bookmark exports as reference entities
896
- fastify.post('/api/repos/:repo/import-bookmarks', async (req, reply) => {
1711
+ // PATCH /api/repos/:repo/foldersrename / move a folder within the repo,
1712
+ // optionally updating its display_name in the same commit. Three shapes:
1713
+ // { from, to } — slug-only rename / move
1714
+ // { from, to, display_name } — slug change + display update
1715
+ // { from, to: from, display_name } — display-only update (no FS move)
1716
+ // `display_name: ""` (or null) clears the override and reverts to the slug
1717
+ // fallback.
1718
+ fastify.patch('/api/repos/:repo/folders', async (req, reply) => {
897
1719
  const { repo } = req.params;
898
- if (!hasRepoAccess(req, repo, workspaceManager, authService)) {
899
- return reply.status(403).send({ error: 'Access denied' });
1720
+ const { from, to, display_name } = req.body ?? {};
1721
+ if (!from || !to) {
1722
+ return reply.status(400).send({ error: 'from and to are required' });
1723
+ }
1724
+ // Access enforced by graphForWrite (WorkspaceAccessService).
1725
+ const graph = graphForWrite(req, reply, repo);
1726
+ if (!graph)
1727
+ return;
1728
+ try {
1729
+ if (sessionManager)
1730
+ await sessionManager.flush(repo);
1731
+ await graph.renameFolder(from, to, undefined, reqGitUser(req), {
1732
+ displayName: display_name === undefined ? undefined : display_name,
1733
+ });
1734
+ // Rename/move relocates entries on disk, so the _folder sentinel's
1735
+ // refreshRepoAfterFolderMutation (tree + entity summaries) is exactly
1736
+ // what other clients need. Carries the new path as the entityId.
1737
+ wsHub?.broadcast({
1738
+ type: 'entity_change', action: 'updated', repo, entityType: '_folder', entityId: validateFolderPath(to),
1739
+ actor: req.user?.displayName ?? 'API',
1740
+ source: 'api', timestamp: new Date().toISOString(),
1741
+ });
1742
+ return reply.send({ success: true, from, to });
900
1743
  }
901
- const body = req.body;
902
- if (!body?.content || typeof body.content !== 'string') {
903
- return reply.status(400).send({ error: 'Expected { content: string }' });
1744
+ catch (err) {
1745
+ const status = err?.name === 'InvalidFolderPathError' ? 400
1746
+ : err.message?.includes('not found') ? 404
1747
+ : err.message?.includes('already exists') ? 409
1748
+ : 400;
1749
+ return reply.status(status).send({ error: err.message });
1750
+ }
1751
+ });
1752
+ // DELETE /api/repos/:repo/folders?path=X&recursive=true — delete a folder.
1753
+ // Without recursive=true, non-empty folders return 409 with blast-radius
1754
+ // counts so the UI can confirm before retrying.
1755
+ fastify.delete('/api/repos/:repo/folders', async (req, reply) => {
1756
+ const { repo } = req.params;
1757
+ const { path, recursive } = req.query;
1758
+ if (!path) {
1759
+ return reply.status(400).send({ error: 'path query parameter is required' });
1760
+ }
1761
+ // Access enforced by graphForWrite (WorkspaceAccessService).
1762
+ const graph = graphForWrite(req, reply, repo);
1763
+ if (!graph)
1764
+ return;
1765
+ // Workspace Assets store gate (codex P1 / D2). A recursive delete in a
1766
+ // workspace_wide store destroys every asset under the folder, bypassing
1767
+ // the per-asset uploader/owner rule — restrict it to a workspace owner.
1768
+ // (Non-recursive delete is blocked on non-empty folders by FOLDER_NOT_EMPTY,
1769
+ // so it can't take assets with it.)
1770
+ if (recursive === 'true' && workspaceManager.getRepoConfig(repo)?.workspace_wide && !isWorkspaceOwner(reqUser(req)?.role ?? '')) {
1771
+ return reply.status(403).send({ error: 'Only a workspace owner can recursively delete folders in the shared Assets store.' });
904
1772
  }
905
1773
  try {
906
- const { isNetscapeBookmarkFile, importBookmarks } = await import('../../services/bookmark-import-service.js');
907
- if (!isNetscapeBookmarkFile(body.content)) {
908
- return reply.status(400).send({
909
- error: "This doesn't appear to be a browser bookmark export. Export bookmarks from your browser's bookmark manager to generate the correct format.",
1774
+ if (sessionManager)
1775
+ await sessionManager.flush(repo);
1776
+ const result = await graph.deleteFolder(path, {
1777
+ recursive: recursive === 'true',
1778
+ user: reqGitUser(req),
1779
+ });
1780
+ // Emit the _folder sentinel so other clients drop the folder (and any
1781
+ // entries it held, via the entity-summary refresh) from their view.
1782
+ wsHub?.broadcast({
1783
+ type: 'entity_change', action: 'deleted', repo, entityType: '_folder', entityId: validateFolderPath(path),
1784
+ actor: req.user?.displayName ?? 'API',
1785
+ source: 'api', timestamp: new Date().toISOString(),
1786
+ });
1787
+ return reply.send({ success: true, ...result });
1788
+ }
1789
+ catch (err) {
1790
+ if (err?.code === 'FOLDER_NOT_EMPTY') {
1791
+ return reply.status(409).send({
1792
+ error: err.message,
1793
+ code: 'FOLDER_NOT_EMPTY',
1794
+ blast_radius: err.blastRadius,
910
1795
  });
911
1796
  }
912
- const graph = workspaceManager.getGraph(repo);
913
- const user = reqGitUser(req);
914
- const result = await importBookmarks(graph, body.content, user);
915
- // Single git commit for all created entities
916
- if (result.created > 0) {
917
- const git = graph.getGitService();
918
- if (git) {
919
- const commitUser = user ?? { id: 'import', name: 'Import', email: 'import@studiograph' };
920
- git.commitAll(`Import ${result.created} bookmark${result.created !== 1 ? 's' : ''} as references`, commitUser);
921
- }
922
- }
923
- return {
924
- success: true,
925
- created: result.created,
926
- skipped: result.skipped,
927
- errors: result.errors,
928
- };
1797
+ const status = err?.name === 'InvalidFolderPathError' ? 400
1798
+ : err.message?.includes('not found') ? 404
1799
+ : 400;
1800
+ return reply.status(status).send({ error: err.message });
929
1801
  }
930
- catch (err) {
931
- return reply.status(400).send({
932
- success: false,
933
- error: err instanceof Error ? err.message : 'Unknown error',
934
- });
1802
+ });
1803
+ // POST /api/repos/:repo/commit — flush deferred commits (navigate-away signal)
1804
+ fastify.post('/api/repos/:repo/commit', async (req, reply) => {
1805
+ if (!sessionManager) {
1806
+ return reply.send({ committed: false });
1807
+ }
1808
+ const { repo } = req.params;
1809
+ // Access enforced via accessService.canRead \u2014 flush operates on the
1810
+ // SessionManager (no graph instance needed), so a boolean check is
1811
+ // sufficient. Pending commits were already authored by an authenticated
1812
+ // writer; this endpoint just flushes them on navigate-away.
1813
+ if (!accessService.canRead(reqUser(req), repo)) {
1814
+ return reply.status(404).send({ error: `Folder \u201C${repo}\u201D not found` });
935
1815
  }
1816
+ const committed = await sessionManager.flush(repo);
1817
+ return reply.send({ committed });
936
1818
  });
937
- // POST /api/repos/:repo/import-imagesimport images as reference entities
938
- fastify.post('/api/repos/:repo/import-images', { bodyLimit: 50 * 1024 * 1024 }, async (req, reply) => {
1819
+ // POST /api/repos/:repo/assetsgeneric asset upload.
1820
+ //
1821
+ // Multipart `file` field. Asset-as-embed (B2): stores a FREE-STANDING,
1822
+ // folder-owned asset (assets.db row + immutable-key blob) — NO wrapper
1823
+ // entity. Returns { assetId, asset_url, ... } for the caller to embed.
1824
+ // For an IMAGE, graph.uploadAsset fires the caption/OCR trigger (assetId-
1825
+ // keyed), and the caption folds into any entry that embeds the asset. See
1826
+ // docs/asset-architecture.md.
1827
+ fastify.post('/api/repos/:repo/assets', async (req, reply) => {
939
1828
  const { repo } = req.params;
940
- if (!hasRepoAccess(req, repo, workspaceManager, authService)) {
941
- return reply.status(403).send({ error: 'Access denied' });
1829
+ // Access enforced by graphForWrite (WorkspaceAccessService).
1830
+ const graph = graphForWrite(req, reply, repo);
1831
+ if (!graph)
1832
+ return;
1833
+ // Per-route fileSize override — the global multipart limit (10MB) is
1834
+ // tuned for inline image embeds; full asset uploads accept up to the
1835
+ // 100MB cap enforced inside AssetService.upload().
1836
+ let file;
1837
+ try {
1838
+ file = await req.file({ limits: { fileSize: 100 * 1024 * 1024 } });
942
1839
  }
943
- const body = req.body;
944
- if (!body?.files || !Array.isArray(body.files)) {
945
- return reply.status(400).send({ error: 'Expected { files: [{ filename, content }] }' });
1840
+ catch (err) {
1841
+ return reply.status(400).send({ error: err?.message ?? 'Invalid multipart upload' });
946
1842
  }
1843
+ if (!file)
1844
+ return reply.status(400).send({ error: 'No file uploaded' });
1845
+ const buffer = await file.toBuffer();
947
1846
  try {
948
- const { importImages } = await import('../../services/image-import-service.js');
949
- const graph = workspaceManager.getGraph(repo);
950
- const user = reqGitUser(req);
951
- const result = await importImages(graph, body.files, user);
952
- // Single git commit for all created entities
953
- if (result.created > 0) {
954
- const git = graph.getGitService();
955
- if (git) {
956
- const commitUser = user ?? { id: 'import', name: 'Import', email: 'import@studiograph' };
957
- git.commitAll(`Import ${result.created} image${result.created !== 1 ? 's' : ''} as references`, commitUser);
958
- }
959
- }
960
- return {
1847
+ // Asset-as-embed (B2): uploading to the Assets store produces a
1848
+ // FREE-STANDING, folder-owned asset — NOT an `asset` entity. The asset
1849
+ // manager lists it directly; the homepage capture embeds its URL into
1850
+ // the entry it creates and deletes it by assetId if unused. Folder-owned
1851
+ // assets are repo-scoped, so the `folder` subpath doesn't apply here.
1852
+ const displayName = req.query.name?.trim()
1853
+ || deriveAssetDisplayName(file.filename)
1854
+ || file.filename;
1855
+ const stored = await graph.uploadAsset('material', // ignored for ownership when `owner` is set
1856
+ 'asset', // ignored
1857
+ file.filename, buffer, undefined, {
1858
+ owner: { type: 'folder', id: repo },
1859
+ uploaderUserId: reqUser(req)?.id != null ? String(reqUser(req).id) : null,
1860
+ name: displayName,
1861
+ });
1862
+ wsHub?.broadcast({
1863
+ type: 'repo_sync',
1864
+ repo,
1865
+ actor: req.user?.displayName ?? 'API',
1866
+ source: 'api',
1867
+ summary: 'uploaded an asset',
1868
+ workspaceWide: workspaceManager.getRepoConfig(repo)?.workspace_wide === true,
1869
+ timestamp: new Date().toISOString(),
1870
+ });
1871
+ // NOTE: image caption/OCR indexing is NOT derived for this
1872
+ // free-standing asset — the caption sweep + live drain key on `asset`
1873
+ // ENTITIES, which this no longer creates. It stays filename-only in
1874
+ // vector search until the caption pipeline is rewired to be
1875
+ // assets.db-row-keyed (tracked follow-up).
1876
+ return reply.send({
961
1877
  success: true,
962
- created: result.created,
963
- skipped: result.skipped,
964
- errors: result.errors,
965
- };
1878
+ assetId: stored.assetId,
1879
+ asset_url: stored.publicUrl,
1880
+ asset_kind: stored.mediaType,
1881
+ filename: stored.filename,
1882
+ });
966
1883
  }
967
1884
  catch (err) {
968
- return reply.status(400).send({
969
- success: false,
970
- error: err instanceof Error ? err.message : 'Unknown error',
971
- });
1885
+ const msg = err instanceof Error ? err.message : String(err);
1886
+ const status = msg.includes('not configured') ? 501
1887
+ : msg.includes('Unsupported asset extension') ? 415
1888
+ : 400;
1889
+ return reply.status(status).send({ success: false, error: msg });
972
1890
  }
973
1891
  });
974
1892
  }