@aphexcms/cms-core 9.7.0 → 9.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (330) hide show
  1. package/dist/agent-chat-state.svelte.d.ts +38 -0
  2. package/dist/agent-chat-state.svelte.d.ts.map +1 -0
  3. package/dist/agent-chat-state.svelte.js +86 -0
  4. package/dist/ai/content-workspace-tools.d.ts +3 -0
  5. package/dist/ai/content-workspace-tools.d.ts.map +1 -0
  6. package/dist/ai/content-workspace-tools.js +50 -0
  7. package/dist/ai/default-system-prompt.d.ts +2 -0
  8. package/dist/ai/default-system-prompt.d.ts.map +1 -0
  9. package/dist/ai/default-system-prompt.js +15 -0
  10. package/dist/ai/index.d.ts +2 -0
  11. package/dist/ai/index.d.ts.map +1 -0
  12. package/dist/ai/index.js +4 -0
  13. package/dist/ai/interfaces/ai-provider.d.ts +67 -0
  14. package/dist/ai/interfaces/ai-provider.d.ts.map +1 -0
  15. package/dist/ai/interfaces/ai-provider.js +16 -0
  16. package/dist/ai/run-agent-turn.d.ts +29 -0
  17. package/dist/ai/run-agent-turn.d.ts.map +1 -0
  18. package/dist/ai/run-agent-turn.js +166 -0
  19. package/dist/api/agent-chat.d.ts +27 -0
  20. package/dist/api/agent-chat.d.ts.map +1 -0
  21. package/dist/api/agent-chat.js +64 -0
  22. package/dist/api/documents.d.ts +8 -3
  23. package/dist/api/documents.d.ts.map +1 -1
  24. package/dist/api/documents.js +6 -6
  25. package/dist/api/schemas/agent-chat.d.ts +45 -0
  26. package/dist/api/schemas/agent-chat.d.ts.map +1 -0
  27. package/dist/api/schemas/agent-chat.js +35 -0
  28. package/dist/api/schemas/agent-operations.d.ts +13 -0
  29. package/dist/api/schemas/agent-operations.d.ts.map +1 -0
  30. package/dist/api/schemas/agent-operations.js +15 -0
  31. package/dist/api/schemas/documents.d.ts +22 -0
  32. package/dist/api/schemas/documents.d.ts.map +1 -1
  33. package/dist/api/schemas/documents.js +20 -2
  34. package/dist/chunk-recovery.d.ts +33 -0
  35. package/dist/chunk-recovery.d.ts.map +1 -0
  36. package/dist/chunk-recovery.js +131 -0
  37. package/dist/client/index.d.ts +1 -0
  38. package/dist/client/index.d.ts.map +1 -1
  39. package/dist/client/index.js +1 -0
  40. package/dist/client/ui.d.ts +1 -0
  41. package/dist/client/ui.d.ts.map +1 -1
  42. package/dist/client/ui.js +1 -0
  43. package/dist/components/AdminApp.svelte +37 -3
  44. package/dist/components/AdminApp.svelte.d.ts.map +1 -1
  45. package/dist/components/admin/ActivityView.svelte +251 -42
  46. package/dist/components/admin/ActivityView.svelte.d.ts.map +1 -1
  47. package/dist/components/admin/AgentChat.svelte +977 -0
  48. package/dist/components/admin/AgentChat.svelte.d.ts +17 -0
  49. package/dist/components/admin/AgentChat.svelte.d.ts.map +1 -0
  50. package/dist/components/admin/DocumentEditor.svelte +273 -60
  51. package/dist/components/admin/DocumentEditor.svelte.d.ts +6 -0
  52. package/dist/components/admin/DocumentEditor.svelte.d.ts.map +1 -1
  53. package/dist/components/admin/ObjectModal.svelte +4 -2
  54. package/dist/components/admin/fields/ArrayField.svelte +4 -1
  55. package/dist/components/admin/fields/ReferenceField.svelte +12 -1
  56. package/dist/components/admin/fields/ReferenceField.svelte.d.ts.map +1 -1
  57. package/dist/components/index.d.ts +1 -0
  58. package/dist/components/index.d.ts.map +1 -1
  59. package/dist/components/index.js +1 -0
  60. package/dist/components/layout/Sidebar.svelte +26 -2
  61. package/dist/components/layout/Sidebar.svelte.d.ts +1 -0
  62. package/dist/components/layout/Sidebar.svelte.d.ts.map +1 -1
  63. package/dist/db/interfaces/agent-change-sets.d.ts +17 -0
  64. package/dist/db/interfaces/agent-change-sets.d.ts.map +1 -0
  65. package/dist/db/interfaces/agent-change-sets.js +1 -0
  66. package/dist/db/interfaces/document.d.ts +25 -3
  67. package/dist/db/interfaces/document.d.ts.map +1 -1
  68. package/dist/db/interfaces/document.js +19 -1
  69. package/dist/db/interfaces/index.d.ts +4 -1
  70. package/dist/db/interfaces/index.d.ts.map +1 -1
  71. package/dist/db/interfaces/index.js +1 -1
  72. package/dist/document-refresh.svelte.d.ts +2 -0
  73. package/dist/document-refresh.svelte.d.ts.map +1 -1
  74. package/dist/document-refresh.svelte.js +15 -0
  75. package/dist/document-workspace-registry.svelte.d.ts +15 -0
  76. package/dist/document-workspace-registry.svelte.d.ts.map +1 -0
  77. package/dist/document-workspace-registry.svelte.js +30 -0
  78. package/dist/field-validation/utils.d.ts.map +1 -1
  79. package/dist/field-validation/utils.js +211 -29
  80. package/dist/hooks.d.ts +2 -0
  81. package/dist/hooks.d.ts.map +1 -1
  82. package/dist/hooks.js +2 -0
  83. package/dist/lib/agent-chat-state.svelte.d.ts +38 -0
  84. package/dist/lib/agent-chat-state.svelte.d.ts.map +1 -0
  85. package/dist/lib/agent-chat-state.svelte.js +87 -0
  86. package/dist/lib/agent-chat-state.svelte.js.map +1 -0
  87. package/dist/lib/ai/content-workspace-tools.d.ts +3 -0
  88. package/dist/lib/ai/content-workspace-tools.d.ts.map +1 -0
  89. package/dist/lib/ai/content-workspace-tools.js +51 -0
  90. package/dist/lib/ai/content-workspace-tools.js.map +1 -0
  91. package/dist/lib/ai/default-system-prompt.d.ts +2 -0
  92. package/dist/lib/ai/default-system-prompt.d.ts.map +1 -0
  93. package/dist/lib/ai/default-system-prompt.js +16 -0
  94. package/dist/lib/ai/default-system-prompt.js.map +1 -0
  95. package/dist/lib/ai/index.d.ts +2 -0
  96. package/dist/lib/ai/index.d.ts.map +1 -0
  97. package/dist/lib/ai/index.js +5 -0
  98. package/dist/lib/ai/index.js.map +1 -0
  99. package/dist/lib/ai/interfaces/ai-provider.d.ts +67 -0
  100. package/dist/lib/ai/interfaces/ai-provider.d.ts.map +1 -0
  101. package/dist/lib/ai/interfaces/ai-provider.js +17 -0
  102. package/dist/lib/ai/interfaces/ai-provider.js.map +1 -0
  103. package/dist/lib/ai/run-agent-turn.d.ts +29 -0
  104. package/dist/lib/ai/run-agent-turn.d.ts.map +1 -0
  105. package/dist/lib/ai/run-agent-turn.js +167 -0
  106. package/dist/lib/ai/run-agent-turn.js.map +1 -0
  107. package/dist/lib/api/agent-chat.d.ts +27 -0
  108. package/dist/lib/api/agent-chat.d.ts.map +1 -0
  109. package/dist/lib/api/agent-chat.js +65 -0
  110. package/dist/lib/api/agent-chat.js.map +1 -0
  111. package/dist/lib/api/documents.d.ts +8 -3
  112. package/dist/lib/api/documents.d.ts.map +1 -1
  113. package/dist/lib/api/documents.js +6 -6
  114. package/dist/lib/api/documents.js.map +1 -1
  115. package/dist/lib/api/schemas/agent-chat.d.ts +45 -0
  116. package/dist/lib/api/schemas/agent-chat.d.ts.map +1 -0
  117. package/dist/lib/api/schemas/agent-chat.js +36 -0
  118. package/dist/lib/api/schemas/agent-chat.js.map +1 -0
  119. package/dist/lib/api/schemas/agent-operations.d.ts +13 -0
  120. package/dist/lib/api/schemas/agent-operations.d.ts.map +1 -0
  121. package/dist/lib/api/schemas/agent-operations.js +16 -0
  122. package/dist/lib/api/schemas/agent-operations.js.map +1 -0
  123. package/dist/lib/api/schemas/documents.d.ts +22 -0
  124. package/dist/lib/api/schemas/documents.d.ts.map +1 -1
  125. package/dist/lib/api/schemas/documents.js +20 -2
  126. package/dist/lib/api/schemas/documents.js.map +1 -1
  127. package/dist/lib/chunk-recovery.d.ts +33 -0
  128. package/dist/lib/chunk-recovery.d.ts.map +1 -0
  129. package/dist/lib/chunk-recovery.js +132 -0
  130. package/dist/lib/chunk-recovery.js.map +1 -0
  131. package/dist/lib/client/index.d.ts +1 -0
  132. package/dist/lib/client/index.d.ts.map +1 -1
  133. package/dist/lib/client/index.js +1 -0
  134. package/dist/lib/client/index.js.map +1 -1
  135. package/dist/lib/client/ui.d.ts +1 -0
  136. package/dist/lib/client/ui.d.ts.map +1 -1
  137. package/dist/lib/client/ui.js +1 -0
  138. package/dist/lib/client/ui.js.map +1 -1
  139. package/dist/lib/components/index.d.ts +1 -0
  140. package/dist/lib/components/index.d.ts.map +1 -1
  141. package/dist/lib/components/index.js +1 -0
  142. package/dist/lib/components/index.js.map +1 -1
  143. package/dist/lib/db/interfaces/agent-change-sets.d.ts +17 -0
  144. package/dist/lib/db/interfaces/agent-change-sets.d.ts.map +1 -0
  145. package/dist/lib/db/interfaces/agent-change-sets.js +2 -0
  146. package/dist/lib/db/interfaces/agent-change-sets.js.map +1 -0
  147. package/dist/lib/db/interfaces/document.d.ts +25 -3
  148. package/dist/lib/db/interfaces/document.d.ts.map +1 -1
  149. package/dist/lib/db/interfaces/document.js +19 -1
  150. package/dist/lib/db/interfaces/document.js.map +1 -1
  151. package/dist/lib/db/interfaces/index.d.ts +4 -1
  152. package/dist/lib/db/interfaces/index.d.ts.map +1 -1
  153. package/dist/lib/db/interfaces/index.js +1 -1
  154. package/dist/lib/db/interfaces/index.js.map +1 -1
  155. package/dist/lib/document-refresh.svelte.d.ts +2 -0
  156. package/dist/lib/document-refresh.svelte.d.ts.map +1 -1
  157. package/dist/lib/document-refresh.svelte.js +15 -0
  158. package/dist/lib/document-refresh.svelte.js.map +1 -1
  159. package/dist/lib/document-workspace-registry.svelte.d.ts +15 -0
  160. package/dist/lib/document-workspace-registry.svelte.d.ts.map +1 -0
  161. package/dist/lib/document-workspace-registry.svelte.js +31 -0
  162. package/dist/lib/document-workspace-registry.svelte.js.map +1 -0
  163. package/dist/lib/field-validation/utils.d.ts.map +1 -1
  164. package/dist/lib/field-validation/utils.js +211 -29
  165. package/dist/lib/field-validation/utils.js.map +1 -1
  166. package/dist/lib/hooks.d.ts +2 -0
  167. package/dist/lib/hooks.d.ts.map +1 -1
  168. package/dist/lib/hooks.js +2 -0
  169. package/dist/lib/hooks.js.map +1 -1
  170. package/dist/lib/local-api/collection-api.d.ts +7 -2
  171. package/dist/lib/local-api/collection-api.d.ts.map +1 -1
  172. package/dist/lib/local-api/collection-api.js +17 -10
  173. package/dist/lib/local-api/collection-api.js.map +1 -1
  174. package/dist/lib/mcp/tools.d.ts +39 -4
  175. package/dist/lib/mcp/tools.d.ts.map +1 -1
  176. package/dist/lib/mcp/tools.js +546 -362
  177. package/dist/lib/mcp/tools.js.map +1 -1
  178. package/dist/lib/plugins/resolver.d.ts +8 -1
  179. package/dist/lib/plugins/resolver.d.ts.map +1 -1
  180. package/dist/lib/plugins/resolver.js +14 -1
  181. package/dist/lib/plugins/resolver.js.map +1 -1
  182. package/dist/lib/plugins/types.d.ts +23 -2
  183. package/dist/lib/plugins/types.d.ts.map +1 -1
  184. package/dist/lib/plugins/types.js.map +1 -1
  185. package/dist/lib/preview/stega.d.ts +2 -2
  186. package/dist/lib/preview/stega.d.ts.map +1 -1
  187. package/dist/lib/preview/stega.js +52 -29
  188. package/dist/lib/preview/stega.js.map +1 -1
  189. package/dist/lib/schema-utils/validator.d.ts.map +1 -1
  190. package/dist/lib/schema-utils/validator.js +5 -2
  191. package/dist/lib/schema-utils/validator.js.map +1 -1
  192. package/dist/lib/server/api/index.d.ts.map +1 -1
  193. package/dist/lib/server/api/index.js +8 -0
  194. package/dist/lib/server/api/index.js.map +1 -1
  195. package/dist/lib/server/api/routes/agent-change-sets.d.ts +4 -0
  196. package/dist/lib/server/api/routes/agent-change-sets.d.ts.map +1 -0
  197. package/dist/lib/server/api/routes/agent-change-sets.js +132 -0
  198. package/dist/lib/server/api/routes/agent-change-sets.js.map +1 -0
  199. package/dist/lib/server/api/routes/agent-chat.d.ts +14 -0
  200. package/dist/lib/server/api/routes/agent-chat.d.ts.map +1 -0
  201. package/dist/lib/server/api/routes/agent-chat.js +224 -0
  202. package/dist/lib/server/api/routes/agent-chat.js.map +1 -0
  203. package/dist/lib/server/api/routes/document-versions.d.ts.map +1 -1
  204. package/dist/lib/server/api/routes/document-versions.js +16 -2
  205. package/dist/lib/server/api/routes/document-versions.js.map +1 -1
  206. package/dist/lib/server/api/routes/documents-by-id.d.ts.map +1 -1
  207. package/dist/lib/server/api/routes/documents-by-id.js +11 -1
  208. package/dist/lib/server/api/routes/documents-by-id.js.map +1 -1
  209. package/dist/lib/server/api/routes/documents-publish.d.ts.map +1 -1
  210. package/dist/lib/server/api/routes/documents-publish.js +36 -3
  211. package/dist/lib/server/api/routes/documents-publish.js.map +1 -1
  212. package/dist/lib/server/api/routes/jobs.d.ts.map +1 -1
  213. package/dist/lib/server/api/routes/jobs.js +4 -2
  214. package/dist/lib/server/api/routes/jobs.js.map +1 -1
  215. package/dist/lib/server/api/routes/resolve-created-by.d.ts +13 -0
  216. package/dist/lib/server/api/routes/resolve-created-by.d.ts.map +1 -0
  217. package/dist/lib/server/api/routes/resolve-created-by.js +31 -0
  218. package/dist/lib/server/api/routes/resolve-created-by.js.map +1 -0
  219. package/dist/lib/server/index.d.ts +1 -0
  220. package/dist/lib/server/index.d.ts.map +1 -1
  221. package/dist/lib/server/index.js +1 -0
  222. package/dist/lib/server/index.js.map +1 -1
  223. package/dist/lib/services/references-service.d.ts +1 -1
  224. package/dist/lib/services/references-service.d.ts.map +1 -1
  225. package/dist/lib/services/version-service.d.ts +4 -4
  226. package/dist/lib/services/version-service.d.ts.map +1 -1
  227. package/dist/lib/services/version-service.js +15 -11
  228. package/dist/lib/services/version-service.js.map +1 -1
  229. package/dist/lib/type-gen.d.ts.map +1 -1
  230. package/dist/lib/type-gen.js +11 -0
  231. package/dist/lib/type-gen.js.map +1 -1
  232. package/dist/lib/types/agent-change-sets.d.ts +70 -0
  233. package/dist/lib/types/agent-change-sets.d.ts.map +1 -0
  234. package/dist/lib/types/agent-change-sets.js +7 -0
  235. package/dist/lib/types/agent-change-sets.js.map +1 -0
  236. package/dist/lib/types/agent-stream.d.ts +60 -0
  237. package/dist/lib/types/agent-stream.d.ts.map +1 -0
  238. package/dist/lib/types/agent-stream.js +12 -0
  239. package/dist/lib/types/agent-stream.js.map +1 -0
  240. package/dist/lib/types/agent-tools.d.ts +65 -0
  241. package/dist/lib/types/agent-tools.d.ts.map +1 -0
  242. package/dist/lib/types/agent-tools.js +2 -0
  243. package/dist/lib/types/agent-tools.js.map +1 -0
  244. package/dist/lib/types/config.d.ts +19 -0
  245. package/dist/lib/types/config.d.ts.map +1 -1
  246. package/dist/lib/types/document-workspace.d.ts +53 -0
  247. package/dist/lib/types/document-workspace.d.ts.map +1 -0
  248. package/dist/lib/types/document-workspace.js +15 -0
  249. package/dist/lib/types/document-workspace.js.map +1 -0
  250. package/dist/lib/types/document.d.ts +5 -0
  251. package/dist/lib/types/document.d.ts.map +1 -1
  252. package/dist/lib/types/index.d.ts +2 -0
  253. package/dist/lib/types/index.d.ts.map +1 -1
  254. package/dist/lib/types/index.js +2 -0
  255. package/dist/lib/types/index.js.map +1 -1
  256. package/dist/lib/types/schemas.js +1 -1
  257. package/dist/lib/types/schemas.js.map +1 -1
  258. package/dist/lib/utils/fetch-remote-file.d.ts +11 -0
  259. package/dist/lib/utils/fetch-remote-file.d.ts.map +1 -0
  260. package/dist/lib/utils/fetch-remote-file.js +169 -0
  261. package/dist/lib/utils/fetch-remote-file.js.map +1 -0
  262. package/dist/local-api/collection-api.d.ts +7 -2
  263. package/dist/local-api/collection-api.d.ts.map +1 -1
  264. package/dist/local-api/collection-api.js +17 -10
  265. package/dist/mcp/tools.d.ts +39 -4
  266. package/dist/mcp/tools.d.ts.map +1 -1
  267. package/dist/mcp/tools.js +546 -362
  268. package/dist/plugins/resolver.d.ts +8 -1
  269. package/dist/plugins/resolver.d.ts.map +1 -1
  270. package/dist/plugins/resolver.js +14 -1
  271. package/dist/plugins/types.d.ts +23 -2
  272. package/dist/plugins/types.d.ts.map +1 -1
  273. package/dist/preview/stega.d.ts +2 -2
  274. package/dist/preview/stega.d.ts.map +1 -1
  275. package/dist/preview/stega.js +52 -29
  276. package/dist/schema-utils/validator.d.ts.map +1 -1
  277. package/dist/schema-utils/validator.js +5 -2
  278. package/dist/server/api/index.d.ts.map +1 -1
  279. package/dist/server/api/index.js +8 -0
  280. package/dist/server/api/routes/agent-change-sets.d.ts +4 -0
  281. package/dist/server/api/routes/agent-change-sets.d.ts.map +1 -0
  282. package/dist/server/api/routes/agent-change-sets.js +131 -0
  283. package/dist/server/api/routes/agent-chat.d.ts +14 -0
  284. package/dist/server/api/routes/agent-chat.d.ts.map +1 -0
  285. package/dist/server/api/routes/agent-chat.js +223 -0
  286. package/dist/server/api/routes/document-versions.d.ts.map +1 -1
  287. package/dist/server/api/routes/document-versions.js +16 -2
  288. package/dist/server/api/routes/documents-by-id.d.ts.map +1 -1
  289. package/dist/server/api/routes/documents-by-id.js +11 -1
  290. package/dist/server/api/routes/documents-publish.d.ts.map +1 -1
  291. package/dist/server/api/routes/documents-publish.js +36 -3
  292. package/dist/server/api/routes/jobs.d.ts.map +1 -1
  293. package/dist/server/api/routes/jobs.js +4 -2
  294. package/dist/server/api/routes/resolve-created-by.d.ts +13 -0
  295. package/dist/server/api/routes/resolve-created-by.d.ts.map +1 -0
  296. package/dist/server/api/routes/resolve-created-by.js +30 -0
  297. package/dist/server/index.d.ts +1 -0
  298. package/dist/server/index.d.ts.map +1 -1
  299. package/dist/server/index.js +1 -0
  300. package/dist/services/references-service.d.ts +1 -1
  301. package/dist/services/references-service.d.ts.map +1 -1
  302. package/dist/services/version-service.d.ts +4 -4
  303. package/dist/services/version-service.d.ts.map +1 -1
  304. package/dist/services/version-service.js +15 -11
  305. package/dist/type-gen.d.ts.map +1 -1
  306. package/dist/type-gen.js +11 -0
  307. package/dist/types/agent-change-sets.d.ts +70 -0
  308. package/dist/types/agent-change-sets.d.ts.map +1 -0
  309. package/dist/types/agent-change-sets.js +6 -0
  310. package/dist/types/agent-stream.d.ts +60 -0
  311. package/dist/types/agent-stream.d.ts.map +1 -0
  312. package/dist/types/agent-stream.js +11 -0
  313. package/dist/types/agent-tools.d.ts +65 -0
  314. package/dist/types/agent-tools.d.ts.map +1 -0
  315. package/dist/types/agent-tools.js +1 -0
  316. package/dist/types/config.d.ts +19 -0
  317. package/dist/types/config.d.ts.map +1 -1
  318. package/dist/types/document-workspace.d.ts +53 -0
  319. package/dist/types/document-workspace.d.ts.map +1 -0
  320. package/dist/types/document-workspace.js +14 -0
  321. package/dist/types/document.d.ts +5 -0
  322. package/dist/types/document.d.ts.map +1 -1
  323. package/dist/types/index.d.ts +2 -0
  324. package/dist/types/index.d.ts.map +1 -1
  325. package/dist/types/index.js +2 -0
  326. package/dist/types/schemas.js +1 -1
  327. package/dist/utils/fetch-remote-file.d.ts +11 -0
  328. package/dist/utils/fetch-remote-file.d.ts.map +1 -0
  329. package/dist/utils/fetch-remote-file.js +168 -0
  330. package/package.json +5 -2
