@aglyn/plugins-ai 1.0.0-beta.230 → 1.0.0-beta.232

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 (305) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.js +8 -4
  3. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  4. package/src/lib/components/ai-assist-provider-on-host.component.d.ts +66 -0
  5. package/src/lib/components/ai-assist-provider-on-host.component.js +78 -0
  6. package/src/lib/components/ai-assist-provider-on-host.component.js.map +1 -0
  7. package/src/lib/components/ai-assist-provider.component.js +9 -10
  8. package/src/lib/components/ai-assist-provider.component.js.map +1 -1
  9. package/src/lib/components/ai-job-follow.component.js +1 -0
  10. package/src/lib/components/ai-job-follow.component.js.map +1 -1
  11. package/src/lib/components/ai-job-links.js +13 -0
  12. package/src/lib/components/ai-job-links.js.map +1 -1
  13. package/src/lib/components/ai-job-plan.component.js +3 -1
  14. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  15. package/src/lib/components/ai-jobs-list-page.component.js +1 -0
  16. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  17. package/src/lib/components/ai-media-create.component.d.ts +20 -7
  18. package/src/lib/components/ai-media-create.component.js +107 -55
  19. package/src/lib/components/ai-media-create.component.js.map +1 -1
  20. package/src/lib/components/ai-permissions-on-host.component.d.ts +3 -46
  21. package/src/lib/components/ai-permissions-on-host.component.js +5 -53
  22. package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
  23. package/src/lib/components/ai-seo-fields-card.component.js +1 -0
  24. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  25. package/src/lib/components/ai-site-build-page.component.js +20 -8
  26. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  27. package/src/lib/components/ai-site-memory-card.component.d.ts +32 -0
  28. package/src/lib/components/ai-site-memory-card.component.js +187 -0
  29. package/src/lib/components/ai-site-memory-card.component.js.map +1 -0
  30. package/src/lib/components/ai-site-start-card.component.js +80 -77
  31. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  32. package/src/lib/components/ai-theme-proposal-card.component.js +4 -0
  33. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  34. package/src/lib/components/ai-usage-strip.component.js +1 -0
  35. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  36. package/src/lib/components/assist-panel.component.d.ts +10 -0
  37. package/src/lib/components/assist-panel.component.js +119 -9
  38. package/src/lib/components/assist-panel.component.js.map +1 -1
  39. package/src/lib/components/assist-signals-page.component.js +2 -0
  40. package/src/lib/components/assist-signals-page.component.js.map +1 -1
  41. package/src/lib/components/billing-ai-allotments.component.js +3 -0
  42. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  43. package/src/lib/components/billing-ai-top-users.component.js +1 -0
  44. package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
  45. package/src/lib/components/billing-assist-overage-card.component.js +2 -0
  46. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  47. package/src/lib/components/member-ai-usage-card.component.js +10 -0
  48. package/src/lib/components/member-ai-usage-card.component.js.map +1 -1
  49. package/src/lib/components/staff-org-ai-card.component.js +1 -0
  50. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  51. package/src/lib/components/staff-org-ai-conversations.component.d.ts +23 -0
  52. package/src/lib/components/staff-org-ai-conversations.component.js +346 -0
  53. package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -0
  54. package/src/lib/jobs/ai-build-capabilities.d.ts +13 -1
  55. package/src/lib/jobs/ai-build-capabilities.js +101 -7
  56. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  57. package/src/lib/jobs/ai-build-unit-outcome.js +4 -2
  58. package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -1
  59. package/src/lib/jobs/ai-business-profile-prefill.d.ts +29 -0
  60. package/src/lib/jobs/ai-business-profile-prefill.js +85 -0
  61. package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -0
  62. package/src/lib/jobs/ai-job-auto-confirm.d.ts +8 -0
  63. package/src/lib/jobs/ai-job-auto-confirm.js +30 -6
  64. package/src/lib/jobs/ai-job-auto-confirm.js.map +1 -1
  65. package/src/lib/jobs/ai-job-build-step.js +14 -1
  66. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  67. package/src/lib/jobs/ai-job-edit-step.d.ts +225 -0
  68. package/src/lib/jobs/ai-job-edit-step.js +631 -0
  69. package/src/lib/jobs/ai-job-edit-step.js.map +1 -0
  70. package/src/lib/jobs/ai-job-generation.d.ts +8 -0
  71. package/src/lib/jobs/ai-job-generation.js +13 -2
  72. package/src/lib/jobs/ai-job-generation.js.map +1 -1
  73. package/src/lib/jobs/ai-job-insight-step.js +29 -5
  74. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  75. package/src/lib/jobs/ai-job-layout-language.d.ts +78 -0
  76. package/src/lib/jobs/ai-job-layout-language.js +200 -0
  77. package/src/lib/jobs/ai-job-layout-language.js.map +1 -0
  78. package/src/lib/jobs/ai-job-layout-site-pages.d.ts +13 -2
  79. package/src/lib/jobs/ai-job-layout-site-pages.js +18 -2
  80. package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
  81. package/src/lib/jobs/ai-job-layout-step.d.ts +5 -0
  82. package/src/lib/jobs/ai-job-layout-step.js +107 -6
  83. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  84. package/src/lib/jobs/ai-job-logic-step.d.ts +20 -0
  85. package/src/lib/jobs/ai-job-logic-step.js +87 -6
  86. package/src/lib/jobs/ai-job-logic-step.js.map +1 -1
  87. package/src/lib/jobs/ai-job-page-language.d.ts +194 -0
  88. package/src/lib/jobs/ai-job-page-language.js +418 -0
  89. package/src/lib/jobs/ai-job-page-language.js.map +1 -0
  90. package/src/lib/jobs/ai-job-page-sections.d.ts +37 -1
  91. package/src/lib/jobs/ai-job-page-sections.js +93 -11
  92. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  93. package/src/lib/jobs/ai-job-page-step.d.ts +4 -4
  94. package/src/lib/jobs/ai-job-page-step.js +115 -14
  95. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  96. package/src/lib/jobs/ai-job-plan-step.d.ts +20 -6
  97. package/src/lib/jobs/ai-job-plan-step.js +62 -22
  98. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  99. package/src/lib/jobs/ai-job-site-content.d.ts +163 -0
  100. package/src/lib/jobs/ai-job-site-content.js +443 -0
  101. package/src/lib/jobs/ai-job-site-content.js.map +1 -0
  102. package/src/lib/jobs/ai-job-site-look.d.ts +112 -0
  103. package/src/lib/jobs/ai-job-site-look.js +322 -0
  104. package/src/lib/jobs/ai-job-site-look.js.map +1 -0
  105. package/src/lib/jobs/ai-job-site-step.d.ts +30 -5
  106. package/src/lib/jobs/ai-job-site-step.js +183 -27
  107. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  108. package/src/lib/jobs/ai-job-workflow-step.js +2 -2
  109. package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
  110. package/src/lib/jobs/ai-jobs-notify.d.ts +6 -1
  111. package/src/lib/jobs/ai-jobs-notify.js +15 -3
  112. package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
  113. package/src/lib/jobs/ai-jobs.d.ts +1 -1
  114. package/src/lib/jobs/ai-jobs.js +17 -8
  115. package/src/lib/jobs/ai-jobs.js.map +1 -1
  116. package/src/lib/jobs/ai-layout-full-height.d.ts +40 -0
  117. package/src/lib/jobs/ai-layout-full-height.js +82 -0
  118. package/src/lib/jobs/ai-layout-full-height.js.map +1 -0
  119. package/src/lib/jobs/ai-layout-site-facts.d.ts +54 -0
  120. package/src/lib/jobs/ai-layout-site-facts.js +153 -0
  121. package/src/lib/jobs/ai-layout-site-facts.js.map +1 -0
  122. package/src/lib/layout-language/ai-layout-compiler.d.ts +154 -0
  123. package/src/lib/layout-language/ai-layout-compiler.js +1235 -0
  124. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -0
  125. package/src/lib/layout-language/ai-layout-copy.d.ts +73 -0
  126. package/src/lib/layout-language/ai-layout-copy.js +137 -0
  127. package/src/lib/layout-language/ai-layout-copy.js.map +1 -0
  128. package/src/lib/layout-language/ai-layout-frame.d.ts +52 -0
  129. package/src/lib/layout-language/ai-layout-frame.js +458 -0
  130. package/src/lib/layout-language/ai-layout-frame.js.map +1 -0
  131. package/src/lib/layout-language/ai-layout-gaps.d.ts +63 -0
  132. package/src/lib/layout-language/ai-layout-gaps.js +345 -0
  133. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -0
  134. package/src/lib/layout-language/ai-layout-language.d.ts +168 -0
  135. package/src/lib/layout-language/ai-layout-language.js +529 -0
  136. package/src/lib/layout-language/ai-layout-language.js.map +1 -0
  137. package/src/lib/layout-language/ai-layout-links.d.ts +80 -0
  138. package/src/lib/layout-language/ai-layout-links.js +109 -0
  139. package/src/lib/layout-language/ai-layout-links.js.map +1 -0
  140. package/src/lib/layout-language/ai-layout-pictures.d.ts +144 -0
  141. package/src/lib/layout-language/ai-layout-pictures.js +214 -0
  142. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -0
  143. package/src/lib/layout-language/ai-layout-store.d.ts +41 -0
  144. package/src/lib/layout-language/ai-layout-store.js +75 -0
  145. package/src/lib/layout-language/ai-layout-store.js.map +1 -0
  146. package/src/lib/layout-language/ai-layout-tree.d.ts +59 -0
  147. package/src/lib/layout-language/ai-layout-tree.js +84 -0
  148. package/src/lib/layout-language/ai-layout-tree.js.map +1 -0
  149. package/src/lib/lazy-widget.d.ts +45 -0
  150. package/src/lib/lazy-widget.js +53 -0
  151. package/src/lib/lazy-widget.js.map +1 -0
  152. package/src/lib/model/ai-build-job.js +2 -1
  153. package/src/lib/model/ai-build-job.js.map +1 -1
  154. package/src/lib/model/ai-build-progress.d.ts +20 -3
  155. package/src/lib/model/ai-build-progress.js +52 -7
  156. package/src/lib/model/ai-build-progress.js.map +1 -1
  157. package/src/lib/model/ai-edit-job.d.ts +109 -0
  158. package/src/lib/model/ai-edit-job.js +157 -0
  159. package/src/lib/model/ai-edit-job.js.map +1 -0
  160. package/src/lib/model/ai-host-publish-context.d.ts +51 -0
  161. package/src/lib/model/ai-host-publish-context.js +104 -0
  162. package/src/lib/model/ai-host-publish-context.js.map +1 -0
  163. package/src/lib/model/ai-job-activity.d.ts +22 -0
  164. package/src/lib/model/ai-job-activity.js +23 -1
  165. package/src/lib/model/ai-job-activity.js.map +1 -1
  166. package/src/lib/model/ai-job-failure-copy.d.ts +38 -1
  167. package/src/lib/model/ai-job-failure-copy.js +56 -1
  168. package/src/lib/model/ai-job-failure-copy.js.map +1 -1
  169. package/src/lib/model/ai-job-notice.d.ts +3 -1
  170. package/src/lib/model/ai-job-notice.js +31 -9
  171. package/src/lib/model/ai-job-notice.js.map +1 -1
  172. package/src/lib/model/ai-jobs.types.d.ts +22 -1
  173. package/src/lib/model/ai-jobs.types.js.map +1 -1
  174. package/src/lib/model/ai-logic-job.d.ts +18 -1
  175. package/src/lib/model/ai-logic-job.js +24 -3
  176. package/src/lib/model/ai-logic-job.js.map +1 -1
  177. package/src/lib/model/ai-media-credits.d.ts +8 -4
  178. package/src/lib/model/ai-media-credits.js +11 -4
  179. package/src/lib/model/ai-media-credits.js.map +1 -1
  180. package/src/lib/model/ai-media-kinds.d.ts +68 -0
  181. package/src/lib/model/ai-media-kinds.js +98 -0
  182. package/src/lib/model/ai-media-kinds.js.map +1 -0
  183. package/src/lib/model/ai-site-build-progress.d.ts +9 -1
  184. package/src/lib/model/ai-site-build-progress.js +54 -14
  185. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  186. package/src/lib/model/ai-site-context.d.ts +111 -0
  187. package/src/lib/model/ai-site-context.js +222 -0
  188. package/src/lib/model/ai-site-context.js.map +1 -0
  189. package/src/lib/model/ai-site-job.d.ts +18 -2
  190. package/src/lib/model/ai-site-job.js +21 -5
  191. package/src/lib/model/ai-site-job.js.map +1 -1
  192. package/src/lib/model/ai-site-kinds.d.ts +164 -0
  193. package/src/lib/model/ai-site-kinds.js +1942 -0
  194. package/src/lib/model/ai-site-kinds.js.map +1 -0
  195. package/src/lib/model/ai-site-look.d.ts +150 -0
  196. package/src/lib/model/ai-site-look.js +1236 -0
  197. package/src/lib/model/ai-site-look.js.map +1 -0
  198. package/src/lib/model/ai-site-memory.d.ts +80 -0
  199. package/src/lib/model/ai-site-memory.js +171 -0
  200. package/src/lib/model/ai-site-memory.js.map +1 -0
  201. package/src/lib/model/ai-site-start-seo.d.ts +1 -1
  202. package/src/lib/model/ai-site-start.d.ts +15 -3
  203. package/src/lib/model/ai-site-start.js +21 -4
  204. package/src/lib/model/ai-site-start.js.map +1 -1
  205. package/src/lib/model/assist-follow-up.d.ts +92 -0
  206. package/src/lib/model/assist-follow-up.js +128 -0
  207. package/src/lib/model/assist-follow-up.js.map +1 -0
  208. package/src/lib/plugin-config.js +2 -0
  209. package/src/lib/plugin-config.js.map +1 -1
  210. package/src/lib/plugin.js +99 -48
  211. package/src/lib/plugin.js.map +1 -1
  212. package/src/lib/providers/anthropic.d.ts +9 -1
  213. package/src/lib/providers/anthropic.js +143 -9
  214. package/src/lib/providers/anthropic.js.map +1 -1
  215. package/src/lib/providers/catalog.d.ts +38 -8
  216. package/src/lib/providers/catalog.js +106 -14
  217. package/src/lib/providers/catalog.js.map +1 -1
  218. package/src/lib/providers/contract.d.ts +29 -0
  219. package/src/lib/providers/contract.js.map +1 -1
  220. package/src/lib/providers/image-contract.d.ts +22 -1
  221. package/src/lib/providers/image-contract.js +40 -1
  222. package/src/lib/providers/image-contract.js.map +1 -1
  223. package/src/lib/providers/image-endpoints.d.ts +24 -0
  224. package/src/lib/providers/image-endpoints.js +28 -0
  225. package/src/lib/providers/image-endpoints.js.map +1 -0
  226. package/src/lib/providers/model-choice.js +23 -2
  227. package/src/lib/providers/model-choice.js.map +1 -1
  228. package/src/lib/providers/routing.js +18 -0
  229. package/src/lib/providers/routing.js.map +1 -1
  230. package/src/lib/providers/vertex-image-endpoint.d.ts +48 -0
  231. package/src/lib/providers/vertex-image-endpoint.js +80 -0
  232. package/src/lib/providers/vertex-image-endpoint.js.map +1 -0
  233. package/src/lib/providers/vertex-image.d.ts +13 -27
  234. package/src/lib/providers/vertex-image.js +15 -42
  235. package/src/lib/providers/vertex-image.js.map +1 -1
  236. package/src/lib/runtime/ai-blog-post-generation.d.ts +62 -0
  237. package/src/lib/runtime/ai-blog-post-generation.js +89 -0
  238. package/src/lib/runtime/ai-blog-post-generation.js.map +1 -0
  239. package/src/lib/runtime/ai-dev-env.d.ts +53 -0
  240. package/src/lib/runtime/ai-dev-env.js +72 -0
  241. package/src/lib/runtime/ai-dev-env.js.map +1 -0
  242. package/src/lib/runtime/ai-dev-replay.d.ts +116 -0
  243. package/src/lib/runtime/ai-dev-replay.js +313 -0
  244. package/src/lib/runtime/ai-dev-replay.js.map +1 -0
  245. package/src/lib/runtime/ai-doctrine-validators.d.ts +82 -0
  246. package/src/lib/runtime/ai-doctrine-validators.js +436 -4
  247. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  248. package/src/lib/runtime/ai-doctrine.js +16 -3
  249. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  250. package/src/lib/runtime/ai-eval-live.js +4 -0
  251. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  252. package/src/lib/runtime/ai-eval.d.ts +13 -1
  253. package/src/lib/runtime/ai-eval.js +84 -1
  254. package/src/lib/runtime/ai-eval.js.map +1 -1
  255. package/src/lib/runtime/ai-icon-library.d.ts +257 -0
  256. package/src/lib/runtime/ai-icon-library.js +254 -0
  257. package/src/lib/runtime/ai-icon-library.js.map +1 -0
  258. package/src/lib/runtime/ai-live-batch.d.ts +45 -0
  259. package/src/lib/runtime/ai-live-batch.js +89 -0
  260. package/src/lib/runtime/ai-live-batch.js.map +1 -0
  261. package/src/lib/runtime/ai-node-tree.d.ts +9 -0
  262. package/src/lib/runtime/ai-node-tree.js +30 -4
  263. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  264. package/src/lib/runtime/ai-palette.d.ts +6 -0
  265. package/src/lib/runtime/ai-palette.generated.js +70 -0
  266. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  267. package/src/lib/runtime/ai-palette.js.map +1 -1
  268. package/src/lib/runtime/ai-runtime.js +46 -2
  269. package/src/lib/runtime/ai-runtime.js.map +1 -1
  270. package/src/lib/runtime/site-context.d.ts +58 -0
  271. package/src/lib/runtime/site-context.js +127 -0
  272. package/src/lib/runtime/site-context.js.map +1 -0
  273. package/src/lib/server/ai-admin-conversations.d.ts +22 -0
  274. package/src/lib/server/ai-admin-conversations.js +197 -0
  275. package/src/lib/server/ai-admin-conversations.js.map +1 -0
  276. package/src/lib/server/ai-media-image.d.ts +5 -5
  277. package/src/lib/server/ai-media-image.js +25 -11
  278. package/src/lib/server/ai-media-image.js.map +1 -1
  279. package/src/lib/server/ai-media-raster-prompt.d.ts +34 -0
  280. package/src/lib/server/ai-media-raster-prompt.js +53 -0
  281. package/src/lib/server/ai-media-raster-prompt.js.map +1 -0
  282. package/src/lib/server/assist-chat.d.ts +11 -0
  283. package/src/lib/server/assist-chat.js +57 -18
  284. package/src/lib/server/assist-chat.js.map +1 -1
  285. package/src/lib/server/assist-edit-applied.js +16 -3
  286. package/src/lib/server/assist-edit-applied.js.map +1 -1
  287. package/src/lib/server/assist-view-context.d.ts +19 -1
  288. package/src/lib/server/assist-view-context.js +38 -1
  289. package/src/lib/server/assist-view-context.js.map +1 -1
  290. package/src/lib/server.js +10 -0
  291. package/src/lib/server.js.map +1 -1
  292. package/src/lib/subprocessors.js +17 -1
  293. package/src/lib/subprocessors.js.map +1 -1
  294. package/src/lib/tools/ai-blog-post-tool.d.ts +60 -0
  295. package/src/lib/tools/ai-blog-post-tool.js +175 -0
  296. package/src/lib/tools/ai-blog-post-tool.js.map +1 -0
  297. package/src/lib/tools/ai-workflow-tool.d.ts +6 -2
  298. package/src/lib/tools/ai-workflow-tool.js +16 -14
  299. package/src/lib/tools/ai-workflow-tool.js.map +1 -1
  300. package/src/lib/usage/assist-usage.d.ts +3 -0
  301. package/src/lib/usage/assist-usage.js +4 -2
  302. package/src/lib/usage/assist-usage.js.map +1 -1
  303. package/src/lib/usage/staff-org-ai-conversations.d.ts +103 -0
  304. package/src/lib/usage/staff-org-ai-conversations.js +152 -0
  305. package/src/lib/usage/staff-org-ai-conversations.js.map +1 -0
