@aglyn/plugins-ai 1.0.0-beta.229 → 1.0.0-beta.231

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 (340) hide show
  1. package/README.md +3 -3
  2. package/package.json +17 -17
  3. package/src/lib/activity/ai-activity-actions.js +7 -1
  4. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  5. package/src/lib/components/ai-assist-provider-on-host.component.d.ts +66 -0
  6. package/src/lib/components/ai-assist-provider-on-host.component.js +78 -0
  7. package/src/lib/components/ai-assist-provider-on-host.component.js.map +1 -0
  8. package/src/lib/components/ai-assist-provider.component.js +9 -10
  9. package/src/lib/components/ai-assist-provider.component.js.map +1 -1
  10. package/src/lib/components/ai-automation-zones.d.ts +38 -0
  11. package/src/lib/components/ai-automation-zones.js +5 -1
  12. package/src/lib/components/ai-automation-zones.js.map +1 -1
  13. package/src/lib/components/ai-brief-dialog.component.d.ts +20 -2
  14. package/src/lib/components/ai-brief-dialog.component.js +87 -8
  15. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  16. package/src/lib/components/ai-campaign-create.component.d.ts +54 -0
  17. package/src/lib/components/ai-campaign-create.component.js +270 -0
  18. package/src/lib/components/ai-campaign-create.component.js.map +1 -0
  19. package/src/lib/components/ai-describe-automation.component.d.ts +8 -3
  20. package/src/lib/components/ai-describe-automation.component.js +15 -6
  21. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  22. package/src/lib/components/ai-describe-button.component.d.ts +20 -8
  23. package/src/lib/components/ai-describe-button.component.js +19 -36
  24. package/src/lib/components/ai-describe-button.component.js.map +1 -1
  25. package/src/lib/components/ai-describe-email.component.d.ts +31 -0
  26. package/src/lib/components/ai-describe-email.component.js +22 -0
  27. package/src/lib/components/ai-describe-email.component.js.map +1 -0
  28. package/src/lib/components/ai-describe-org-automation.component.d.ts +36 -0
  29. package/src/lib/components/ai-describe-org-automation.component.js +238 -0
  30. package/src/lib/components/ai-describe-org-automation.component.js.map +1 -0
  31. package/src/lib/components/ai-experiment-cards.component.d.ts +9 -1
  32. package/src/lib/components/ai-experiment-cards.component.js +104 -18
  33. package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
  34. package/src/lib/components/ai-explain-automation.component.js +1 -5
  35. package/src/lib/components/ai-explain-automation.component.js.map +1 -1
  36. package/src/lib/components/ai-funnel-zones.component.d.ts +61 -0
  37. package/src/lib/components/ai-funnel-zones.component.js +178 -0
  38. package/src/lib/components/ai-funnel-zones.component.js.map +1 -0
  39. package/src/lib/components/ai-insight-card.component.d.ts +42 -0
  40. package/src/lib/components/ai-insight-card.component.js +137 -0
  41. package/src/lib/components/ai-insight-card.component.js.map +1 -0
  42. package/src/lib/components/ai-insight-dialog.component.d.ts +6 -0
  43. package/src/lib/components/ai-insight-dialog.component.js +9 -3
  44. package/src/lib/components/ai-insight-dialog.component.js.map +1 -1
  45. package/src/lib/components/ai-job-plan.component.d.ts +7 -2
  46. package/src/lib/components/ai-job-plan.component.js +38 -6
  47. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  48. package/src/lib/components/ai-job-requests.d.ts +17 -1
  49. package/src/lib/components/ai-job-requests.js +39 -3
  50. package/src/lib/components/ai-job-requests.js.map +1 -1
  51. package/src/lib/components/ai-logic-zones.d.ts +64 -0
  52. package/src/lib/components/ai-logic-zones.js +18 -0
  53. package/src/lib/components/ai-logic-zones.js.map +1 -0
  54. package/src/lib/components/ai-logic.component.d.ts +77 -0
  55. package/src/lib/components/ai-logic.component.js +553 -0
  56. package/src/lib/components/ai-logic.component.js.map +1 -0
  57. package/src/lib/components/ai-marketing-insight.component.d.ts +30 -0
  58. package/src/lib/components/ai-marketing-insight.component.js +80 -0
  59. package/src/lib/components/ai-marketing-insight.component.js.map +1 -0
  60. package/src/lib/components/ai-media-create.component.d.ts +55 -0
  61. package/src/lib/components/ai-media-create.component.js +433 -0
  62. package/src/lib/components/ai-media-create.component.js.map +1 -0
  63. package/src/lib/components/ai-overlay-cards.component.d.ts +83 -0
  64. package/src/lib/components/ai-overlay-cards.component.js +505 -0
  65. package/src/lib/components/ai-overlay-cards.component.js.map +1 -0
  66. package/src/lib/components/ai-permissions-on-host.component.d.ts +3 -46
  67. package/src/lib/components/ai-permissions-on-host.component.js +5 -53
  68. package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
  69. package/src/lib/components/ai-product-zones.d.ts +5 -0
  70. package/src/lib/components/ai-product-zones.js +1 -1
  71. package/src/lib/components/ai-product-zones.js.map +1 -1
  72. package/src/lib/components/ai-products-create-button.component.d.ts +42 -0
  73. package/src/lib/components/ai-products-create-button.component.js +67 -0
  74. package/src/lib/components/ai-products-create-button.component.js.map +1 -0
  75. package/src/lib/components/ai-products-hub-card.component.js +100 -81
  76. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  77. package/src/lib/components/ai-revise-automation.component.d.ts +16 -0
  78. package/src/lib/components/ai-revise-automation.component.js +207 -0
  79. package/src/lib/components/ai-revise-automation.component.js.map +1 -0
  80. package/src/lib/components/ai-site-build-page.component.js +160 -24
  81. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  82. package/src/lib/components/ai-upsell-dialog.component.d.ts +53 -0
  83. package/src/lib/components/ai-upsell-dialog.component.js +177 -0
  84. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -0
  85. package/src/lib/components/assist-build-card.component.d.ts +42 -0
  86. package/src/lib/components/assist-build-card.component.js +183 -0
  87. package/src/lib/components/assist-build-card.component.js.map +1 -0
  88. package/src/lib/components/assist-edit-canvas.d.ts +2 -0
  89. package/src/lib/components/assist-edit-canvas.js +56 -1
  90. package/src/lib/components/assist-edit-canvas.js.map +1 -1
  91. package/src/lib/components/assist-jobs-drawer.component.js +48 -3
  92. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  93. package/src/lib/components/assist-panel.component.js +51 -2
  94. package/src/lib/components/assist-panel.component.js.map +1 -1
  95. package/src/lib/components/use-ai-job-run.d.ts +3 -14
  96. package/src/lib/components/use-ai-job-run.js +0 -50
  97. package/src/lib/components/use-ai-job-run.js.map +1 -1
  98. package/src/lib/jobs/ai-build-capabilities.d.ts +58 -0
  99. package/src/lib/jobs/ai-build-capabilities.js +256 -0
  100. package/src/lib/jobs/ai-build-capabilities.js.map +1 -0
  101. package/src/lib/jobs/ai-build-unit-outcome.d.ts +52 -0
  102. package/src/lib/jobs/ai-build-unit-outcome.js +140 -0
  103. package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -0
  104. package/src/lib/jobs/ai-job-auto-confirm.d.ts +8 -0
  105. package/src/lib/jobs/ai-job-auto-confirm.js +30 -6
  106. package/src/lib/jobs/ai-job-auto-confirm.js.map +1 -1
  107. package/src/lib/jobs/ai-job-build-step.d.ts +144 -0
  108. package/src/lib/jobs/ai-job-build-step.js +668 -0
  109. package/src/lib/jobs/ai-job-build-step.js.map +1 -0
  110. package/src/lib/jobs/ai-job-draft-ids.js +9 -1
  111. package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
  112. package/src/lib/jobs/ai-job-drafts.d.ts +14 -4
  113. package/src/lib/jobs/ai-job-drafts.js +25 -4
  114. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  115. package/src/lib/jobs/ai-job-generation.d.ts +8 -0
  116. package/src/lib/jobs/ai-job-generation.js +13 -2
  117. package/src/lib/jobs/ai-job-generation.js.map +1 -1
  118. package/src/lib/jobs/ai-job-insight-step.js +8 -1
  119. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  120. package/src/lib/jobs/ai-job-layout-language.d.ts +73 -0
  121. package/src/lib/jobs/ai-job-layout-language.js +194 -0
  122. package/src/lib/jobs/ai-job-layout-language.js.map +1 -0
  123. package/src/lib/jobs/ai-job-layout-site-pages.d.ts +13 -2
  124. package/src/lib/jobs/ai-job-layout-site-pages.js +18 -2
  125. package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
  126. package/src/lib/jobs/ai-job-layout-step.d.ts +5 -0
  127. package/src/lib/jobs/ai-job-layout-step.js +105 -5
  128. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  129. package/src/lib/jobs/ai-job-logic-step.d.ts +107 -0
  130. package/src/lib/jobs/ai-job-logic-step.js +395 -0
  131. package/src/lib/jobs/ai-job-logic-step.js.map +1 -0
  132. package/src/lib/jobs/ai-job-overlay-copy.d.ts +70 -0
  133. package/src/lib/jobs/ai-job-overlay-copy.js +178 -0
  134. package/src/lib/jobs/ai-job-overlay-copy.js.map +1 -0
  135. package/src/lib/jobs/ai-job-page-language.d.ts +149 -0
  136. package/src/lib/jobs/ai-job-page-language.js +355 -0
  137. package/src/lib/jobs/ai-job-page-language.js.map +1 -0
  138. package/src/lib/jobs/ai-job-page-sections.d.ts +37 -1
  139. package/src/lib/jobs/ai-job-page-sections.js +93 -11
  140. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  141. package/src/lib/jobs/ai-job-page-step.d.ts +4 -4
  142. package/src/lib/jobs/ai-job-page-step.js +111 -20
  143. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  144. package/src/lib/jobs/ai-job-plan-step.d.ts +27 -2
  145. package/src/lib/jobs/ai-job-plan-step.js +87 -13
  146. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  147. package/src/lib/jobs/ai-job-site-step.d.ts +19 -1
  148. package/src/lib/jobs/ai-job-site-step.js +220 -61
  149. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  150. package/src/lib/jobs/ai-job-text-step.d.ts +22 -1
  151. package/src/lib/jobs/ai-job-text-step.js +12 -0
  152. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  153. package/src/lib/jobs/ai-job-workflow-step.d.ts +54 -4
  154. package/src/lib/jobs/ai-job-workflow-step.js +320 -15
  155. package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
  156. package/src/lib/jobs/ai-jobs.d.ts +80 -2
  157. package/src/lib/jobs/ai-jobs.js +391 -18
  158. package/src/lib/jobs/ai-jobs.js.map +1 -1
  159. package/src/lib/jobs/ai-layout-full-height.d.ts +40 -0
  160. package/src/lib/jobs/ai-layout-full-height.js +82 -0
  161. package/src/lib/jobs/ai-layout-full-height.js.map +1 -0
  162. package/src/lib/jobs/ai-layout-site-facts.d.ts +54 -0
  163. package/src/lib/jobs/ai-layout-site-facts.js +153 -0
  164. package/src/lib/jobs/ai-layout-site-facts.js.map +1 -0
  165. package/src/lib/jobs/ai-logic-records.d.ts +40 -0
  166. package/src/lib/jobs/ai-logic-records.js +55 -0
  167. package/src/lib/jobs/ai-logic-records.js.map +1 -0
  168. package/src/lib/jobs/ai-site-publish.d.ts +7 -0
  169. package/src/lib/jobs/ai-site-publish.js +9 -0
  170. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  171. package/src/lib/jobs/ai-workflow-records.d.ts +4 -0
  172. package/src/lib/jobs/ai-workflow-records.js +40 -1
  173. package/src/lib/jobs/ai-workflow-records.js.map +1 -1
  174. package/src/lib/layout-language/ai-layout-compiler.d.ts +145 -0
  175. package/src/lib/layout-language/ai-layout-compiler.js +1148 -0
  176. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -0
  177. package/src/lib/layout-language/ai-layout-copy.d.ts +73 -0
  178. package/src/lib/layout-language/ai-layout-copy.js +137 -0
  179. package/src/lib/layout-language/ai-layout-copy.js.map +1 -0
  180. package/src/lib/layout-language/ai-layout-frame.d.ts +52 -0
  181. package/src/lib/layout-language/ai-layout-frame.js +457 -0
  182. package/src/lib/layout-language/ai-layout-frame.js.map +1 -0
  183. package/src/lib/layout-language/ai-layout-gaps.d.ts +63 -0
  184. package/src/lib/layout-language/ai-layout-gaps.js +345 -0
  185. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -0
  186. package/src/lib/layout-language/ai-layout-language.d.ts +161 -0
  187. package/src/lib/layout-language/ai-layout-language.js +446 -0
  188. package/src/lib/layout-language/ai-layout-language.js.map +1 -0
  189. package/src/lib/layout-language/ai-layout-links.d.ts +80 -0
  190. package/src/lib/layout-language/ai-layout-links.js +109 -0
  191. package/src/lib/layout-language/ai-layout-links.js.map +1 -0
  192. package/src/lib/layout-language/ai-layout-store.d.ts +39 -0
  193. package/src/lib/layout-language/ai-layout-store.js +69 -0
  194. package/src/lib/layout-language/ai-layout-store.js.map +1 -0
  195. package/src/lib/layout-language/ai-layout-tree.d.ts +59 -0
  196. package/src/lib/layout-language/ai-layout-tree.js +84 -0
  197. package/src/lib/layout-language/ai-layout-tree.js.map +1 -0
  198. package/src/lib/lazy-widget.d.ts +45 -0
  199. package/src/lib/lazy-widget.js +53 -0
  200. package/src/lib/lazy-widget.js.map +1 -0
  201. package/src/lib/model/ai-automation-draft.d.ts +18 -0
  202. package/src/lib/model/ai-automation-draft.js +86 -0
  203. package/src/lib/model/ai-automation-draft.js.map +1 -1
  204. package/src/lib/model/ai-build-job.d.ts +201 -0
  205. package/src/lib/model/ai-build-job.js +472 -0
  206. package/src/lib/model/ai-build-job.js.map +1 -0
  207. package/src/lib/model/ai-build-plan.d.ts +61 -0
  208. package/src/lib/model/ai-build-plan.js +154 -9
  209. package/src/lib/model/ai-build-plan.js.map +1 -1
  210. package/src/lib/model/ai-build-progress.d.ts +73 -0
  211. package/src/lib/model/ai-build-progress.js +170 -0
  212. package/src/lib/model/ai-build-progress.js.map +1 -0
  213. package/src/lib/model/ai-host-publish-context.d.ts +51 -0
  214. package/src/lib/model/ai-host-publish-context.js +104 -0
  215. package/src/lib/model/ai-host-publish-context.js.map +1 -0
  216. package/src/lib/model/ai-insight.d.ts +20 -10
  217. package/src/lib/model/ai-insight.js +72 -9
  218. package/src/lib/model/ai-insight.js.map +1 -1
  219. package/src/lib/model/ai-job-activity.d.ts +22 -0
  220. package/src/lib/model/ai-job-activity.js +25 -2
  221. package/src/lib/model/ai-job-activity.js.map +1 -1
  222. package/src/lib/model/ai-job-failure-copy.d.ts +38 -1
  223. package/src/lib/model/ai-job-failure-copy.js +56 -1
  224. package/src/lib/model/ai-job-failure-copy.js.map +1 -1
  225. package/src/lib/model/ai-job-notice.d.ts +3 -1
  226. package/src/lib/model/ai-job-notice.js +31 -9
  227. package/src/lib/model/ai-job-notice.js.map +1 -1
  228. package/src/lib/model/ai-jobs.types.d.ts +99 -2
  229. package/src/lib/model/ai-jobs.types.js +3 -1
  230. package/src/lib/model/ai-jobs.types.js.map +1 -1
  231. package/src/lib/model/ai-logic-job.d.ts +108 -0
  232. package/src/lib/model/ai-logic-job.js +327 -0
  233. package/src/lib/model/ai-logic-job.js.map +1 -0
  234. package/src/lib/model/ai-media-credits.d.ts +52 -0
  235. package/src/lib/model/ai-media-credits.js +61 -0
  236. package/src/lib/model/ai-media-credits.js.map +1 -0
  237. package/src/lib/model/ai-overlay-copy.d.ts +114 -0
  238. package/src/lib/model/ai-overlay-copy.js +175 -0
  239. package/src/lib/model/ai-overlay-copy.js.map +1 -0
  240. package/src/lib/model/ai-site-build-progress.d.ts +25 -4
  241. package/src/lib/model/ai-site-build-progress.js +194 -11
  242. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  243. package/src/lib/model/ai-site-job.d.ts +8 -0
  244. package/src/lib/model/ai-site-job.js +9 -0
  245. package/src/lib/model/ai-site-job.js.map +1 -1
  246. package/src/lib/model/ai-site-start-seo.d.ts +1 -1
  247. package/src/lib/model/ai-site-start.d.ts +5 -2
  248. package/src/lib/model/ai-site-start.js +5 -2
  249. package/src/lib/model/ai-site-start.js.map +1 -1
  250. package/src/lib/model/ai-svg.d.ts +69 -0
  251. package/src/lib/model/ai-svg.js +265 -0
  252. package/src/lib/model/ai-svg.js.map +1 -0
  253. package/src/lib/model/ai-usage-by-user.d.ts +3 -2
  254. package/src/lib/model/ai-usage-by-user.js.map +1 -1
  255. package/src/lib/model/ai-workflow-job.d.ts +92 -4
  256. package/src/lib/model/ai-workflow-job.js +124 -7
  257. package/src/lib/model/ai-workflow-job.js.map +1 -1
  258. package/src/lib/model/assist-build.d.ts +58 -0
  259. package/src/lib/model/assist-build.js +52 -0
  260. package/src/lib/model/assist-build.js.map +1 -0
  261. package/src/lib/model/assist-edit.d.ts +48 -3
  262. package/src/lib/model/assist-edit.js +48 -4
  263. package/src/lib/model/assist-edit.js.map +1 -1
  264. package/src/lib/plugin-config.js +2 -0
  265. package/src/lib/plugin-config.js.map +1 -1
  266. package/src/lib/plugin.js +308 -47
  267. package/src/lib/plugin.js.map +1 -1
  268. package/src/lib/providers/catalog.d.ts +82 -5
  269. package/src/lib/providers/catalog.js +83 -12
  270. package/src/lib/providers/catalog.js.map +1 -1
  271. package/src/lib/providers/image-contract.d.ts +116 -0
  272. package/src/lib/providers/image-contract.js +74 -0
  273. package/src/lib/providers/image-contract.js.map +1 -0
  274. package/src/lib/providers/model-choice.js +11 -0
  275. package/src/lib/providers/model-choice.js.map +1 -1
  276. package/src/lib/providers/routing.js +18 -0
  277. package/src/lib/providers/routing.js.map +1 -1
  278. package/src/lib/providers/vertex-image.d.ts +121 -0
  279. package/src/lib/providers/vertex-image.js +312 -0
  280. package/src/lib/providers/vertex-image.js.map +1 -0
  281. package/src/lib/runtime/ai-doctrine-validators.d.ts +101 -0
  282. package/src/lib/runtime/ai-doctrine-validators.js +525 -4
  283. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  284. package/src/lib/runtime/ai-doctrine.js +21 -6
  285. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  286. package/src/lib/runtime/ai-eval-live.js +4 -0
  287. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  288. package/src/lib/runtime/ai-eval.d.ts +15 -1
  289. package/src/lib/runtime/ai-eval.js +52 -1
  290. package/src/lib/runtime/ai-eval.js.map +1 -1
  291. package/src/lib/runtime/ai-icon-library.d.ts +257 -0
  292. package/src/lib/runtime/ai-icon-library.js +254 -0
  293. package/src/lib/runtime/ai-icon-library.js.map +1 -0
  294. package/src/lib/runtime/ai-node-tree.d.ts +9 -0
  295. package/src/lib/runtime/ai-node-tree.js +30 -4
  296. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  297. package/src/lib/runtime/ai-palette.d.ts +6 -0
  298. package/src/lib/runtime/ai-palette.generated.js +70 -0
  299. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  300. package/src/lib/runtime/ai-palette.js.map +1 -1
  301. package/src/lib/runtime/experiment-variant-copy.d.ts +34 -0
  302. package/src/lib/runtime/experiment-variant-copy.js +102 -0
  303. package/src/lib/runtime/experiment-variant-copy.js.map +1 -0
  304. package/src/lib/server/ai-experiment-versions.d.ts +51 -0
  305. package/src/lib/server/ai-experiment-versions.js +270 -0
  306. package/src/lib/server/ai-experiment-versions.js.map +1 -0
  307. package/src/lib/server/ai-jobs-resume.js +74 -4
  308. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  309. package/src/lib/server/ai-media-image.d.ts +66 -0
  310. package/src/lib/server/ai-media-image.js +521 -0
  311. package/src/lib/server/ai-media-image.js.map +1 -0
  312. package/src/lib/server/ai-media-svg.d.ts +91 -0
  313. package/src/lib/server/ai-media-svg.js +186 -0
  314. package/src/lib/server/ai-media-svg.js.map +1 -0
  315. package/src/lib/server/assist-build.d.ts +54 -0
  316. package/src/lib/server/assist-build.js +121 -0
  317. package/src/lib/server/assist-build.js.map +1 -0
  318. package/src/lib/server/assist-chat.js +108 -12
  319. package/src/lib/server/assist-chat.js.map +1 -1
  320. package/src/lib/server/assist-edit.js +213 -3
  321. package/src/lib/server/assist-edit.js.map +1 -1
  322. package/src/lib/server/assist-view-context.js +242 -4
  323. package/src/lib/server/assist-view-context.js.map +1 -1
  324. package/src/lib/server.js +26 -0
  325. package/src/lib/server.js.map +1 -1
  326. package/src/lib/tools/ai-logic-tool.d.ts +27 -0
  327. package/src/lib/tools/ai-logic-tool.js +207 -0
  328. package/src/lib/tools/ai-logic-tool.js.map +1 -0
  329. package/src/lib/tools/ai-overlay-tool.d.ts +35 -0
  330. package/src/lib/tools/ai-overlay-tool.js +107 -0
  331. package/src/lib/tools/ai-overlay-tool.js.map +1 -0
  332. package/src/lib/tools/ai-workflow-tool.d.ts +17 -2
  333. package/src/lib/tools/ai-workflow-tool.js +47 -14
  334. package/src/lib/tools/ai-workflow-tool.js.map +1 -1
  335. package/src/lib/usage/assist-job-refund.d.ts +28 -1
  336. package/src/lib/usage/assist-job-refund.js +44 -9
  337. package/src/lib/usage/assist-job-refund.js.map +1 -1
  338. package/src/lib/usage/assist-usage.d.ts +5 -0
  339. package/src/lib/usage/assist-usage.js +2 -0
  340. package/src/lib/usage/assist-usage.js.map +1 -1