@@ -0,0 +1,223 @@
1
+ import { Hono } from 'hono';
2
+ import { streamSSE } from 'hono/streaming';
3
+ import { z } from 'zod';
4
+ import { agentChatRequest } from '../../../api/schemas/agent-chat.js';
5
+ import { recordWorkspaceOperationRequest } from '../../../api/schemas/agent-operations.js';
6
+ import { authToContext } from '../../../local-api/auth-helpers.js';
7
+ import { resolveAgentTools } from '../../../mcp/tools.js';
8
+ import { runAgentTurn } from '../../../ai/run-agent-turn.js';
9
+ import { DEFAULT_AGENT_SYSTEM_PROMPT } from '../../../ai/default-system-prompt.js';
10
+ import { cmsLogger } from '../../../utils/logger.js';
11
+ const SUMMARY_MAX_LENGTH = 200;
12
+ // A mutating tool's `data` is `unknown` at this boundary (AgentToolResult.data) — every tool
13
+ // returns a different shape (list_collections -> {collections}, upload_asset -> {asset, ...},
14
+ // document tools -> {document, validation}), and this route dispatches by name against
15
+ // whatever's registered, including plugin-contributed tools it's never seen at compile time.
16
+ // There's no static type to give it, so the id is pulled out via a runtime-validated parse
17
+ // (the same idiom `run-agent-turn.ts` already uses for tool arguments) rather than an `as`
18
+ // cast asserting a shape without checking it.
19
+ const toolResultWithDocumentId = z.object({ document: z.object({ id: z.string() }) });
20
+ /** `create_document` has no `id` argument (nothing to reference before it exists) — its new
21
+ * id only shows up in the result. Falls back to that when the args don't have one, so a
22
+ * create is still attributable to a document instead of being silently dropped. */
23
+ function resolveDocumentId(args, data) {
24
+ if (typeof args.id === 'string')
25
+ return args.id;
26
+ const parsed = toolResultWithDocumentId.safeParse(data);
27
+ return parsed.success ? parsed.data.document.id : null;
28
+ }
29
+ /**
30
+ * Best-effort audit record for one mutating tool call — looks up the two most recent
31
+ * document-version rows to capture `versionBefore`/`versionAfter` (the version an undo would
32
+ * restore to, and the one this write produced), then records the operation. Never throws: a
33
+ * failure to record the audit trail must never break the actual tool call or the SSE stream.
34
+ * Exported for unit testing — not part of the route's own public surface.
35
+ */
36
+ export async function recordMutatingOperation(aphexCMS, context, changeSetId, toolName, args, success, error, data) {
37
+ const collection = typeof args.collection === 'string' ? args.collection : null;
38
+ // `resolveDocumentId` checks `args.id` first, so a failed update_document/publish_document
39
+ // (which does have `args.id`) is still attributable — it only falls through to parsing
40
+ // `data` for the rare case for `create_document`, whose result won't parse on failure anyway.
41
+ const documentId = resolveDocumentId(args, data);
42
+ if (!collection || !documentId)
43
+ return;
44
+ try {
45
+ const { databaseAdapter, localAPI } = aphexCMS;
46
+ const { versions } = await localAPI.versionService.listVersions(databaseAdapter, context.organizationId, documentId, { limit: 2 });
47
+ const [versionAfter, versionBefore] = versions;
48
+ await databaseAdapter.recordOperation({
49
+ changeSetId,
50
+ organizationId: context.organizationId,
51
+ collection,
52
+ documentId,
53
+ toolName,
54
+ arguments: args,
55
+ success,
56
+ error,
57
+ versionBefore: versionBefore?.versionNumber ?? null,
58
+ versionAfter: versionAfter?.versionNumber ?? null
59
+ });
60
+ }
61
+ catch (err) {
62
+ cmsLogger.error('[agent-chat] failed to record agent operation (continuing):', err);
63
+ }
64
+ }
65
+ export const agentChatRouter = new Hono();
66
+ /**
67
+ * POST /api/agent/chat — the in-admin agent's streaming chat endpoint (Milestone 2 item 5
68
+ * of references/content-copilot-phase-1-plan.md). Stateless per call: the browser sends
69
+ * the full running conversation each time (conversation persistence is a separate,
70
+ * not-yet-built piece — see the plan). Requires an authenticated session; individual tool
71
+ * calls are separately capability-gated by `resolveAgentTools`/each tool's own checks —
72
+ * this endpoint is a dumb transport over the same tool-execution service MCP uses, not an
73
+ * additional authorization layer.
74
+ *
75
+ * 404s (rather than 401) when no `aiProvider` is configured, so the route doesn't exist as
76
+ * a surface at all on an instance that hasn't opted in — same "don't advertise an unset
77
+ * feature" posture as `workers-run.ts`'s worker secret gate.
78
+ *
79
+ * Also records an audit/undo trail (`cms_agent_change_sets`/`cms_agent_operations`): a
80
+ * change-set row is created eagerly for every turn (so token usage is captured even for a
81
+ * pure Q&A turn with no mutations), and every mutating tool call gets an operation row. All
82
+ * of this recording is best-effort — see `recordMutatingOperation` above and the try/catch
83
+ * around change-set creation/completion below — a failure here must never break the chat.
84
+ */
85
+ agentChatRouter.post('/chat', async (c) => {
86
+ const { aphexCMS, auth } = c.var;
87
+ const { aiProvider, agentModel } = aphexCMS.config;
88
+ const { databaseAdapter } = aphexCMS;
89
+ if (!aiProvider)
90
+ return c.json({ success: false, error: 'Not found' }, 404);
91
+ if (!auth)
92
+ return c.json({ success: false, error: 'Unauthorized' }, 401);
93
+ if (!agentModel) {
94
+ return c.json({ success: false, error: 'Server misconfigured: no agentModel configured for aiProvider' }, 501);
95
+ }
96
+ const body = await c.req.json().catch(() => null);
97
+ const parsed = agentChatRequest.safeParse(body);
98
+ if (!parsed.success) {
99
+ return c.json({ success: false, error: 'Invalid request', issues: parsed.error.issues }, 400);
100
+ }
101
+ const context = authToContext(auth);
102
+ const tools = resolveAgentTools({ aphexCMS, context }, { documentContext: parsed.data.documentContext });
103
+ const toolsByName = new Map(tools.map((t) => [t.definition.name, t]));
104
+ const model = parsed.data.model ?? agentModel;
105
+ const abortController = new AbortController();
106
+ // A turn paused for a workspace tool resumes as a brand-new HTTP request (see
107
+ // `types/agent-stream.ts`) — the client echoes back the change-set id from the leg that
108
+ // paused so every leg of one logical turn records against the same row, instead of a
109
+ // fresh `cms_agent_change_sets` row per leg.
110
+ let changeSetId = parsed.data.changeSetId ?? null;
111
+ if (!changeSetId) {
112
+ try {
113
+ const firstUserMessage = parsed.data.messages.find((m) => m.role === 'user')?.content ?? '';
114
+ const changeSet = await databaseAdapter.createChangeSet({
115
+ organizationId: context.organizationId,
116
+ createdBy: context.user?.id ?? null,
117
+ summary: firstUserMessage.slice(0, SUMMARY_MAX_LENGTH) || null,
118
+ provider: aiProvider.name,
119
+ model
120
+ });
121
+ changeSetId = changeSet.id;
122
+ }
123
+ catch (err) {
124
+ cmsLogger.error('[agent-chat] failed to create change-set (continuing without one):', err);
125
+ }
126
+ }
127
+ return streamSSE(c, async (stream) => {
128
+ c.req.raw.signal.addEventListener('abort', () => abortController.abort());
129
+ let promptTokens = parsed.data.priorUsage?.promptTokens ?? 0;
130
+ let completionTokens = parsed.data.priorUsage?.completionTokens ?? 0;
131
+ let turnFailed = false;
132
+ let turnPaused = false;
133
+ // toolCall args aren't repeated on the matching toolResult event, so stash them here
134
+ // to correlate the two when it's time to record a mutating operation.
135
+ const argsByToolCallId = new Map();
136
+ try {
137
+ for await (const event of runAgentTurn({
138
+ aiProvider,
139
+ model,
140
+ messages: parsed.data.messages,
141
+ tools,
142
+ toolContext: { aphexCMS, context },
143
+ systemPrompt: aphexCMS.config.agentSystemPrompt ?? DEFAULT_AGENT_SYSTEM_PROMPT,
144
+ signal: abortController.signal
145
+ })) {
146
+ if (event.type === 'usage') {
147
+ promptTokens += event.promptTokens;
148
+ completionTokens += event.completionTokens;
149
+ }
150
+ else if (event.type === 'toolCall') {
151
+ argsByToolCallId.set(event.toolCallId, event.arguments);
152
+ }
153
+ else if (event.type === 'toolResult' && changeSetId) {
154
+ const tool = toolsByName.get(event.name);
155
+ const args = argsByToolCallId.get(event.toolCallId);
156
+ if (tool?.definition.mutates && args) {
157
+ await recordMutatingOperation(aphexCMS, context, changeSetId, event.name, args, event.success, event.error, event.data);
158
+ }
159
+ }
160
+ else if (event.type === 'error') {
161
+ turnFailed = true;
162
+ }
163
+ if (event.type === 'done') {
164
+ event.changeSetId = changeSetId;
165
+ if (event.finishReason === 'awaiting_workspace_tool')
166
+ turnPaused = true;
167
+ }
168
+ await stream.writeSSE({ data: JSON.stringify(event) });
169
+ }
170
+ }
171
+ catch (err) {
172
+ turnFailed = true;
173
+ cmsLogger.error('[agent-chat] turn failed:', err);
174
+ await stream.writeSSE({
175
+ data: JSON.stringify({
176
+ type: 'error',
177
+ message: err instanceof Error ? err.message : 'Unknown error'
178
+ })
179
+ });
180
+ }
181
+ finally {
182
+ // A paused leg isn't the end of the turn — the client resumes with the same
183
+ // changeSetId, and only the truly-terminal leg should mark the change-set complete.
184
+ if (changeSetId && !turnPaused) {
185
+ try {
186
+ await databaseAdapter.completeChangeSet(context.organizationId, changeSetId, {
187
+ status: turnFailed ? 'failed' : 'completed',
188
+ promptTokens,
189
+ completionTokens
190
+ });
191
+ }
192
+ catch (err) {
193
+ cmsLogger.error('[agent-chat] failed to complete change-set (continuing):', err);
194
+ }
195
+ }
196
+ }
197
+ });
198
+ });
199
+ /**
200
+ * POST /api/agent/operations — records an audit/undo row for a tool the *client* executed
201
+ * against a live `DocumentWorkspace` (the `content_patch_fields`/`content_save_draft` bridge
202
+ * tools — see `ai/content-workspace-tools.ts`). `agent-chat.ts`'s own `recordMutatingOperation`
203
+ * call above only sees tool results resolved server-side inside `runAgentTurn`'s loop, so a
204
+ * workspace tool's result — resolved client-side after a paused turn — needs this separate
205
+ * path to reach the same `cms_agent_change_sets`/`cms_agent_operations` tables. Same
206
+ * session-authenticated posture as `/chat`; not itself a mutation of document data (the
207
+ * client already made that call directly via the normal document API), purely bookkeeping.
208
+ */
209
+ agentChatRouter.post('/operations', async (c) => {
210
+ const { aphexCMS, auth } = c.var;
211
+ if (!aphexCMS.config.aiProvider)
212
+ return c.json({ success: false, error: 'Not found' }, 404);
213
+ if (!auth)
214
+ return c.json({ success: false, error: 'Unauthorized' }, 401);
215
+ const body = await c.req.json().catch(() => null);
216
+ const parsed = recordWorkspaceOperationRequest.safeParse(body);
217
+ if (!parsed.success) {
218
+ return c.json({ success: false, error: 'Invalid request', issues: parsed.error.issues }, 400);
219
+ }
220
+ const context = authToContext(auth);
221
+ await recordMutatingOperation(aphexCMS, context, parsed.data.changeSetId, parsed.data.toolName, { collection: parsed.data.collection, id: parsed.data.id, ...parsed.data.arguments }, parsed.data.success, parsed.data.error, parsed.data.data);
222
+ return c.json({ success: true });
223
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"document-versions.d.ts","sourceRoot":"","sources":["../../../../src/lib/server/api/routes/document-versions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAM5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC,eAAO,MAAM,sBAAsB,EAAE,IAAI,CAAC,QAAQ,CAiK/C,CAAC"}
1
+ {"version":3,"file":"document-versions.d.ts","sourceRoot":"","sources":["../../../../src/lib/server/api/routes/document-versions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAO5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC,eAAO,MAAM,sBAAsB,EAAE,IAAI,CAAC,QAAQ,CAsL/C,CAAC"}
@@ -3,7 +3,8 @@ import { zValidator } from '@hono/zod-validator';
3
3
  import { authToContext } from '../../../local-api/auth-helpers.js';
4
4
  import { cmsLogger } from '../../../utils/logger.js';
5
5
  import { hasCapability } from '../../../types/capabilities.js';
6
- import { listVersionsQuery } from '../../../api/schemas/documents.js';
6
+ import { RevisionConflictError } from '../../../db/interfaces/index.js';
7
+ import { listVersionsQuery, restoreVersionRequest } from '../../../api/schemas/documents.js';
7
8
  export const documentVersionsRouter = new Hono()
8
9
  .get('/:id/versions', zValidator('query', listVersionsQuery, (result, c) => {
9
10
  if (!result.success) {
@@ -103,7 +104,12 @@ export const documentVersionsRouter = new Hono()
103
104
  if (isNaN(versionNumber)) {
104
105
  return c.json({ success: false, error: 'Version must be a number' }, 400);
105
106
  }
106
- const document = await localAPI.versionService.restoreVersion(databaseAdapter, context.organizationId, id, versionNumber, context.user?.id);
107
+ const body = await c.req.json().catch(() => ({}));
108
+ const parsed = restoreVersionRequest.safeParse(body);
109
+ if (!parsed.success) {
110
+ return c.json({ success: false, error: 'Invalid request', issues: parsed.error.issues }, 400);
111
+ }
112
+ const document = await localAPI.versionService.restoreVersion(databaseAdapter, context.organizationId, id, versionNumber, context.user?.id, parsed.data.expectedRevision);
107
113
  if (!document) {
108
114
  return c.json({ success: false, error: 'Version not found or restore failed' }, 404);
109
115
  }
@@ -115,6 +121,14 @@ export const documentVersionsRouter = new Hono()
115
121
  }
116
122
  catch (error) {
117
123
  cmsLogger.error('Failed to restore document version:', error);
124
+ if (error instanceof RevisionConflictError) {
125
+ return c.json({
126
+ success: false,
127
+ error: 'Conflict',
128
+ message: error.message,
129
+ currentRevision: error.currentRevision
130
+ }, 409);
131
+ }
118
132
  return c.json({ success: false, error: 'Failed to restore version' }, 500);
119
133
  }
120
134
  });
@@ -1 +1 @@
1
- {"version":3,"file":"documents-by-id.d.ts","sourceRoot":"","sources":["../../../../src/lib/server/api/routes/documents-by-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAO5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC,eAAO,MAAM,mBAAmB,EAAE,IAAI,CAAC,QAAQ,CAqN5C,CAAC"}
1
+ {"version":3,"file":"documents-by-id.d.ts","sourceRoot":"","sources":["../../../../src/lib/server/api/routes/documents-by-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAQ5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC,eAAO,MAAM,mBAAmB,EAAE,IAAI,CAAC,QAAQ,CAiO5C,CAAC"}
@@ -3,6 +3,7 @@ import { zValidator } from '@hono/zod-validator';
3
3
  import { authToContext } from '../../../local-api/auth-helpers.js';
4
4
  import { PermissionError } from '../../../local-api/permissions.js';
5
5
  import { SingletonOperationError } from '../../../local-api/collection-api.js';
6
+ import { RevisionConflictError } from '../../../db/interfaces/index.js';
6
7
  import { cmsLogger } from '../../../utils/logger.js';
7
8
  import { updateDocumentRequest } from '../../../api/schemas/documents.js';
8
9
  export const documentsByIdRouter = new Hono()
@@ -82,7 +83,8 @@ export const documentsByIdRouter = new Hono()
82
83
  }, 400);
83
84
  }
84
85
  const result = await collection.update(context, id, documentData, {
85
- publish: shouldPublish
86
+ publish: shouldPublish,
87
+ expectedRevision: parsed.expectedRevision
86
88
  });
87
89
  if (!result) {
88
90
  return c.json({ success: false, error: 'Document not found' }, 404);
@@ -98,6 +100,14 @@ export const documentsByIdRouter = new Hono()
98
100
  if (error instanceof PermissionError) {
99
101
  return c.json({ success: false, error: 'Forbidden', message: error.message }, 403);
100
102
  }
103
+ if (error instanceof RevisionConflictError) {
104
+ return c.json({
105
+ success: false,
106
+ error: 'Conflict',
107
+ message: error.message,
108
+ currentRevision: error.currentRevision
109
+ }, 409);
110
+ }
101
111
  if (error instanceof Error && error.message.includes('validation errors')) {
102
112
  return c.json({ success: false, error: 'Validation failed', message: error.message }, 400);
103
113
  }
@@ -1 +1 @@
1
- {"version":3,"file":"documents-publish.d.ts","sourceRoot":"","sources":["../../../../src/lib/server/api/routes/documents-publish.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAK5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC,eAAO,MAAM,sBAAsB,EAAE,IAAI,CAAC,QAAQ,CAoR/C,CAAC"}
1
+ {"version":3,"file":"documents-publish.d.ts","sourceRoot":"","sources":["../../../../src/lib/server/api/routes/documents-publish.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAU5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC,eAAO,MAAM,sBAAsB,EAAE,IAAI,CAAC,QAAQ,CAkU/C,CAAC"}
@@ -1,8 +1,9 @@
1
1
  import { Hono } from 'hono';
2
2
  import { authToContext } from '../../../local-api/auth-helpers.js';
3
3
  import { PermissionError } from '../../../local-api/permissions.js';
4
+ import { RevisionConflictError } from '../../../db/interfaces/index.js';
4
5
  import { cmsLogger } from '../../../utils/logger.js';
5
- import { scheduleDocumentRequest } from '../../../api/schemas/documents.js';
6
+ import { scheduleDocumentRequest, publishDocumentRequest, unpublishDocumentRequest } from '../../../api/schemas/documents.js';
6
7
  export const documentsPublishRouter = new Hono()
7
8
  .post('/:id/schedule', async (c) => {
8
9
  try {
@@ -141,7 +142,16 @@ export const documentsPublishRouter = new Hono()
141
142
  message: `Collection '${found.type}' not found`
142
143
  }, 400);
143
144
  }
144
- const publishedDocument = await collection.publish(context, id);
145
+ // Body is optional — a bare POST with no body is still valid, only
146
+ // carries the CAS guard when the caller has one to send.
147
+ const body = await c.req.json().catch(() => ({}));
148
+ const parsed = publishDocumentRequest.safeParse(body);
149
+ if (!parsed.success) {
150
+ return c.json({ success: false, error: 'Invalid request', issues: parsed.error.issues }, 400);
151
+ }
152
+ const publishedDocument = await collection.publish(context, id, {
153
+ expectedRevision: parsed.data.expectedRevision
154
+ });
145
155
  if (!publishedDocument) {
146
156
  return c.json({
147
157
  success: false,
@@ -160,6 +170,14 @@ export const documentsPublishRouter = new Hono()
160
170
  if (error instanceof PermissionError) {
161
171
  return c.json({ success: false, error: 'Forbidden', message: error.message }, 403);
162
172
  }
173
+ if (error instanceof RevisionConflictError) {
174
+ return c.json({
175
+ success: false,
176
+ error: 'Conflict',
177
+ message: error.message,
178
+ currentRevision: error.currentRevision
179
+ }, 409);
180
+ }
163
181
  if (error instanceof Error && error.message.includes('validation errors')) {
164
182
  return c.json({
165
183
  success: false,
@@ -202,7 +220,14 @@ export const documentsPublishRouter = new Hono()
202
220
  message: `Collection '${found.type}' not found`
203
221
  }, 400);
204
222
  }
205
- const unpublishedDocument = await collection.unpublish(context, id);
223
+ const body = await c.req.json().catch(() => ({}));
224
+ const parsed = unpublishDocumentRequest.safeParse(body);
225
+ if (!parsed.success) {
226
+ return c.json({ success: false, error: 'Invalid request', issues: parsed.error.issues }, 400);
227
+ }
228
+ const unpublishedDocument = await collection.unpublish(context, id, {
229
+ expectedRevision: parsed.data.expectedRevision
230
+ });
206
231
  if (!unpublishedDocument) {
207
232
  return c.json({
208
233
  success: false,
@@ -221,6 +246,14 @@ export const documentsPublishRouter = new Hono()
221
246
  if (error instanceof PermissionError) {
222
247
  return c.json({ success: false, error: 'Forbidden', message: error.message }, 403);
223
248
  }
249
+ if (error instanceof RevisionConflictError) {
250
+ return c.json({
251
+ success: false,
252
+ error: 'Conflict',
253
+ message: error.message,
254
+ currentRevision: error.currentRevision
255
+ }, 409);
256
+ }
224
257
  return c.json({
225
258
  success: false,
226
259
  error: 'Failed to unpublish document',
@@ -1 +1 @@
1
- {"version":3,"file":"jobs.d.ts","sourceRoot":"","sources":["../../../../src/lib/server/api/routes/jobs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAK5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AA4CzC,eAAO,MAAM,UAAU,EAAE,IAAI,CAAC,QAAQ,CAiDnC,CAAC"}
1
+ {"version":3,"file":"jobs.d.ts","sourceRoot":"","sources":["../../../../src/lib/server/api/routes/jobs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAK5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AA6CzC,eAAO,MAAM,UAAU,EAAE,IAAI,CAAC,QAAQ,CAkDnC,CAAC"}
@@ -3,6 +3,7 @@ import { z } from 'zod';
3
3
  import { authToContext } from '../../../local-api/auth-helpers.js';
4
4
  import { hasCapability } from '../../../types/capabilities.js';
5
5
  import { cmsLogger } from '../../../utils/logger.js';
6
+ import { withCreatedByNames } from './resolve-created-by.js';
6
7
  // Read-only history / observability for the durable spine. Gated on `document.read`:
7
8
  // the operational log of publish work is visible to anyone who can read content.
8
9
  // Mirrors JobStatus (types/events) as a zod enum — the API contract's source of truth.
@@ -76,14 +77,15 @@ export const jobsRouter = new Hono()
76
77
  return c.json({ success: false, error: 'Invalid query', issues: parsed.error.issues }, 400);
77
78
  }
78
79
  const { type, limit, offset } = parsed.data;
79
- const { databaseAdapter } = c.var.aphexCMS;
80
+ const { databaseAdapter, auth } = c.var.aphexCMS;
80
81
  const page = await databaseAdapter.listEvents({
81
82
  organizationId: gate.organizationId,
82
83
  type,
83
84
  limit,
84
85
  offset
85
86
  });
86
- return c.json({ success: true, data: page.items, pagination: toPagination(page) });
87
+ const items = await withCreatedByNames(page.items, auth);
88
+ return c.json({ success: true, data: items, pagination: toPagination(page) });
87
89
  }
88
90
  catch (error) {
89
91
  cmsLogger.error('Failed to list events:', error);
@@ -0,0 +1,13 @@
1
+ import type { AuthProvider } from '../../../auth/provider.js';
2
+ /**
3
+ * Resolves `createdBy` user ids on a list of rows to a display name (or `'API Key'` for a
4
+ * `apikey:<id>` synthetic id — see `authToContext`'s API-key branch). Shared by every
5
+ * read-only history view (`GET /api/events`, `GET /api/agent/change-sets`, ...) — an audit
6
+ * trail whose whole point is accountability needs to show *who*, not a raw id.
7
+ */
8
+ export declare function withCreatedByNames<T extends {
9
+ createdBy: string | null;
10
+ }>(rows: T[], auth: AuthProvider | undefined): Promise<Array<T & {
11
+ createdByName: string | null;
12
+ }>>;
13
+ //# sourceMappingURL=resolve-created-by.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-created-by.d.ts","sourceRoot":"","sources":["../../../../src/lib/server/api/routes/resolve-created-by.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,SAAS;IAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAC9E,IAAI,EAAE,CAAC,EAAE,EACT,IAAI,EAAE,YAAY,GAAG,SAAS,GAC5B,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG;IAAE,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAAC,CAyBtD"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Resolves `createdBy` user ids on a list of rows to a display name (or `'API Key'` for a
3
+ * `apikey:<id>` synthetic id — see `authToContext`'s API-key branch). Shared by every
4
+ * read-only history view (`GET /api/events`, `GET /api/agent/change-sets`, ...) — an audit
5
+ * trail whose whole point is accountability needs to show *who*, not a raw id.
6
+ */
7
+ export async function withCreatedByNames(rows, auth) {
8
+ const userIds = [...new Set(rows.map((r) => r.createdBy).filter((id) => !!id))];
9
+ const userMap = new Map();
10
+ if (userIds.length > 0 && auth) {
11
+ await Promise.all(userIds.map(async (userId) => {
12
+ if (userId.startsWith('apikey:')) {
13
+ userMap.set(userId, 'API Key');
14
+ return;
15
+ }
16
+ try {
17
+ const user = await auth.getUserById(userId);
18
+ if (user)
19
+ userMap.set(userId, user.name || user.email);
20
+ }
21
+ catch {
22
+ // User not found — skip
23
+ }
24
+ }));
25
+ }
26
+ return rows.map((r) => ({
27
+ ...r,
28
+ createdByName: r.createdBy ? (userMap.get(r.createdBy) ?? null) : null
29
+ }));
30
+ }
@@ -2,6 +2,7 @@ export * from '../types/index.js';
2
2
  export * from '../auth/provider.js';
3
3
  export * from '../cache/index.js';
4
4
  export * from '../email/index.js';
5
+ export * from '../ai/index.js';
5
6
  export { AuthError, type AuthErrorCode } from '../auth/auth-errors.js';
6
7
  export { createCMSConfig } from '../config.js';
7
8
  export { cmsLogger, setLogger, type Logger } from '../utils/logger.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/server/index.ts"],"names":[],"mappings":"AAIA,cAAc,gBAAgB,CAAC;AAE/B,cAAc,kBAAkB,CAAC;AAEjC,cAAc,gBAAgB,CAAC;AAE/B,cAAc,gBAAgB,CAAC;AAG/B,OAAO,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGpE,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAG5C,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAGpE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,OAAO,EAAE,aAAa,EAAE,0BAA0B,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAGxF,cAAc,wBAAwB,CAAC;AAGvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAG7C,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAKzD,OAAO,EAAE,GAAG,IAAI,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAI5D,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,aAAa,EAAE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAG/F,cAAc,eAAe,CAAC;AAG9B,cAAc,uBAAuB,CAAC;AAGtC,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAGhE,OAAO,EAAE,qBAAqB,EAAE,KAAK,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAGxF,OAAO,EACN,oBAAoB,EACpB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACN,QAAQ,EACR,cAAc,EACd,WAAW,EACX,aAAa,EACb,uBAAuB,EACvB,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,WAAW,EACX,aAAa,EACb,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EACN,iBAAiB,EACjB,KAAK,OAAO,EACZ,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/server/index.ts"],"names":[],"mappings":"AAIA,cAAc,gBAAgB,CAAC;AAE/B,cAAc,kBAAkB,CAAC;AAEjC,cAAc,gBAAgB,CAAC;AAE/B,cAAc,gBAAgB,CAAC;AAE/B,cAAc,aAAa,CAAC;AAG5B,OAAO,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGpE,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAG5C,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAGpE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,OAAO,EAAE,aAAa,EAAE,0BAA0B,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAGxF,cAAc,wBAAwB,CAAC;AAGvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAG7C,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAKzD,OAAO,EAAE,GAAG,IAAI,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAI5D,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,aAAa,EAAE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAG/F,cAAc,eAAe,CAAC;AAG9B,cAAc,uBAAuB,CAAC;AAGtC,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAGhE,OAAO,EAAE,qBAAqB,EAAE,KAAK,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAGxF,OAAO,EACN,oBAAoB,EACpB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACN,QAAQ,EACR,cAAc,EACd,WAAW,EACX,aAAa,EACb,uBAAuB,EACvB,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,WAAW,EACX,aAAa,EACb,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EACN,iBAAiB,EACjB,KAAK,OAAO,EACZ,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,MAAM,cAAc,CAAC"}
@@ -5,6 +5,7 @@ export * from '../types/index.js';
5
5
  export * from '../auth/provider.js';
6
6
  export * from '../cache/index.js';
7
7
  export * from '../email/index.js';
8
+ export * from '../ai/index.js';
8
9
  // Authentication errors
9
10
  export { AuthError } from '../auth/auth-errors.js';
10
11
  // Configuration system
@@ -1,4 +1,4 @@
1
- import type { DatabaseAdapter } from '../db/interfaces.js';
1
+ import type { DatabaseAdapter } from '../db/interfaces/index.js';
2
2
  import type { SchemaType } from '../types/schemas.js';
3
3
  /**
4
4
  * Maintains the back-reference index. After every doc save the collection-API
@@ -1 +1 @@
1
- {"version":3,"file":"references-service.d.ts","sourceRoot":"","sources":["../../src/lib/services/references-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD;;;;;;;;;GASG;AACH,qBAAa,iBAAiB;IACjB,OAAO,CAAC,eAAe;gBAAf,eAAe,EAAE,eAAe;IAEpD;;;OAGG;IACG,iBAAiB,CACtB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,UAAU,GAAG,IAAI,EACzB,QAAQ,EAAE,UAAU,EAAE,GACpB,OAAO,CAAC,IAAI,CAAC;IAShB;;;;;;;OAOG;IACG,eAAe,CACpB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,UAAU,EAAE,EACrB,gBAAgB,EAAE,MAAM,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC,GACjF,OAAO,CAAC,IAAI,CAAC;CAwBhB"}
1
+ {"version":3,"file":"references-service.d.ts","sourceRoot":"","sources":["../../src/lib/services/references-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD;;;;;;;;;GASG;AACH,qBAAa,iBAAiB;IACjB,OAAO,CAAC,eAAe;gBAAf,eAAe,EAAE,eAAe;IAEpD;;;OAGG;IACG,iBAAiB,CACtB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,UAAU,GAAG,IAAI,EACzB,QAAQ,EAAE,UAAU,EAAE,GACpB,OAAO,CAAC,IAAI,CAAC;IAShB;;;;;;;OAOG;IACG,eAAe,CACpB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,UAAU,EAAE,EACrB,gBAAgB,EAAE,MAAM,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC,GACjF,OAAO,CAAC,IAAI,CAAC;CAwBhB"}
@@ -27,7 +27,7 @@ export declare class VersionService {
27
27
  * Publish + snapshot on an already-transactional adapter. Caller owns the tx
28
28
  * and retention. Returns the published document (or null if publish was a no-op).
29
29
  */
30
- publishTx(tx: DatabaseAdapter, organizationId: string, documentId: string): Promise<Document | null>;
30
+ publishTx(tx: DatabaseAdapter, organizationId: string, documentId: string, expectedRevision?: number): Promise<Document | null>;
31
31
  /**
32
32
  * Public retention trigger for callers that manage their own transaction and
33
33
  * therefore can't rely on `saveWithVersion`/`publishWithVersion` to run it.
@@ -36,15 +36,15 @@ export declare class VersionService {
36
36
  /**
37
37
  * Save draft and create version atomically using adapter transaction.
38
38
  */
39
- saveWithVersion(db: DatabaseAdapter, organizationId: string, documentId: string, data: any, userId?: string): Promise<Document | null>;
39
+ saveWithVersion(db: DatabaseAdapter, organizationId: string, documentId: string, data: any, userId?: string, expectedRevision?: number): Promise<Document | null>;
40
40
  /**
41
41
  * Publish and create version.
42
42
  */
43
- publishWithVersion(db: DatabaseAdapter, organizationId: string, documentId: string): Promise<Document | null>;
43
+ publishWithVersion(db: DatabaseAdapter, organizationId: string, documentId: string, expectedRevision?: number): Promise<Document | null>;
44
44
  /**
45
45
  * Restore a version to draft. Creates a 'draft' version entry.
46
46
  */
47
- restoreVersion(db: DatabaseAdapter, organizationId: string, documentId: string, versionNumber: number, userId?: string): Promise<Document | null>;
47
+ restoreVersion(db: DatabaseAdapter, organizationId: string, documentId: string, versionNumber: number, userId?: string, expectedRevision?: number): Promise<Document | null>;
48
48
  listVersions(db: DatabaseAdapter, organizationId: string, documentId: string, options?: {
49
49
  limit?: number;
50
50
  offset?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"version-service.d.ts","sourceRoot":"","sources":["../../src/lib/services/version-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAG7E;;;;;;GAMG;AACH,qBAAa,cAAc;IAC1B,OAAO,CAAC,WAAW,CAAS;gBAEhB,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE;IAI9C;;OAEG;IACG,aAAa,CAClB,EAAE,EAAE,eAAe,EACnB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,OAAO,GAAG,SAAS,EAC9B,IAAI,EAAE,GAAG,EACT,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GACpB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAelC;;;;OAIG;IACG,UAAU,CACf,EAAE,EAAE,eAAe,EACnB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,OAAO,GAAG,SAAS,EAC9B,IAAI,EAAE,GAAG,EACT,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GACpB,OAAO,CAAC,IAAI,CAAC;IAWhB;;;OAGG;IACG,SAAS,CACd,EAAE,EAAE,eAAe,EACnB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,GAChB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAoB3B;;;OAGG;IACG,mBAAmB,CACxB,EAAE,EAAE,eAAe,EACnB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IAIhB;;OAEG;IACG,eAAe,CACpB,EAAE,EAAE,eAAe,EACnB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,GAAG,EACT,MAAM,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAgB3B;;OAEG;IACG,kBAAkB,CACvB,EAAE,EAAE,eAAe,EACnB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,GAChB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAiB3B;;OAEG;IACG,cAAc,CACnB,EAAE,EAAE,eAAe,EACnB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,MAAM,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IA0BrB,YAAY,CACjB,EAAE,EAAE,eAAe,EACnB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3C,OAAO,CAAC,mBAAmB,CAAC;IAKzB,UAAU,CACf,EAAE,EAAE,eAAe,EACnB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,GACnB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;YAKpB,gBAAgB;CAuB9B"}
1
+ {"version":3,"file":"version-service.d.ts","sourceRoot":"","sources":["../../src/lib/services/version-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAG7E;;;;;;GAMG;AACH,qBAAa,cAAc;IAC1B,OAAO,CAAC,WAAW,CAAS;gBAEhB,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE;IAI9C;;OAEG;IACG,aAAa,CAClB,EAAE,EAAE,eAAe,EACnB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,OAAO,GAAG,SAAS,EAC9B,IAAI,EAAE,GAAG,EACT,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GACpB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAelC;;;;OAIG;IACG,UAAU,CACf,EAAE,EAAE,eAAe,EACnB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,OAAO,GAAG,SAAS,EAC9B,IAAI,EAAE,GAAG,EACT,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GACpB,OAAO,CAAC,IAAI,CAAC;IAWhB;;;OAGG;IACG,SAAS,CACd,EAAE,EAAE,eAAe,EACnB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,gBAAgB,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAoB3B;;;OAGG;IACG,mBAAmB,CACxB,EAAE,EAAE,eAAe,EACnB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IAIhB;;OAEG;IACG,eAAe,CACpB,EAAE,EAAE,eAAe,EACnB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,GAAG,EACT,MAAM,CAAC,EAAE,MAAM,EACf,gBAAgB,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAwB3B;;OAEG;IACG,kBAAkB,CACvB,EAAE,EAAE,eAAe,EACnB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,gBAAgB,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAiB3B;;OAEG;IACG,cAAc,CACnB,EAAE,EAAE,eAAe,EACnB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,MAAM,CAAC,EAAE,MAAM,EACf,gBAAgB,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IA6BrB,YAAY,CACjB,EAAE,EAAE,eAAe,EACnB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3C,OAAO,CAAC,mBAAmB,CAAC;IAKzB,UAAU,CACf,EAAE,EAAE,eAAe,EACnB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,GACnB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;YAKpB,gBAAgB;CAuB9B"}
@@ -47,8 +47,8 @@ export class VersionService {
47
47
  * Publish + snapshot on an already-transactional adapter. Caller owns the tx
48
48
  * and retention. Returns the published document (or null if publish was a no-op).
49
49
  */
50
- async publishTx(tx, organizationId, documentId) {
51
- const result = await tx.publishDoc(organizationId, documentId);
50
+ async publishTx(tx, organizationId, documentId, expectedRevision) {
51
+ const result = await tx.publishDoc(organizationId, documentId, expectedRevision);
52
52
  if (result) {
53
53
  await this.snapshotTx(tx, organizationId, documentId, 'publish', result.publishedData, result.updatedBy);
54
54
  // Transactional outbox: record the durable fact in the same tx as the publish, so
@@ -69,13 +69,15 @@ export class VersionService {
69
69
  /**
70
70
  * Save draft and create version atomically using adapter transaction.
71
71
  */
72
- async saveWithVersion(db, organizationId, documentId, data, userId) {
72
+ async saveWithVersion(db, organizationId, documentId, data, userId, expectedRevision) {
73
73
  // No versioning support: a single write, atomic on its own.
74
74
  if (!db.createDocumentVersion) {
75
- return db.updateDocDraft(organizationId, documentId, data, userId);
75
+ return db.updateDocDraft(organizationId, documentId, data, userId, expectedRevision);
76
76
  }
77
+ // RevisionConflictError propagates out of the transaction un-swallowed —
78
+ // callers must surface it distinctly from a validation error.
77
79
  const updated = await db.withTransaction(async (txAdapter) => {
78
- const result = await txAdapter.updateDocDraft(organizationId, documentId, data, userId);
80
+ const result = await txAdapter.updateDocDraft(organizationId, documentId, data, userId, expectedRevision);
79
81
  if (result)
80
82
  await this.snapshotTx(txAdapter, organizationId, documentId, 'draft', data, userId);
81
83
  return result;
@@ -87,15 +89,15 @@ export class VersionService {
87
89
  /**
88
90
  * Publish and create version.
89
91
  */
90
- async publishWithVersion(db, organizationId, documentId) {
92
+ async publishWithVersion(db, organizationId, documentId, expectedRevision) {
91
93
  // Publish + version snapshot must commit together: a crash between them
92
94
  // would leave a published document with no 'publish' version row. Mirror
93
95
  // saveWithVersion / restoreVersion and run both writes in one transaction.
94
96
  // No versioning support: a single write, atomic on its own.
95
97
  if (!db.createDocumentVersion) {
96
- return db.publishDoc(organizationId, documentId);
98
+ return db.publishDoc(organizationId, documentId, expectedRevision);
97
99
  }
98
- const published = await db.withTransaction((txAdapter) => this.publishTx(txAdapter, organizationId, documentId));
100
+ const published = await db.withTransaction((txAdapter) => this.publishTx(txAdapter, organizationId, documentId, expectedRevision));
99
101
  if (published)
100
102
  await this.enforceRetention(db, documentId, organizationId);
101
103
  return published;
@@ -103,18 +105,20 @@ export class VersionService {
103
105
  /**
104
106
  * Restore a version to draft. Creates a 'draft' version entry.
105
107
  */
106
- async restoreVersion(db, organizationId, documentId, versionNumber, userId) {
108
+ async restoreVersion(db, organizationId, documentId, versionNumber, userId, expectedRevision) {
107
109
  if (!db.getDocumentVersion)
108
110
  return null;
109
111
  const version = await db.getDocumentVersion(organizationId, documentId, versionNumber);
110
112
  if (!version)
111
113
  return null;
114
+ // A restore is itself a draft write — just as capable of clobbering a
115
+ // concurrent edit as a normal save, so it goes through the same CAS guard.
112
116
  // No versioning support: a single write, atomic on its own.
113
117
  if (!db.createDocumentVersion) {
114
- return db.updateDocDraft(organizationId, documentId, version.data, userId);
118
+ return db.updateDocDraft(organizationId, documentId, version.data, userId, expectedRevision);
115
119
  }
116
120
  const restored = await db.withTransaction(async (txAdapter) => {
117
- const result = await txAdapter.updateDocDraft(organizationId, documentId, version.data, userId);
121
+ const result = await txAdapter.updateDocDraft(organizationId, documentId, version.data, userId, expectedRevision);
118
122
  if (result)
119
123
  await this.snapshotTx(txAdapter, organizationId, documentId, 'draft', version.data, userId);
120
124
  return result;
@@ -1 +1 @@
1
- {"version":3,"file":"type-gen.d.ts","sourceRoot":"","sources":["../src/lib/type-gen.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAoJzD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CAG3E;AA6VD;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CA8I3D;AAgGD,wBAAsB,uBAAuB,CAC5C,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,WAAW,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAoDf"}
1
+ {"version":3,"file":"type-gen.d.ts","sourceRoot":"","sources":["../src/lib/type-gen.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAoJzD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CAG3E;AA6VD;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CA8I3D;AA2GD,wBAAsB,uBAAuB,CAC5C,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,WAAW,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAoDf"}