@@ -0,0 +1,60 @@
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
+ */
17
+ import type { AiTool } from '../providers/contract';
18
+ import type { AiGenerationCheckResult } from '../runtime/ai-doctrine';
19
+ /**
20
+ * The strict tool a site's first posts are written through (AGL-3676), and
21
+ * the check that holds each post to what the site's collection publishes.
22
+ *
23
+ * A post is published with the site, as its own words, so nothing in it may
24
+ * mark a gap for later: no square brackets, no link, no markup the entry
25
+ * renderer does not read. Its body is the markdown-lite the entry editor
26
+ * writes — `##` subheadings, `-` lists, `**bold**`, `*italic*` — and its
27
+ * title, excerpt and search description are plain text. The storefront copy
28
+ * rules hold every part (no health, financial or legal promise; no
29
+ * certification, award or endorsement the brief does not state; no price),
30
+ * and so does a rule of its own: no star rating, review score or testimonial
31
+ * nobody gave.
32
+ */
33
+ export declare const AI_BLOG_POST_TOOL_NAME = "write_blog_post";
34
+ /** A post's bounds, as the tool states them and its check holds them. */
35
+ export declare const AI_BLOG_POST_LIMITS: {
36
+ readonly titleMax: 80;
37
+ readonly excerptMax: 200;
38
+ readonly bodyMin: 600;
39
+ readonly bodyMax: 4000;
40
+ readonly seoDescriptionMax: number;
41
+ };
42
+ /** A post as its check reads it. */
43
+ export interface AiBlogPost {
44
+ title: string;
45
+ excerpt: string;
46
+ /** Markdown-lite. */
47
+ body: string;
48
+ seoDescription: string;
49
+ }
50
+ export declare const AI_BLOG_POST_TOOL: AiTool;
51
+ /**
52
+ * A post's answer held to the collection it is published in and the copy
53
+ * rules. `merchantWords` are the brief and the site's own answers, the only
54
+ * source a stated claim or price may come from; `earlierTitles` are the posts
55
+ * already written, which this one must not repeat.
56
+ */
57
+ export declare function checkAiBlogPost(answer: Record<string, unknown>, context: {
58
+ merchantWords: string;
59
+ earlierTitles?: readonly string[];
60
+ }): AiGenerationCheckResult<AiBlogPost>;
@@ -0,0 +1,175 @@
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 { SEO_LISTING_FIELDS } from "@aglyn/aglyn/app-utils/seo-listing-fields";
17
+ import { findInventedPrices, findStorefrontClaims, storefrontClaimViolations } from "../model/ai-storefront-claims.js";
18
+ import { detectOffVoiceCopy } from "../runtime/ai-doctrine-validators.js";
19
+ /**
20
+ * The strict tool a site's first posts are written through (AGL-3676), and
21
+ * the check that holds each post to what the site's collection publishes.
22
+ *
23
+ * A post is published with the site, as its own words, so nothing in it may
24
+ * mark a gap for later: no square brackets, no link, no markup the entry
25
+ * renderer does not read. Its body is the markdown-lite the entry editor
26
+ * writes — `##` subheadings, `-` lists, `**bold**`, `*italic*` — and its
27
+ * title, excerpt and search description are plain text. The storefront copy
28
+ * rules hold every part (no health, financial or legal promise; no
29
+ * certification, award or endorsement the brief does not state; no price),
30
+ * and so does a rule of its own: no star rating, review score or testimonial
31
+ * nobody gave.
32
+ */ export const AI_BLOG_POST_TOOL_NAME = 'write_blog_post';
33
+ /** A post's bounds, as the tool states them and its check holds them. */ export const AI_BLOG_POST_LIMITS = {
34
+ titleMax: 80,
35
+ excerptMax: 200,
36
+ bodyMin: 600,
37
+ bodyMax: 4000,
38
+ seoDescriptionMax: SEO_LISTING_FIELDS.description.maxLength
39
+ };
40
+ const string = (description)=>({
41
+ type: 'string',
42
+ description
43
+ });
44
+ export const AI_BLOG_POST_TOOL = {
45
+ name: AI_BLOG_POST_TOOL_NAME,
46
+ description: 'Write one post of a new blog. Every field is required.',
47
+ strict: true,
48
+ inputSchema: {
49
+ type: 'object',
50
+ additionalProperties: false,
51
+ required: [
52
+ 'title',
53
+ 'excerpt',
54
+ 'body',
55
+ 'seoDescription'
56
+ ],
57
+ properties: {
58
+ title: string(`Plain text, at most ${AI_BLOG_POST_LIMITS.titleMax} characters.`),
59
+ excerpt: string(`One or two plain sentences, at most ${AI_BLOG_POST_LIMITS.excerptMax} characters.`),
60
+ body: string(`Markdown-lite, ${AI_BLOG_POST_LIMITS.bodyMin} to ${AI_BLOG_POST_LIMITS.bodyMax} characters: short paragraphs, ## subheadings, - lists, **bold**. No title line.`),
61
+ seoDescription: string(`One plain sentence, at most ${AI_BLOG_POST_LIMITS.seoDescriptionMax} characters.`)
62
+ }
63
+ }
64
+ };
65
+ function fail(findings, at, code, message, value) {
66
+ findings.violations.push({
67
+ rule: null,
68
+ code,
69
+ message,
70
+ paths: [
71
+ at
72
+ ]
73
+ });
74
+ if (!(at in findings.offending)) findings.offending[at] = value;
75
+ }
76
+ const line = (value)=>typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : '';
77
+ /** Paragraphs: spaces folded within a line, at most one blank line between paragraphs. */ const paragraphs = (value)=>typeof value === 'string' ? value.replace(/\r\n?/g, '\n').split('\n').map((part)=>part.replace(/[ \t\f\v]+/g, ' ').trimEnd()).join('\n').replace(/\n{3,}/g, '\n\n').trim() : '';
78
+ const HTML = /<\/?[a-z][^>]*>/i;
79
+ const PLAIN_MARKS = /(^|\n)\s*#{1,6}\s|\*\*[^*\n]+\*\*|__[^_\n]+__|`/;
80
+ const EMOJI = RegExp("[\\u00A9\\u00AE\\u203C\\u2049\\u2122\\u2139\\u2194-\\u2199\\u21A9-\\u21AA\\u231A-\\u231B\\u2328\\u23CF\\u23E9-\\u23F3\\u23F8-\\u23FA\\u24C2\\u25AA-\\u25AB\\u25B6\\u25C0\\u25FB-\\u25FE\\u2600-\\u2604\\u260E\\u2611\\u2614-\\u2615\\u2618\\u261D\\u2620\\u2622-\\u2623\\u2626\\u262A\\u262E-\\u262F\\u2638-\\u263A\\u2640\\u2642\\u2648-\\u2653\\u265F-\\u2660\\u2663\\u2665-\\u2666\\u2668\\u267B\\u267E-\\u267F\\u2692-\\u2697\\u2699\\u269B-\\u269C\\u26A0-\\u26A1\\u26A7\\u26AA-\\u26AB\\u26B0-\\u26B1\\u26BD-\\u26BE\\u26C4-\\u26C5\\u26C8\\u26CE-\\u26CF\\u26D1\\u26D3-\\u26D4\\u26E9-\\u26EA\\u26F0-\\u26F5\\u26F7-\\u26FA\\u26FD\\u2702\\u2705\\u2708-\\u270D\\u270F\\u2712\\u2714\\u2716\\u271D\\u2721\\u2728\\u2733-\\u2734\\u2744\\u2747\\u274C\\u274E\\u2753-\\u2755\\u2757\\u2763-\\u2764\\u2795-\\u2797\\u27A1\\u27B0\\u27BF\\u2934-\\u2935\\u2B05-\\u2B07\\u2B1B-\\u2B1C\\u2B50\\u2B55\\u3030\\u303D\\u3297\\u3299\\u{1F004}\\u{1F02C}-\\u{1F02F}\\u{1F094}-\\u{1F09F}\\u{1F0AF}-\\u{1F0B0}\\u{1F0C0}\\u{1F0CF}-\\u{1F0D0}\\u{1F0F6}-\\u{1F0FF}\\u{1F170}-\\u{1F171}\\u{1F17E}-\\u{1F17F}\\u{1F18E}\\u{1F191}-\\u{1F19A}\\u{1F1AE}-\\u{1F1E5}\\u{1F201}-\\u{1F20F}\\u{1F21A}\\u{1F22F}\\u{1F232}-\\u{1F23A}\\u{1F23C}-\\u{1F23F}\\u{1F249}-\\u{1F25F}\\u{1F266}-\\u{1F321}\\u{1F324}-\\u{1F393}\\u{1F396}-\\u{1F397}\\u{1F399}-\\u{1F39B}\\u{1F39E}-\\u{1F3F0}\\u{1F3F3}-\\u{1F3F5}\\u{1F3F7}-\\u{1F3FA}\\u{1F400}-\\u{1F4FD}\\u{1F4FF}-\\u{1F53D}\\u{1F549}-\\u{1F54E}\\u{1F550}-\\u{1F567}\\u{1F56F}-\\u{1F570}\\u{1F573}-\\u{1F57A}\\u{1F587}\\u{1F58A}-\\u{1F58D}\\u{1F590}\\u{1F595}-\\u{1F596}\\u{1F5A4}-\\u{1F5A5}\\u{1F5A8}\\u{1F5B1}-\\u{1F5B2}\\u{1F5BC}\\u{1F5C2}-\\u{1F5C4}\\u{1F5D1}-\\u{1F5D3}\\u{1F5DC}-\\u{1F5DE}\\u{1F5E1}\\u{1F5E3}\\u{1F5E8}\\u{1F5EF}\\u{1F5F3}\\u{1F5FA}-\\u{1F64F}\\u{1F680}-\\u{1F6C5}\\u{1F6CB}-\\u{1F6D2}\\u{1F6D5}-\\u{1F6E5}\\u{1F6E9}\\u{1F6EB}-\\u{1F6F0}\\u{1F6F3}-\\u{1F6FF}\\u{1F7DA}-\\u{1F7FF}\\u{1F80C}-\\u{1F80F}\\u{1F848}-\\u{1F84F}\\u{1F85A}-\\u{1F85F}\\u{1F888}-\\u{1F88F}\\u{1F8AE}-\\u{1F8AF}\\u{1F8BC}-\\u{1F8BF}\\u{1F8C2}-\\u{1F8CF}\\u{1F8D9}-\\u{1F8FF}\\u{1F90C}-\\u{1F93A}\\u{1F93C}-\\u{1F945}\\u{1F947}-\\u{1F9FF}\\u{1FA58}-\\u{1FA5F}\\u{1FA6E}-\\u{1FAFF}\\u{1FC00}-\\u{1FFFD}]", "u");
81
+ const BRACKET = /[[\]]/;
82
+ const URL = /\bhttps?:\/\/|\bwww\./i;
83
+ /** A heading deeper than the entry renderer's subheadings, or a title line. */ const TITLE_HEADING = /(^|\n)\s*#\s/;
84
+ /** A rating or review score nobody gave. */ const RATING = /\b\d(?:\.\d)?\s*(?:\/\s*(?:5|10)\b|out of (?:5|five|10|ten)\b|stars?\b)|\b(?:five|5|four|4)[- ]star\b|\brated\b|\breviews? (?:say|rave|agree)\b/i;
85
+ /** One plain-text field: there, within its length, unmarked. */ function checkPlain(findings, at, raw, label, max) {
86
+ if (typeof raw !== 'string') {
87
+ fail(findings, at, 'type', `${label} must be text.`, raw);
88
+ return '';
89
+ }
90
+ const value = line(raw);
91
+ if (!value) {
92
+ fail(findings, at, 'missing', `${label} is required.`, raw);
93
+ return '';
94
+ }
95
+ if (value.length > max) fail(findings, at, 'too-long', `${label} is ${value.length} characters; it may have ${max}.`, raw);
96
+ if (HTML.test(value) || PLAIN_MARKS.test(value)) fail(findings, at, 'markup', `${label} is plain text, with no formatting marks.`, raw);
97
+ if (EMOJI.test(value)) fail(findings, at, 'emoji', `${label} holds no emoji.`, raw);
98
+ if (URL.test(value)) fail(findings, at, 'link', `${label} holds no link.`, raw);
99
+ if (BRACKET.test(value)) {
100
+ fail(findings, at, 'bracket', `${label} is published as written, so it marks no missing fact in brackets.`, raw);
101
+ }
102
+ return value;
103
+ }
104
+ /**
105
+ * A post's answer held to the collection it is published in and the copy
106
+ * rules. `merchantWords` are the brief and the site's own answers, the only
107
+ * source a stated claim or price may come from; `earlierTitles` are the posts
108
+ * already written, which this one must not repeat.
109
+ */ export function checkAiBlogPost(answer, context) {
110
+ var _context_earlierTitles;
111
+ const findings = {
112
+ violations: [],
113
+ offending: {}
114
+ };
115
+ const limits = AI_BLOG_POST_LIMITS;
116
+ const title = checkPlain(findings, 'title', answer['title'], 'The title', limits.titleMax);
117
+ if (title && ((_context_earlierTitles = context.earlierTitles) != null ? _context_earlierTitles : []).some((earlier)=>earlier.trim().toLowerCase() === title.toLowerCase())) {
118
+ fail(findings, 'title', 'title-duplicate', 'Another post already has this title; write about something else.', answer['title']);
119
+ }
120
+ const excerpt = checkPlain(findings, 'excerpt', answer['excerpt'], 'The excerpt', limits.excerptMax);
121
+ const seoDescription = checkPlain(findings, 'seoDescription', answer['seoDescription'], 'The search description', limits.seoDescriptionMax);
122
+ const raw = answer['body'];
123
+ const body = paragraphs(raw);
124
+ if (typeof raw !== 'string' || !body) {
125
+ fail(findings, 'body', 'missing', 'The body is required.', raw);
126
+ } else {
127
+ if (body.length < limits.bodyMin || body.length > limits.bodyMax) {
128
+ fail(findings, 'body', 'length', `The body is ${body.length} characters; write ${limits.bodyMin} to ${limits.bodyMax}.`, raw);
129
+ }
130
+ if (HTML.test(body)) fail(findings, 'body', 'markup', 'The body is markdown-lite, with no HTML.', raw);
131
+ if (TITLE_HEADING.test(body)) fail(findings, 'body', 'title-line', 'The title renders on its own; the body uses ## subheadings only.', raw);
132
+ if (EMOJI.test(body)) fail(findings, 'body', 'emoji', 'The body holds no emoji.', raw);
133
+ if (URL.test(body)) fail(findings, 'body', 'link', 'The body holds no link.', raw);
134
+ if (BRACKET.test(body)) {
135
+ fail(findings, 'body', 'bracket', 'The body is published as written, so it holds no square brackets: no links and no missing fact marked.', raw);
136
+ }
137
+ }
138
+ const samples = [
139
+ {
140
+ at: 'title',
141
+ text: title
142
+ },
143
+ {
144
+ at: 'excerpt',
145
+ text: excerpt
146
+ },
147
+ {
148
+ at: 'body',
149
+ text: body
150
+ },
151
+ {
152
+ at: 'seoDescription',
153
+ text: seoDescription
154
+ }
155
+ ];
156
+ for (const sample of samples){
157
+ const rating = RATING.exec(sample.text);
158
+ if (rating && !context.merchantWords.toLowerCase().includes(rating[0].toLowerCase())) {
159
+ fail(findings, sample.at, 'rating-invented', `The post gives a rating or review nobody gave. Remove: "${rating[0]}".`, sample.text);
160
+ }
161
+ }
162
+ findings.violations.push(...storefrontClaimViolations(findStorefrontClaims(samples, context.merchantWords), findInventedPrices(samples, context.merchantWords)), ...detectOffVoiceCopy(samples, null));
163
+ return {
164
+ value: findings.violations.length ? null : {
165
+ title,
166
+ excerpt,
167
+ body,
168
+ seoDescription
169
+ },
170
+ violations: findings.violations,
171
+ offending: findings.offending
172
+ };
173
+ }
174
+
175
+ //# sourceMappingURL=ai-blog-post-tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/tools/ai-blog-post-tool.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 { SEO_LISTING_FIELDS } from '@aglyn/aglyn/app-utils/seo-listing-fields'\nimport {\n findInventedPrices,\n findStorefrontClaims,\n storefrontClaimViolations,\n type AiStorefrontCopySample,\n} from '../model/ai-storefront-claims'\nimport type { AiTool } from '../providers/contract'\nimport type { AiGenerationCheckResult } from '../runtime/ai-doctrine'\nimport { detectOffVoiceCopy, type AiDoctrineViolation } from '../runtime/ai-doctrine-validators'\n\n/**\n * The strict tool a site's first posts are written through (AGL-3676), and\n * the check that holds each post to what the site's collection publishes.\n *\n * A post is published with the site, as its own words, so nothing in it may\n * mark a gap for later: no square brackets, no link, no markup the entry\n * renderer does not read. Its body is the markdown-lite the entry editor\n * writes — `##` subheadings, `-` lists, `**bold**`, `*italic*` — and its\n * title, excerpt and search description are plain text. The storefront copy\n * rules hold every part (no health, financial or legal promise; no\n * certification, award or endorsement the brief does not state; no price),\n * and so does a rule of its own: no star rating, review score or testimonial\n * nobody gave.\n */\n\nexport const AI_BLOG_POST_TOOL_NAME = 'write_blog_post'\n\n/** A post's bounds, as the tool states them and its check holds them. */\nexport const AI_BLOG_POST_LIMITS = {\n titleMax: 80,\n excerptMax: 200,\n bodyMin: 600,\n bodyMax: 4_000,\n seoDescriptionMax: SEO_LISTING_FIELDS.description.maxLength,\n} as const\n\n/** A post as its check reads it. */\nexport interface AiBlogPost {\n title: string\n excerpt: string\n /** Markdown-lite. */\n body: string\n seoDescription: string\n}\n\nconst string = (description: string) => ({ type: 'string', description })\n\nexport const AI_BLOG_POST_TOOL: AiTool = {\n name: AI_BLOG_POST_TOOL_NAME,\n description: 'Write one post of a new blog. Every field is required.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['title', 'excerpt', 'body', 'seoDescription'],\n properties: {\n title: string(`Plain text, at most ${AI_BLOG_POST_LIMITS.titleMax} characters.`),\n excerpt: string(`One or two plain sentences, at most ${AI_BLOG_POST_LIMITS.excerptMax} characters.`),\n body: string(\n `Markdown-lite, ${AI_BLOG_POST_LIMITS.bodyMin} to ${AI_BLOG_POST_LIMITS.bodyMax} characters: short paragraphs, ## subheadings, - lists, **bold**. No title line.`,\n ),\n seoDescription: string(`One plain sentence, at most ${AI_BLOG_POST_LIMITS.seoDescriptionMax} characters.`),\n },\n },\n}\n\ninterface Findings {\n violations: AiDoctrineViolation[]\n offending: Record<string, unknown>\n}\n\nfunction fail(findings: Findings, at: string, code: string, message: string, value: unknown): void {\n findings.violations.push({ rule: null, code, message, paths: [at] })\n if (!(at in findings.offending)) findings.offending[at] = value\n}\n\nconst line = (value: unknown): string => (typeof value === 'string' ? value.replace(/\\s+/g, ' ').trim() : '')\n\n/** Paragraphs: spaces folded within a line, at most one blank line between paragraphs. */\nconst paragraphs = (value: unknown): string =>\n typeof value === 'string'\n ? value\n .replace(/\\r\\n?/g, '\\n')\n .split('\\n')\n .map((part) => part.replace(/[ \\t\\f\\v]+/g, ' ').trimEnd())\n .join('\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim()\n : ''\n\nconst HTML = /<\\/?[a-z][^>]*>/i\nconst PLAIN_MARKS = /(^|\\n)\\s*#{1,6}\\s|\\*\\*[^*\\n]+\\*\\*|__[^_\\n]+__|`/\nconst EMOJI = /\\p{Extended_Pictographic}/u\nconst BRACKET = /[[\\]]/\nconst URL = /\\bhttps?:\\/\\/|\\bwww\\./i\n/** A heading deeper than the entry renderer's subheadings, or a title line. */\nconst TITLE_HEADING = /(^|\\n)\\s*#\\s/\n/** A rating or review score nobody gave. */\nconst RATING = /\\b\\d(?:\\.\\d)?\\s*(?:\\/\\s*(?:5|10)\\b|out of (?:5|five|10|ten)\\b|stars?\\b)|\\b(?:five|5|four|4)[- ]star\\b|\\brated\\b|\\breviews? (?:say|rave|agree)\\b/i\n\n/** One plain-text field: there, within its length, unmarked. */\nfunction checkPlain(findings: Findings, at: string, raw: unknown, label: string, max: number): string {\n if (typeof raw !== 'string') {\n fail(findings, at, 'type', `${label} must be text.`, raw)\n return ''\n }\n const value = line(raw)\n if (!value) {\n fail(findings, at, 'missing', `${label} is required.`, raw)\n return ''\n }\n if (value.length > max) fail(findings, at, 'too-long', `${label} is ${value.length} characters; it may have ${max}.`, raw)\n if (HTML.test(value) || PLAIN_MARKS.test(value)) fail(findings, at, 'markup', `${label} is plain text, with no formatting marks.`, raw)\n if (EMOJI.test(value)) fail(findings, at, 'emoji', `${label} holds no emoji.`, raw)\n if (URL.test(value)) fail(findings, at, 'link', `${label} holds no link.`, raw)\n if (BRACKET.test(value)) {\n fail(findings, at, 'bracket', `${label} is published as written, so it marks no missing fact in brackets.`, raw)\n }\n return value\n}\n\n/**\n * A post's answer held to the collection it is published in and the copy\n * rules. `merchantWords` are the brief and the site's own answers, the only\n * source a stated claim or price may come from; `earlierTitles` are the posts\n * already written, which this one must not repeat.\n */\nexport function checkAiBlogPost(\n answer: Record<string, unknown>,\n context: { merchantWords: string; earlierTitles?: readonly string[] },\n): AiGenerationCheckResult<AiBlogPost> {\n const findings: Findings = { violations: [], offending: {} }\n const limits = AI_BLOG_POST_LIMITS\n const title = checkPlain(findings, 'title', answer['title'], 'The title', limits.titleMax)\n if (title && (context.earlierTitles ?? []).some((earlier) => earlier.trim().toLowerCase() === title.toLowerCase())) {\n fail(findings, 'title', 'title-duplicate', 'Another post already has this title; write about something else.', answer['title'])\n }\n const excerpt = checkPlain(findings, 'excerpt', answer['excerpt'], 'The excerpt', limits.excerptMax)\n const seoDescription = checkPlain(\n findings,\n 'seoDescription',\n answer['seoDescription'],\n 'The search description',\n limits.seoDescriptionMax,\n )\n const raw = answer['body']\n const body = paragraphs(raw)\n if (typeof raw !== 'string' || !body) {\n fail(findings, 'body', 'missing', 'The body is required.', raw)\n } else {\n if (body.length < limits.bodyMin || body.length > limits.bodyMax) {\n fail(findings, 'body', 'length', `The body is ${body.length} characters; write ${limits.bodyMin} to ${limits.bodyMax}.`, raw)\n }\n if (HTML.test(body)) fail(findings, 'body', 'markup', 'The body is markdown-lite, with no HTML.', raw)\n if (TITLE_HEADING.test(body)) fail(findings, 'body', 'title-line', 'The title renders on its own; the body uses ## subheadings only.', raw)\n if (EMOJI.test(body)) fail(findings, 'body', 'emoji', 'The body holds no emoji.', raw)\n if (URL.test(body)) fail(findings, 'body', 'link', 'The body holds no link.', raw)\n if (BRACKET.test(body)) {\n fail(findings, 'body', 'bracket', 'The body is published as written, so it holds no square brackets: no links and no missing fact marked.', raw)\n }\n }\n const samples: AiStorefrontCopySample[] = [\n { at: 'title', text: title },\n { at: 'excerpt', text: excerpt },\n { at: 'body', text: body },\n { at: 'seoDescription', text: seoDescription },\n ]\n for (const sample of samples) {\n const rating = RATING.exec(sample.text)\n if (rating && !context.merchantWords.toLowerCase().includes(rating[0].toLowerCase())) {\n fail(findings, sample.at, 'rating-invented', `The post gives a rating or review nobody gave. Remove: \"${rating[0]}\".`, sample.text)\n }\n }\n findings.violations.push(\n ...storefrontClaimViolations(\n findStorefrontClaims(samples, context.merchantWords),\n findInventedPrices(samples, context.merchantWords),\n ),\n ...detectOffVoiceCopy(samples, null),\n )\n return {\n value: findings.violations.length ? null : { title, excerpt, body, seoDescription },\n violations: findings.violations,\n offending: findings.offending,\n }\n}\n"],"names":["SEO_LISTING_FIELDS","findInventedPrices","findStorefrontClaims","storefrontClaimViolations","detectOffVoiceCopy","AI_BLOG_POST_TOOL_NAME","AI_BLOG_POST_LIMITS","titleMax","excerptMax","bodyMin","bodyMax","seoDescriptionMax","description","maxLength","string","type","AI_BLOG_POST_TOOL","name","strict","inputSchema","additionalProperties","required","properties","title","excerpt","body","seoDescription","fail","findings","at","code","message","value","violations","push","rule","paths","offending","line","replace","trim","paragraphs","split","map","part","trimEnd","join","HTML","PLAIN_MARKS","EMOJI","BRACKET","URL","TITLE_HEADING","RATING","checkPlain","raw","label","max","length","test","checkAiBlogPost","answer","context","limits","earlierTitles","some","earlier","toLowerCase","samples","text","sample","rating","exec","merchantWords","includes"],"mappings":"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"}
@@ -52,7 +52,11 @@ import type { AiGenerationCheckResult } from '../runtime/ai-doctrine';
52
52
  * is compiled once for the whole union instead of once per variant;