@@ -0,0 +1,265 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { sanitizeSvg } from "@aglyn/aglyn/app-utils/sanitize-svg";
17
+ /**
18
+ * The SVG an illustration arrives as (AGL-3602), and the one gate it passes
19
+ * before it is stored.
20
+ *
21
+ * A model writes SVG as text, and SVG is a document: it can carry script,
22
+ * event handlers, links out and HTML. The upload route strips all of that
23
+ * from anything it stores (`sanitize-svg`), so a dangerous answer could never
24
+ * be SERVED — but a stripped answer is no longer the picture the model meant,
25
+ * so this does not quietly accept one. Anything the sanitizer would have to
26
+ * remove, and anything a self-contained static picture has no use for, is a
27
+ * problem the model is told about and asked to fix once; a second answer with
28
+ * a problem is not stored at all.
29
+ *
30
+ * A picture that passes is:
31
+ *
32
+ * - one `<svg>` root in the SVG namespace, with a `viewBox` in the shape asked
33
+ * for, and balanced tags;
34
+ * - at most `AI_SVG_MAX_BYTES`;
35
+ * - self-contained: no `<image>`, no `<foreignObject>`, no `<script>`, no
36
+ * `<a>`, no event handler, no link or `url(…)` to anything but a `#fragment`
37
+ * in the same document, no `@import` and no `@font-face`;
38
+ * - static: no `<animate>`, `<set>` or other animation element;
39
+ * - for a logo mark, free of `<text>`: a mark, never a wordmark;
40
+ * - unchanged by `sanitizeSvg`.
41
+ */ /** The largest illustration stored, in bytes of UTF-8. */ export const AI_SVG_MAX_BYTES = 200 * 1024;
42
+ /** The `viewBox` each shape is drawn in. */ export const AI_SVG_VIEWBOX = {
43
+ '1:1': '0 0 512 512',
44
+ '4:3': '0 0 640 480',
45
+ '3:4': '0 0 480 640',
46
+ '16:9': '0 0 960 540',
47
+ '9:16': '0 0 540 960'
48
+ };
49
+ /** How far a `viewBox`'s proportions may stray from the shape asked for. */ const RATIO_TOLERANCE = 0.03;
50
+ const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
51
+ const FORBIDDEN_ELEMENTS = [
52
+ [
53
+ /<\s*(?:[\w-]+:)?script\b/i,
54
+ 'script',
55
+ 'Remove every <script> element.'
56
+ ],
57
+ [
58
+ /<\s*(?:[\w-]+:)?foreignObject\b/i,
59
+ 'foreign-object',
60
+ 'Remove every <foreignObject> element.'
61
+ ],
62
+ [
63
+ /<\s*(?:[\w-]+:)?image\b/i,
64
+ 'image',
65
+ 'Remove every <image> element: draw with shapes and paths only.'
66
+ ],
67
+ [
68
+ /<\s*(?:[\w-]+:)?(?:iframe|embed|object|audio|video|canvas)\b/i,
69
+ 'embed',
70
+ 'Remove every embedded document or media element.'
71
+ ],
72
+ [
73
+ /<\s*(?:[\w-]+:)?a\b/i,
74
+ 'link',
75
+ 'Remove every <a> element: the picture links nowhere.'
76
+ ],
77
+ [
78
+ /<\s*(?:[\w-]+:)?(?:animate|animateMotion|animateTransform|set|discard)\b/i,
79
+ 'animation',
80
+ 'Remove every animation element: the picture is static.'
81
+ ]
82
+ ];
83
+ /** The proportions of a `viewBox` value, or `null` when it is not one. */ function viewBoxRatio(value) {
84
+ const parts = value.trim().split(/[\s,]+/).map(Number);
85
+ if (parts.length !== 4 || parts.some((part)=>!Number.isFinite(part))) return null;
86
+ const [, , width, height] = parts;
87
+ return width > 0 && height > 0 ? width / height : null;
88
+ }
89
+ /**
90
+ * Whether every element opened is closed, in order, under ONE root that
91
+ * closes last. Comments and CDATA are skipped.
92
+ */ function tagsBalance(svg) {
93
+ const stripped = svg.replace(/<!--[\s\S]*?-->/g, '').replace(/<!\[CDATA\[[\s\S]*?\]\]>/g, '');
94
+ const stack = [];
95
+ let closedRoot = false;
96
+ for (const match of stripped.matchAll(/<(\/?)([A-Za-z][\w:.-]*)[^<>]*?(\/?)>/g)){
97
+ const [, closing, name, selfClosing] = match;
98
+ // Anything after the root closed is a second root.
99
+ if (closedRoot) return false;
100
+ if (selfClosing) {
101
+ if (!stack.length) return false;
102
+ continue;
103
+ }
104
+ if (closing) {
105
+ if (stack.pop() !== name) return false;
106
+ if (!stack.length) closedRoot = true;
107
+ } else {
108
+ stack.push(name);
109
+ }
110
+ }
111
+ return stack.length === 0 && closedRoot;
112
+ }
113
+ /**
114
+ * The gate. `markup` is the model's answer; the result is the document to
115
+ * store, or the problems to send back.
116
+ */ export function checkAiSvgMarkup(markup, aspectRatio, style) {
117
+ const problems = [];
118
+ const svg = String(markup != null ? markup : '').trim()// An XML declaration is harmless and the sanitizer would drop it.
119
+ .replace(/^<\?xml[^>]*\?>\s*/i, '');
120
+ if (!svg) {
121
+ return {
122
+ svg: null,
123
+ problems: [
124
+ {
125
+ code: 'empty',
126
+ detail: 'Answer with the SVG document itself.'
127
+ }
128
+ ]
129
+ };
130
+ }
131
+ const bytes = new TextEncoder().encode(svg).length;
132
+ if (bytes > AI_SVG_MAX_BYTES) {
133
+ problems.push({
134
+ code: 'too-large',
135
+ detail: `The SVG is ${Math.round(bytes / 1024)} KB; keep it under ${AI_SVG_MAX_BYTES / 1024} KB with fewer, simpler paths.`
136
+ });
137
+ }
138
+ const root = /^<svg\b([^>]*)>/i.exec(svg);
139
+ if (!root || !/<\/svg>$/i.test(svg)) {
140
+ problems.push({
141
+ code: 'root',
142
+ detail: 'Answer with exactly one <svg> element and nothing around it.'
143
+ });
144
+ } else {
145
+ const attributes = root[1];
146
+ if (!new RegExp(`xmlns\\s*=\\s*["']${SVG_NAMESPACE.replace(/[.]/g, '\\.')}["']`).test(attributes)) {
147
+ problems.push({
148
+ code: 'namespace',
149
+ detail: `Give the <svg> element xmlns="${SVG_NAMESPACE}".`
150
+ });
151
+ }
152
+ const viewBox = /\bviewBox\s*=\s*["']([^"']*)["']/.exec(attributes);
153
+ const expected = viewBoxRatio(AI_SVG_VIEWBOX[aspectRatio]);
154
+ const ratio = viewBox ? viewBoxRatio(viewBox[1]) : null;
155
+ if (ratio === null) {
156
+ problems.push({
157
+ code: 'viewbox',
158
+ detail: `Give the <svg> element viewBox="${AI_SVG_VIEWBOX[aspectRatio]}".`
159
+ });
160
+ } else if (Math.abs(ratio - expected) / expected > RATIO_TOLERANCE) {
161
+ problems.push({
162
+ code: 'viewbox-shape',
163
+ detail: `The viewBox is the wrong shape; use viewBox="${AI_SVG_VIEWBOX[aspectRatio]}".`
164
+ });
165
+ }
166
+ }
167
+ if (root && !tagsBalance(svg)) {
168
+ problems.push({
169
+ code: 'malformed',
170
+ detail: 'The SVG is not well-formed: close every element you open.'
171
+ });
172
+ }
173
+ for (const [pattern, code, detail] of FORBIDDEN_ELEMENTS){
174
+ if (pattern.test(svg)) problems.push({
175
+ code,
176
+ detail
177
+ });
178
+ }
179
+ if (/\son[a-z]+\s*=/i.test(svg)) {
180
+ problems.push({
181
+ code: 'event-handler',
182
+ detail: 'Remove every on… event attribute.'
183
+ });
184
+ }
185
+ for (const match of svg.matchAll(/\s(?:xlink:)?href\s*=\s*["']([^"']*)["']/gi)){
186
+ if (!match[1].trim().startsWith('#')) {
187
+ problems.push({
188
+ code: 'external-ref',
189
+ detail: 'A href may only point at an element of this same SVG (#id).'
190
+ });
191
+ break;
192
+ }
193
+ }
194
+ for (const match of svg.matchAll(/url\(\s*['"]?([^)'"]*)/gi)){
195
+ if (!match[1].trim().startsWith('#')) {
196
+ problems.push({
197
+ code: 'external-url',
198
+ detail: 'url(…) may only point at an element of this same SVG (#id).'
199
+ });
200
+ break;
201
+ }
202
+ }
203
+ if (/@import\b/i.test(svg) || /@font-face\b/i.test(svg)) {
204
+ problems.push({
205
+ code: 'external-css',
206
+ detail: 'Remove @import and @font-face; use generic font families (sans-serif, serif) if any.'
207
+ });
208
+ }
209
+ if (style === 'logo' && /<\s*(?:[\w-]+:)?text\b/i.test(svg)) {
210
+ problems.push({
211
+ code: 'wordmark',
212
+ detail: 'A logo mark has no lettering: draw it with shapes and paths only.'
213
+ });
214
+ }
215
+ if (!problems.length) {
216
+ const sanitized = sanitizeSvg(svg);
217
+ if (sanitized.changed) {
218
+ problems.push({
219
+ code: 'unsafe',
220
+ detail: `Remove what an SVG served as an image may not carry: ${sanitized.removed.join(', ')}.`
221
+ });
222
+ }
223
+ }
224
+ return problems.length ? {
225
+ svg: null,
226
+ problems
227
+ } : {
228
+ svg,
229
+ problems
230
+ };
231
+ }
232
+ /** A color a person may name: `#rgb` or `#rrggbb`. */ export function isAiSvgColor(value) {
233
+ return typeof value === 'string' && /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test(value.trim());
234
+ }
235
+ /**
236
+ * The colors a site's theme names, in the order a picture reaches for them:
237
+ * the primary and secondary colors first, then the backgrounds and text.
238
+ */ export function aiSvgThemePalette(colors) {
239
+ if (!colors) return [];
240
+ const order = [
241
+ 'primary.main',
242
+ 'secondary.main',
243
+ 'background.default',
244
+ 'background.paper',
245
+ 'text.primary',
246
+ 'primary.light',
247
+ 'primary.dark',
248
+ 'secondary.light',
249
+ 'secondary.dark'
250
+ ];
251
+ const keys = [
252
+ ...order.filter((key)=>key in colors),
253
+ ...Object.keys(colors).filter((key)=>!order.includes(key))
254
+ ];
255
+ const palette = [];
256
+ for (const key of keys){
257
+ var _colors_key;
258
+ const value = String((_colors_key = colors[key]) != null ? _colors_key : '').trim().toLowerCase();
259
+ if (isAiSvgColor(value) && !palette.includes(value)) palette.push(value);
260
+ if (palette.length >= 6) break;
261
+ }
262
+ return palette;
263
+ }
264
+
265
+ //# sourceMappingURL=ai-svg.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-svg.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { sanitizeSvg } from '@aglyn/aglyn/app-utils/sanitize-svg'\nimport type { AiImageAspectRatio, AiSvgStyle } from '../providers/image-contract'\n\n/**\n * The SVG an illustration arrives as (AGL-3602), and the one gate it passes\n * before it is stored.\n *\n * A model writes SVG as text, and SVG is a document: it can carry script,\n * event handlers, links out and HTML. The upload route strips all of that\n * from anything it stores (`sanitize-svg`), so a dangerous answer could never\n * be SERVED — but a stripped answer is no longer the picture the model meant,\n * so this does not quietly accept one. Anything the sanitizer would have to\n * remove, and anything a self-contained static picture has no use for, is a\n * problem the model is told about and asked to fix once; a second answer with\n * a problem is not stored at all.\n *\n * A picture that passes is:\n *\n * - one `<svg>` root in the SVG namespace, with a `viewBox` in the shape asked\n * for, and balanced tags;\n * - at most `AI_SVG_MAX_BYTES`;\n * - self-contained: no `<image>`, no `<foreignObject>`, no `<script>`, no\n * `<a>`, no event handler, no link or `url(…)` to anything but a `#fragment`\n * in the same document, no `@import` and no `@font-face`;\n * - static: no `<animate>`, `<set>` or other animation element;\n * - for a logo mark, free of `<text>`: a mark, never a wordmark;\n * - unchanged by `sanitizeSvg`.\n */\n\n/** The largest illustration stored, in bytes of UTF-8. */\nexport const AI_SVG_MAX_BYTES = 200 * 1024\n\n/** The `viewBox` each shape is drawn in. */\nexport const AI_SVG_VIEWBOX: Readonly<Record<AiImageAspectRatio, string>> = {\n '1:1': '0 0 512 512',\n '4:3': '0 0 640 480',\n '3:4': '0 0 480 640',\n '16:9': '0 0 960 540',\n '9:16': '0 0 540 960',\n}\n\n/** How far a `viewBox`'s proportions may stray from the shape asked for. */\nconst RATIO_TOLERANCE = 0.03\n\nconst SVG_NAMESPACE = 'http://www.w3.org/2000/svg'\n\nexport interface AiSvgProblem {\n /** Stable per finding, for specs and logs. */\n code: string\n /** What the model is told to fix. */\n detail: string\n}\n\nexport interface AiSvgCheck {\n /** The document to store, or `null` when it has a problem. */\n svg: string | null\n problems: AiSvgProblem[]\n}\n\nconst FORBIDDEN_ELEMENTS: ReadonlyArray<[RegExp, string, string]> = [\n [/<\\s*(?:[\\w-]+:)?script\\b/i, 'script', 'Remove every <script> element.'],\n [/<\\s*(?:[\\w-]+:)?foreignObject\\b/i, 'foreign-object', 'Remove every <foreignObject> element.'],\n [/<\\s*(?:[\\w-]+:)?image\\b/i, 'image', 'Remove every <image> element: draw with shapes and paths only.'],\n [/<\\s*(?:[\\w-]+:)?(?:iframe|embed|object|audio|video|canvas)\\b/i, 'embed', 'Remove every embedded document or media element.'],\n [/<\\s*(?:[\\w-]+:)?a\\b/i, 'link', 'Remove every <a> element: the picture links nowhere.'],\n [\n /<\\s*(?:[\\w-]+:)?(?:animate|animateMotion|animateTransform|set|discard)\\b/i,\n 'animation',\n 'Remove every animation element: the picture is static.',\n ],\n]\n\n/** The proportions of a `viewBox` value, or `null` when it is not one. */\nfunction viewBoxRatio(value: string): number | null {\n const parts = value.trim().split(/[\\s,]+/).map(Number)\n if (parts.length !== 4 || parts.some((part) => !Number.isFinite(part))) return null\n const [, , width, height] = parts\n return width > 0 && height > 0 ? width / height : null\n}\n\n/**\n * Whether every element opened is closed, in order, under ONE root that\n * closes last. Comments and CDATA are skipped.\n */\nfunction tagsBalance(svg: string): boolean {\n const stripped = svg.replace(/<!--[\\s\\S]*?-->/g, '').replace(/<!\\[CDATA\\[[\\s\\S]*?\\]\\]>/g, '')\n const stack: string[] = []\n let closedRoot = false\n for (const match of stripped.matchAll(/<(\\/?)([A-Za-z][\\w:.-]*)[^<>]*?(\\/?)>/g)) {\n const [, closing, name, selfClosing] = match\n // Anything after the root closed is a second root.\n if (closedRoot) return false\n if (selfClosing) {\n if (!stack.length) return false\n continue\n }\n if (closing) {\n if (stack.pop() !== name) return false\n if (!stack.length) closedRoot = true\n } else {\n stack.push(name)\n }\n }\n return stack.length === 0 && closedRoot\n}\n\n/**\n * The gate. `markup` is the model's answer; the result is the document to\n * store, or the problems to send back.\n */\nexport function checkAiSvgMarkup(\n markup: unknown,\n aspectRatio: AiImageAspectRatio,\n style: AiSvgStyle,\n): AiSvgCheck {\n const problems: AiSvgProblem[] = []\n const svg = String(markup ?? '')\n .trim()\n // An XML declaration is harmless and the sanitizer would drop it.\n .replace(/^<\\?xml[^>]*\\?>\\s*/i, '')\n if (!svg) {\n return { svg: null, problems: [{ code: 'empty', detail: 'Answer with the SVG document itself.' }] }\n }\n const bytes = new TextEncoder().encode(svg).length\n if (bytes > AI_SVG_MAX_BYTES) {\n problems.push({\n code: 'too-large',\n detail: `The SVG is ${Math.round(bytes / 1024)} KB; keep it under ${AI_SVG_MAX_BYTES / 1024} KB with fewer, simpler paths.`,\n })\n }\n const root = /^<svg\\b([^>]*)>/i.exec(svg)\n if (!root || !/<\\/svg>$/i.test(svg)) {\n problems.push({ code: 'root', detail: 'Answer with exactly one <svg> element and nothing around it.' })\n } else {\n const attributes = root[1]\n if (!new RegExp(`xmlns\\\\s*=\\\\s*[\"']${SVG_NAMESPACE.replace(/[.]/g, '\\\\.')}[\"']`).test(attributes)) {\n problems.push({ code: 'namespace', detail: `Give the <svg> element xmlns=\"${SVG_NAMESPACE}\".` })\n }\n const viewBox = /\\bviewBox\\s*=\\s*[\"']([^\"']*)[\"']/.exec(attributes)\n const expected = viewBoxRatio(AI_SVG_VIEWBOX[aspectRatio]) as number\n const ratio = viewBox ? viewBoxRatio(viewBox[1]) : null\n if (ratio === null) {\n problems.push({\n code: 'viewbox',\n detail: `Give the <svg> element viewBox=\"${AI_SVG_VIEWBOX[aspectRatio]}\".`,\n })\n } else if (Math.abs(ratio - expected) / expected > RATIO_TOLERANCE) {\n problems.push({\n code: 'viewbox-shape',\n detail: `The viewBox is the wrong shape; use viewBox=\"${AI_SVG_VIEWBOX[aspectRatio]}\".`,\n })\n }\n }\n if (root && !tagsBalance(svg)) {\n problems.push({ code: 'malformed', detail: 'The SVG is not well-formed: close every element you open.' })\n }\n for (const [pattern, code, detail] of FORBIDDEN_ELEMENTS) {\n if (pattern.test(svg)) problems.push({ code, detail })\n }\n if (/\\son[a-z]+\\s*=/i.test(svg)) {\n problems.push({ code: 'event-handler', detail: 'Remove every on… event attribute.' })\n }\n for (const match of svg.matchAll(/\\s(?:xlink:)?href\\s*=\\s*[\"']([^\"']*)[\"']/gi)) {\n if (!match[1].trim().startsWith('#')) {\n problems.push({\n code: 'external-ref',\n detail: 'A href may only point at an element of this same SVG (#id).',\n })\n break\n }\n }\n for (const match of svg.matchAll(/url\\(\\s*['\"]?([^)'\"]*)/gi)) {\n if (!match[1].trim().startsWith('#')) {\n problems.push({ code: 'external-url', detail: 'url(…) may only point at an element of this same SVG (#id).' })\n break\n }\n }\n if (/@import\\b/i.test(svg) || /@font-face\\b/i.test(svg)) {\n problems.push({\n code: 'external-css',\n detail: 'Remove @import and @font-face; use generic font families (sans-serif, serif) if any.',\n })\n }\n if (style === 'logo' && /<\\s*(?:[\\w-]+:)?text\\b/i.test(svg)) {\n problems.push({ code: 'wordmark', detail: 'A logo mark has no lettering: draw it with shapes and paths only.' })\n }\n if (!problems.length) {\n const sanitized = sanitizeSvg(svg)\n if (sanitized.changed) {\n problems.push({\n code: 'unsafe',\n detail: `Remove what an SVG served as an image may not carry: ${sanitized.removed.join(', ')}.`,\n })\n }\n }\n return problems.length ? { svg: null, problems } : { svg, problems }\n}\n\n/** A color a person may name: `#rgb` or `#rrggbb`. */\nexport function isAiSvgColor(value: unknown): value is string {\n return typeof value === 'string' && /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test(value.trim())\n}\n\n/**\n * The colors a site's theme names, in the order a picture reaches for them:\n * the primary and secondary colors first, then the backgrounds and text.\n */\nexport function aiSvgThemePalette(colors: Readonly<Record<string, string>> | null | undefined): string[] {\n if (!colors) return []\n const order = [\n 'primary.main',\n 'secondary.main',\n 'background.default',\n 'background.paper',\n 'text.primary',\n 'primary.light',\n 'primary.dark',\n 'secondary.light',\n 'secondary.dark',\n ]\n const keys = [...order.filter((key) => key in colors), ...Object.keys(colors).filter((key) => !order.includes(key))]\n const palette: string[] = []\n for (const key of keys) {\n const value = String(colors[key] ?? '').trim().toLowerCase()\n if (isAiSvgColor(value) && !palette.includes(value)) palette.push(value)\n if (palette.length >= 6) break\n }\n return palette\n}\n"],"names":["sanitizeSvg","AI_SVG_MAX_BYTES","AI_SVG_VIEWBOX","RATIO_TOLERANCE","SVG_NAMESPACE","FORBIDDEN_ELEMENTS","viewBoxRatio","value","parts","trim","split","map","Number","length","some","part","isFinite","width","height","tagsBalance","svg","stripped","replace","stack","closedRoot","match","matchAll","closing","name","selfClosing","pop","push","checkAiSvgMarkup","markup","aspectRatio","style","problems","String","code","detail","bytes","TextEncoder","encode","Math","round","root","exec","test","attributes","RegExp","viewBox","expected","ratio","abs","pattern","startsWith","sanitized","changed","removed","join","isAiSvgColor","aiSvgThemePalette","colors","order","keys","filter","key","Object","includes","palette","toLowerCase"],"mappings":"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"}
@@ -44,11 +44,12 @@ export declare const AI_USAGE_BY_USER_RETENTION_MONTHS = 13;
44
44
  * What an AI request was FOR, as the per-user rollup buckets it.
45
45
  *
46
46
  * `assist` is a console chat turn; `element`, `blog` and `section` are the
47
- * besigner's three assist modes; the rest are generation job kinds, named
47
+ * besigner's three assist modes; `image` is a picture made in the Media
48
+ * library (AGL-3602); the rest are generation job kinds, named
48
49
  * exactly as the job document names them so a reader can join the two — or
49
50
  * a purpose another plugin named on the text-generation seam.
50
51
  */
51
- export type AiUsageKind = 'assist' | 'element' | 'blog' | 'section' | AiJobKind | PluginAiUsageKind;
52
+ export type AiUsageKind = 'assist' | 'element' | 'blog' | 'section' | 'image' | AiJobKind | PluginAiUsageKind;
52
53
  /**
53
54
  * A purpose another plugin named on the core's text-generation seam
54
55
  * (AGL-3324): the plugin's id and a word, hyphenated — `outreach-curate` —
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-usage-by-user.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AiJobKind } from './ai-jobs.types'\nimport { assistCreditsFromUsd } from '../usage/assist-credits'\n\n/**\n * PER-USER AI USAGE (AGL-2928): the pure half.\n *\n * The org rollup at `assistUsage/{month}` says what a workspace spent on AI\n * and nothing about who spent it. This module is the shape and the arithmetic\n * of the second rollup — one document per person per month, under the org,\n * keyed by uid — so an agency or a multi-brand workspace can see which of its\n * people, on which site, is generating what. No prose, no PII beyond the uid\n * the document is keyed by.\n *\n * The write lives in the plugin's `usage/ai-usage-by-user.ts`, inside the\n * same batch as the org rollup. Everything here is dependency-free so the\n * plugin's tables and the doors that serve them read one definition of a\n * share, a month key and a retention window.\n */\n\n/** `orgs/{orgId}/{this}/{uid}/{months}/{YYYY-MM}` — the subcollection names. */\nexport const AI_USAGE_BY_USER_COLLECTION = 'aiUsageByUser'\nexport const AI_USAGE_BY_USER_MONTHS_COLLECTION = 'months'\n\n/**\n * How many months a person's usage is kept, counted from the month it\n * describes. Thirteen: a full year of month-over-month comparison for a\n * workspace admin, plus the month in progress. A TTL policy on the month\n * document's `expiresAt` enforces it — see `aiUsageByUserExpiry`.\n */\nexport const AI_USAGE_BY_USER_RETENTION_MONTHS = 13\n\n/**\n * What an AI request was FOR, as the per-user rollup buckets it.\n *\n * `assist` is a console chat turn; `element`, `blog` and `section` are the\n * besigner's three assist modes; the rest are generation job kinds, named\n * exactly as the job document names them so a reader can join the two — or\n * a purpose another plugin named on the text-generation seam.\n */\nexport type AiUsageKind =\n | 'assist'\n | 'element'\n | 'blog'\n | 'section'\n | AiJobKind\n | PluginAiUsageKind\n\n/**\n * A purpose another plugin named on the core's text-generation seam\n * (AGL-3324): the plugin's id and a word, hyphenated — `outreach-curate` —\n * so its spend sits in the same `byKind` map as a job kind and a reader can\n * tell whose it was.\n */\nexport type PluginAiUsageKind = `${string}-${string}`\n\n/** One person's month, zero-filled — the shape every reader hands on. */\nexport interface AiUsageByUserMonth {\n uid: string\n month: string\n /** Credits drawn, each request rounded up as the meter rounds. */\n credits: number\n /**\n * The measured provider spend behind `credits`. Server-side only: no\n * customer surface renders it, and `aiUsageShare` is how it becomes a\n * figure a customer may see.\n */\n estCostUsd: number\n requests: number\n refusals: number\n /** Credits by `AiUsageKind`. */\n byKind: Record<string, number>\n /** Credits by host id, for requests that named a site. */\n byHost: Record<string, number>\n}\n\nconst MONTH_KEY = /^\\d{4}-(0[1-9]|1[0-2])$/\n\n/** Whether `value` is a `YYYY-MM` month key. */\nexport function isAiUsageMonthKey(value: unknown): value is string {\n return typeof value === 'string' && MONTH_KEY.test(value)\n}\n\n/**\n * The month keys a reader may ask for, newest first: the month in progress\n * and the twelve before it — the retention window, so a picker built from\n * this list never names a month the TTL has reaped.\n */\nexport function aiUsageMonthKeys(\n now: Date = new Date(),\n count: number = AI_USAGE_BY_USER_RETENTION_MONTHS,\n): string[] {\n const keys: string[] = []\n for (let back = 0; back < count; back += 1) {\n keys.push(\n new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - back, 1))\n .toISOString()\n .slice(0, 7),\n )\n }\n return keys\n}\n\n/** Whether `month` is inside the window `aiUsageMonthKeys` describes. */\nexport function aiUsageMonthWithinRetention(\n month: string,\n now: Date = new Date(),\n): boolean {\n return isAiUsageMonthKey(month) && aiUsageMonthKeys(now).includes(month)\n}\n\n/**\n * When a month document expires: the first instant of the month that is\n * `AI_USAGE_BY_USER_RETENTION_MONTHS` after the one it describes has closed.\n *\n * A `Date`, not a number: the TTL policy keys on a Firestore Timestamp and\n * silently governs nothing when the field is a number. Deterministic per\n * month, so every increment into the document re-stamps the same value and\n * the policy never moves under a month that is still being written.\n */\nexport function aiUsageByUserExpiry(month: string): Date {\n if (!isAiUsageMonthKey(month)) {\n throw new Error(`not a month key: ${month}`)\n }\n const [year, monthOfYear] = month.split('-').map(Number)\n return new Date(\n Date.UTC(year, monthOfYear + AI_USAGE_BY_USER_RETENTION_MONTHS, 1),\n )\n}\n\n/**\n * The kind an assist request was, read off the route it arrived on — for the\n * meters that carry a route and no kind of their own. The three besigner\n * modes name themselves in the path; everything else is a chat turn.\n */\nexport function aiUsageKindFromRoute(route: string | null | undefined): AiUsageKind {\n const tail = String(route ?? '').split('/').filter(Boolean).pop() ?? ''\n if (tail === 'element' || tail === 'blog' || tail === 'section') return tail\n return 'assist'\n}\n\n/**\n * One person's share of the workspace's spend for the month, in `[0, 1]`.\n *\n * Measured on the dollar figures, never on credits: the org meter rounds the\n * month's spend up ONCE, while a person's credits round each request up, so\n * a roster's credits can sum to a little more than the pool they drew from.\n * The ratio of measured spends has no such drift. Clamped, because a month\n * document written a moment after the org rollup is still a fraction, not a\n * percentage over one hundred — and rounded to a basis point, which is finer\n * than any surface renders and coarser than floating-point noise.\n */\nexport function aiUsageShare(\n userEstCostUsd: number,\n orgEstCostUsd: number,\n): number {\n const user = Number(userEstCostUsd)\n const org = Number(orgEstCostUsd)\n if (!Number.isFinite(user) || user <= 0) return 0\n if (!Number.isFinite(org) || org <= 0) return 0\n return Math.min(1, Math.round((user / org) * 10_000) / 10_000)\n}\n\nconst finite = (value: unknown): number => {\n const parsed = Number(value ?? 0)\n return Number.isFinite(parsed) && parsed > 0 ? parsed : 0\n}\n\nconst creditMap = (raw: unknown): Record<string, number> => {\n const out: Record<string, number> = {}\n for (const [key, value] of Object.entries(\n (raw ?? {}) as Record<string, unknown>,\n )) {\n const credits = Math.floor(finite(value))\n if (credits > 0) out[key] = credits\n }\n return out\n}\n\n/**\n * A month document as a complete record. Absent and malformed fields read as\n * zero; a `credits` the writer never set is derived from the spend, so a\n * document written before the field existed still ranks.\n */\nexport function aiUsageByUserMonthFrom(\n raw: Record<string, unknown> | null | undefined,\n uid: string,\n month: string,\n): AiUsageByUserMonth {\n const estCostUsd = finite(raw?.['estCostUsd'])\n const stored = Math.floor(finite(raw?.['credits']))\n return {\n uid,\n month,\n credits: stored > 0 ? stored : assistCreditsFromUsd(estCostUsd),\n estCostUsd,\n requests: Math.floor(finite(raw?.['requests'])),\n refusals: Math.floor(finite(raw?.['refusals'])),\n byKind: creditMap(raw?.['byKind']),\n byHost: creditMap(raw?.['byHost']),\n }\n}\n\n/** The kinds a person drew the most credits on, dearest first. */\nexport function topAiUsageKinds(\n byKind: Record<string, number>,\n limit = 3,\n): Array<{ kind: string; credits: number }> {\n return Object.entries(byKind)\n .map(([kind, credits]) => ({ kind, credits }))\n .sort((a, b) => b.credits - a.credits || a.kind.localeCompare(b.kind))\n .slice(0, limit)\n}\n\n/** Two month documents for one person, summed — the roster column's figure. */\nexport function sumAiUsageCredits(months: readonly AiUsageByUserMonth[]): number {\n return months.reduce((sum, entry) => sum + entry.credits, 0)\n}\n"],"names":["assistCreditsFromUsd","AI_USAGE_BY_USER_COLLECTION","AI_USAGE_BY_USER_MONTHS_COLLECTION","AI_USAGE_BY_USER_RETENTION_MONTHS","MONTH_KEY","isAiUsageMonthKey","value","test","aiUsageMonthKeys","now","Date","count","keys","back","push","UTC","getUTCFullYear","getUTCMonth","toISOString","slice","aiUsageMonthWithinRetention","month","includes","aiUsageByUserExpiry","Error","year","monthOfYear","split","map","Number","aiUsageKindFromRoute","route","String","tail","filter","Boolean","pop","aiUsageShare","userEstCostUsd","orgEstCostUsd","user","org","isFinite","Math","min","round","finite","parsed","creditMap","raw","out","key","Object","entries","credits","floor","aiUsageByUserMonthFrom","uid","estCostUsd","stored","requests","refusals","byKind","byHost","topAiUsageKinds","limit","kind","sort","a","b","localeCompare","sumAiUsageCredits","months","reduce","sum","entry"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-usage-by-user.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AiJobKind } from './ai-jobs.types'\nimport { assistCreditsFromUsd } from '../usage/assist-credits'\n\n/**\n * PER-USER AI USAGE (AGL-2928): the pure half.\n *\n * The org rollup at `assistUsage/{month}` says what a workspace spent on AI\n * and nothing about who spent it. This module is the shape and the arithmetic\n * of the second rollup — one document per person per month, under the org,\n * keyed by uid — so an agency or a multi-brand workspace can see which of its\n * people, on which site, is generating what. No prose, no PII beyond the uid\n * the document is keyed by.\n *\n * The write lives in the plugin's `usage/ai-usage-by-user.ts`, inside the\n * same batch as the org rollup. Everything here is dependency-free so the\n * plugin's tables and the doors that serve them read one definition of a\n * share, a month key and a retention window.\n */\n\n/** `orgs/{orgId}/{this}/{uid}/{months}/{YYYY-MM}` — the subcollection names. */\nexport const AI_USAGE_BY_USER_COLLECTION = 'aiUsageByUser'\nexport const AI_USAGE_BY_USER_MONTHS_COLLECTION = 'months'\n\n/**\n * How many months a person's usage is kept, counted from the month it\n * describes. Thirteen: a full year of month-over-month comparison for a\n * workspace admin, plus the month in progress. A TTL policy on the month\n * document's `expiresAt` enforces it — see `aiUsageByUserExpiry`.\n */\nexport const AI_USAGE_BY_USER_RETENTION_MONTHS = 13\n\n/**\n * What an AI request was FOR, as the per-user rollup buckets it.\n *\n * `assist` is a console chat turn; `element`, `blog` and `section` are the\n * besigner's three assist modes; `image` is a picture made in the Media\n * library (AGL-3602); the rest are generation job kinds, named\n * exactly as the job document names them so a reader can join the two — or\n * a purpose another plugin named on the text-generation seam.\n */\nexport type AiUsageKind =\n | 'assist'\n | 'element'\n | 'blog'\n | 'section'\n | 'image'\n | AiJobKind\n | PluginAiUsageKind\n\n/**\n * A purpose another plugin named on the core's text-generation seam\n * (AGL-3324): the plugin's id and a word, hyphenated — `outreach-curate` —\n * so its spend sits in the same `byKind` map as a job kind and a reader can\n * tell whose it was.\n */\nexport type PluginAiUsageKind = `${string}-${string}`\n\n/** One person's month, zero-filled — the shape every reader hands on. */\nexport interface AiUsageByUserMonth {\n uid: string\n month: string\n /** Credits drawn, each request rounded up as the meter rounds. */\n credits: number\n /**\n * The measured provider spend behind `credits`. Server-side only: no\n * customer surface renders it, and `aiUsageShare` is how it becomes a\n * figure a customer may see.\n */\n estCostUsd: number\n requests: number\n refusals: number\n /** Credits by `AiUsageKind`. */\n byKind: Record<string, number>\n /** Credits by host id, for requests that named a site. */\n byHost: Record<string, number>\n}\n\nconst MONTH_KEY = /^\\d{4}-(0[1-9]|1[0-2])$/\n\n/** Whether `value` is a `YYYY-MM` month key. */\nexport function isAiUsageMonthKey(value: unknown): value is string {\n return typeof value === 'string' && MONTH_KEY.test(value)\n}\n\n/**\n * The month keys a reader may ask for, newest first: the month in progress\n * and the twelve before it — the retention window, so a picker built from\n * this list never names a month the TTL has reaped.\n */\nexport function aiUsageMonthKeys(\n now: Date = new Date(),\n count: number = AI_USAGE_BY_USER_RETENTION_MONTHS,\n): string[] {\n const keys: string[] = []\n for (let back = 0; back < count; back += 1) {\n keys.push(\n new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - back, 1))\n .toISOString()\n .slice(0, 7),\n )\n }\n return keys\n}\n\n/** Whether `month` is inside the window `aiUsageMonthKeys` describes. */\nexport function aiUsageMonthWithinRetention(\n month: string,\n now: Date = new Date(),\n): boolean {\n return isAiUsageMonthKey(month) && aiUsageMonthKeys(now).includes(month)\n}\n\n/**\n * When a month document expires: the first instant of the month that is\n * `AI_USAGE_BY_USER_RETENTION_MONTHS` after the one it describes has closed.\n *\n * A `Date`, not a number: the TTL policy keys on a Firestore Timestamp and\n * silently governs nothing when the field is a number. Deterministic per\n * month, so every increment into the document re-stamps the same value and\n * the policy never moves under a month that is still being written.\n */\nexport function aiUsageByUserExpiry(month: string): Date {\n if (!isAiUsageMonthKey(month)) {\n throw new Error(`not a month key: ${month}`)\n }\n const [year, monthOfYear] = month.split('-').map(Number)\n return new Date(\n Date.UTC(year, monthOfYear + AI_USAGE_BY_USER_RETENTION_MONTHS, 1),\n )\n}\n\n/**\n * The kind an assist request was, read off the route it arrived on — for the\n * meters that carry a route and no kind of their own. The three besigner\n * modes name themselves in the path; everything else is a chat turn.\n */\nexport function aiUsageKindFromRoute(route: string | null | undefined): AiUsageKind {\n const tail = String(route ?? '').split('/').filter(Boolean).pop() ?? ''\n if (tail === 'element' || tail === 'blog' || tail === 'section') return tail\n return 'assist'\n}\n\n/**\n * One person's share of the workspace's spend for the month, in `[0, 1]`.\n *\n * Measured on the dollar figures, never on credits: the org meter rounds the\n * month's spend up ONCE, while a person's credits round each request up, so\n * a roster's credits can sum to a little more than the pool they drew from.\n * The ratio of measured spends has no such drift. Clamped, because a month\n * document written a moment after the org rollup is still a fraction, not a\n * percentage over one hundred — and rounded to a basis point, which is finer\n * than any surface renders and coarser than floating-point noise.\n */\nexport function aiUsageShare(\n userEstCostUsd: number,\n orgEstCostUsd: number,\n): number {\n const user = Number(userEstCostUsd)\n const org = Number(orgEstCostUsd)\n if (!Number.isFinite(user) || user <= 0) return 0\n if (!Number.isFinite(org) || org <= 0) return 0\n return Math.min(1, Math.round((user / org) * 10_000) / 10_000)\n}\n\nconst finite = (value: unknown): number => {\n const parsed = Number(value ?? 0)\n return Number.isFinite(parsed) && parsed > 0 ? parsed : 0\n}\n\nconst creditMap = (raw: unknown): Record<string, number> => {\n const out: Record<string, number> = {}\n for (const [key, value] of Object.entries(\n (raw ?? {}) as Record<string, unknown>,\n )) {\n const credits = Math.floor(finite(value))\n if (credits > 0) out[key] = credits\n }\n return out\n}\n\n/**\n * A month document as a complete record. Absent and malformed fields read as\n * zero; a `credits` the writer never set is derived from the spend, so a\n * document written before the field existed still ranks.\n */\nexport function aiUsageByUserMonthFrom(\n raw: Record<string, unknown> | null | undefined,\n uid: string,\n month: string,\n): AiUsageByUserMonth {\n const estCostUsd = finite(raw?.['estCostUsd'])\n const stored = Math.floor(finite(raw?.['credits']))\n return {\n uid,\n month,\n credits: stored > 0 ? stored : assistCreditsFromUsd(estCostUsd),\n estCostUsd,\n requests: Math.floor(finite(raw?.['requests'])),\n refusals: Math.floor(finite(raw?.['refusals'])),\n byKind: creditMap(raw?.['byKind']),\n byHost: creditMap(raw?.['byHost']),\n }\n}\n\n/** The kinds a person drew the most credits on, dearest first. */\nexport function topAiUsageKinds(\n byKind: Record<string, number>,\n limit = 3,\n): Array<{ kind: string; credits: number }> {\n return Object.entries(byKind)\n .map(([kind, credits]) => ({ kind, credits }))\n .sort((a, b) => b.credits - a.credits || a.kind.localeCompare(b.kind))\n .slice(0, limit)\n}\n\n/** Two month documents for one person, summed — the roster column's figure. */\nexport function sumAiUsageCredits(months: readonly AiUsageByUserMonth[]): number {\n return months.reduce((sum, entry) => sum + entry.credits, 0)\n}\n"],"names":["assistCreditsFromUsd","AI_USAGE_BY_USER_COLLECTION","AI_USAGE_BY_USER_MONTHS_COLLECTION","AI_USAGE_BY_USER_RETENTION_MONTHS","MONTH_KEY","isAiUsageMonthKey","value","test","aiUsageMonthKeys","now","Date","count","keys","back","push","UTC","getUTCFullYear","getUTCMonth","toISOString","slice","aiUsageMonthWithinRetention","month","includes","aiUsageByUserExpiry","Error","year","monthOfYear","split","map","Number","aiUsageKindFromRoute","route","String","tail","filter","Boolean","pop","aiUsageShare","userEstCostUsd","orgEstCostUsd","user","org","isFinite","Math","min","round","finite","parsed","creditMap","raw","out","key","Object","entries","credits","floor","aiUsageByUserMonthFrom","uid","estCostUsd","stored","requests","refusals","byKind","byHost","topAiUsageKinds","limit","kind","sort","a","b","localeCompare","sumAiUsageCredits","months","reduce","sum","entry"],"mappings":"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"}
@@ -20,7 +20,7 @@ import type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-bi
20
20
  * A `workflow` job (AGL-2919): an automation drafted from a description, or an