53
53
  * - a variant serves every step type that carries the same fields, named for
54
54
  * what they hold: the six steps that name a record share `reference`, and
55
- * the three that carry one line share `text`;
55
+ * the three that carry one line share `text`. A step whose fields are a
56
+ * subset of another's rides that variant with the rest empty: `exitFlow`
57
+ * with an empty `reference`, `wait` with `waitForEvent`'s empty `event`.
58
+ * Each of those saved a whole object once the order, return and funnel
59
+ * triggers (AGL-3611, AGL-3605) grew the events' enum past the proof;
56
60
  * - every field stays required and every vocabulary but one stays an enum,
57
61
  * so the model is still decoded into the Actions editor's words. The one
58
62
  * is the event a waitForEvent step waits for: the trigger's `event` already
@@ -63,7 +67,7 @@ import type { AiGenerationCheckResult } from '../runtime/ai-doctrine';
63
67
  * - a description is free: a compiler drops it (`aiToolSchemaCompiledBytes`),
64
68
  * so the words a field needs go there and never into the grammar.
65
69
  *
66
- * Ten variants carry all seventeen step types, and the reader below maps the
70
+ * Eight variants carry all seventeen step types, and the reader below maps the
67
71
  * shared fields back onto the step's own key.
68
72
  */
69
73
  export declare const AI_AUTOMATION_TOOL_NAME = "submit_automation";
@@ -52,7 +52,11 @@ import { HOSTILE_TEXT } from "../runtime/ai-node-tree.js";
52
52
  * is compiled once for the whole union instead of once per variant;
53
53
  * - a variant serves every step type that carries the same fields, named for
54
54
  * what they hold: the six steps that name a record share `reference`, and
55
- * the three that carry one line share `text`;
55
+ * the three that carry one line share `text`. A step whose fields are a
56
+ * subset of another's rides that variant with the rest empty: `exitFlow`
57
+ * with an empty `reference`, `wait` with `waitForEvent`'s empty `event`.
58
+ * Each of those saved a whole object once the order, return and funnel
59
+ * triggers (AGL-3611, AGL-3605) grew the events' enum past the proof;
56
60
  * - every field stays required and every vocabulary but one stays an enum,
57
61
  * so the model is still decoded into the Actions editor's words. The one
58
62
  * is the event a waitForEvent step waits for: the trigger's `event` already
@@ -63,7 +67,7 @@ import { HOSTILE_TEXT } from "../runtime/ai-node-tree.js";
63
67
  * - a description is free: a compiler drops it (`aiToolSchemaCompiledBytes`),
64
68
  * so the words a field needs go there and never into the grammar.
65
69
  *
66
- * Ten variants carry all seventeen step types, and the reader below maps the
70
+ * Eight variants carry all seventeen step types, and the reader below maps the
67
71
  * shared fields back onto the step's own key.