21
21
  * existing automation explained — what it does, or why one of its runs failed.
22
22
  *
23
- * One kind, three modes, because the three share everything a job carries:
23
+ * One kind, four modes, because the four share everything a job carries:
24
24
  * the site, the gates, the meter and the automation vocabulary. `inputs.mode`
25
25
  * picks the mode, and a job created without one drafts.
26
26
  *
@@ -32,17 +32,44 @@ import type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-bi
32
32
  * answers in plain words what it does.
33
33
  * - `diagnose` reads a saved automation and one of its FAILED runs, as the
34
34
  * run history recorded it, and answers why it failed and what to change.
35
+ * - `revise` reads a saved ACTION and a change asked of it — "fix it", or
36
+ * "also tag them" — and writes the action as it would be after the change
37
+ * as a NEW draft, OFF, through the same draft writer and vocabulary as
38
+ * `draft`. The saved action is never written: the person compares the two,
39
+ * switches the new one on and the old one off. An action that holds what
40
+ * the vocabulary cannot write (an on-page step, a page event, a filter
41
+ * expression) is refused rather than revised without it.
35
42
  *
36
- * Nothing in any mode switches an automation on, runs one, or changes one.
43
+ * `draft` has a second scope (AGL-3603): `scope: 'org'` drafts one of the
44
+ * WORKSPACE's automations — the ones the workflows plugin's Org automations
45
+ * section places on several sites — with no site named. Its vocabulary is
46
+ * the narrower one that section's zone hands the widget (`triggers` and
47
+ * `steps`, each a comma-separated list, intersected here with the drafting
48
+ * vocabulary below), its records are the workspace's, and nothing is written:
49
+ * the automation rides on an `orgAutomation` output as a proposal that the
50
+ * section's editor opens unsaved, switched off, where the person picks its
51
+ * sites and saves it through that section's own route.
52
+ *
53
+ * Nothing in any mode switches an automation on, runs one, or changes a saved
54
+ * one.
37
55
  */
38
- export declare const AI_WORKFLOW_JOB_MODES: readonly ["draft", "explain", "diagnose"];
56
+ export declare const AI_WORKFLOW_JOB_MODES: readonly ["draft", "explain", "diagnose", "revise"];
39
57
  export type AiWorkflowJobMode = (typeof AI_WORKFLOW_JOB_MODES)[number];
40
58
  /** What an explained automation is: an action, or a workflow of function calls. */
41
59
  export declare const AI_WORKFLOW_TARGET_TYPES: readonly ["action", "workflow"];
42
60
  export type AiWorkflowTargetType = (typeof AI_WORKFLOW_TARGET_TYPES)[number];
61
+ /** What an org automation may start on and do, as the zone that asks for one says. */
62
+ export interface AiOrgAutomationVocabulary {
63
+ triggers: HostEventType[];
64
+ steps: AiAutomationStepType[];
65
+ }
43
66
  export type AiWorkflowJobInputs = {
44
67
  mode: 'draft';
45
- } | {
68
+ scope?: 'site';
69
+ } | ({
70
+ mode: 'draft';
71
+ scope: 'org';
72
+ } & AiOrgAutomationVocabulary) | {
46
73
  mode: 'explain';
47
74
  targetType: AiWorkflowTargetType;
48
75
  targetId: string;
@@ -51,9 +78,25 @@ export type AiWorkflowJobInputs = {
51
78
  targetType: AiWorkflowTargetType;
52
79
  targetId: string;
53
80
  runId: string;
81
+ } | {
82
+ mode: 'revise';
83
+ targetType: 'action';
84
+ targetId: string;
54
85
  };
55
86
  /** A job's inputs read into its mode, or the sentence that says what is wrong with them. */
56
87
  export declare function parseAiWorkflowJobInputs(inputs: Readonly<Record<string, unknown>> | null | undefined): AiWorkflowJobInputs | string;
88
+ /**
89
+ * An org automation's vocabulary from a job's inputs: the triggers and steps
90
+ * the zone named that the drafting vocabulary also has, or `null` when that
91
+ * leaves no trigger or no step. The zone can only narrow what is drafted;
92
+ * the section's save route holds what is saved to its own rules.
93
+ */
94
+ export declare function readAiOrgAutomationVocabulary(inputs: Readonly<Record<string, unknown>> | null | undefined): AiOrgAutomationVocabulary | null;
95
+ /** An org automation's vocabulary as a job's inputs carry it. */
96
+ export declare function aiOrgAutomationInputs(vocabulary: {
97
+ triggers: readonly string[];
98
+ steps: readonly string[];
99
+ }): Record<string, string>;
57
100
  /** A part of the platform some triggers and steps need beyond the actions builder itself. */
58
101
  export type AiAutomationNeed = 'crm' | 'webhooks' | 'bookings';
59
102
  export type AiAutomationCapabilities = Readonly<Record<AiAutomationNeed, boolean>>;
@@ -107,3 +150,48 @@ export declare const AI_WORKFLOW_RUN_NOT_FAILED_COPY = "That run did not fail, s
107
150
  export declare const AI_WORKFLOW_NO_DRAFT_COPY = "The AI could not draft an automation from this description. Try describing what happens, and what should happen then.";
108
151
  export declare const AI_WORKFLOW_NO_EXPLANATION_COPY = "The AI could not explain this automation. Try again.";
109
152
  export declare const AI_WORKFLOW_SAVE_FAILURE_COPY = "The automation was drafted but could not be saved. Try the job again.";
153
+ export declare const AI_ORG_AUTOMATION_NO_VOCABULARY_COPY = "Open Org automations to draft one: the page says what an org automation can start on and do.";
154
+ export declare const AI_ORG_AUTOMATION_UNAVAILABLE_COPY = "Turn on Automation for this workspace before drafting an org automation.";
155
+ export declare const AI_ORG_AUTOMATION_PLAN_COPY = "Org automations are built from the actions builder, which this workspace\u2019s plan does not include. Upgrade in Billing to build them.";
156
+ /**
157
+ * The output resource an org automation's proposal rides on (AGL-3603): never
158
+ * a document — the Org automations editor opens it unsaved.
159
+ */
160
+ export declare const AI_ORG_AUTOMATION_RESOURCE = "orgAutomation";
161
+ /** The automation an `orgAutomation` output proposes, as the editor opens it. */
162
+ export interface AiOrgAutomationProposal {
163
+ name: string;
164
+ trigger: {
165
+ event: string;
166
+ conditions?: Array<{
167
+ field: string;
168
+ op: string;
169
+ value?: string;
170
+ }>;
171
+ combinator?: 'and' | 'or';
172
+ };
173
+ steps: Array<Record<string, unknown> & {
174
+ type: string;
175
+ }>;
176
+ }
177
+ /** An output's proposal read back, or `null` for one that is not an org automation. */
178
+ export declare function readAiOrgAutomationProposal(value: unknown): AiOrgAutomationProposal | null;
179
+ export declare const AI_WORKFLOW_REVISE_WORKFLOW_COPY = "Only an action can be changed with AI. A workflow can be explained, and edited in its own editor.";
180
+ /** Why a saved action cannot be revised, each with the sentence a person reads. */
181
+ export declare const AI_WORKFLOW_REVISE_BLOCKERS: readonly ["page-event", "page-step", "filter"];
182
+ export type AiWorkflowReviseBlocker = (typeof AI_WORKFLOW_REVISE_BLOCKERS)[number];
183
+ export declare const AI_WORKFLOW_REVISE_BLOCKER_COPY: Readonly<Record<AiWorkflowReviseBlocker, string>>;
184
+ /**
185
+ * Why a saved action cannot be revised, or `null` when every part of it is
186
+ * one the drafting vocabulary writes — so the revised draft can hold the
187
+ * whole action and lose nothing the person did not ask to change.
188
+ */
189
+ export declare function aiActionReviseBlocker(action: {
190
+ trigger?: {
191
+ event?: unknown;
192
+ filter?: unknown;
193
+ } | null;
194
+ steps?: ReadonlyArray<{
195
+ type?: unknown;
196
+ } | null | undefined> | null;
197
+ }): AiWorkflowReviseBlocker | null;