68
72
  */ export const AI_AUTOMATION_TOOL_NAME = 'submit_automation';
69
73
  export const AI_WORKFLOW_EXPLANATION_TOOL_NAME = 'submit_explanation';
@@ -211,12 +215,15 @@ const minutes = (what)=>({
211
215
  };
212
216
  }
213
217
  /** Every step type an automation may carry, grouped by the fields it needs. */ export const AI_AUTOMATION_STEP_VARIANTS = [
218
+ // `exitFlow` rides here with an empty reference: a variant of its own
219
+ // costs the grammar a whole object for a step that carries nothing.
214
220
  stepVariant([
215
- ...REFERENCE_STEPS
221
+ ...REFERENCE_STEPS,
222
+ 'exitFlow'
216
223
  ], {
217
224
  reference: {
218
225
  type: 'string',
219
- description: 'The record the step acts on — the email list, campaign, workflow, dataset or outbound webhook — in the words of the description, such as "newsletter". Never an id.'
226
+ description: 'The record the step acts on — the email list, campaign, workflow, dataset or outbound webhook — in the words of the description, such as "newsletter". Never an id. Empty for exitFlow.'
220
227
  }
221
228
  }),
222
229
  stepVariant([
@@ -257,24 +264,19 @@ const minutes = (what)=>({
257
264
  ]
258
265
  }
259
266
  }),
267
+ // `wait` shares `waitForEvent`'s variant, its event empty: one object in
268
+ // the grammar instead of two.
260
269
  stepVariant([
261
- 'wait'
262
- ], {
263
- minutes: minutes('How long to wait')
264
- }),
265
- stepVariant([
270
+ 'wait',
266
271
  'waitForEvent'
267
272
  ], {
268
273
  // A string, not the events' enum again — see the header.
269
274
  event: {
270
275
  type: 'string',
271
- description: 'One of the trigger events.'
276
+ description: 'waitForEvent: one of the trigger events. Empty for wait.'
272
277
  },
273
- minutes: minutes('When to give up')
278
+ minutes: minutes('wait: how long to wait. waitForEvent: when to give up')
274
279
  }),
275
- stepVariant([
276
- 'exitFlow'
277
- ], {}),
278
280
  stepVariant([
279
281
  'setContactStage'
280
282
  ], {