@aglyn/plugins-ai 1.0.0-beta.218 → 1.0.0-beta.220

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 (311) hide show
  1. package/README.md +2 -2
  2. package/package.json +18 -18
  3. package/src/index.d.ts +1 -0
  4. package/src/index.js.map +1 -1
  5. package/src/lib/activity/ai-activity-actions.js +8 -1
  6. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  7. package/src/lib/billing/ai-month-meter.d.ts +51 -0
  8. package/src/lib/billing/ai-month-meter.js +89 -0
  9. package/src/lib/billing/ai-month-meter.js.map +1 -0
  10. package/src/lib/billing/ai-overage-close.d.ts +11 -33
  11. package/src/lib/billing/ai-overage-close.js +4 -3
  12. package/src/lib/billing/ai-overage-close.js.map +1 -1
  13. package/src/lib/billing/ai-overage-cutover.d.ts +28 -12
  14. package/src/lib/billing/ai-overage-cutover.js +1 -28
  15. package/src/lib/billing/ai-overage-cutover.js.map +1 -1
  16. package/src/lib/billing/ai-overage-ledger.js +1 -1
  17. package/src/lib/billing/ai-overage-ledger.js.map +1 -1
  18. package/src/lib/billing/ai-overage-standing.js +1 -1
  19. package/src/lib/billing/ai-overage-standing.js.map +1 -1
  20. package/src/lib/components/ai-allotment-editor.component.js +1 -1
  21. package/src/lib/components/ai-allotment-editor.component.js.map +1 -1
  22. package/src/lib/components/ai-assist-provider.component.js +5 -4
  23. package/src/lib/components/ai-assist-provider.component.js.map +1 -1
  24. package/src/lib/components/ai-automation-zones.d.ts +63 -0
  25. package/src/lib/components/ai-automation-zones.js +31 -0
  26. package/src/lib/components/ai-automation-zones.js.map +1 -0
  27. package/src/lib/components/ai-brief-dialog.component.js +3 -3
  28. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  29. package/src/lib/components/ai-describe-automation.component.d.ts +1 -1
  30. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  31. package/src/lib/components/ai-describe-button.component.d.ts +6 -3
  32. package/src/lib/components/ai-describe-button.component.js +4 -1
  33. package/src/lib/components/ai-describe-button.component.js.map +1 -1
  34. package/src/lib/components/ai-experiment-cards.component.js +1 -1
  35. package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
  36. package/src/lib/components/ai-explain-automation.component.d.ts +1 -1
  37. package/src/lib/components/ai-explain-automation.component.js.map +1 -1
  38. package/src/lib/components/ai-job-plan.component.d.ts +8 -0
  39. package/src/lib/components/ai-job-plan.component.js +21 -4
  40. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  41. package/src/lib/components/ai-permissions-on-host.component.d.ts +12 -2
  42. package/src/lib/components/ai-permissions-on-host.component.js +12 -3
  43. package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
  44. package/src/lib/components/ai-product-copy-card.component.d.ts +1 -1
  45. package/src/lib/components/ai-product-copy-card.component.js.map +1 -1
  46. package/src/lib/components/ai-product-import-option.component.d.ts +1 -1
  47. package/src/lib/components/ai-product-import-option.component.js.map +1 -1
  48. package/src/lib/components/ai-product-zones.d.ts +124 -0
  49. package/src/lib/components/ai-product-zones.js +32 -0
  50. package/src/lib/components/ai-product-zones.js.map +1 -0
  51. package/src/lib/components/ai-products-hub-card.component.d.ts +1 -1
  52. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  53. package/src/lib/components/ai-products-proposals.component.d.ts +1 -1
  54. package/src/lib/components/ai-products-proposals.component.js.map +1 -1
  55. package/src/lib/components/ai-seo-audit-card.component.js +35 -92
  56. package/src/lib/components/ai-seo-audit-card.component.js.map +1 -1
  57. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  58. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  59. package/src/lib/components/assist-jobs-drawer.component.js +21 -15
  60. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  61. package/src/lib/components/assist-panel.component.js +2 -2
  62. package/src/lib/components/assist-panel.component.js.map +1 -1
  63. package/src/lib/components/assist-signals-page.component.js +2 -2
  64. package/src/lib/components/assist-signals-page.component.js.map +1 -1
  65. package/src/lib/components/billing-ai-allotments.component.js +1 -1
  66. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  67. package/src/lib/components/billing-assist-overage-card.component.js +1 -1
  68. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  69. package/src/lib/constants.d.ts +5 -0
  70. package/src/lib/constants.js +4 -0
  71. package/src/lib/constants.js.map +1 -1
  72. package/src/lib/declarations.js +2 -0
  73. package/src/lib/declarations.js.map +1 -1
  74. package/src/lib/declarations.server.js +24 -10
  75. package/src/lib/declarations.server.js.map +1 -1
  76. package/src/lib/insights/ai-dataset-catalog.d.ts +49 -0
  77. package/src/lib/insights/ai-dataset-catalog.js +57 -0
  78. package/src/lib/insights/ai-dataset-catalog.js.map +1 -0
  79. package/src/lib/insights/ai-figure-readers.d.ts +6 -39
  80. package/src/lib/insights/ai-figure-readers.js +2 -391
  81. package/src/lib/insights/ai-figure-readers.js.map +1 -1
  82. package/src/lib/insights/ai-insight-digest.d.ts +10 -1
  83. package/src/lib/insights/ai-insight-digest.js +16 -4
  84. package/src/lib/insights/ai-insight-digest.js.map +1 -1
  85. package/src/lib/insights/ai-insight-readers.d.ts +1 -1
  86. package/src/lib/insights/ai-insight-readers.js.map +1 -1
  87. package/src/lib/jobs/ai-email-bindings.d.ts +28 -21
  88. package/src/lib/jobs/ai-email-bindings.js +75 -42
  89. package/src/lib/jobs/ai-email-bindings.js.map +1 -1
  90. package/src/lib/jobs/ai-job-campaign-step.d.ts +0 -2
  91. package/src/lib/jobs/ai-job-campaign-step.js +5 -10
  92. package/src/lib/jobs/ai-job-campaign-step.js.map +1 -1
  93. package/src/lib/jobs/ai-job-component-step.d.ts +6 -1
  94. package/src/lib/jobs/ai-job-component-step.js +32 -9
  95. package/src/lib/jobs/ai-job-component-step.js.map +1 -1
  96. package/src/lib/jobs/ai-job-draft-ids.js +11 -0
  97. package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
  98. package/src/lib/jobs/ai-job-drafts.js +1 -1
  99. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  100. package/src/lib/jobs/ai-job-email-step.d.ts +5 -3
  101. package/src/lib/jobs/ai-job-email-step.js +11 -4
  102. package/src/lib/jobs/ai-job-email-step.js.map +1 -1
  103. package/src/lib/jobs/ai-job-experiment-step.d.ts +1 -1
  104. package/src/lib/jobs/ai-job-experiment-step.js +1 -1
  105. package/src/lib/jobs/ai-job-experiment-step.js.map +1 -1
  106. package/src/lib/jobs/ai-job-form-step.d.ts +17 -1
  107. package/src/lib/jobs/ai-job-form-step.js +60 -8
  108. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  109. package/src/lib/jobs/ai-job-generation.d.ts +22 -1
  110. package/src/lib/jobs/ai-job-generation.js +63 -3
  111. package/src/lib/jobs/ai-job-generation.js.map +1 -1
  112. package/src/lib/jobs/ai-job-insight-step.js +1 -1
  113. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  114. package/src/lib/jobs/ai-job-layout-step.d.ts +15 -4
  115. package/src/lib/jobs/ai-job-layout-step.js +46 -13
  116. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  117. package/src/lib/jobs/ai-job-page-sections.d.ts +4 -2
  118. package/src/lib/jobs/ai-job-page-sections.js +7 -4
  119. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  120. package/src/lib/jobs/ai-job-page-step.js +7 -2
  121. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  122. package/src/lib/jobs/ai-job-plan-conformance.d.ts +5 -0
  123. package/src/lib/jobs/ai-job-plan-conformance.js +40 -1
  124. package/src/lib/jobs/ai-job-plan-conformance.js.map +1 -1
  125. package/src/lib/jobs/ai-job-plan-creations.d.ts +80 -0
  126. package/src/lib/jobs/ai-job-plan-creations.js +199 -0
  127. package/src/lib/jobs/ai-job-plan-creations.js.map +1 -0
  128. package/src/lib/jobs/ai-job-plan-step.d.ts +11 -2
  129. package/src/lib/jobs/ai-job-plan-step.js +49 -23
  130. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  131. package/src/lib/jobs/ai-job-plugin-drafts.d.ts +14 -6
  132. package/src/lib/jobs/ai-job-plugin-drafts.js +12 -4
  133. package/src/lib/jobs/ai-job-plugin-drafts.js.map +1 -1
  134. package/src/lib/jobs/ai-job-products-step.d.ts +7 -0
  135. package/src/lib/jobs/ai-job-products-step.js +54 -32
  136. package/src/lib/jobs/ai-job-products-step.js.map +1 -1
  137. package/src/lib/jobs/ai-job-seo-step.d.ts +3 -24
  138. package/src/lib/jobs/ai-job-seo-step.js +35 -108
  139. package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
  140. package/src/lib/jobs/ai-job-site-step.d.ts +2 -0
  141. package/src/lib/jobs/ai-job-site-step.js +6 -6
  142. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  143. package/src/lib/jobs/ai-job-template-step.d.ts +9 -6
  144. package/src/lib/jobs/ai-job-template-step.js +73 -15
  145. package/src/lib/jobs/ai-job-template-step.js.map +1 -1
  146. package/src/lib/jobs/ai-job-theme-step.d.ts +1 -1
  147. package/src/lib/jobs/ai-job-theme-step.js +1 -1
  148. package/src/lib/jobs/ai-job-theme-step.js.map +1 -1
  149. package/src/lib/jobs/ai-job-workflow-step.js +17 -9
  150. package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
  151. package/src/lib/jobs/ai-jobs.js +1 -1
  152. package/src/lib/jobs/ai-jobs.js.map +1 -1
  153. package/src/lib/jobs/ai-workflow-records.d.ts +20 -11
  154. package/src/lib/jobs/ai-workflow-records.js +80 -43
  155. package/src/lib/jobs/ai-workflow-records.js.map +1 -1
  156. package/src/lib/model/ai-allotments.d.ts +27 -6
  157. package/src/lib/model/ai-allotments.js +54 -14
  158. package/src/lib/model/ai-allotments.js.map +1 -1
  159. package/src/lib/model/ai-automation-outline.d.ts +21 -2
  160. package/src/lib/model/ai-automation-outline.js.map +1 -1
  161. package/src/lib/model/ai-build-plan.d.ts +57 -0
  162. package/src/lib/model/ai-build-plan.js +116 -7
  163. package/src/lib/model/ai-build-plan.js.map +1 -1
  164. package/src/lib/model/ai-job-creations.d.ts +55 -0
  165. package/src/lib/model/ai-job-creations.js +85 -0
  166. package/src/lib/model/ai-job-creations.js.map +1 -0
  167. package/src/lib/model/ai-page-job.d.ts +3 -2
  168. package/src/lib/model/ai-page-job.js +4 -3
  169. package/src/lib/model/ai-page-job.js.map +1 -1
  170. package/src/lib/model/ai-seo.d.ts +11 -51
  171. package/src/lib/model/ai-seo.js +0 -17
  172. package/src/lib/model/ai-seo.js.map +1 -1
  173. package/src/lib/model/ai-site-inventory.js +1 -1
  174. package/src/lib/model/ai-site-inventory.js.map +1 -1
  175. package/src/lib/model/ai-site-job.d.ts +4 -2
  176. package/src/lib/model/ai-site-job.js +8 -4
  177. package/src/lib/model/ai-site-job.js.map +1 -1
  178. package/src/lib/model/ai-template-subjects.d.ts +10 -0
  179. package/src/lib/model/ai-template-subjects.js +16 -2
  180. package/src/lib/model/ai-template-subjects.js.map +1 -1
  181. package/src/lib/model/ai-theme-proposal.d.ts +1 -1
  182. package/src/lib/model/ai-theme-proposal.js +1 -1
  183. package/src/lib/model/ai-theme-proposal.js.map +1 -1
  184. package/src/lib/model/ai-tokens.js +1 -1
  185. package/src/lib/model/ai-tokens.js.map +1 -1
  186. package/src/lib/model/ai-usage-by-user.js +1 -1
  187. package/src/lib/model/ai-usage-by-user.js.map +1 -1
  188. package/src/lib/model/ai-workflow-job.d.ts +1 -3
  189. package/src/lib/model/ai-workflow-job.js +1 -2
  190. package/src/lib/model/ai-workflow-job.js.map +1 -1
  191. package/src/lib/plan-entitlements.d.ts +149 -0
  192. package/src/lib/plan-entitlements.js +168 -0
  193. package/src/lib/plan-entitlements.js.map +1 -0
  194. package/src/lib/plugin.js +1 -1
  195. package/src/lib/plugin.js.map +1 -1
  196. package/src/lib/providers/catalog.js +1 -1
  197. package/src/lib/providers/catalog.js.map +1 -1
  198. package/src/lib/providers/model-choice.js +1 -1
  199. package/src/lib/providers/model-choice.js.map +1 -1
  200. package/src/lib/runtime/ai-device-audit.d.ts +1 -1
  201. package/src/lib/runtime/ai-device-audit.js +1 -1
  202. package/src/lib/runtime/ai-device-audit.js.map +1 -1
  203. package/src/lib/runtime/ai-doctrine-validators.d.ts +25 -4
  204. package/src/lib/runtime/ai-doctrine-validators.js +178 -21
  205. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  206. package/src/lib/runtime/ai-doctrine.js +5 -5
  207. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  208. package/src/lib/runtime/ai-eval-live.js +30 -15
  209. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  210. package/src/lib/runtime/ai-eval.d.ts +9 -2
  211. package/src/lib/runtime/ai-eval.js +29 -2
  212. package/src/lib/runtime/ai-eval.js.map +1 -1
  213. package/src/lib/runtime/ai-gate.js +1 -1
  214. package/src/lib/runtime/ai-gate.js.map +1 -1
  215. package/src/lib/runtime/ai-node-tree.js +3 -3
  216. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  217. package/src/lib/runtime/ai-palette.d.ts +7 -1
  218. package/src/lib/runtime/ai-palette.generated.d.ts +1 -1
  219. package/src/lib/runtime/ai-palette.generated.js +16 -13
  220. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  221. package/src/lib/runtime/ai-palette.js +7 -1
  222. package/src/lib/runtime/ai-palette.js.map +1 -1
  223. package/src/lib/runtime/seo-audit.d.ts +13 -63
  224. package/src/lib/runtime/seo-audit.js +22 -207
  225. package/src/lib/runtime/seo-audit.js.map +1 -1
  226. package/src/lib/runtime/site-inventory.js +29 -23
  227. package/src/lib/runtime/site-inventory.js.map +1 -1
  228. package/src/lib/server/ai-admin-org.js +1 -1
  229. package/src/lib/server/ai-admin-org.js.map +1 -1
  230. package/src/lib/server/ai-admin-orgs-spend.js +1 -1
  231. package/src/lib/server/ai-admin-orgs-spend.js.map +1 -1
  232. package/src/lib/server/ai-admin-signals.js +1 -1
  233. package/src/lib/server/ai-admin-signals.js.map +1 -1
  234. package/src/lib/server/ai-allotments.js +1 -1
  235. package/src/lib/server/ai-allotments.js.map +1 -1
  236. package/src/lib/server/ai-assist-prompts.js +3 -3
  237. package/src/lib/server/ai-assist-prompts.js.map +1 -1
  238. package/src/lib/server/ai-assist.js +2 -2
  239. package/src/lib/server/ai-assist.js.map +1 -1
  240. package/src/lib/server/assist-chat.d.ts +1 -1
  241. package/src/lib/server/assist-chat.js +13 -12
  242. package/src/lib/server/assist-chat.js.map +1 -1
  243. package/src/lib/server/assist-view-context.js +23 -23
  244. package/src/lib/server/assist-view-context.js.map +1 -1
  245. package/src/lib/server/billing-credits.d.ts +5 -0
  246. package/src/lib/server/billing-credits.js +8 -2
  247. package/src/lib/server/billing-credits.js.map +1 -1
  248. package/src/lib/server/billing-overage.js +2 -2
  249. package/src/lib/server/billing-overage.js.map +1 -1
  250. package/src/lib/server.d.ts +1 -0
  251. package/src/lib/server.js.map +1 -1
  252. package/src/lib/testing/stand-in-automation-index.d.ts +20 -0
  253. package/src/lib/testing/stand-in-automation-index.js +102 -0
  254. package/src/lib/testing/stand-in-automation-index.js.map +1 -0
  255. package/src/lib/testing/stand-in-dataset-index.d.ts +20 -0
  256. package/src/lib/testing/stand-in-dataset-index.js +80 -0
  257. package/src/lib/testing/stand-in-dataset-index.js.map +1 -0
  258. package/src/lib/testing/stand-in-list-send-time.d.ts +4 -0
  259. package/src/lib/testing/stand-in-list-send-time.js +77 -0
  260. package/src/lib/testing/stand-in-list-send-time.js.map +1 -0
  261. package/src/lib/testing/stand-in-pipeline-index.d.ts +32 -0
  262. package/src/lib/testing/stand-in-pipeline-index.js +59 -0
  263. package/src/lib/testing/stand-in-pipeline-index.js.map +1 -0
  264. package/src/lib/testing/stand-in-product-index.d.ts +20 -0
  265. package/src/lib/testing/stand-in-product-index.js +105 -0
  266. package/src/lib/testing/stand-in-product-index.js.map +1 -0
  267. package/src/lib/tools/ai-component-tool.js +2 -2
  268. package/src/lib/tools/ai-component-tool.js.map +1 -1
  269. package/src/lib/tools/ai-duplicate-resource-tool.js +1 -1
  270. package/src/lib/tools/ai-duplicate-resource-tool.js.map +1 -1
  271. package/src/lib/tools/ai-seo-tool.d.ts +6 -18
  272. package/src/lib/tools/ai-seo-tool.js +7 -38
  273. package/src/lib/tools/ai-seo-tool.js.map +1 -1
  274. package/src/lib/tools/ai-workflow-tool.d.ts +1 -1
  275. package/src/lib/tools/ai-workflow-tool.js +1 -1
  276. package/src/lib/tools/ai-workflow-tool.js.map +1 -1
  277. package/src/lib/usage/ai-allotment-alerts.d.ts +10 -7
  278. package/src/lib/usage/ai-allotment-alerts.js +39 -15
  279. package/src/lib/usage/ai-allotment-alerts.js.map +1 -1
  280. package/src/lib/usage/ai-quota-checks.d.ts +37 -0
  281. package/src/lib/usage/ai-quota-checks.js +69 -0
  282. package/src/lib/usage/ai-quota-checks.js.map +1 -0
  283. package/src/lib/usage/ai-usage-alerts.js +22 -10
  284. package/src/lib/usage/ai-usage-alerts.js.map +1 -1
  285. package/src/lib/usage/ai-usage-by-user.js +1 -1
  286. package/src/lib/usage/ai-usage-by-user.js.map +1 -1
  287. package/src/lib/usage/assist-ceiling.d.ts +2 -2
  288. package/src/lib/usage/assist-ceiling.js +4 -4
  289. package/src/lib/usage/assist-ceiling.js.map +1 -1
  290. package/src/lib/usage/assist-credits.d.ts +494 -0
  291. package/src/lib/usage/assist-credits.js +524 -0
  292. package/src/lib/usage/assist-credits.js.map +1 -0
  293. package/src/lib/usage/assist-free-taste.d.ts +1 -1
  294. package/src/lib/usage/assist-free-taste.js +3 -2
  295. package/src/lib/usage/assist-free-taste.js.map +1 -1
  296. package/src/lib/usage/assist-signal-mining.js +1 -1
  297. package/src/lib/usage/assist-signal-mining.js.map +1 -1
  298. package/src/lib/usage/assist-spend-guards.d.ts +153 -0
  299. package/src/lib/usage/assist-spend-guards.js +182 -0
  300. package/src/lib/usage/assist-spend-guards.js.map +1 -0
  301. package/src/lib/usage/assist-usage.d.ts +1 -1
  302. package/src/lib/usage/assist-usage.js +1 -1
  303. package/src/lib/usage/assist-usage.js.map +1 -1
  304. package/src/lib/usage/staff-org-ai.js +1 -1
  305. package/src/lib/usage/staff-org-ai.js.map +1 -1
  306. package/src/lib/usage-axes.d.ts +49 -0
  307. package/src/lib/usage-axes.js +108 -0
  308. package/src/lib/usage-axes.js.map +1 -0
  309. package/src/lib/runtime/seo-page-facts.d.ts +0 -61
  310. package/src/lib/runtime/seo-page-facts.js +0 -150
  311. package/src/lib/runtime/seo-page-facts.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-experiment-step.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 {\n AI_EXPERIMENT_FIGURE_READER,\n AI_EXPERIMENT_MAX_VARIANTS,\n AI_EXPERIMENT_MIN_VARIANTS,\n AI_EXPERIMENT_SUBJECT_MAX_CHARS,\n aiExperimentNextStep,\n aiExperimentTarget,\n aiExperimentTask,\n aiExperimentVerdictNamesWinner,\n checkAiExperimentExplanation,\n checkAiExperimentVariants,\n readAiExperimentArms,\n readAiExperimentResult,\n type AiExperimentArm,\n type AiExperimentExplanation,\n type AiExperimentReading,\n type AiExperimentTarget,\n type AiExperimentVariantsProposal,\n} from '../model/ai-experiment'\nimport type { AiJob, AiJobOutput } from '../model/ai-jobs.types'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n AI_ACCEPTABLE_USE_BLOCK,\n runAiRequest,\n type AiMessage,\n type AiSystemBlock,\n} from '../runtime/ai-runtime'\nimport { AI_GENERATION_MAX_ATTEMPTS } from '../runtime/ai-generation-bounds'\nimport { aiInsightReaders, readAiInsightTables } from '../insights/ai-insight-readers'\nimport {\n registerAiJobAdmission,\n type AiJobAdmissionContext,\n type AiJobAdmissionRefusal,\n} from './ai-job-admission'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport type { AiJobStepContext, AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\nimport {\n AI_EXPERIMENT_EXPLAIN_TOOL_NAME,\n AI_EXPERIMENT_VARIANTS_TOOL_NAME,\n aiExperimentExplainTool,\n aiExperimentVariantsTool,\n parseAiExperimentExplanation,\n parseAiExperimentVariants,\n} from '../tools/ai-experiment-tool'\n\n/**\n * The `experiment` step (AGL-2914): A/B tests by AI.\n *\n * A/B testing already exists — the marketing plugin models the experiment,\n * buckets visitors, counts exposures and conversions and compares the arms.\n * This step is the AI layer over it, and it does two things:\n *\n * 1. **Variants.** From the copy under test, it proposes between two and four\n * variants of it. It proposes COPY and nothing else: no weight, no traffic\n * split, no goal, no schedule and no winner. A person puts them into the\n * A/B testing card, which is the only thing that creates or starts a test.\n * 2. **The result.** Code reads the test's figures through the reader\n * marketing registers for them, decides what they support, and only then\n * asks the model for the words. The verdict is never the model's, and an\n * answer that claims more than the verdict is cut back to it.\n *\n * ## Nothing here writes a test\n *\n * Both answers are proposals, like a theme's or a listing's. The experiment\n * document, its variants' screen versions and its traffic are marketing's and\n * the besigner's, and a job that could write them could start an experiment on\n * live visitors without anyone deciding to.\n *\n * ## What reaches the model\n *\n * For variants: the brief, what the test varies, and the copy under test as\n * the surface handed it over. For a result: the test's name, its arms' counts\n * and rates as the results reader published them, and the verdict code\n * reached. No visitor, no session and no assignment is read, because the\n * reader publishes none — it answers in aggregates, which is the whole reason\n * a figure reader is what this reads.\n */\n\n/** The reader whose figures a result is explained from, and the plugin that owns it. */\nexport const AI_EXPERIMENT_READER_ID = AI_EXPERIMENT_FIGURE_READER\n\n/** The window a result is read over: a test's figures are totals since it started. */\nexport const AI_EXPERIMENT_READ_DAYS = 0\n\nexport const AI_EXPERIMENT_NO_TASK_COPY =\n 'Start this from the A/B testing card, which says whether you want variants or an explanation.'\nexport const AI_EXPERIMENT_NO_TARGET_COPY =\n 'Say whether the test varies a screen, a section or an email.'\nexport const AI_EXPERIMENT_NO_SITE_COPY = 'Open the site whose test this is.'\nexport const AI_EXPERIMENT_NO_READER_COPY =\n 'A/B testing is not available on this workspace, so there is no test to work on.'\nexport const AI_EXPERIMENT_NO_SUBJECT_COPY =\n 'Give the copy the test varies, so there is something to write variants of.'\nexport const AI_EXPERIMENT_NO_TEST_COPY =\n 'Name the test to explain, as A/B testing lists it.'\nexport const AI_EXPERIMENT_NO_FIGURES_COPY =\n 'That test’s figures could not be read. Try again in a moment.'\nexport const AI_EXPERIMENT_UNREADABLE_RESULT_COPY =\n 'That test has no first variant and something to compare against it yet, so there is nothing to explain.'\nexport const AI_EXPERIMENT_NO_VARIANTS_COPY =\n 'The AI did not write variants that could be used. Try the brief in different words.'\nexport const AI_EXPERIMENT_NO_EXPLANATION_COPY =\n 'The AI did not explain the result in words that match its figures. Try again.'\n\n/** The step's answer ceiling, from the routing table. */\nexport const AI_JOB_EXPERIMENT_MAX_TOKENS = AI_ROUTING_TABLE['job.experiment'].maxTokens\n\n/**\n * The step's time: at most one re-ask at the routing ceiling, plus the one\n * figure read an explanation makes. Variants read nothing.\n */\nexport const AI_JOB_EXPERIMENT_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.experiment'],\n maxTokens: AI_JOB_EXPERIMENT_MAX_TOKENS,\n attempts: AI_GENERATION_MAX_ATTEMPTS,\n lookups: 1,\n})\n\nexport const AI_JOB_EXPERIMENT_STEP_MINIMUM_MS = AI_JOB_EXPERIMENT_STEP_BUDGET.minimumMs\n\n/** The rules both tasks are sent: static, byte-identical for every workspace. */\nexport const AI_EXPERIMENT_RULES = [\n 'You work on A/B tests for a website builder. A test shows visitors one of several variants of the same thing and counts which converts best. You never start, stop or change a test: you write variants for a person to put into the A/B testing card, and you put a result someone else measured into plain words.',\n '',\n `When you are given copy under test, call ${AI_EXPERIMENT_VARIANTS_TOOL_NAME} once:`,\n `- Answer with between ${AI_EXPERIMENT_MIN_VARIANTS} and ${AI_EXPERIMENT_MAX_VARIANTS} variants. The first is the copy as it stands, unchanged, so the test has something to measure against.`,\n '- Each later variant changes ONE idea — the promise, the specificity, the length, the call to action — so a result says which idea won.',\n '- No two variants say the same thing in different words.',\n '- Write plain text: no HTML, no markdown, no emoji, no fences.',\n '- Never invent a fact, a price, a name, a statistic or a guarantee the copy under test did not give you.',\n '- Write in the language of the copy under test.',\n '',\n `When you are given a result, call ${AI_EXPERIMENT_EXPLAIN_TOOL_NAME} once:`,\n '- The request states the verdict. It was decided from the counts before you were asked, and it is not yours to revise.',\n '- Where the verdict names no winner, write nothing that says or implies one: no variant is ahead, none is better, nothing is significant, and you recommend nothing. Say what the figures show and why they do not settle it.',\n '- Where the verdict names a winner, name that variant and no other.',\n '- Every number you write appears in the figures you were given, as they show it or rounded. Never compute one.',\n '- Plain sentences: no markdown, no headings, no lists.',\n '- Write in the language of the brief.',\n].join('\\n')\n\nexport const AI_JOB_EXPERIMENT_SYSTEM: AiSystemBlock[] = [\n { text: `${AI_EXPERIMENT_RULES}\\n\\n${AI_ACCEPTABLE_USE_BLOCK}`, cacheBreakpoint: true },\n]\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** What the test varies, in the words the prompt names it by. */\nconst TARGET_NOUN: Record<AiExperimentTarget, string> = {\n screen: 'a page',\n section: 'one section of a page',\n email: 'an email',\n}\n\n/** The user turn for variants: the brief, what varies, and the copy as it stands. */\nexport function aiExperimentVariantsPrompt(input: {\n brief: string\n target: AiExperimentTarget\n subject: string\n goal: string\n}): string {\n return [\n `Brief: ${input.brief}`,\n `The test varies ${TARGET_NOUN[input.target]}.`,\n ...(input.goal ? [`It is trying to move: ${input.goal}`] : []),\n '',\n 'The copy under test, as it stands:',\n input.subject,\n ].join('\\n')\n}\n\n/** One arm as the explanation prompt shows it. */\nfunction armText(arm: AiExperimentArm, index: number): string {\n const rate = arm.rate === null ? 'no rate' : `${arm.rate}%`\n const against =\n index === 0\n ? 'the first variant, which the others are compared against'\n : [\n arm.lift === null ? 'lift not measurable' : `lift ${arm.lift}%`,\n arm.confidence === null\n ? 'confidence not measurable'\n : `confidence ${arm.confidence}%`,\n ].join(', ')\n return `- ${arm.id}: shown ${arm.exposures}, converted ${arm.conversions}, rate ${rate} (${against})`\n}\n\n/** What the verdict tells the model it may say. */\nexport function aiExperimentVerdictText(reading: AiExperimentReading): string {\n if (reading.verdict === 'winner') {\n return `VERDICT: ${reading.winnerId} is ahead of the first variant by more than chance explains, at the ${reading.threshold}% confidence this test is called at. Name ${reading.winnerId} and no other variant.`\n }\n if (reading.verdict === 'control') {\n return `VERDICT: every other variant is behind the first one, ${reading.winnerId}, by more than chance explains. Name ${reading.winnerId} and no other variant.`\n }\n return [\n `VERDICT: THERE IS NO WINNER — ${reading.reasons.join('; ')}.`,\n 'Say what the figures show and why they do not settle it. Name no winner, say no variant is ahead, better, winning or significant, and recommend no variant.',\n `Leave winner and next empty; what to do next is already decided: ${aiExperimentNextStep(reading.verdict)}`,\n ].join('\\n')\n}\n\n/** The user turn for an explanation: the test, its arms, and the verdict. */\nexport function aiExperimentExplainPrompt(input: {\n brief: string\n reading: AiExperimentReading\n}): string {\n return [\n ...(input.brief ? [`Brief: ${input.brief}`] : []),\n `Test: ${input.reading.test}`,\n 'Figures, totals since the test started:',\n ...input.reading.arms.map(armText),\n '',\n aiExperimentVerdictText(input.reading),\n ].join('\\n')\n}\n\n/** The proposal an answer becomes, as the job's output carries it. */\nfunction experimentOutput(\n job: Pick<AiJob, '$id' | 'hostId' | 'inputs'>,\n label: string,\n proposal: Record<string, unknown>,\n): AiJobOutput {\n return {\n resource: 'experiment',\n // The test the surface named, so a person opens the one they asked about;\n // a variants job for a test that does not exist yet names the job itself.\n id: str(job.inputs?.['experimentId']) || job.$id,\n versionId: null,\n hostId: job.hostId ?? null,\n hostSubdomain: str(job.inputs?.['hostSubdomain']) || null,\n label,\n proposal,\n }\n}\n\n/**\n * The A/B test results for this job's site, or `null` when the workspace has\n * no such reader. The insight path's own resolver is what decides whether a\n * reader may be read here — the plan's entitlement, the owning plugin's\n * release flag, and whether that plugin runs on this workspace and this site\n * — so this asks it rather than restating any of it.\n */\nasync function readExperimentFigures(\n context: Pick<AiJobStepContext, 'firestore' | 'org' | 'now'>,\n place: { orgId: string; hostId: string | null; uid: string | null },\n): Promise<{ table: Parameters<typeof readAiExperimentArms>[0]; refusals: string[] } | null> {\n if (!place.hostId) return null\n const snapshot = await context.firestore.collection('hosts').doc(place.hostId).get()\n const host = snapshot.exists ? ((snapshot.data() ?? {}) as Record<string, unknown>) : null\n if (!host || host['orgId'] !== place.orgId) return null\n const readers = await aiInsightReaders({\n orgId: place.orgId,\n hostId: place.hostId,\n org: context.org ?? null,\n host,\n surface: 'analytics',\n })\n const experiments = readers.filter(({ reader }) => reader.id === AI_EXPERIMENT_READER_ID)\n if (!experiments.length) return null\n const read = await readAiInsightTables(\n experiments,\n [{ reader: AI_EXPERIMENT_READER_ID, days: AI_EXPERIMENT_READ_DAYS, params: {} }],\n { orgId: place.orgId, hostId: place.hostId, uid: place.uid, now: context.now },\n 1,\n )\n return { table: read.tables[0] ?? null, refusals: read.refusals }\n}\n\nexport const runAiJobExperimentStep: AiJobStepRunner = async (\n context: AiJobStepContext,\n) => {\n const { job, signal } = context\n const model = context.modelFor?.('job.experiment') ?? aiModelForStep('job.experiment')\n const maxTokens = AI_JOB_EXPERIMENT_STEP_BUDGET.maxTokens(model)\n const task = aiExperimentTask(job.inputs)\n const spent = (result: { usage: unknown; estCostUsd: number; stopReason: string | null }) => ({\n usage: result.usage as AiJobStepOutcome['usage'],\n estCostUsd: result.estCostUsd,\n model,\n stopReason: result.stopReason,\n effort: null,\n })\n const stop = (failure: string): AiJobStepOutcome => ({\n outputs: [],\n usage: { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 },\n estCostUsd: 0,\n model,\n stopReason: null,\n failure,\n })\n\n if (!task) return stop(AI_EXPERIMENT_NO_TASK_COPY)\n if (!job.hostId) return stop(AI_EXPERIMENT_NO_SITE_COPY)\n\n const ask = (messages: AiMessage[], tool: ReturnType<typeof aiExperimentExplainTool>) =>\n runAiRequest({\n model,\n system: AI_JOB_EXPERIMENT_SYSTEM,\n messages,\n tools: [tool],\n maxTokens,\n ...(AI_ROUTING_TABLE['job.experiment'].thinking\n ? { thinking: AI_ROUTING_TABLE['job.experiment'].thinking }\n : {}),\n stream: false,\n ...(signal ? { signal } : {}),\n })\n\n if (task === 'variants') {\n const target = aiExperimentTarget(job.inputs)\n if (!target) return stop(AI_EXPERIMENT_NO_TARGET_COPY)\n const subject = str(job.inputs?.['subject']).slice(0, AI_EXPERIMENT_SUBJECT_MAX_CHARS)\n if (!subject) return stop(AI_EXPERIMENT_NO_SUBJECT_COPY)\n const tool = aiExperimentVariantsTool(target)\n let messages: AiMessage[] = [\n {\n role: 'user',\n content: aiExperimentVariantsPrompt({\n brief: job.brief,\n target,\n subject,\n goal: str(job.inputs?.['goal']),\n }),\n },\n ]\n let last: AiJobStepOutcome | null = null\n for (let attempt = 0; attempt < AI_GENERATION_MAX_ATTEMPTS; attempt += 1) {\n const result = await ask(messages, tool)\n if (result.kind === 'refusal') {\n return { outputs: [], ...spent(result), refused: true }\n }\n const parsed = parseAiExperimentVariants(\n result.toolUse.find((use) => use.name === AI_EXPERIMENT_VARIANTS_TOOL_NAME)?.input,\n )\n const checked = checkAiExperimentVariants(parsed, target)\n last = { outputs: [], ...spent(result), failure: AI_EXPERIMENT_NO_VARIANTS_COPY }\n if (checked) {\n if (checked.findings.length) {\n console.info('ai experiment variants dropped', {\n jobId: job.$id,\n findings: checked.findings,\n })\n }\n return {\n outputs: [variantsOutput(job, target, checked.proposal)],\n ...spent(result),\n }\n }\n messages = [\n ...messages,\n { role: 'assistant', content: `Answered with ${AI_EXPERIMENT_VARIANTS_TOOL_NAME}.` },\n {\n role: 'user',\n content:\n `Those variants could not be used. Answer again with ${AI_EXPERIMENT_VARIANTS_TOOL_NAME}: ` +\n `at least ${AI_EXPERIMENT_MIN_VARIANTS} variants, each with copy of its own, none repeating ` +\n 'another, and plain text throughout.',\n },\n ]\n }\n return last ?? stop(AI_EXPERIMENT_NO_VARIANTS_COPY)\n }\n\n const test = str(job.inputs?.['test'])\n if (!test) return stop(AI_EXPERIMENT_NO_TEST_COPY)\n const figures = await readExperimentFigures(context, {\n orgId: job.orgId,\n hostId: job.hostId,\n uid: job.createdBy,\n })\n if (!figures) return stop(AI_EXPERIMENT_NO_READER_COPY)\n if (!figures.table) {\n if (figures.refusals.length) {\n console.warn('ai experiment figures refused', { jobId: job.$id, refusals: figures.refusals })\n }\n return stop(AI_EXPERIMENT_NO_FIGURES_COPY)\n }\n const reading = readAiExperimentResult(test, readAiExperimentArms(figures.table, test))\n // Nothing to explain, and nothing spent finding that out: the verdict was\n // reached from the figures alone.\n if (reading.verdict === 'unreadable') return stop(AI_EXPERIMENT_UNREADABLE_RESULT_COPY)\n\n const result = await ask(\n [{ role: 'user', content: aiExperimentExplainPrompt({ brief: job.brief, reading }) }],\n aiExperimentExplainTool(),\n )\n if (result.kind === 'refusal') return { outputs: [], ...spent(result), refused: true }\n const explained = checkAiExperimentExplanation(\n parseAiExperimentExplanation(\n result.toolUse.find((use) => use.name === AI_EXPERIMENT_EXPLAIN_TOOL_NAME)?.input,\n ),\n reading,\n )\n if (!explained) {\n return { outputs: [], ...spent(result), failure: AI_EXPERIMENT_NO_EXPLANATION_COPY }\n }\n if (explained.findings.length) {\n console.info('ai experiment explanation cut back to its reading', {\n jobId: job.$id,\n verdict: reading.verdict,\n findings: explained.findings,\n })\n }\n return { outputs: [explanationOutput(job, reading, explained)], ...spent(result) }\n}\n\n/** A variants answer as the job's output. */\nfunction variantsOutput(\n job: Pick<AiJob, '$id' | 'hostId' | 'inputs'>,\n target: AiExperimentTarget,\n proposal: AiExperimentVariantsProposal,\n): AiJobOutput {\n return {\n ...experimentOutput(job, `${proposal.variants.length} variants to test`, {\n task: 'variants',\n target,\n goal: proposal.goal,\n variants: proposal.variants,\n }),\n note: 'Put these into A/B testing to start the test; nothing has been changed on the site.',\n }\n}\n\n/** An explanation as the job's output, carrying the verdict code reached. */\nfunction explanationOutput(\n job: Pick<AiJob, '$id' | 'hostId' | 'inputs'>,\n reading: AiExperimentReading,\n explained: AiExperimentExplanation,\n): AiJobOutput {\n return experimentOutput(job, explained.test || 'A/B test result', {\n task: 'explain',\n verdict: explained.verdict,\n headline: explained.headline,\n points: explained.points,\n // Only ever the reading's arm: an explanation carries no winner the\n // figures did not support, whatever the answer said.\n winnerId: aiExperimentVerdictNamesWinner(reading.verdict) ? explained.winnerId : null,\n next: explained.next,\n threshold: reading.threshold,\n arms: reading.arms,\n })\n}\n\n/**\n * Whether an experiment job may run here: a site of the job's own org, a task\n * the step runs, and A/B testing available on it — which is the results\n * reader being one this workspace may read, asked of the owning plugin rather\n * than restated.\n */\nexport async function aiExperimentAdmissionRefusal(\n context: AiJobAdmissionContext,\n): Promise<AiJobAdmissionRefusal | null> {\n if (!aiExperimentTask(context.inputs)) {\n return { status: 400, error: AI_EXPERIMENT_NO_TASK_COPY }\n }\n if (!context.hostId) return { status: 400, error: AI_EXPERIMENT_NO_SITE_COPY }\n const snapshot = await context.firestore.collection('hosts').doc(context.hostId).get()\n const host = snapshot.exists ? ((snapshot.data() ?? {}) as Record<string, unknown>) : null\n if (!host || host['orgId'] !== context.orgId) {\n return { status: 404, error: 'Unknown site' }\n }\n const readers = await aiInsightReaders({\n orgId: context.orgId,\n hostId: context.hostId,\n org: context.org,\n host,\n surface: 'analytics',\n })\n return readers.some(({ reader }) => reader.id === AI_EXPERIMENT_READER_ID)\n ? null\n : { status: 403, error: AI_EXPERIMENT_NO_READER_COPY }\n}\n\n/**\n * The experiment kind's registrations, called by the console surface\n * (`registerAiJobKinds` in `server.ts`), never made by importing this module\n * (AGL-3025).\n */\nexport function registerAiExperimentJob(): void {\n registerAiJobStep('experiment', runAiJobExperimentStep, {\n minimumMs: AI_JOB_EXPERIMENT_STEP_MINIMUM_MS,\n })\n registerAiJobAdmission('experiment', aiExperimentAdmissionRefusal)\n}\n"],"names":["AI_EXPERIMENT_FIGURE_READER","AI_EXPERIMENT_MAX_VARIANTS","AI_EXPERIMENT_MIN_VARIANTS","AI_EXPERIMENT_SUBJECT_MAX_CHARS","aiExperimentNextStep","aiExperimentTarget","aiExperimentTask","aiExperimentVerdictNamesWinner","checkAiExperimentExplanation","checkAiExperimentVariants","readAiExperimentArms","readAiExperimentResult","AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","AI_ACCEPTABLE_USE_BLOCK","runAiRequest","AI_GENERATION_MAX_ATTEMPTS","aiInsightReaders","readAiInsightTables","registerAiJobAdmission","aiJobStepBudget","registerAiJobStep","AI_EXPERIMENT_EXPLAIN_TOOL_NAME","AI_EXPERIMENT_VARIANTS_TOOL_NAME","aiExperimentExplainTool","aiExperimentVariantsTool","parseAiExperimentExplanation","parseAiExperimentVariants","AI_EXPERIMENT_READER_ID","AI_EXPERIMENT_READ_DAYS","AI_EXPERIMENT_NO_TASK_COPY","AI_EXPERIMENT_NO_TARGET_COPY","AI_EXPERIMENT_NO_SITE_COPY","AI_EXPERIMENT_NO_READER_COPY","AI_EXPERIMENT_NO_SUBJECT_COPY","AI_EXPERIMENT_NO_TEST_COPY","AI_EXPERIMENT_NO_FIGURES_COPY","AI_EXPERIMENT_UNREADABLE_RESULT_COPY","AI_EXPERIMENT_NO_VARIANTS_COPY","AI_EXPERIMENT_NO_EXPLANATION_COPY","AI_JOB_EXPERIMENT_MAX_TOKENS","maxTokens","AI_JOB_EXPERIMENT_STEP_BUDGET","tier","attempts","lookups","AI_JOB_EXPERIMENT_STEP_MINIMUM_MS","minimumMs","AI_EXPERIMENT_RULES","join","AI_JOB_EXPERIMENT_SYSTEM","text","cacheBreakpoint","str","value","trim","TARGET_NOUN","screen","section","email","aiExperimentVariantsPrompt","input","brief","target","goal","subject","armText","arm","index","rate","against","lift","confidence","id","exposures","conversions","aiExperimentVerdictText","reading","verdict","winnerId","threshold","reasons","aiExperimentExplainPrompt","test","arms","map","experimentOutput","job","label","proposal","resource","inputs","$id","versionId","hostId","hostSubdomain","readExperimentFigures","context","place","snapshot","read","firestore","collection","doc","get","host","exists","data","orgId","readers","org","surface","experiments","filter","reader","length","days","params","uid","now","table","tables","refusals","runAiJobExperimentStep","result","signal","model","modelFor","task","spent","usage","estCostUsd","stopReason","effort","stop","failure","outputs","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","ask","messages","tool","system","tools","thinking","stream","slice","role","content","last","attempt","kind","refused","parsed","toolUse","find","use","name","checked","findings","console","info","jobId","variantsOutput","figures","createdBy","warn","explained","explanationOutput","variants","note","headline","points","next","aiExperimentAdmissionRefusal","status","error","some","registerAiExperimentJob"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,2BAA2B,EAC3BC,0BAA0B,EAC1BC,0BAA0B,EAC1BC,+BAA+B,EAC/BC,oBAAoB,EACpBC,kBAAkB,EAClBC,gBAAgB,EAChBC,8BAA8B,EAC9BC,4BAA4B,EAC5BC,yBAAyB,EACzBC,oBAAoB,EACpBC,sBAAsB,QAMjB,4BAAwB;AAE/B,SAASC,aAAa,QAAQ,0BAAsB;AACpD,SAASC,gBAAgB,EAAEC,cAAc,QAAQ,0BAAsB;AACvE,SACEC,uBAAuB,EACvBC,YAAY,QAGP,2BAAuB;AAC9B,SAASC,0BAA0B,QAAQ,qCAAiC;AAC5E,SAASC,gBAAgB,EAAEC,mBAAmB,QAAQ,oCAAgC;AACtF,SACEC,sBAAsB,QAGjB,wBAAoB;AAC3B,SAASC,eAAe,QAAQ,qBAAiB;AAEjD,SAASC,iBAAiB,QAAQ,eAAW;AAC7C,SACEC,+BAA+B,EAC/BC,gCAAgC,EAChCC,uBAAuB,EACvBC,wBAAwB,EACxBC,4BAA4B,EAC5BC,yBAAyB,QACpB,iCAA6B;AAEpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BC,GAED,sFAAsF,GACtF,OAAO,MAAMC,0BAA0B7B,4BAA2B;AAElE,oFAAoF,GACpF,OAAO,MAAM8B,0BAA0B,EAAC;AAExC,OAAO,MAAMC,6BACX,gGAA+F;AACjG,OAAO,MAAMC,+BACX,+DAA8D;AAChE,OAAO,MAAMC,6BAA6B,oCAAmC;AAC7E,OAAO,MAAMC,+BACX,kFAAiF;AACnF,OAAO,MAAMC,gCACX,6EAA4E;AAC9E,OAAO,MAAMC,6BACX,qDAAoD;AACtD,OAAO,MAAMC,gCACX,gEAA+D;AACjE,OAAO,MAAMC,uCACX,0GAAyG;AAC3G,OAAO,MAAMC,iCACX,sFAAqF;AACvF,OAAO,MAAMC,oCACX,gFAA+E;AAEjF,uDAAuD,GACvD,OAAO,MAAMC,+BAA+B5B,gBAAgB,CAAC,iBAAiB,CAAC6B,SAAS,CAAA;AAExF;;;CAGC,GACD,OAAO,MAAMC,gCAAgCtB,gBAAgB;IAC3DuB,MAAMhC,aAAa,CAAC,iBAAiB;IACrC8B,WAAWD;IACXI,UAAU5B;IACV6B,SAAS;AACX,GAAE;AAEF,OAAO,MAAMC,oCAAoCJ,8BAA8BK,SAAS,CAAA;AAExF,+EAA+E,GAC/E,OAAO,MAAMC,sBAAsB;IACjC;IACA;IACA,CAAC,yCAAyC,EAAEzB,iCAAiC,MAAM,CAAC;IACpF,CAAC,sBAAsB,EAAEtB,2BAA2B,KAAK,EAAED,2BAA2B,uGAAuG,CAAC;IAC9L;IACA;IACA;IACA;IACA;IACA;IACA,CAAC,kCAAkC,EAAEsB,gCAAgC,MAAM,CAAC;IAC5E;IACA;IACA;IACA;IACA;IACA;CACD,CAAC2B,IAAI,CAAC,MAAK;AAEZ,OAAO,MAAMC,2BAA4C;IACvD;QAAEC,MAAM,GAAGH,oBAAoB,IAAI,EAAElC,yBAAyB;QAAEsC,iBAAiB;IAAK;CACvF,CAAA;AAED,MAAMC,MAAM,CAACC,QAA4B,OAAOA,UAAU,WAAWA,MAAMC,IAAI,KAAK;AAEpF,+DAA+D,GAC/D,MAAMC,cAAkD;IACtDC,QAAQ;IACRC,SAAS;IACTC,OAAO;AACT;AAEA,mFAAmF,GACnF,OAAO,SAASC,2BAA2BC,KAK1C;IACC,OAAO;QACL,CAAC,OAAO,EAAEA,MAAMC,KAAK,EAAE;QACvB,CAAC,gBAAgB,EAAEN,WAAW,CAACK,MAAME,MAAM,CAAC,CAAC,CAAC,CAAC;WAC3CF,MAAMG,IAAI,GAAG;YAAC,CAAC,sBAAsB,EAAEH,MAAMG,IAAI,EAAE;SAAC,GAAG,EAAE;QAC7D;QACA;QACAH,MAAMI,OAAO;KACd,CAAChB,IAAI,CAAC;AACT;AAEA,gDAAgD,GAChD,SAASiB,QAAQC,GAAoB,EAAEC,KAAa;IAClD,MAAMC,OAAOF,IAAIE,IAAI,KAAK,OAAO,YAAY,GAAGF,IAAIE,IAAI,CAAC,CAAC,CAAC;IAC3D,MAAMC,UACJF,UAAU,IACN,6DACA;QACED,IAAII,IAAI,KAAK,OAAO,wBAAwB,CAAC,KAAK,EAAEJ,IAAII,IAAI,CAAC,CAAC,CAAC;QAC/DJ,IAAIK,UAAU,KAAK,OACf,8BACA,CAAC,WAAW,EAAEL,IAAIK,UAAU,CAAC,CAAC,CAAC;KACpC,CAACvB,IAAI,CAAC;IACb,OAAO,CAAC,EAAE,EAAEkB,IAAIM,EAAE,CAAC,QAAQ,EAAEN,IAAIO,SAAS,CAAC,YAAY,EAAEP,IAAIQ,WAAW,CAAC,OAAO,EAAEN,KAAK,EAAE,EAAEC,QAAQ,CAAC,CAAC;AACvG;AAEA,iDAAiD,GACjD,OAAO,SAASM,wBAAwBC,OAA4B;IAClE,IAAIA,QAAQC,OAAO,KAAK,UAAU;QAChC,OAAO,CAAC,SAAS,EAAED,QAAQE,QAAQ,CAAC,oEAAoE,EAAEF,QAAQG,SAAS,CAAC,0CAA0C,EAAEH,QAAQE,QAAQ,CAAC,sBAAsB,CAAC;IAClN;IACA,IAAIF,QAAQC,OAAO,KAAK,WAAW;QACjC,OAAO,CAAC,sDAAsD,EAAED,QAAQE,QAAQ,CAAC,qCAAqC,EAAEF,QAAQE,QAAQ,CAAC,sBAAsB,CAAC;IAClK;IACA,OAAO;QACL,CAAC,8BAA8B,EAAEF,QAAQI,OAAO,CAAChC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9D;QACA,CAAC,iEAAiE,EAAE9C,qBAAqB0E,QAAQC,OAAO,GAAG;KAC5G,CAAC7B,IAAI,CAAC;AACT;AAEA,2EAA2E,GAC3E,OAAO,SAASiC,0BAA0BrB,KAGzC;IACC,OAAO;WACDA,MAAMC,KAAK,GAAG;YAAC,CAAC,OAAO,EAAED,MAAMC,KAAK,EAAE;SAAC,GAAG,EAAE;QAChD,CAAC,MAAM,EAAED,MAAMgB,OAAO,CAACM,IAAI,EAAE;QAC7B;WACGtB,MAAMgB,OAAO,CAACO,IAAI,CAACC,GAAG,CAACnB;QAC1B;QACAU,wBAAwBf,MAAMgB,OAAO;KACtC,CAAC5B,IAAI,CAAC;AACT;AAEA,oEAAoE,GACpE,SAASqC,iBACPC,GAA6C,EAC7CC,KAAa,EACbC,QAAiC;QAQvBF;QAFAA,aAGWA;IAPrB,OAAO;QACLG,UAAU;QACV,0EAA0E;QAC1E,0EAA0E;QAC1EjB,IAAIpB,KAAIkC,cAAAA,IAAII,MAAM,qBAAVJ,WAAY,CAAC,eAAe,KAAKA,IAAIK,GAAG;QAChDC,WAAW;QACXC,MAAM,GAAEP,cAAAA,IAAIO,MAAM,YAAVP,cAAc;QACtBQ,eAAe1C,KAAIkC,eAAAA,IAAII,MAAM,qBAAVJ,YAAY,CAAC,gBAAgB,KAAK;QACrDC;QACAC;IACF;AACF;AAEA;;;;;;CAMC,GACD,eAAeO,sBACbC,OAA4D,EAC5DC,KAAmE;QAIlCC,gBAK1BF,cAYSG;IAnBhB,IAAI,CAACF,MAAMJ,MAAM,EAAE,OAAO;IAC1B,MAAMK,WAAW,MAAMF,QAAQI,SAAS,CAACC,UAAU,CAAC,SAASC,GAAG,CAACL,MAAMJ,MAAM,EAAEU,GAAG;IAClF,MAAMC,OAAON,SAASO,MAAM,IAAKP,iBAAAA,SAASQ,IAAI,cAAbR,iBAAmB,CAAC,IAAiC;IACtF,IAAI,CAACM,QAAQA,IAAI,CAAC,QAAQ,KAAKP,MAAMU,KAAK,EAAE,OAAO;IACnD,MAAMC,UAAU,MAAM5F,iBAAiB;QACrC2F,OAAOV,MAAMU,KAAK;QAClBd,QAAQI,MAAMJ,MAAM;QACpBgB,GAAG,GAAEb,eAAAA,QAAQa,GAAG,YAAXb,eAAe;QACpBQ;QACAM,SAAS;IACX;IACA,MAAMC,cAAcH,QAAQI,MAAM,CAAC,CAAC,EAAEC,MAAM,EAAE,GAAKA,OAAOzC,EAAE,KAAK7C;IACjE,IAAI,CAACoF,YAAYG,MAAM,EAAE,OAAO;IAChC,MAAMf,OAAO,MAAMlF,oBACjB8F,aACA;QAAC;YAAEE,QAAQtF;YAAyBwF,MAAMvF;YAAyBwF,QAAQ,CAAC;QAAE;KAAE,EAChF;QAAET,OAAOV,MAAMU,KAAK;QAAEd,QAAQI,MAAMJ,MAAM;QAAEwB,KAAKpB,MAAMoB,GAAG;QAAEC,KAAKtB,QAAQsB,GAAG;IAAC,GAC7E;IAEF,OAAO;QAAEC,KAAK,GAAEpB,gBAAAA,KAAKqB,MAAM,CAAC,EAAE,YAAdrB,gBAAkB;QAAMsB,UAAUtB,KAAKsB,QAAQ;IAAC;AAClE;AAEA,OAAO,MAAMC,yBAA0C,OACrD1B;;QA8FiBV,aA0BbqC;IAtHJ,MAAM,EAAErC,GAAG,EAAEsC,MAAM,EAAE,GAAG5B;IACxB,MAAM6B,gBAAQ7B,QAAQ8B,QAAQ,oBAAhB9B,QAAQ8B,QAAQ,MAAhB9B,SAAmB,oCAAqBpF,eAAe;IACrE,MAAM4B,YAAYC,8BAA8BD,SAAS,CAACqF;IAC1D,MAAME,OAAO3H,iBAAiBkF,IAAII,MAAM;IACxC,MAAMsC,QAAQ,CAACL,SAA+E,CAAA;YAC5FM,OAAON,OAAOM,KAAK;YACnBC,YAAYP,OAAOO,UAAU;YAC7BL;YACAM,YAAYR,OAAOQ,UAAU;YAC7BC,QAAQ;QACV,CAAA;IACA,MAAMC,OAAO,CAACC,UAAuC,CAAA;YACnDC,SAAS,EAAE;YACXN,OAAO;gBAAEO,aAAa;gBAAGC,cAAc;gBAAGC,iBAAiB;gBAAGC,kBAAkB;YAAE;YAClFT,YAAY;YACZL;YACAM,YAAY;YACZG;QACF,CAAA;IAEA,IAAI,CAACP,MAAM,OAAOM,KAAKxG;IACvB,IAAI,CAACyD,IAAIO,MAAM,EAAE,OAAOwC,KAAKtG;IAE7B,MAAM6G,MAAM,CAACC,UAAuBC,OAClChI,aAAa;YACX+G;YACAkB,QAAQ9F;YACR4F;YACAG,OAAO;gBAACF;aAAK;YACbtG;WACI7B,gBAAgB,CAAC,iBAAiB,CAACsI,QAAQ,GAC3C;YAAEA,UAAUtI,gBAAgB,CAAC,iBAAiB,CAACsI,QAAQ;QAAC,IACxD,CAAC;YACLC,QAAQ;WACJtB,SAAS;YAAEA;QAAO,IAAI,CAAC;IAG/B,IAAIG,SAAS,YAAY;YAGHzC,cAUJA;QAZhB,MAAMxB,SAAS3D,mBAAmBmF,IAAII,MAAM;QAC5C,IAAI,CAAC5B,QAAQ,OAAOuE,KAAKvG;QACzB,MAAMkC,UAAUZ,KAAIkC,eAAAA,IAAII,MAAM,qBAAVJ,YAAY,CAAC,UAAU,EAAE6D,KAAK,CAAC,GAAGlJ;QACtD,IAAI,CAAC+D,SAAS,OAAOqE,KAAKpG;QAC1B,MAAM6G,OAAOtH,yBAAyBsC;QACtC,IAAI+E,WAAwB;YAC1B;gBACEO,MAAM;gBACNC,SAAS1F,2BAA2B;oBAClCE,OAAOyB,IAAIzB,KAAK;oBAChBC;oBACAE;oBACAD,MAAMX,KAAIkC,eAAAA,IAAII,MAAM,qBAAVJ,YAAY,CAAC,OAAO;gBAChC;YACF;SACD;QACD,IAAIgE,OAAgC;QACpC,IAAK,IAAIC,UAAU,GAAGA,UAAUxI,4BAA4BwI,WAAW,EAAG;gBAMtE5B;YALF,MAAMA,SAAS,MAAMiB,IAAIC,UAAUC;YACnC,IAAInB,OAAO6B,IAAI,KAAK,WAAW;gBAC7B,OAAO;oBAAEjB,SAAS,EAAE;mBAAKP,MAAML;oBAAS8B,SAAS;;YACnD;YACA,MAAMC,SAAShI,2BACbiG,wBAAAA,OAAOgC,OAAO,CAACC,IAAI,CAAC,CAACC,MAAQA,IAAIC,IAAI,KAAKxI,sDAA1CqG,sBAA6E/D,KAAK;YAEpF,MAAMmG,UAAUxJ,0BAA0BmJ,QAAQ5F;YAClDwF,OAAO;gBAAEf,SAAS,EAAE;eAAKP,MAAML;gBAASW,SAASjG;;YACjD,IAAI0H,SAAS;gBACX,IAAIA,QAAQC,QAAQ,CAAC9C,MAAM,EAAE;oBAC3B+C,QAAQC,IAAI,CAAC,kCAAkC;wBAC7CC,OAAO7E,IAAIK,GAAG;wBACdqE,UAAUD,QAAQC,QAAQ;oBAC5B;gBACF;gBACA,OAAO;oBACLzB,SAAS;wBAAC6B,eAAe9E,KAAKxB,QAAQiG,QAAQvE,QAAQ;qBAAE;mBACrDwC,MAAML;YAEb;YACAkB,WAAW;mBACNA;gBACH;oBAAEO,MAAM;oBAAaC,SAAS,CAAC,cAAc,EAAE/H,iCAAiC,CAAC,CAAC;gBAAC;gBACnF;oBACE8H,MAAM;oBACNC,SACE,CAAC,oDAAoD,EAAE/H,iCAAiC,EAAE,CAAC,GAC3F,CAAC,SAAS,EAAEtB,2BAA2B,qDAAqD,CAAC,GAC7F;gBACJ;aACD;QACH;QACA,OAAOsJ,eAAAA,OAAQjB,KAAKhG;IACtB;IAEA,MAAM6C,OAAO9B,KAAIkC,cAAAA,IAAII,MAAM,qBAAVJ,WAAY,CAAC,OAAO;IACrC,IAAI,CAACJ,MAAM,OAAOmD,KAAKnG;IACvB,MAAMmI,UAAU,MAAMtE,sBAAsBC,SAAS;QACnDW,OAAOrB,IAAIqB,KAAK;QAChBd,QAAQP,IAAIO,MAAM;QAClBwB,KAAK/B,IAAIgF,SAAS;IACpB;IACA,IAAI,CAACD,SAAS,OAAOhC,KAAKrG;IAC1B,IAAI,CAACqI,QAAQ9C,KAAK,EAAE;QAClB,IAAI8C,QAAQ5C,QAAQ,CAACP,MAAM,EAAE;YAC3B+C,QAAQM,IAAI,CAAC,iCAAiC;gBAAEJ,OAAO7E,IAAIK,GAAG;gBAAE8B,UAAU4C,QAAQ5C,QAAQ;YAAC;QAC7F;QACA,OAAOY,KAAKlG;IACd;IACA,MAAMyC,UAAUnE,uBAAuByE,MAAM1E,qBAAqB6J,QAAQ9C,KAAK,EAAErC;IACjF,0EAA0E;IAC1E,kCAAkC;IAClC,IAAIN,QAAQC,OAAO,KAAK,cAAc,OAAOwD,KAAKjG;IAElD,MAAMuF,SAAS,MAAMiB,IACnB;QAAC;YAAEQ,MAAM;YAAQC,SAASpE,0BAA0B;gBAAEpB,OAAOyB,IAAIzB,KAAK;gBAAEe;YAAQ;QAAG;KAAE,EACrFrD;IAEF,IAAIoG,OAAO6B,IAAI,KAAK,WAAW,OAAO;QAAEjB,SAAS,EAAE;OAAKP,MAAML;QAAS8B,SAAS;;IAChF,MAAMe,YAAYlK,6BAChBmB,8BACEkG,uBAAAA,OAAOgC,OAAO,CAACC,IAAI,CAAC,CAACC,MAAQA,IAAIC,IAAI,KAAKzI,qDAA1CsG,qBAA4E/D,KAAK,GAEnFgB;IAEF,IAAI,CAAC4F,WAAW;QACd,OAAO;YAAEjC,SAAS,EAAE;WAAKP,MAAML;YAASW,SAAShG;;IACnD;IACA,IAAIkI,UAAUR,QAAQ,CAAC9C,MAAM,EAAE;QAC7B+C,QAAQC,IAAI,CAAC,qDAAqD;YAChEC,OAAO7E,IAAIK,GAAG;YACdd,SAASD,QAAQC,OAAO;YACxBmF,UAAUQ,UAAUR,QAAQ;QAC9B;IACF;IACA,OAAO;QAAEzB,SAAS;YAACkC,kBAAkBnF,KAAKV,SAAS4F;SAAW;OAAKxC,MAAML;AAC3E,EAAC;AAED,2CAA2C,GAC3C,SAASyC,eACP9E,GAA6C,EAC7CxB,MAA0B,EAC1B0B,QAAsC;IAEtC,OAAO,aACFH,iBAAiBC,KAAK,GAAGE,SAASkF,QAAQ,CAACxD,MAAM,CAAC,iBAAiB,CAAC,EAAE;QACvEa,MAAM;QACNjE;QACAC,MAAMyB,SAASzB,IAAI;QACnB2G,UAAUlF,SAASkF,QAAQ;IAC7B;QACAC,MAAM;;AAEV;AAEA,2EAA2E,GAC3E,SAASF,kBACPnF,GAA6C,EAC7CV,OAA4B,EAC5B4F,SAAkC;IAElC,OAAOnF,iBAAiBC,KAAKkF,UAAUtF,IAAI,IAAI,mBAAmB;QAChE6C,MAAM;QACNlD,SAAS2F,UAAU3F,OAAO;QAC1B+F,UAAUJ,UAAUI,QAAQ;QAC5BC,QAAQL,UAAUK,MAAM;QACxB,oEAAoE;QACpE,qDAAqD;QACrD/F,UAAUzE,+BAA+BuE,QAAQC,OAAO,IAAI2F,UAAU1F,QAAQ,GAAG;QACjFgG,MAAMN,UAAUM,IAAI;QACpB/F,WAAWH,QAAQG,SAAS;QAC5BI,MAAMP,QAAQO,IAAI;IACpB;AACF;AAEA;;;;;CAKC,GACD,OAAO,eAAe4F,6BACpB/E,OAA8B;QAOGE;IALjC,IAAI,CAAC9F,iBAAiB4F,QAAQN,MAAM,GAAG;QACrC,OAAO;YAAEsF,QAAQ;YAAKC,OAAOpJ;QAA2B;IAC1D;IACA,IAAI,CAACmE,QAAQH,MAAM,EAAE,OAAO;QAAEmF,QAAQ;QAAKC,OAAOlJ;IAA2B;IAC7E,MAAMmE,WAAW,MAAMF,QAAQI,SAAS,CAACC,UAAU,CAAC,SAASC,GAAG,CAACN,QAAQH,MAAM,EAAEU,GAAG;IACpF,MAAMC,OAAON,SAASO,MAAM,IAAKP,iBAAAA,SAASQ,IAAI,cAAbR,iBAAmB,CAAC,IAAiC;IACtF,IAAI,CAACM,QAAQA,IAAI,CAAC,QAAQ,KAAKR,QAAQW,KAAK,EAAE;QAC5C,OAAO;YAAEqE,QAAQ;YAAKC,OAAO;QAAe;IAC9C;IACA,MAAMrE,UAAU,MAAM5F,iBAAiB;QACrC2F,OAAOX,QAAQW,KAAK;QACpBd,QAAQG,QAAQH,MAAM;QACtBgB,KAAKb,QAAQa,GAAG;QAChBL;QACAM,SAAS;IACX;IACA,OAAOF,QAAQsE,IAAI,CAAC,CAAC,EAAEjE,MAAM,EAAE,GAAKA,OAAOzC,EAAE,KAAK7C,2BAC9C,OACA;QAAEqJ,QAAQ;QAAKC,OAAOjJ;IAA6B;AACzD;AAEA;;;;CAIC,GACD,OAAO,SAASmJ;IACd/J,kBAAkB,cAAcsG,wBAAwB;QACtD5E,WAAWD;IACb;IACA3B,uBAAuB,cAAc6J;AACvC"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-experiment-step.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 {\n AI_EXPERIMENT_FIGURE_READER,\n AI_EXPERIMENT_MAX_VARIANTS,\n AI_EXPERIMENT_MIN_VARIANTS,\n AI_EXPERIMENT_SUBJECT_MAX_CHARS,\n aiExperimentNextStep,\n aiExperimentTarget,\n aiExperimentTask,\n aiExperimentVerdictNamesWinner,\n checkAiExperimentExplanation,\n checkAiExperimentVariants,\n readAiExperimentArms,\n readAiExperimentResult,\n type AiExperimentArm,\n type AiExperimentExplanation,\n type AiExperimentReading,\n type AiExperimentTarget,\n type AiExperimentVariantsProposal,\n} from '../model/ai-experiment'\nimport type { AiJob, AiJobOutput } from '../model/ai-jobs.types'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n AI_ACCEPTABLE_USE_BLOCK,\n runAiRequest,\n type AiMessage,\n type AiSystemBlock,\n} from '../runtime/ai-runtime'\nimport { AI_GENERATION_MAX_ATTEMPTS } from '../runtime/ai-generation-bounds'\nimport { aiInsightReaders, readAiInsightTables } from '../insights/ai-insight-readers'\nimport {\n registerAiJobAdmission,\n type AiJobAdmissionContext,\n type AiJobAdmissionRefusal,\n} from './ai-job-admission'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport type { AiJobStepContext, AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\nimport {\n AI_EXPERIMENT_EXPLAIN_TOOL_NAME,\n AI_EXPERIMENT_VARIANTS_TOOL_NAME,\n aiExperimentExplainTool,\n aiExperimentVariantsTool,\n parseAiExperimentExplanation,\n parseAiExperimentVariants,\n} from '../tools/ai-experiment-tool'\n\n/**\n * The `experiment` step (AGL-2914): A/B tests by AI.\n *\n * A/B testing already exists — the marketing plugin models the experiment,\n * buckets visitors, counts exposures and conversions and compares the arms.\n * This step is the AI layer over it, and it does two things:\n *\n * 1. **Variants.** From the copy under test, it proposes between two and four\n * variants of it. It proposes COPY and nothing else: no weight, no traffic\n * split, no goal, no schedule and no winner. A person puts them into the\n * A/B testing card, which is the only thing that creates or starts a test.\n * 2. **The result.** Code reads the test's figures through the reader\n * marketing registers for them, decides what they support, and only then\n * asks the model for the words. The verdict is never the model's, and an\n * answer that claims more than the verdict is cut back to it.\n *\n * ## Nothing here writes a test\n *\n * Both answers are proposals, like a theme's or a listing's. The experiment\n * document, its variants' screen versions and its traffic are marketing's and\n * the besigner's, and a job that could write them could start an experiment on\n * live visitors without anyone deciding to.\n *\n * ## What reaches the model\n *\n * For variants: the brief, what the test varies, and the copy under test as\n * the surface handed it over. For a result: the test's name, its arms' counts\n * and rates as the results reader published them, and the verdict code\n * reached. No visitor, no session and no assignment is read, because the\n * reader publishes none — it answers in aggregates, which is the whole reason\n * a figure reader is what this reads.\n */\n\n/** The reader whose figures a result is explained from, and the plugin that owns it. */\nexport const AI_EXPERIMENT_READER_ID = AI_EXPERIMENT_FIGURE_READER\n\n/** The window a result is read over: a test's figures are totals since it started. */\nexport const AI_EXPERIMENT_READ_DAYS = 0\n\nexport const AI_EXPERIMENT_NO_TASK_COPY =\n 'Start this from the A/B testing card, which says whether you want variants or an explanation.'\nexport const AI_EXPERIMENT_NO_TARGET_COPY =\n 'Say whether the test varies a page, a section or an email.'\nexport const AI_EXPERIMENT_NO_SITE_COPY = 'Open the site whose test this is.'\nexport const AI_EXPERIMENT_NO_READER_COPY =\n 'A/B testing is not available on this workspace, so there is no test to work on.'\nexport const AI_EXPERIMENT_NO_SUBJECT_COPY =\n 'Give the copy the test varies, so there is something to write variants of.'\nexport const AI_EXPERIMENT_NO_TEST_COPY =\n 'Name the test to explain, as A/B testing lists it.'\nexport const AI_EXPERIMENT_NO_FIGURES_COPY =\n 'That test’s figures could not be read. Try again in a moment.'\nexport const AI_EXPERIMENT_UNREADABLE_RESULT_COPY =\n 'That test has no first variant and something to compare against it yet, so there is nothing to explain.'\nexport const AI_EXPERIMENT_NO_VARIANTS_COPY =\n 'The AI did not write variants that could be used. Try the brief in different words.'\nexport const AI_EXPERIMENT_NO_EXPLANATION_COPY =\n 'The AI did not explain the result in words that match its figures. Try again.'\n\n/** The step's answer ceiling, from the routing table. */\nexport const AI_JOB_EXPERIMENT_MAX_TOKENS = AI_ROUTING_TABLE['job.experiment'].maxTokens\n\n/**\n * The step's time: at most one re-ask at the routing ceiling, plus the one\n * figure read an explanation makes. Variants read nothing.\n */\nexport const AI_JOB_EXPERIMENT_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.experiment'],\n maxTokens: AI_JOB_EXPERIMENT_MAX_TOKENS,\n attempts: AI_GENERATION_MAX_ATTEMPTS,\n lookups: 1,\n})\n\nexport const AI_JOB_EXPERIMENT_STEP_MINIMUM_MS = AI_JOB_EXPERIMENT_STEP_BUDGET.minimumMs\n\n/** The rules both tasks are sent: static, byte-identical for every workspace. */\nexport const AI_EXPERIMENT_RULES = [\n 'You work on A/B tests for a website builder. A test shows visitors one of several variants of the same thing and counts which converts best. You never start, stop or change a test: you write variants for a person to put into the A/B testing card, and you put a result someone else measured into plain words.',\n '',\n `When you are given copy under test, call ${AI_EXPERIMENT_VARIANTS_TOOL_NAME} once:`,\n `- Answer with between ${AI_EXPERIMENT_MIN_VARIANTS} and ${AI_EXPERIMENT_MAX_VARIANTS} variants. The first is the copy as it stands, unchanged, so the test has something to measure against.`,\n '- Each later variant changes ONE idea — the promise, the specificity, the length, the call to action — so a result says which idea won.',\n '- No two variants say the same thing in different words.',\n '- Write plain text: no HTML, no markdown, no emoji, no fences.',\n '- Never invent a fact, a price, a name, a statistic or a guarantee the copy under test did not give you.',\n '- Write in the language of the copy under test.',\n '',\n `When you are given a result, call ${AI_EXPERIMENT_EXPLAIN_TOOL_NAME} once:`,\n '- The request states the verdict. It was decided from the counts before you were asked, and it is not yours to revise.',\n '- Where the verdict names no winner, write nothing that says or implies one: no variant is ahead, none is better, nothing is significant, and you recommend nothing. Say what the figures show and why they do not settle it.',\n '- Where the verdict names a winner, name that variant and no other.',\n '- Every number you write appears in the figures you were given, as they show it or rounded. Never compute one.',\n '- Plain sentences: no markdown, no headings, no lists.',\n '- Write in the language of the brief.',\n].join('\\n')\n\nexport const AI_JOB_EXPERIMENT_SYSTEM: AiSystemBlock[] = [\n { text: `${AI_EXPERIMENT_RULES}\\n\\n${AI_ACCEPTABLE_USE_BLOCK}`, cacheBreakpoint: true },\n]\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** What the test varies, in the words the prompt names it by. */\nconst TARGET_NOUN: Record<AiExperimentTarget, string> = {\n screen: 'a page',\n section: 'one section of a page',\n email: 'an email',\n}\n\n/** The user turn for variants: the brief, what varies, and the copy as it stands. */\nexport function aiExperimentVariantsPrompt(input: {\n brief: string\n target: AiExperimentTarget\n subject: string\n goal: string\n}): string {\n return [\n `Brief: ${input.brief}`,\n `The test varies ${TARGET_NOUN[input.target]}.`,\n ...(input.goal ? [`It is trying to move: ${input.goal}`] : []),\n '',\n 'The copy under test, as it stands:',\n input.subject,\n ].join('\\n')\n}\n\n/** One arm as the explanation prompt shows it. */\nfunction armText(arm: AiExperimentArm, index: number): string {\n const rate = arm.rate === null ? 'no rate' : `${arm.rate}%`\n const against =\n index === 0\n ? 'the first variant, which the others are compared against'\n : [\n arm.lift === null ? 'lift not measurable' : `lift ${arm.lift}%`,\n arm.confidence === null\n ? 'confidence not measurable'\n : `confidence ${arm.confidence}%`,\n ].join(', ')\n return `- ${arm.id}: shown ${arm.exposures}, converted ${arm.conversions}, rate ${rate} (${against})`\n}\n\n/** What the verdict tells the model it may say. */\nexport function aiExperimentVerdictText(reading: AiExperimentReading): string {\n if (reading.verdict === 'winner') {\n return `VERDICT: ${reading.winnerId} is ahead of the first variant by more than chance explains, at the ${reading.threshold}% confidence this test is called at. Name ${reading.winnerId} and no other variant.`\n }\n if (reading.verdict === 'control') {\n return `VERDICT: every other variant is behind the first one, ${reading.winnerId}, by more than chance explains. Name ${reading.winnerId} and no other variant.`\n }\n return [\n `VERDICT: THERE IS NO WINNER — ${reading.reasons.join('; ')}.`,\n 'Say what the figures show and why they do not settle it. Name no winner, say no variant is ahead, better, winning or significant, and recommend no variant.',\n `Leave winner and next empty; what to do next is already decided: ${aiExperimentNextStep(reading.verdict)}`,\n ].join('\\n')\n}\n\n/** The user turn for an explanation: the test, its arms, and the verdict. */\nexport function aiExperimentExplainPrompt(input: {\n brief: string\n reading: AiExperimentReading\n}): string {\n return [\n ...(input.brief ? [`Brief: ${input.brief}`] : []),\n `Test: ${input.reading.test}`,\n 'Figures, totals since the test started:',\n ...input.reading.arms.map(armText),\n '',\n aiExperimentVerdictText(input.reading),\n ].join('\\n')\n}\n\n/** The proposal an answer becomes, as the job's output carries it. */\nfunction experimentOutput(\n job: Pick<AiJob, '$id' | 'hostId' | 'inputs'>,\n label: string,\n proposal: Record<string, unknown>,\n): AiJobOutput {\n return {\n resource: 'experiment',\n // The test the surface named, so a person opens the one they asked about;\n // a variants job for a test that does not exist yet names the job itself.\n id: str(job.inputs?.['experimentId']) || job.$id,\n versionId: null,\n hostId: job.hostId ?? null,\n hostSubdomain: str(job.inputs?.['hostSubdomain']) || null,\n label,\n proposal,\n }\n}\n\n/**\n * The A/B test results for this job's site, or `null` when the workspace has\n * no such reader. The insight path's own resolver is what decides whether a\n * reader may be read here — the plan's entitlement, the owning plugin's\n * release flag, and whether that plugin runs on this workspace and this site\n * — so this asks it rather than restating any of it.\n */\nasync function readExperimentFigures(\n context: Pick<AiJobStepContext, 'firestore' | 'org' | 'now'>,\n place: { orgId: string; hostId: string | null; uid: string | null },\n): Promise<{ table: Parameters<typeof readAiExperimentArms>[0]; refusals: string[] } | null> {\n if (!place.hostId) return null\n const snapshot = await context.firestore.collection('hosts').doc(place.hostId).get()\n const host = snapshot.exists ? ((snapshot.data() ?? {}) as Record<string, unknown>) : null\n if (!host || host['orgId'] !== place.orgId) return null\n const readers = await aiInsightReaders({\n orgId: place.orgId,\n hostId: place.hostId,\n org: context.org ?? null,\n host,\n surface: 'analytics',\n })\n const experiments = readers.filter(({ reader }) => reader.id === AI_EXPERIMENT_READER_ID)\n if (!experiments.length) return null\n const read = await readAiInsightTables(\n experiments,\n [{ reader: AI_EXPERIMENT_READER_ID, days: AI_EXPERIMENT_READ_DAYS, params: {} }],\n { orgId: place.orgId, hostId: place.hostId, uid: place.uid, now: context.now },\n 1,\n )\n return { table: read.tables[0] ?? null, refusals: read.refusals }\n}\n\nexport const runAiJobExperimentStep: AiJobStepRunner = async (\n context: AiJobStepContext,\n) => {\n const { job, signal } = context\n const model = context.modelFor?.('job.experiment') ?? aiModelForStep('job.experiment')\n const maxTokens = AI_JOB_EXPERIMENT_STEP_BUDGET.maxTokens(model)\n const task = aiExperimentTask(job.inputs)\n const spent = (result: { usage: unknown; estCostUsd: number; stopReason: string | null }) => ({\n usage: result.usage as AiJobStepOutcome['usage'],\n estCostUsd: result.estCostUsd,\n model,\n stopReason: result.stopReason,\n effort: null,\n })\n const stop = (failure: string): AiJobStepOutcome => ({\n outputs: [],\n usage: { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 },\n estCostUsd: 0,\n model,\n stopReason: null,\n failure,\n })\n\n if (!task) return stop(AI_EXPERIMENT_NO_TASK_COPY)\n if (!job.hostId) return stop(AI_EXPERIMENT_NO_SITE_COPY)\n\n const ask = (messages: AiMessage[], tool: ReturnType<typeof aiExperimentExplainTool>) =>\n runAiRequest({\n model,\n system: AI_JOB_EXPERIMENT_SYSTEM,\n messages,\n tools: [tool],\n maxTokens,\n ...(AI_ROUTING_TABLE['job.experiment'].thinking\n ? { thinking: AI_ROUTING_TABLE['job.experiment'].thinking }\n : {}),\n stream: false,\n ...(signal ? { signal } : {}),\n })\n\n if (task === 'variants') {\n const target = aiExperimentTarget(job.inputs)\n if (!target) return stop(AI_EXPERIMENT_NO_TARGET_COPY)\n const subject = str(job.inputs?.['subject']).slice(0, AI_EXPERIMENT_SUBJECT_MAX_CHARS)\n if (!subject) return stop(AI_EXPERIMENT_NO_SUBJECT_COPY)\n const tool = aiExperimentVariantsTool(target)\n let messages: AiMessage[] = [\n {\n role: 'user',\n content: aiExperimentVariantsPrompt({\n brief: job.brief,\n target,\n subject,\n goal: str(job.inputs?.['goal']),\n }),\n },\n ]\n let last: AiJobStepOutcome | null = null\n for (let attempt = 0; attempt < AI_GENERATION_MAX_ATTEMPTS; attempt += 1) {\n const result = await ask(messages, tool)\n if (result.kind === 'refusal') {\n return { outputs: [], ...spent(result), refused: true }\n }\n const parsed = parseAiExperimentVariants(\n result.toolUse.find((use) => use.name === AI_EXPERIMENT_VARIANTS_TOOL_NAME)?.input,\n )\n const checked = checkAiExperimentVariants(parsed, target)\n last = { outputs: [], ...spent(result), failure: AI_EXPERIMENT_NO_VARIANTS_COPY }\n if (checked) {\n if (checked.findings.length) {\n console.info('ai experiment variants dropped', {\n jobId: job.$id,\n findings: checked.findings,\n })\n }\n return {\n outputs: [variantsOutput(job, target, checked.proposal)],\n ...spent(result),\n }\n }\n messages = [\n ...messages,\n { role: 'assistant', content: `Answered with ${AI_EXPERIMENT_VARIANTS_TOOL_NAME}.` },\n {\n role: 'user',\n content:\n `Those variants could not be used. Answer again with ${AI_EXPERIMENT_VARIANTS_TOOL_NAME}: ` +\n `at least ${AI_EXPERIMENT_MIN_VARIANTS} variants, each with copy of its own, none repeating ` +\n 'another, and plain text throughout.',\n },\n ]\n }\n return last ?? stop(AI_EXPERIMENT_NO_VARIANTS_COPY)\n }\n\n const test = str(job.inputs?.['test'])\n if (!test) return stop(AI_EXPERIMENT_NO_TEST_COPY)\n const figures = await readExperimentFigures(context, {\n orgId: job.orgId,\n hostId: job.hostId,\n uid: job.createdBy,\n })\n if (!figures) return stop(AI_EXPERIMENT_NO_READER_COPY)\n if (!figures.table) {\n if (figures.refusals.length) {\n console.warn('ai experiment figures refused', { jobId: job.$id, refusals: figures.refusals })\n }\n return stop(AI_EXPERIMENT_NO_FIGURES_COPY)\n }\n const reading = readAiExperimentResult(test, readAiExperimentArms(figures.table, test))\n // Nothing to explain, and nothing spent finding that out: the verdict was\n // reached from the figures alone.\n if (reading.verdict === 'unreadable') return stop(AI_EXPERIMENT_UNREADABLE_RESULT_COPY)\n\n const result = await ask(\n [{ role: 'user', content: aiExperimentExplainPrompt({ brief: job.brief, reading }) }],\n aiExperimentExplainTool(),\n )\n if (result.kind === 'refusal') return { outputs: [], ...spent(result), refused: true }\n const explained = checkAiExperimentExplanation(\n parseAiExperimentExplanation(\n result.toolUse.find((use) => use.name === AI_EXPERIMENT_EXPLAIN_TOOL_NAME)?.input,\n ),\n reading,\n )\n if (!explained) {\n return { outputs: [], ...spent(result), failure: AI_EXPERIMENT_NO_EXPLANATION_COPY }\n }\n if (explained.findings.length) {\n console.info('ai experiment explanation cut back to its reading', {\n jobId: job.$id,\n verdict: reading.verdict,\n findings: explained.findings,\n })\n }\n return { outputs: [explanationOutput(job, reading, explained)], ...spent(result) }\n}\n\n/** A variants answer as the job's output. */\nfunction variantsOutput(\n job: Pick<AiJob, '$id' | 'hostId' | 'inputs'>,\n target: AiExperimentTarget,\n proposal: AiExperimentVariantsProposal,\n): AiJobOutput {\n return {\n ...experimentOutput(job, `${proposal.variants.length} variants to test`, {\n task: 'variants',\n target,\n goal: proposal.goal,\n variants: proposal.variants,\n }),\n note: 'Put these into A/B testing to start the test; nothing has been changed on the site.',\n }\n}\n\n/** An explanation as the job's output, carrying the verdict code reached. */\nfunction explanationOutput(\n job: Pick<AiJob, '$id' | 'hostId' | 'inputs'>,\n reading: AiExperimentReading,\n explained: AiExperimentExplanation,\n): AiJobOutput {\n return experimentOutput(job, explained.test || 'A/B test result', {\n task: 'explain',\n verdict: explained.verdict,\n headline: explained.headline,\n points: explained.points,\n // Only ever the reading's arm: an explanation carries no winner the\n // figures did not support, whatever the answer said.\n winnerId: aiExperimentVerdictNamesWinner(reading.verdict) ? explained.winnerId : null,\n next: explained.next,\n threshold: reading.threshold,\n arms: reading.arms,\n })\n}\n\n/**\n * Whether an experiment job may run here: a site of the job's own org, a task\n * the step runs, and A/B testing available on it — which is the results\n * reader being one this workspace may read, asked of the owning plugin rather\n * than restated.\n */\nexport async function aiExperimentAdmissionRefusal(\n context: AiJobAdmissionContext,\n): Promise<AiJobAdmissionRefusal | null> {\n if (!aiExperimentTask(context.inputs)) {\n return { status: 400, error: AI_EXPERIMENT_NO_TASK_COPY }\n }\n if (!context.hostId) return { status: 400, error: AI_EXPERIMENT_NO_SITE_COPY }\n const snapshot = await context.firestore.collection('hosts').doc(context.hostId).get()\n const host = snapshot.exists ? ((snapshot.data() ?? {}) as Record<string, unknown>) : null\n if (!host || host['orgId'] !== context.orgId) {\n return { status: 404, error: 'Unknown site' }\n }\n const readers = await aiInsightReaders({\n orgId: context.orgId,\n hostId: context.hostId,\n org: context.org,\n host,\n surface: 'analytics',\n })\n return readers.some(({ reader }) => reader.id === AI_EXPERIMENT_READER_ID)\n ? null\n : { status: 403, error: AI_EXPERIMENT_NO_READER_COPY }\n}\n\n/**\n * The experiment kind's registrations, called by the console surface\n * (`registerAiJobKinds` in `server.ts`), never made by importing this module\n * (AGL-3025).\n */\nexport function registerAiExperimentJob(): void {\n registerAiJobStep('experiment', runAiJobExperimentStep, {\n minimumMs: AI_JOB_EXPERIMENT_STEP_MINIMUM_MS,\n })\n registerAiJobAdmission('experiment', aiExperimentAdmissionRefusal)\n}\n"],"names":["AI_EXPERIMENT_FIGURE_READER","AI_EXPERIMENT_MAX_VARIANTS","AI_EXPERIMENT_MIN_VARIANTS","AI_EXPERIMENT_SUBJECT_MAX_CHARS","aiExperimentNextStep","aiExperimentTarget","aiExperimentTask","aiExperimentVerdictNamesWinner","checkAiExperimentExplanation","checkAiExperimentVariants","readAiExperimentArms","readAiExperimentResult","AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","AI_ACCEPTABLE_USE_BLOCK","runAiRequest","AI_GENERATION_MAX_ATTEMPTS","aiInsightReaders","readAiInsightTables","registerAiJobAdmission","aiJobStepBudget","registerAiJobStep","AI_EXPERIMENT_EXPLAIN_TOOL_NAME","AI_EXPERIMENT_VARIANTS_TOOL_NAME","aiExperimentExplainTool","aiExperimentVariantsTool","parseAiExperimentExplanation","parseAiExperimentVariants","AI_EXPERIMENT_READER_ID","AI_EXPERIMENT_READ_DAYS","AI_EXPERIMENT_NO_TASK_COPY","AI_EXPERIMENT_NO_TARGET_COPY","AI_EXPERIMENT_NO_SITE_COPY","AI_EXPERIMENT_NO_READER_COPY","AI_EXPERIMENT_NO_SUBJECT_COPY","AI_EXPERIMENT_NO_TEST_COPY","AI_EXPERIMENT_NO_FIGURES_COPY","AI_EXPERIMENT_UNREADABLE_RESULT_COPY","AI_EXPERIMENT_NO_VARIANTS_COPY","AI_EXPERIMENT_NO_EXPLANATION_COPY","AI_JOB_EXPERIMENT_MAX_TOKENS","maxTokens","AI_JOB_EXPERIMENT_STEP_BUDGET","tier","attempts","lookups","AI_JOB_EXPERIMENT_STEP_MINIMUM_MS","minimumMs","AI_EXPERIMENT_RULES","join","AI_JOB_EXPERIMENT_SYSTEM","text","cacheBreakpoint","str","value","trim","TARGET_NOUN","screen","section","email","aiExperimentVariantsPrompt","input","brief","target","goal","subject","armText","arm","index","rate","against","lift","confidence","id","exposures","conversions","aiExperimentVerdictText","reading","verdict","winnerId","threshold","reasons","aiExperimentExplainPrompt","test","arms","map","experimentOutput","job","label","proposal","resource","inputs","$id","versionId","hostId","hostSubdomain","readExperimentFigures","context","place","snapshot","read","firestore","collection","doc","get","host","exists","data","orgId","readers","org","surface","experiments","filter","reader","length","days","params","uid","now","table","tables","refusals","runAiJobExperimentStep","result","signal","model","modelFor","task","spent","usage","estCostUsd","stopReason","effort","stop","failure","outputs","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","ask","messages","tool","system","tools","thinking","stream","slice","role","content","last","attempt","kind","refused","parsed","toolUse","find","use","name","checked","findings","console","info","jobId","variantsOutput","figures","createdBy","warn","explained","explanationOutput","variants","note","headline","points","next","aiExperimentAdmissionRefusal","status","error","some","registerAiExperimentJob"],"mappings":"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"}
@@ -231,6 +231,17 @@ export declare function aiCopiedFormReview(firestore: FirebaseFirestore.Firestor
231
231
  name: string;
232
232
  plan: AiJobPlan | null;
233
233
  }): Promise<AiJobReview | null>;
234
+ /**
235
+ * What the form a short copy would have started from collects, as the build
236
+ * that replaces the copy is told it (AGL-3143 §14): every field it asks for,
237
+ * so the build keeps them beside what the plan adds. Empty when the form
238
+ * cannot be read.
239
+ */
240
+ export declare function aiFormSourceLines(firestore: FirebaseFirestore.Firestore, input: {
241
+ hostId: string;
242
+ id: string;
243
+ name: string;
244
+ }): Promise<string[]>;
234
245
  /**
235
246
  * What the person decides next, in words the step writes itself: a lead
236
247
  * routing to keep or switch off, the list a proposal could not store, and what
@@ -257,7 +268,12 @@ export declare function aiJobFormPrompt(job: Pick<AiJob, 'brief'>, plan: AiJobPl
257
268
  audience?: string;
258
269
  submissions?: AiSiteSubmissions | null;
259
270
  }): string;
260
- /** A form job is admitted for a site of the job's own org whose plan has forms, with a form to spare. */
271
+ /**
272
+ * A form job is admitted for a site of the job's own org whose plan has
273
+ * forms, with a form to spare, and confirmed only for a plan that creates
274
+ * nothing but the form: a form places nothing another job builds
275
+ * (AGL-3143 §15).
276
+ */
261
277
  export declare const aiFormJobAdmission: AiJobAdmission;
262
278
  export interface AiJobFormStepDeps {
263
279
  /** The inventory reader; specs hand in a fake. */
@@ -27,6 +27,7 @@ import { aiDoctrineTreeTool, runValidatedGeneration } from "../runtime/ai-doctri
27
27
  import { AI_PALETTE } from "../runtime/ai-palette.generated.js";
28
28
  import { readSiteInventory } from "../runtime/site-inventory.js";
29
29
  import { registerAiJobAdmission } from "./ai-job-admission.js";
30
+ import { aiJobPlanCreationsRefusal } from "./ai-job-plan-creations.js";
30
31
  import { aiJobDraftId } from "./ai-job-draft-ids.js";
31
32
  import { aiDraftAdmissionRefusal, aiDraftAllowanceRefusal, aiSiteSubdomain, readAiDraft, writeAiDraft } from "./ai-job-drafts.js";
32
33
  import { aiBracketedFactsNote, aiConfirmedPlan, aiDoctrineReview, aiGenerationSpent, aiJobBriefLine, aiLimitReview, aiModelNodeIds, aiPlanCreation, aiPlanReferenceLines, aiUnspentOutcome } from "./ai-job-generation.js";
@@ -439,6 +440,32 @@ function oneLine(value, max) {
439
440
  ]
440
441
  });
441
442
  }
443
+ /**
444
+ * What the form a short copy would have started from collects, as the build
445
+ * that replaces the copy is told it (AGL-3143 §14): every field it asks for,
446
+ * so the build keeps them beside what the plan adds. Empty when the form
447
+ * cannot be read.
448
+ */ export async function aiFormSourceLines(firestore, input) {
449
+ var _snapshot_get;
450
+ const snapshot = await firestore.collection('hosts').doc(input.hostId).collection('forms').doc(input.id).get();
451
+ const fields = snapshot.exists ? (_snapshot_get = snapshot.get('fields')) != null ? _snapshot_get : [] : [];
452
+ const asked = fields.flatMap((field)=>{
453
+ var _ref, _row_fieldName, _row_fieldType;
454
+ if (!field || typeof field !== 'object') return [];
455
+ const row = field;
456
+ const name = String((_ref = (_row_fieldName = row['fieldName']) != null ? _row_fieldName : row['name']) != null ? _ref : '').trim();
457
+ if (!name) return [];
458
+ const fieldType = (_row_fieldType = row['fieldType']) != null ? _row_fieldType : row['type'];
459
+ const type = typeof fieldType === 'string' ? ` (${fieldType}${row['required'] ? ', required' : ''})` : '';
460
+ return [
461
+ `${name}${type}`
462
+ ];
463
+ });
464
+ if (!asked.length) return [];
465
+ return [
466
+ `The plan starts from a copy of the form "${input.name}", which lacks part of what the plan gives it. Build from it: it collects ${asked.join(', ')}. Keep each of those, and add what the plan adds.`
467
+ ];
468
+ }
442
469
  function listOf(items) {
443
470
  return items.length < 2 ? items.join('') : `${items.slice(0, -1).join(', ')} and ${items[items.length - 1]}`;
444
471
  }
@@ -490,11 +517,17 @@ function listOf(items) {
490
517
  inbox: 'the Inbox, to be read — routing.kind is inbox',
491
518
  lead: 'the Inbox, and each one with an email address is a sales lead — routing.kind is lead'
492
519
  };
493
- /** A form job is admitted for a site of the job's own org whose plan has forms, with a form to spare. */ export const aiFormJobAdmission = (context)=>aiDraftAdmissionRefusal(context.firestore, {
520
+ /**
521
+ * A form job is admitted for a site of the job's own org whose plan has
522
+ * forms, with a form to spare, and confirmed only for a plan that creates
523
+ * nothing but the form: a form places nothing another job builds
524
+ * (AGL-3143 §15).
525
+ */ export const aiFormJobAdmission = (context)=>aiDraftAdmissionRefusal(context.firestore, {
494
526
  orgId: context.orgId,
495
527
  hostId: context.hostId,
496
528
  kind: 'form',
497
- org: context.org
529
+ org: context.org,
530
+ ownCheck: (hostId)=>aiJobPlanCreationsRefusal('form', context, hostId)
498
531
  });
499
532
  export function createAiJobFormStep(deps = {}) {
500
533
  var _deps_readInventory, _deps_duplicate;
@@ -542,8 +575,24 @@ export function createAiJobFormStep(deps = {}) {
542
575
  const name = (creation == null ? void 0 : creation.name) || AI_JOB_FORM_DEFAULT_NAME;
543
576
  // The plan starts from a copy of a form the site has (rule 15): the copy
544
577
  // is the draft, and nothing is generated. A source gone since the plan
545
- // was made is built from the brief instead.
546
- if ((creation == null ? void 0 : creation.duplicateOf) && inventory.forms.some((form)=>form.id === creation.duplicateOf)) {
578
+ // was made is built from the brief instead, and one that does not collect
579
+ // a field the plan gives the form is built from the plan, told what the
580
+ // source collects (AGL-3143 §14): copying it could only end in the review
581
+ // below. A source this step cannot read still copies.
582
+ const source = (creation == null ? void 0 : creation.duplicateOf) ? inventory.forms.find((form)=>form.id === creation.duplicateOf) : undefined;
583
+ const sourceReview = source ? await aiCopiedFormReview(firestore, {
584
+ hostId,
585
+ formId: source.id,
586
+ name: source.name,
587
+ plan
588
+ }) : null;
589
+ const sourceShort = Boolean(sourceReview == null ? void 0 : sourceReview.findings.length);
590
+ const sourceLines = source && sourceShort ? await aiFormSourceLines(firestore, {
591
+ hostId,
592
+ id: source.id,
593
+ name: source.name
594
+ }) : [];
595
+ if ((creation == null ? void 0 : creation.duplicateOf) && source && !sourceShort) {
547
596
  const copy = await duplicate('form', {
548
597
  orgId: job.orgId,
549
598
  hostId,
@@ -625,10 +674,13 @@ export function createAiJobFormStep(deps = {}) {
625
674
  messages: [
626
675
  {
627
676
  role: 'user',
628
- content: aiJobFormPrompt(job, plan, name, {
629
- audience,
630
- submissions: decisions.submissions
631
- })
677
+ content: [
678
+ aiJobFormPrompt(job, plan, name, {
679
+ audience,
680
+ submissions: decisions.submissions
681
+ }),
682
+ ...sourceLines
683
+ ].join('\n')
632
684
  }
633
685
  ],
634
686
  tool: AI_JOB_FORM_TOOL,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-form-step.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 {\n checkFormContract,\n formFieldsCanYieldAnEmail,\n} from '@aglyn/aglyn/app-utils/form-contract'\nimport {\n collectFormFieldNodeIds,\n FORM_COMPONENT_ID,\n FORM_FIELD_COMPONENT_ID,\n FORM_ID_PROP,\n formFieldDeclsFromNodes,\n formNodeIdIn,\n isMarketingConsentFieldName,\n MARKETING_CONSENT_FORM_FIELD,\n type FormDocument,\n type FormFieldDecl,\n type FormRouting,\n} from '@aglyn/aglyn/app-utils/forms'\nimport { decodeStoredNodes } from '@aglyn/aglyn/app-utils/stored-nodes'\nimport { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { NodeType, type NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { duplicateResource } from '@aglyn/tenant-data-admin/server/duplicate-resource'\nimport type { AiJob, AiJobOutput, AiJobPlan, AiJobReview } from '../model/ai-jobs.types'\nimport { aiSiteSubmissions, aiSiteWords, type AiSiteSubmissions } from '../model/ai-site-job'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n aiDoctrineTreeTool,\n runValidatedGeneration,\n type AiValidatedTree,\n} from '../runtime/ai-doctrine'\nimport type { AiDoctrineTree, AiDoctrineViolation } from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport { AI_PALETTE } from '../runtime/ai-palette.generated'\nimport type { AiSystemBlock, AiTool } from '../runtime/ai-runtime'\nimport { readSiteInventory } from '../runtime/site-inventory'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport { aiJobDraftId } from './ai-job-draft-ids'\nimport {\n aiDraftAdmissionRefusal,\n aiDraftAllowanceRefusal,\n aiSiteSubdomain,\n readAiDraft,\n writeAiDraft,\n type AiDraftRecord,\n} from './ai-job-drafts'\nimport {\n aiBracketedFactsNote,\n aiConfirmedPlan,\n aiDoctrineReview,\n aiGenerationSpent,\n aiJobBriefLine,\n aiLimitReview,\n aiModelNodeIds,\n aiPlanCreation,\n aiPlanReferenceLines,\n aiUnspentOutcome,\n} from './ai-job-generation'\nimport type { AiJobStepRunner } from './ai-job-text-step'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The form step (AGL-2913): the generation step of a `form` job, run once a\n * member confirmed the job's plan.\n *\n * A form is two halves that must agree for its submissions to arrive: the\n * DESIGN a visitor fills in (`rootId`, `nodes`) and the DECLARATION the submit\n * route reads (`fields`, `consentFieldName`, `routing`). `checkFormContract`\n * is the rule that holds them together when a form is published. So the model\n * writes only the design, through `runValidatedGeneration('form', …)`, and\n * everything else is derived from it with the functions the publish paths\n * use:\n *\n * - `fields` is `formFieldDeclsFromNodes` over the design;\n * - a form that can yield an email address gets the platform's marketing\n * consent field (`MARKETING_CONSENT_FORM_FIELD`, the Forms editor preset's own\n * props) in place of any consent-like field the model drew, named as its\n * `consentFieldName`;\n * - `routing` is `{ lead: true }` when the answer proposes a lead, and\n * nothing otherwise. The stored routing has no place for an email list,\n * so a list proposal leaves the form on the Inbox and says, in the output's\n * `note`, how to enroll the people who tick consent — naming a list only\n * when the brief named one. The step reads no list, contact, CRM record or\n * submission, and sends none.\n *\n * The design is then stamped the way the Forms page's Create stamps one — the\n * form node bound to the new form's id, a canvas root above it — and\n * `checkFormContract` runs on it through `extend`, beside the doctrine's own\n * checks, so a design the contract would refuse at publish is asked for again\n * with the violation named.\n *\n * The form lands as a new draft (`ai-job-drafts.ts`) that no page places and\n * nothing promotes. A plan that starts from a copy of a form the site has gets\n * that copy, through the platform's duplicate module, and nothing generated —\n * and because that path reaches no model, `extend` never runs on it, so the\n * step asks `checkFormContract` of the stored copy itself before it reports\n * one (AGL-3024). A copy still bound to the form it was copied from is a\n * failure, never an output.\n *\n * Like the other generation steps, it runs on the job beat, never at an inline\n * door: it registers the least time its lookup rounds, its answer and its\n * re-ask need (AGL-3035), and runs without extended thinking under the routing\n * table's ceiling for a form, which is the doctrine's own.\n */\n\n/**\n * The longest answer a form may run to: the routing table's `job.form`\n * ceiling, which holds the largest form the output budget admits\n * (`ai-job-form-step.spec.ts` measures it) and is the doctrine's ceiling for\n * the form kind.\n */\nexport const AI_JOB_FORM_MAX_TOKENS = AI_ROUTING_TABLE['job.form'].maxTokens\n\n/**\n * The form step's time (AGL-3035, AGL-3036): its two inventory-lookup rounds,\n * its answer and its re-ask at that ceiling on the tier `job.form` is served\n * from, fitted to what a beat can give a step.\n */\nexport const AI_JOB_FORM_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.form'],\n maxTokens: AI_JOB_FORM_MAX_TOKENS,\n})\n\n/** The least time one form step needs before it starts. */\nexport const AI_JOB_FORM_STEP_MINIMUM_MS = AI_JOB_FORM_STEP_BUDGET.minimumMs\n\n/** A form's name when the plan names none. */\nexport const AI_JOB_FORM_DEFAULT_NAME = 'New form'\n\n/** Where a proposal routes a form's submissions. */\nexport const AI_FORM_ROUTING_KINDS = ['inbox', 'list', 'lead'] as const\n\nexport type AiFormRoutingKind = (typeof AI_FORM_ROUTING_KINDS)[number]\n\n/** The most things a note says the brief asked for and a form cannot collect. */\nexport const AI_FORM_CANNOT_COLLECT_MAX = 3\n\nconst CANNOT_COLLECT_MAX_CHARS = 60\nconst LIST_NAME_MAX_CHARS = 80\n\n/** The node id the platform's consent field takes in a generated design. */\nexport const AI_FORM_CONSENT_NODE_ID = 'marketingConsentField'\n\n/** The form step's own instructions, cached after the doctrine. */\nexport const AI_JOB_FORM_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n 'You build one form for a website: the fields a visitor fills in and the button that sends them. Answer with submit_form: the whole form as one flat node map, how its submissions are routed, and what the brief asks for that a form cannot collect.',\n 'The root is one Form (form) holding its Form Fields (formField) in the order a visitor fills them in. Give the form a submitLabel and a successMessage in the site’s voice.',\n 'Every field has a fieldName that is unique in the form and says what it holds in camelCase, such as fullName, email, phone or roofType; a label a visitor reads; and required only when the form cannot do its job without it. Use fieldType email for an email address, textarea for a description, select or radio with options for one choice, checkbox with options for several, and rating for a score. Put each option on its own line and never put a comma inside an option, because a comma starts a new one.',\n 'A form collects text, choices and ratings only. When the brief asks for something else, such as a file or photo upload, a signature or a payment, leave it out and name it in cannotCollect.',\n 'Never draw a marketing consent, newsletter or subscribe checkbox: when the form asks for an email address, the platform adds its own.',\n 'routing.kind is inbox when submissions only need to be read, lead when each one is a sales lead to follow up, and list when the people who send it should join an email list. routing.list is the list’s name only when the brief names one, and null otherwise.',\n ].join('\\n'),\n },\n]\n\nconst TREE_TOOL = aiDoctrineTreeTool('form')\n\n/**\n * The doctrine's form tree tool, with the two things a design cannot say: the\n * routing it proposes and what the brief asked for that no field can collect.\n * Structured fields only, so the step writes every sentence a person reads.\n */\nexport const AI_JOB_FORM_TOOL: AiTool = {\n name: TREE_TOOL.name,\n description:\n 'Submit the form as one flat node map, how its submissions are routed, and what the brief asks for that a form cannot collect.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['tree', 'routing', 'cannotCollect'],\n properties: {\n tree: (TREE_TOOL.inputSchema['properties'] as Record<string, unknown>)['tree'],\n routing: {\n type: 'object',\n additionalProperties: false,\n required: ['kind', 'list'],\n properties: {\n kind: {\n type: 'string',\n enum: [...AI_FORM_ROUTING_KINDS],\n description:\n 'inbox: submissions are read in the Inbox. lead: each submission with an email address is a sales lead. list: the people who send it should join an email list.',\n },\n list: {\n anyOf: [{ type: 'string' }, { type: 'null' }],\n description: 'The email list the brief names, as it names it; null when it names none.',\n },\n },\n },\n cannotCollect: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'What the brief asks the form to collect that its field types cannot, a few words each, such as \"photo upload\"; empty when there is nothing.',\n },\n },\n },\n}\n\n/** The routing and the gaps an answer proposes, read defensively. */\nexport interface AiFormAnswer {\n routing: { kind: AiFormRoutingKind; list: string | null }\n cannotCollect: string[]\n}\n\n/**\n * What the person already decided about this form, where they were asked\n * (AGL-2918).\n *\n * Only the guided start asks, so only a scaffold's form unit carries one.\n * Whether a submission is a note to read or a lead to chase is a fact about\n * how a business runs, not something readable off a brief, so an answer here\n * BINDS the routing instead of joining the evidence the model weighs: a\n * person who said \"the Inbox\" and got a form filing leads would have been\n * asked a question for nothing.\n */\nexport interface AiFormDecisions {\n /** Where submissions go; `null` where nobody was asked. */\n submissions: AiSiteSubmissions | null\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction oneLine(value: string, max: number): string {\n return value.replace(/\\s+/g, ' ').trim().slice(0, max).trim()\n}\n\n/**\n * An answer's routing and gaps: an unknown kind is the Inbox, and the rest is\n * trimmed and capped. Where the person was asked where submissions go, their\n * answer replaces the model's proposal — the whole of the difference a\n * question makes.\n */\nexport function parseAiFormAnswer(\n answer: Record<string, unknown>,\n decisions: AiFormDecisions = { submissions: null },\n): AiFormAnswer {\n const routing = isRecord(answer['routing']) ? answer['routing'] : {}\n const proposed = (AI_FORM_ROUTING_KINDS as readonly unknown[]).includes(routing['kind'])\n ? (routing['kind'] as AiFormRoutingKind)\n : 'inbox'\n const kind: AiFormRoutingKind = decisions.submissions ?? proposed\n const list = typeof routing['list'] === 'string' ? oneLine(routing['list'], LIST_NAME_MAX_CHARS) : ''\n const seen = new Set<string>()\n const cannotCollect: string[] = []\n for (const entry of Array.isArray(answer['cannotCollect']) ? answer['cannotCollect'] : []) {\n if (typeof entry !== 'string') continue\n const text = oneLine(entry, CANNOT_COLLECT_MAX_CHARS)\n if (!text || seen.has(text.toLowerCase())) continue\n seen.add(text.toLowerCase())\n cannotCollect.push(text)\n if (cannotCollect.length === AI_FORM_CANNOT_COLLECT_MAX) break\n }\n return { routing: { kind, list: kind === 'list' && list ? list : null }, cannotCollect }\n}\n\n/** A generated form as its draft is written: the stamped design and what it declares. */\nexport interface AiFormDraft {\n /** The design as the Forms page's Create stores one: a canvas root above the bound form. */\n nodes: NodesMap\n rootId: string\n formNodeId: string\n fields: FormFieldDecl[]\n consentFieldName: string | null\n routing: FormRouting | null\n answer: AiFormAnswer\n}\n\n/**\n * The draft a validated design and its answer make: the form node bound to\n * `formId` and captioned `name`, no dataset binding, the platform's consent\n * field when the fields can yield an email address, the canvas root above,\n * and the declaration read off the result. Pure; the input tree is not\n * changed.\n */\nexport function aiFormDraft(\n tree: Pick<AiValidatedTree, 'rootId' | 'nodes'>,\n answer: Record<string, unknown>,\n identity: { formId: string; name: string; decisions?: AiFormDecisions },\n): AiFormDraft {\n const parsed = parseAiFormAnswer(answer, identity.decisions ?? { submissions: null })\n const formNodeId = tree.rootId\n const nodes: NodesMap = {}\n for (const [id, node] of Object.entries(tree.nodes)) {\n nodes[id] = {\n ...node,\n props: { ...(node.props ?? {}) },\n nodes: Array.isArray(node.nodes) ? [...(node.nodes as string[])] : [],\n }\n }\n const form = nodes[formNodeId]\n let consentFieldName: string | null = null\n if (form?.componentId === FORM_COMPONENT_ID) {\n const props = form.props as Record<string, unknown>\n // A dataset binding decides where every submission is also written; it\n // stays the person's choice, made on the Form element.\n delete props['datasetId']\n delete props['datasetName']\n props[FORM_ID_PROP] = identity.formId\n props['formName'] = identity.name\n\n for (const id of collectFormFieldNodeIds(nodes as never, formNodeId)) {\n const fieldName = (nodes[id]?.props as Record<string, unknown> | undefined)?.['fieldName']\n if (!isMarketingConsentFieldName(fieldName)) continue\n const parent = nodes[String(nodes[id]?.parentId ?? '')]\n if (parent && Array.isArray(parent.nodes)) {\n parent.nodes = (parent.nodes as string[]).filter((child) => child !== id)\n }\n delete nodes[id]\n }\n if (formFieldsCanYieldAnEmail(formFieldDeclsFromNodes(nodes as never, formNodeId))) {\n nodes[AI_FORM_CONSENT_NODE_ID] = {\n $id: AI_FORM_CONSENT_NODE_ID,\n type: NodeType.NODE,\n componentId: FORM_FIELD_COMPONENT_ID,\n pluginId: AI_PALETTE[FORM_FIELD_COMPONENT_ID]?.pluginId,\n parentId: formNodeId,\n nodes: [],\n props: { ...MARKETING_CONSENT_FORM_FIELD },\n } as NodesMap[string]\n form.nodes = [...(form.nodes as string[]), AI_FORM_CONSENT_NODE_ID]\n consentFieldName = MARKETING_CONSENT_FORM_FIELD.fieldName\n }\n form.parentId = CANVAS_ROOT_ELEMENT_ID\n }\n nodes[CANVAS_ROOT_ELEMENT_ID] = {\n $id: CANVAS_ROOT_ELEMENT_ID,\n componentId: 'div',\n nodes: [formNodeId],\n } as NodesMap[string]\n return {\n nodes,\n rootId: CANVAS_ROOT_ELEMENT_ID,\n formNodeId,\n fields: formFieldDeclsFromNodes(nodes as never, formNodeId),\n consentFieldName,\n routing: parsed.routing.kind === 'lead' ? { lead: true } : null,\n answer: parsed,\n }\n}\n\n/**\n * What a draft would break once published, as building-rule violations\n * (rule 3: a form is built with its fields, validation, consent and routing\n * together): the form contract's findings, a form with no field, and a list\n * proposal on a form that asks for no email address. Offending nodes are\n * named by the ids the model wrote.\n */\nexport function aiFormDraftViolations(\n draft: AiFormDraft,\n formId: string,\n sourceIds: Readonly<Record<string, string>>,\n): AiDoctrineViolation[] {\n const violations: AiDoctrineViolation[] = checkFormContract({\n form: {\n ...(draft.routing ? { routing: draft.routing } : {}),\n ...(draft.consentFieldName ? { consentFieldName: draft.consentFieldName } : {}),\n },\n formId,\n nodes: draft.nodes as never,\n formNodeId: draft.formNodeId,\n }).map((violation) => ({\n rule: 3 as const,\n code: violation.code,\n message: violation.message,\n ...(violation.nodeId ? { nodeIds: aiModelNodeIds([violation.nodeId], sourceIds) } : {}),\n }))\n if (!draft.fields.length) {\n violations.push({\n rule: 3,\n code: 'form-has-no-fields',\n message:\n 'This form has no named fields, so a visitor has nothing to fill in. Draw the fields the brief asks for.',\n })\n }\n if (draft.answer.routing.kind === 'list' && !formFieldsCanYieldAnEmail(draft.fields)) {\n violations.push({\n rule: 3,\n code: 'list-routing-has-no-email-field',\n message:\n 'This form proposes adding the people who send it to an email list, and it asks for no email address. Add an email field, or route its submissions to the Inbox.',\n paths: ['routing.kind'],\n })\n }\n return violations\n}\n\n/**\n * The door's check for `extend`, and the draft each admitted tree made: the\n * tree a generation returns is the object its last check saw, so the step\n * writes exactly the draft the contract passed.\n */\nexport function aiFormDraftCheck(identity: {\n formId: string\n name: string\n decisions?: AiFormDecisions\n}): {\n extend: (tree: AiValidatedTree, answer: Record<string, unknown>) => AiDoctrineViolation[]\n draftFor: (tree: AiValidatedTree) => AiFormDraft | undefined\n} {\n const drafts = new WeakMap<AiValidatedTree, AiFormDraft>()\n return {\n extend: (tree, answer) => {\n const draft = aiFormDraft(tree, answer, identity)\n drafts.set(tree, draft)\n return aiFormDraftViolations(draft, identity.formId, tree.sourceIds)\n },\n draftFor: (tree) => drafts.get(tree),\n }\n}\n\n/**\n * What a job says when the copy it asked for came back naming the form it\n * was copied FROM (AGL-3024). The contract's own sentence follows it and\n * names the consequence; this one names what to do about it.\n */\nexport const AI_FORM_COPY_UNBOUND_FAILURE =\n 'The copy of that form did not take on an identity of its own, so it is on ' +\n 'the site and not safe to collect with — delete it in Forms and try again.'\n\n/**\n * Whether the copy the platform just wrote is bound to ITSELF, asked of the\n * document rather than of the answer — `null` when it is, and the sentence a\n * person reads when it is not.\n *\n * ## Why a step that generated nothing still has something to check\n *\n * A \"creates X from a copy of Y\" plan reaches no model: the copy IS the\n * output, so there is no answer for the doctrine to refuse and `extend`\n * never runs. That left the one thing a copy can get wrong unasked. A form's\n * design names its own form inside itself, and a copy that kept the source's\n * name files every submission under the source — the console's form page\n * banners exactly this, and the job reported Done beside it.\n *\n * So the check is `checkFormContract`, the SAME function the console's\n * banner and the promotion route ask, run here on the stored copy. Not a\n * second predicate shaped like it: a copy this one passed and that one\n * refused would be a form the job called finished and the console called\n * broken, which is the state this exists to make impossible.\n *\n * Narrowed to `form-id-unbound`, and only that, because it is the one\n * violation duplication CAUSES. Every other code the contract can report —\n * an unnamed field, a consent field the design lost — the copy inherited\n * from a form the site already has and the member chose to copy; refusing\n * those would be refusing to copy a form they can already see.\n */\nexport async function aiFormCopyUnboundFailure(\n firestore: FirebaseFirestore.Firestore,\n copy: { hostId: string; formId: string },\n): Promise<string | null> {\n const snapshot = await firestore\n .collection('hosts')\n .doc(copy.hostId)\n .collection('forms')\n .doc(copy.formId)\n .get()\n const form = (snapshot.data() ?? null) as Partial<FormDocument> | null\n const nodes = decodeStoredNodes(form?.nodes)\n const unbound = checkFormContract({\n form,\n formId: copy.formId,\n nodes: nodes as never,\n formNodeId: formNodeIdIn(nodes as never),\n }).find((violation) => violation.code === 'form-id-unbound')\n return unbound ? `${AI_FORM_COPY_UNBOUND_FAILURE} ${unbound.message}` : null\n}\n\n/**\n * The fields the confirmed plan gives the form, against the form a COPY\n * actually produced (AGL-3024); `null` when the copy keeps the plan, or when\n * the plan promised no field to keep.\n *\n * The fourth kind to need this, after the layout, the page and the component.\n * `aiFormCopyUnboundFailure` above already refuses the one violation\n * duplication CAUSES; this refuses the one duplication HIDES — a plan reading\n * \"start from the consultation form and add a case-type question\" getting the\n * consultation form and nothing else, reported as built.\n *\n * A field the plan names is matched on its name alone: a copy that collects\n * the answer under a different label is still collecting it, and this check\n * has no way to tell a renamed field from a missing one without guessing.\n */\nexport async function aiCopiedFormReview(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; formId: string; name: string; plan: AiJobPlan | null },\n): Promise<AiJobReview | null> {\n const promised = (aiPlanCreation(input.plan, 'form')?.fields ?? [])\n .map((field) => field.split(':')[0]?.trim().toLowerCase())\n .filter((field): field is string => Boolean(field))\n if (!promised.length) return null\n const snapshot = await firestore\n .collection('hosts')\n .doc(input.hostId)\n .collection('forms')\n .doc(input.formId)\n .get()\n if (!snapshot.exists) {\n return {\n reason: 'doctrine',\n message: `\"${input.name}\" was copied from the form the plan names, and this job could not read the copy back to check it collects what the plan gives it. Open the form and check it before you put it on a page.`,\n findings: [],\n }\n }\n const fields = (snapshot.get('fields') ?? []) as unknown[]\n const collected = new Set(\n fields\n .map((field) =>\n field && typeof field === 'object'\n ? String((field as Record<string, unknown>)['fieldName'] ?? (field as Record<string, unknown>)['name'] ?? '')\n : '',\n )\n .filter(Boolean)\n .map((name) => name.toLowerCase()),\n )\n const missing = promised.filter((name) => !collected.has(name))\n if (!missing.length) return null\n const one = missing.length === 1\n return aiDoctrineReview({\n message: `\"${input.name}\" is a copy of the form the plan names, and a copy is all it is: the plan gives it ${\n one ? 'a field' : 'fields'\n } the copy does not collect.`,\n violations: [\n {\n rule: null,\n code: 'plan-fields-missing',\n message: `The confirmed plan says this form collects ${missing.join(', ')}, and the copy collects ${\n collected.size ? [...collected].join(', ') : 'nothing'\n }. Add ${one ? 'it' : 'each of them'}, or the copy is the form it was copied from.`,\n },\n ],\n })\n}\n\nfunction listOf(items: readonly string[]): string {\n return items.length < 2\n ? items.join('')\n : `${items.slice(0, -1).join(', ')} and ${items[items.length - 1]}`\n}\n\n/**\n * What the person decides next, in words the step writes itself: a lead\n * routing to keep or switch off, the list a proposal could not store, and what\n * the brief asked for that a form cannot collect. `null` when there is none.\n */\nexport function aiFormOutputNote(answer: AiFormAnswer): string | null {\n const sentences: string[] = []\n if (answer.routing.kind === 'lead') {\n sentences.push(\n 'Each submission with an email address also files a lead in CRM → Leads; you can switch that off on the form’s page.',\n )\n }\n if (answer.routing.kind === 'list') {\n const list = answer.routing.list ? `your \"${answer.routing.list}\" list` : 'an email list'\n sentences.push(\n `Submissions arrive in the Inbox. To add the people who tick \"${MARKETING_CONSENT_FORM_FIELD.label}\" to ${list}, add an action on form submissions in Automation that enrolls them.`,\n )\n }\n if (answer.cannotCollect.length) {\n sentences.push(\n `Forms cannot collect ${listOf(answer.cannotCollect)} yet, so this form leaves ${\n answer.cannotCollect.length === 1 ? 'it' : 'them'\n } out.`,\n )\n }\n return sentences.length ? sentences.join(' ') : null\n}\n\n/**\n * The form job as the generation's user turn: the name, who fills the form\n * in, the brief, and the confirmed plan.\n *\n * Who the form is for (AGL-2918) is the answer a contact form's FIELDS turn\n * on — a trades business asks where the work is, a practice asks what the\n * appointment is about — and it is not something a brief about a whole site\n * says in a place the form can find. It rides here, in the turn, because it\n * is one site's own words.\n *\n * Where submissions go rides here too, and it is NOT a hint: the step binds\n * the routing to it whatever the model answers. It is stated so the model\n * does not spend its answer proposing something that will be replaced, and\n * so a form told its submissions are leads asks for the address that makes\n * one.\n */\nexport function aiJobFormPrompt(\n job: Pick<AiJob, 'brief'>,\n plan: AiJobPlan | null,\n name: string,\n context: { audience?: string; submissions?: AiSiteSubmissions | null } = {},\n): string {\n const audience = (context.audience ?? '').trim()\n return [\n `Form name: ${name}`,\n audience ? `The people who fill it in: ${audience}` : '',\n context.submissions ? `Where its submissions go: ${SUBMISSION_WORDS[context.submissions]}` : '',\n aiJobBriefLine(job),\n ...aiPlanReferenceLines(plan),\n ]\n .filter(Boolean)\n .join('\\n')\n}\n\n/** What each decided routing is, said to the model the way the tool says it. */\nconst SUBMISSION_WORDS: Record<AiSiteSubmissions, string> = {\n inbox: 'the Inbox, to be read — routing.kind is inbox',\n lead: 'the Inbox, and each one with an email address is a sales lead — routing.kind is lead',\n}\n\n/** A form job is admitted for a site of the job's own org whose plan has forms, with a form to spare. */\nexport const aiFormJobAdmission: AiJobAdmission = (context) =>\n aiDraftAdmissionRefusal(context.firestore, {\n orgId: context.orgId,\n hostId: context.hostId,\n kind: 'form',\n org: context.org,\n })\n\nexport interface AiJobFormStepDeps {\n /** The inventory reader; specs hand in a fake. */\n readInventory?: typeof readSiteInventory\n /** The platform's duplicate module, for a plan that starts from a copy. */\n duplicate?: typeof duplicateResource\n}\n\nexport function createAiJobFormStep(deps: AiJobFormStepDeps = {}): AiJobStepRunner {\n const readInventory = deps.readInventory ?? readSiteInventory\n const duplicate = deps.duplicate ?? duplicateResource\n return async ({ job, now, signal, firestore, modelFor }) => {\n const hostId = job.hostId\n if (!hostId) throw new Error('a form job names no site, and its admission refuses one')\n const output = (\n draft: AiDraftRecord,\n extra: { load?: AiLoadEstimate | null; note?: string | null } = {},\n ): AiJobOutput => ({\n resource: 'form',\n id: draft.id,\n versionId: draft.versionId,\n hostId,\n hostSubdomain: draft.hostSubdomain,\n label: draft.name,\n ...(extra.load ? { load: extra.load } : {}),\n ...(extra.note ? { note: extra.note } : {}),\n })\n // The switch's answer for this job, else the routing table's (AGL-2942).\n const model = modelFor?.('job.form') ?? aiModelForStep('job.form')\n\n // A run cut off after its draft was written reports that draft, found by the id the job recorded.\n const draftId = aiJobDraftId(job, 'form')\n const written = await readAiDraft(firestore, { kind: 'form', hostId, id: draftId })\n if (written) return aiUnspentOutcome(model, { outputs: [output(written)] })\n\n const [inventory, orgSnapshot] = await Promise.all([\n readInventory(job.orgId, hostId, { firestore }),\n firestore.collection('orgs').doc(job.orgId).get(),\n ])\n const org = (orgSnapshot.data() ?? null) as Partial<AglynOrgBilling> | null\n const plan = aiConfirmedPlan(job)\n const creation = aiPlanCreation(plan, 'form')\n const name = creation?.name || AI_JOB_FORM_DEFAULT_NAME\n\n // The plan starts from a copy of a form the site has (rule 15): the copy\n // is the draft, and nothing is generated. A source gone since the plan\n // was made is built from the brief instead.\n if (creation?.duplicateOf && inventory.forms.some((form) => form.id === creation.duplicateOf)) {\n const copy = await duplicate('form', {\n orgId: job.orgId,\n hostId,\n sourceId: creation.duplicateOf,\n name,\n uid: job.createdBy,\n org: org as Record<string, unknown> | null,\n // The same claim the page step makes (AGL-3024): a copy core mints is\n // written under core's own id, never `draftId`, so a step that runs\n // again — a pass resumed, a run cut off after the copy — would mint a\n // second. The job's recorded id makes core replay the one it made.\n attemptKey: draftId,\n })\n if (copy.ok) {\n // The copy is the whole output, so the contract is asked of it here\n // or nowhere: a copy still bound to its source is not a form this\n // job may report as built (AGL-3024).\n const unbound = await aiFormCopyUnboundFailure(firestore, { hostId, formId: copy.id })\n if (unbound) return { ...aiUnspentOutcome(model), failure: unbound }\n const hostSubdomain = await aiSiteSubdomain(firestore, hostId)\n // And the one duplication hides: what the plan promised of the copy,\n // read back (AGL-3024). This branch generates nothing, so nothing\n // else can answer for it.\n const review = await aiCopiedFormReview(firestore, {\n hostId,\n formId: copy.id,\n name: copy.name,\n plan,\n })\n if (review) return aiUnspentOutcome(model, { review })\n return aiUnspentOutcome(model, {\n outputs: [output({ id: copy.id, versionId: copy.versionId, name: copy.name, hostSubdomain })],\n })\n }\n if (copy.ok === false && copy.status === 403) {\n return aiUnspentOutcome(model, { review: aiLimitReview(copy.error) })\n }\n }\n\n const allowance = await aiDraftAllowanceRefusal(firestore, { kind: 'form', hostId, org })\n if (allowance) return aiUnspentOutcome(model, { review: aiLimitReview(allowance) })\n\n // What the person answered about this form, where they were asked\n // (AGL-2918): a scaffold's form unit carries the guided start's inputs.\n const decisions: AiFormDecisions = { submissions: aiSiteSubmissions(job.inputs) }\n const { audience } = aiSiteWords(job.inputs)\n const check = aiFormDraftCheck({ formId: draftId, name, decisions })\n const result = await runValidatedGeneration('form', {\n step: 'job.form',\n model,\n instructions: AI_JOB_FORM_INSTRUCTIONS,\n inventory,\n messages: [\n {\n role: 'user',\n content: aiJobFormPrompt(job, plan, name, {\n audience,\n submissions: decisions.submissions,\n }),\n },\n ],\n tool: AI_JOB_FORM_TOOL,\n maxTokens: AI_JOB_FORM_STEP_BUDGET.maxTokens(model),\n thinking: 'off',\n extend: check.extend,\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result) }\n\n const draft = check.draftFor(result.value)\n if (!draft) throw new Error('a form the doctrine admitted has no draft from its check')\n const write = await writeAiDraft(firestore, {\n kind: 'form',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: draft.nodes,\n form: {\n rootId: draft.rootId,\n formNodeId: draft.formNodeId,\n fields: draft.fields,\n consentFieldName: draft.consentFieldName,\n routing: draft.routing,\n },\n now,\n })\n if (write.ok === false) {\n if (write.status === 404) throw new Error(`site ${hostId} vanished while its form was generated`)\n return { ...spent, review: aiLimitReview(write.error) }\n }\n // What the person decides next, then the facts the brief did not give (AGL-3056).\n const note = [\n aiFormOutputNote(draft.answer),\n aiBracketedFactsNote({\n tree: { rootId: result.value.rootId, nodes: result.value.nodes as unknown as AiDoctrineTree['nodes'] },\n inventory,\n }),\n ]\n .filter(Boolean)\n .join(' ')\n return {\n ...spent,\n outputs: [output(write, { load: result.value.load, note })],\n }\n }\n}\n\nexport const runAiJobFormStep = createAiJobFormStep()\n\n/** Registers the form step and the check a form job passes before it is created or resumed. */\nexport function registerAiFormJob(): void {\n registerAiJobStep('form', runAiJobFormStep, { minimumMs: AI_JOB_FORM_STEP_MINIMUM_MS })\n registerAiJobAdmission('form', aiFormJobAdmission)\n}\n"],"names":["checkFormContract","formFieldsCanYieldAnEmail","collectFormFieldNodeIds","FORM_COMPONENT_ID","FORM_FIELD_COMPONENT_ID","FORM_ID_PROP","formFieldDeclsFromNodes","formNodeIdIn","isMarketingConsentFieldName","MARKETING_CONSENT_FORM_FIELD","decodeStoredNodes","CANVAS_ROOT_ELEMENT_ID","NodeType","duplicateResource","aiSiteSubmissions","aiSiteWords","AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","aiDoctrineTreeTool","runValidatedGeneration","AI_PALETTE","readSiteInventory","registerAiJobAdmission","aiJobDraftId","aiDraftAdmissionRefusal","aiDraftAllowanceRefusal","aiSiteSubdomain","readAiDraft","writeAiDraft","aiBracketedFactsNote","aiConfirmedPlan","aiDoctrineReview","aiGenerationSpent","aiJobBriefLine","aiLimitReview","aiModelNodeIds","aiPlanCreation","aiPlanReferenceLines","aiUnspentOutcome","aiJobStepBudget","registerAiJobStep","AI_JOB_FORM_MAX_TOKENS","maxTokens","AI_JOB_FORM_STEP_BUDGET","tier","AI_JOB_FORM_STEP_MINIMUM_MS","minimumMs","AI_JOB_FORM_DEFAULT_NAME","AI_FORM_ROUTING_KINDS","AI_FORM_CANNOT_COLLECT_MAX","CANNOT_COLLECT_MAX_CHARS","LIST_NAME_MAX_CHARS","AI_FORM_CONSENT_NODE_ID","AI_JOB_FORM_INSTRUCTIONS","text","join","TREE_TOOL","AI_JOB_FORM_TOOL","name","description","strict","inputSchema","type","additionalProperties","required","properties","tree","routing","kind","enum","list","anyOf","cannotCollect","items","isRecord","value","Array","isArray","oneLine","max","replace","trim","slice","parseAiFormAnswer","answer","decisions","submissions","proposed","includes","seen","Set","entry","has","toLowerCase","add","push","length","aiFormDraft","identity","parsed","formNodeId","rootId","nodes","id","node","Object","entries","props","form","consentFieldName","componentId","formId","fieldName","parent","String","parentId","filter","child","$id","NODE","pluginId","fields","lead","aiFormDraftViolations","draft","sourceIds","violations","map","violation","rule","code","message","nodeId","nodeIds","paths","aiFormDraftCheck","drafts","WeakMap","extend","set","draftFor","get","AI_FORM_COPY_UNBOUND_FAILURE","aiFormCopyUnboundFailure","firestore","copy","snapshot","collection","doc","hostId","data","unbound","find","aiCopiedFormReview","input","promised","plan","field","split","Boolean","exists","reason","findings","collected","missing","one","size","listOf","aiFormOutputNote","sentences","label","aiJobFormPrompt","job","context","audience","SUBMISSION_WORDS","inbox","aiFormJobAdmission","orgId","org","createAiJobFormStep","deps","readInventory","duplicate","now","signal","modelFor","orgSnapshot","Error","output","extra","resource","versionId","hostSubdomain","load","note","model","draftId","written","outputs","inventory","Promise","all","creation","duplicateOf","forms","some","sourceId","uid","createdBy","attemptKey","ok","failure","review","status","error","allowance","inputs","check","result","step","instructions","messages","role","content","tool","thinking","spent","refused","write","runAiJobFormStep","registerAiFormJob"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-form-step.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 {\n checkFormContract,\n formFieldsCanYieldAnEmail,\n} from '@aglyn/aglyn/app-utils/form-contract'\nimport {\n collectFormFieldNodeIds,\n FORM_COMPONENT_ID,\n FORM_FIELD_COMPONENT_ID,\n FORM_ID_PROP,\n formFieldDeclsFromNodes,\n formNodeIdIn,\n isMarketingConsentFieldName,\n MARKETING_CONSENT_FORM_FIELD,\n type FormDocument,\n type FormFieldDecl,\n type FormRouting,\n} from '@aglyn/aglyn/app-utils/forms'\nimport { decodeStoredNodes } from '@aglyn/aglyn/app-utils/stored-nodes'\nimport { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { NodeType, type NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { duplicateResource } from '@aglyn/tenant-data-admin/server/duplicate-resource'\nimport type { AiJob, AiJobOutput, AiJobPlan, AiJobReview } from '../model/ai-jobs.types'\nimport { aiSiteSubmissions, aiSiteWords, type AiSiteSubmissions } from '../model/ai-site-job'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n aiDoctrineTreeTool,\n runValidatedGeneration,\n type AiValidatedTree,\n} from '../runtime/ai-doctrine'\nimport type { AiDoctrineTree, AiDoctrineViolation } from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport { AI_PALETTE } from '../runtime/ai-palette.generated'\nimport type { AiSystemBlock, AiTool } from '../runtime/ai-runtime'\nimport { readSiteInventory } from '../runtime/site-inventory'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport { aiJobPlanCreationsRefusal } from './ai-job-plan-creations'\nimport { aiJobDraftId } from './ai-job-draft-ids'\nimport {\n aiDraftAdmissionRefusal,\n aiDraftAllowanceRefusal,\n aiSiteSubdomain,\n readAiDraft,\n writeAiDraft,\n type AiDraftRecord,\n} from './ai-job-drafts'\nimport {\n aiBracketedFactsNote,\n aiConfirmedPlan,\n aiDoctrineReview,\n aiGenerationSpent,\n aiJobBriefLine,\n aiLimitReview,\n aiModelNodeIds,\n aiPlanCreation,\n aiPlanReferenceLines,\n aiUnspentOutcome,\n} from './ai-job-generation'\nimport type { AiJobStepRunner } from './ai-job-text-step'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The form step (AGL-2913): the generation step of a `form` job, run once a\n * member confirmed the job's plan.\n *\n * A form is two halves that must agree for its submissions to arrive: the\n * DESIGN a visitor fills in (`rootId`, `nodes`) and the DECLARATION the submit\n * route reads (`fields`, `consentFieldName`, `routing`). `checkFormContract`\n * is the rule that holds them together when a form is published. So the model\n * writes only the design, through `runValidatedGeneration('form', …)`, and\n * everything else is derived from it with the functions the publish paths\n * use:\n *\n * - `fields` is `formFieldDeclsFromNodes` over the design;\n * - a form that can yield an email address gets the platform's marketing\n * consent field (`MARKETING_CONSENT_FORM_FIELD`, the Forms editor preset's own\n * props) in place of any consent-like field the model drew, named as its\n * `consentFieldName`;\n * - `routing` is `{ lead: true }` when the answer proposes a lead, and\n * nothing otherwise. The stored routing has no place for an email list,\n * so a list proposal leaves the form on the Inbox and says, in the output's\n * `note`, how to enroll the people who tick consent — naming a list only\n * when the brief named one. The step reads no list, contact, CRM record or\n * submission, and sends none.\n *\n * The design is then stamped the way the Forms page's Create stamps one — the\n * form node bound to the new form's id, a canvas root above it — and\n * `checkFormContract` runs on it through `extend`, beside the doctrine's own\n * checks, so a design the contract would refuse at publish is asked for again\n * with the violation named.\n *\n * The form lands as a new draft (`ai-job-drafts.ts`) that no page places and\n * nothing promotes. A plan that starts from a copy of a form the site has gets\n * that copy, through the platform's duplicate module, and nothing generated —\n * and because that path reaches no model, `extend` never runs on it, so the\n * step asks `checkFormContract` of the stored copy itself before it reports\n * one (AGL-3024). A copy still bound to the form it was copied from is a\n * failure, never an output.\n *\n * Like the other generation steps, it runs on the job beat, never at an inline\n * door: it registers the least time its lookup rounds, its answer and its\n * re-ask need (AGL-3035), and runs without extended thinking under the routing\n * table's ceiling for a form, which is the doctrine's own.\n */\n\n/**\n * The longest answer a form may run to: the routing table's `job.form`\n * ceiling, which holds the largest form the output budget admits\n * (`ai-job-form-step.spec.ts` measures it) and is the doctrine's ceiling for\n * the form kind.\n */\nexport const AI_JOB_FORM_MAX_TOKENS = AI_ROUTING_TABLE['job.form'].maxTokens\n\n/**\n * The form step's time (AGL-3035, AGL-3036): its two inventory-lookup rounds,\n * its answer and its re-ask at that ceiling on the tier `job.form` is served\n * from, fitted to what a beat can give a step.\n */\nexport const AI_JOB_FORM_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.form'],\n maxTokens: AI_JOB_FORM_MAX_TOKENS,\n})\n\n/** The least time one form step needs before it starts. */\nexport const AI_JOB_FORM_STEP_MINIMUM_MS = AI_JOB_FORM_STEP_BUDGET.minimumMs\n\n/** A form's name when the plan names none. */\nexport const AI_JOB_FORM_DEFAULT_NAME = 'New form'\n\n/** Where a proposal routes a form's submissions. */\nexport const AI_FORM_ROUTING_KINDS = ['inbox', 'list', 'lead'] as const\n\nexport type AiFormRoutingKind = (typeof AI_FORM_ROUTING_KINDS)[number]\n\n/** The most things a note says the brief asked for and a form cannot collect. */\nexport const AI_FORM_CANNOT_COLLECT_MAX = 3\n\nconst CANNOT_COLLECT_MAX_CHARS = 60\nconst LIST_NAME_MAX_CHARS = 80\n\n/** The node id the platform's consent field takes in a generated design. */\nexport const AI_FORM_CONSENT_NODE_ID = 'marketingConsentField'\n\n/** The form step's own instructions, cached after the doctrine. */\nexport const AI_JOB_FORM_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n 'You build one form for a website: the fields a visitor fills in and the button that sends them. Answer with submit_form: the whole form as one flat node map, how its submissions are routed, and what the brief asks for that a form cannot collect.',\n 'The root is one Form (form) holding its Form Fields (formField) in the order a visitor fills them in. Give the form a submitLabel and a successMessage in the site’s voice.',\n 'Every field has a fieldName that is unique in the form and says what it holds in camelCase, such as fullName, email, phone or roofType; a label a visitor reads; and required only when the form cannot do its job without it. Use fieldType email for an email address, textarea for a description, select or radio with options for one choice, checkbox with options for several, and rating for a score. Put each option on its own line and never put a comma inside an option, because a comma starts a new one.',\n 'A form collects text, choices and ratings only. When the brief asks for something else, such as a file or photo upload, a signature or a payment, leave it out and name it in cannotCollect.',\n 'Never draw a marketing consent, newsletter or subscribe checkbox: when the form asks for an email address, the platform adds its own.',\n 'routing.kind is inbox when submissions only need to be read, lead when each one is a sales lead to follow up, and list when the people who send it should join an email list. routing.list is the list’s name only when the brief names one, and null otherwise.',\n ].join('\\n'),\n },\n]\n\nconst TREE_TOOL = aiDoctrineTreeTool('form')\n\n/**\n * The doctrine's form tree tool, with the two things a design cannot say: the\n * routing it proposes and what the brief asked for that no field can collect.\n * Structured fields only, so the step writes every sentence a person reads.\n */\nexport const AI_JOB_FORM_TOOL: AiTool = {\n name: TREE_TOOL.name,\n description:\n 'Submit the form as one flat node map, how its submissions are routed, and what the brief asks for that a form cannot collect.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['tree', 'routing', 'cannotCollect'],\n properties: {\n tree: (TREE_TOOL.inputSchema['properties'] as Record<string, unknown>)['tree'],\n routing: {\n type: 'object',\n additionalProperties: false,\n required: ['kind', 'list'],\n properties: {\n kind: {\n type: 'string',\n enum: [...AI_FORM_ROUTING_KINDS],\n description:\n 'inbox: submissions are read in the Inbox. lead: each submission with an email address is a sales lead. list: the people who send it should join an email list.',\n },\n list: {\n anyOf: [{ type: 'string' }, { type: 'null' }],\n description: 'The email list the brief names, as it names it; null when it names none.',\n },\n },\n },\n cannotCollect: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'What the brief asks the form to collect that its field types cannot, a few words each, such as \"photo upload\"; empty when there is nothing.',\n },\n },\n },\n}\n\n/** The routing and the gaps an answer proposes, read defensively. */\nexport interface AiFormAnswer {\n routing: { kind: AiFormRoutingKind; list: string | null }\n cannotCollect: string[]\n}\n\n/**\n * What the person already decided about this form, where they were asked\n * (AGL-2918).\n *\n * Only the guided start asks, so only a scaffold's form unit carries one.\n * Whether a submission is a note to read or a lead to chase is a fact about\n * how a business runs, not something readable off a brief, so an answer here\n * BINDS the routing instead of joining the evidence the model weighs: a\n * person who said \"the Inbox\" and got a form filing leads would have been\n * asked a question for nothing.\n */\nexport interface AiFormDecisions {\n /** Where submissions go; `null` where nobody was asked. */\n submissions: AiSiteSubmissions | null\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction oneLine(value: string, max: number): string {\n return value.replace(/\\s+/g, ' ').trim().slice(0, max).trim()\n}\n\n/**\n * An answer's routing and gaps: an unknown kind is the Inbox, and the rest is\n * trimmed and capped. Where the person was asked where submissions go, their\n * answer replaces the model's proposal — the whole of the difference a\n * question makes.\n */\nexport function parseAiFormAnswer(\n answer: Record<string, unknown>,\n decisions: AiFormDecisions = { submissions: null },\n): AiFormAnswer {\n const routing = isRecord(answer['routing']) ? answer['routing'] : {}\n const proposed = (AI_FORM_ROUTING_KINDS as readonly unknown[]).includes(routing['kind'])\n ? (routing['kind'] as AiFormRoutingKind)\n : 'inbox'\n const kind: AiFormRoutingKind = decisions.submissions ?? proposed\n const list = typeof routing['list'] === 'string' ? oneLine(routing['list'], LIST_NAME_MAX_CHARS) : ''\n const seen = new Set<string>()\n const cannotCollect: string[] = []\n for (const entry of Array.isArray(answer['cannotCollect']) ? answer['cannotCollect'] : []) {\n if (typeof entry !== 'string') continue\n const text = oneLine(entry, CANNOT_COLLECT_MAX_CHARS)\n if (!text || seen.has(text.toLowerCase())) continue\n seen.add(text.toLowerCase())\n cannotCollect.push(text)\n if (cannotCollect.length === AI_FORM_CANNOT_COLLECT_MAX) break\n }\n return { routing: { kind, list: kind === 'list' && list ? list : null }, cannotCollect }\n}\n\n/** A generated form as its draft is written: the stamped design and what it declares. */\nexport interface AiFormDraft {\n /** The design as the Forms page's Create stores one: a canvas root above the bound form. */\n nodes: NodesMap\n rootId: string\n formNodeId: string\n fields: FormFieldDecl[]\n consentFieldName: string | null\n routing: FormRouting | null\n answer: AiFormAnswer\n}\n\n/**\n * The draft a validated design and its answer make: the form node bound to\n * `formId` and captioned `name`, no dataset binding, the platform's consent\n * field when the fields can yield an email address, the canvas root above,\n * and the declaration read off the result. Pure; the input tree is not\n * changed.\n */\nexport function aiFormDraft(\n tree: Pick<AiValidatedTree, 'rootId' | 'nodes'>,\n answer: Record<string, unknown>,\n identity: { formId: string; name: string; decisions?: AiFormDecisions },\n): AiFormDraft {\n const parsed = parseAiFormAnswer(answer, identity.decisions ?? { submissions: null })\n const formNodeId = tree.rootId\n const nodes: NodesMap = {}\n for (const [id, node] of Object.entries(tree.nodes)) {\n nodes[id] = {\n ...node,\n props: { ...(node.props ?? {}) },\n nodes: Array.isArray(node.nodes) ? [...(node.nodes as string[])] : [],\n }\n }\n const form = nodes[formNodeId]\n let consentFieldName: string | null = null\n if (form?.componentId === FORM_COMPONENT_ID) {\n const props = form.props as Record<string, unknown>\n // A dataset binding decides where every submission is also written; it\n // stays the person's choice, made on the Form element.\n delete props['datasetId']\n delete props['datasetName']\n props[FORM_ID_PROP] = identity.formId\n props['formName'] = identity.name\n\n for (const id of collectFormFieldNodeIds(nodes as never, formNodeId)) {\n const fieldName = (nodes[id]?.props as Record<string, unknown> | undefined)?.['fieldName']\n if (!isMarketingConsentFieldName(fieldName)) continue\n const parent = nodes[String(nodes[id]?.parentId ?? '')]\n if (parent && Array.isArray(parent.nodes)) {\n parent.nodes = (parent.nodes as string[]).filter((child) => child !== id)\n }\n delete nodes[id]\n }\n if (formFieldsCanYieldAnEmail(formFieldDeclsFromNodes(nodes as never, formNodeId))) {\n nodes[AI_FORM_CONSENT_NODE_ID] = {\n $id: AI_FORM_CONSENT_NODE_ID,\n type: NodeType.NODE,\n componentId: FORM_FIELD_COMPONENT_ID,\n pluginId: AI_PALETTE[FORM_FIELD_COMPONENT_ID]?.pluginId,\n parentId: formNodeId,\n nodes: [],\n props: { ...MARKETING_CONSENT_FORM_FIELD },\n } as NodesMap[string]\n form.nodes = [...(form.nodes as string[]), AI_FORM_CONSENT_NODE_ID]\n consentFieldName = MARKETING_CONSENT_FORM_FIELD.fieldName\n }\n form.parentId = CANVAS_ROOT_ELEMENT_ID\n }\n nodes[CANVAS_ROOT_ELEMENT_ID] = {\n $id: CANVAS_ROOT_ELEMENT_ID,\n componentId: 'div',\n nodes: [formNodeId],\n } as NodesMap[string]\n return {\n nodes,\n rootId: CANVAS_ROOT_ELEMENT_ID,\n formNodeId,\n fields: formFieldDeclsFromNodes(nodes as never, formNodeId),\n consentFieldName,\n routing: parsed.routing.kind === 'lead' ? { lead: true } : null,\n answer: parsed,\n }\n}\n\n/**\n * What a draft would break once published, as building-rule violations\n * (rule 3: a form is built with its fields, validation, consent and routing\n * together): the form contract's findings, a form with no field, and a list\n * proposal on a form that asks for no email address. Offending nodes are\n * named by the ids the model wrote.\n */\nexport function aiFormDraftViolations(\n draft: AiFormDraft,\n formId: string,\n sourceIds: Readonly<Record<string, string>>,\n): AiDoctrineViolation[] {\n const violations: AiDoctrineViolation[] = checkFormContract({\n form: {\n ...(draft.routing ? { routing: draft.routing } : {}),\n ...(draft.consentFieldName ? { consentFieldName: draft.consentFieldName } : {}),\n },\n formId,\n nodes: draft.nodes as never,\n formNodeId: draft.formNodeId,\n }).map((violation) => ({\n rule: 3 as const,\n code: violation.code,\n message: violation.message,\n ...(violation.nodeId ? { nodeIds: aiModelNodeIds([violation.nodeId], sourceIds) } : {}),\n }))\n if (!draft.fields.length) {\n violations.push({\n rule: 3,\n code: 'form-has-no-fields',\n message:\n 'This form has no named fields, so a visitor has nothing to fill in. Draw the fields the brief asks for.',\n })\n }\n if (draft.answer.routing.kind === 'list' && !formFieldsCanYieldAnEmail(draft.fields)) {\n violations.push({\n rule: 3,\n code: 'list-routing-has-no-email-field',\n message:\n 'This form proposes adding the people who send it to an email list, and it asks for no email address. Add an email field, or route its submissions to the Inbox.',\n paths: ['routing.kind'],\n })\n }\n return violations\n}\n\n/**\n * The door's check for `extend`, and the draft each admitted tree made: the\n * tree a generation returns is the object its last check saw, so the step\n * writes exactly the draft the contract passed.\n */\nexport function aiFormDraftCheck(identity: {\n formId: string\n name: string\n decisions?: AiFormDecisions\n}): {\n extend: (tree: AiValidatedTree, answer: Record<string, unknown>) => AiDoctrineViolation[]\n draftFor: (tree: AiValidatedTree) => AiFormDraft | undefined\n} {\n const drafts = new WeakMap<AiValidatedTree, AiFormDraft>()\n return {\n extend: (tree, answer) => {\n const draft = aiFormDraft(tree, answer, identity)\n drafts.set(tree, draft)\n return aiFormDraftViolations(draft, identity.formId, tree.sourceIds)\n },\n draftFor: (tree) => drafts.get(tree),\n }\n}\n\n/**\n * What a job says when the copy it asked for came back naming the form it\n * was copied FROM (AGL-3024). The contract's own sentence follows it and\n * names the consequence; this one names what to do about it.\n */\nexport const AI_FORM_COPY_UNBOUND_FAILURE =\n 'The copy of that form did not take on an identity of its own, so it is on ' +\n 'the site and not safe to collect with — delete it in Forms and try again.'\n\n/**\n * Whether the copy the platform just wrote is bound to ITSELF, asked of the\n * document rather than of the answer — `null` when it is, and the sentence a\n * person reads when it is not.\n *\n * ## Why a step that generated nothing still has something to check\n *\n * A \"creates X from a copy of Y\" plan reaches no model: the copy IS the\n * output, so there is no answer for the doctrine to refuse and `extend`\n * never runs. That left the one thing a copy can get wrong unasked. A form's\n * design names its own form inside itself, and a copy that kept the source's\n * name files every submission under the source — the console's form page\n * banners exactly this, and the job reported Done beside it.\n *\n * So the check is `checkFormContract`, the SAME function the console's\n * banner and the promotion route ask, run here on the stored copy. Not a\n * second predicate shaped like it: a copy this one passed and that one\n * refused would be a form the job called finished and the console called\n * broken, which is the state this exists to make impossible.\n *\n * Narrowed to `form-id-unbound`, and only that, because it is the one\n * violation duplication CAUSES. Every other code the contract can report —\n * an unnamed field, a consent field the design lost — the copy inherited\n * from a form the site already has and the member chose to copy; refusing\n * those would be refusing to copy a form they can already see.\n */\nexport async function aiFormCopyUnboundFailure(\n firestore: FirebaseFirestore.Firestore,\n copy: { hostId: string; formId: string },\n): Promise<string | null> {\n const snapshot = await firestore\n .collection('hosts')\n .doc(copy.hostId)\n .collection('forms')\n .doc(copy.formId)\n .get()\n const form = (snapshot.data() ?? null) as Partial<FormDocument> | null\n const nodes = decodeStoredNodes(form?.nodes)\n const unbound = checkFormContract({\n form,\n formId: copy.formId,\n nodes: nodes as never,\n formNodeId: formNodeIdIn(nodes as never),\n }).find((violation) => violation.code === 'form-id-unbound')\n return unbound ? `${AI_FORM_COPY_UNBOUND_FAILURE} ${unbound.message}` : null\n}\n\n/**\n * The fields the confirmed plan gives the form, against the form a COPY\n * actually produced (AGL-3024); `null` when the copy keeps the plan, or when\n * the plan promised no field to keep.\n *\n * The fourth kind to need this, after the layout, the page and the component.\n * `aiFormCopyUnboundFailure` above already refuses the one violation\n * duplication CAUSES; this refuses the one duplication HIDES — a plan reading\n * \"start from the consultation form and add a case-type question\" getting the\n * consultation form and nothing else, reported as built.\n *\n * A field the plan names is matched on its name alone: a copy that collects\n * the answer under a different label is still collecting it, and this check\n * has no way to tell a renamed field from a missing one without guessing.\n */\nexport async function aiCopiedFormReview(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; formId: string; name: string; plan: AiJobPlan | null },\n): Promise<AiJobReview | null> {\n const promised = (aiPlanCreation(input.plan, 'form')?.fields ?? [])\n .map((field) => field.split(':')[0]?.trim().toLowerCase())\n .filter((field): field is string => Boolean(field))\n if (!promised.length) return null\n const snapshot = await firestore\n .collection('hosts')\n .doc(input.hostId)\n .collection('forms')\n .doc(input.formId)\n .get()\n if (!snapshot.exists) {\n return {\n reason: 'doctrine',\n message: `\"${input.name}\" was copied from the form the plan names, and this job could not read the copy back to check it collects what the plan gives it. Open the form and check it before you put it on a page.`,\n findings: [],\n }\n }\n const fields = (snapshot.get('fields') ?? []) as unknown[]\n const collected = new Set(\n fields\n .map((field) =>\n field && typeof field === 'object'\n ? String((field as Record<string, unknown>)['fieldName'] ?? (field as Record<string, unknown>)['name'] ?? '')\n : '',\n )\n .filter(Boolean)\n .map((name) => name.toLowerCase()),\n )\n const missing = promised.filter((name) => !collected.has(name))\n if (!missing.length) return null\n const one = missing.length === 1\n return aiDoctrineReview({\n message: `\"${input.name}\" is a copy of the form the plan names, and a copy is all it is: the plan gives it ${\n one ? 'a field' : 'fields'\n } the copy does not collect.`,\n violations: [\n {\n rule: null,\n code: 'plan-fields-missing',\n message: `The confirmed plan says this form collects ${missing.join(', ')}, and the copy collects ${\n collected.size ? [...collected].join(', ') : 'nothing'\n }. Add ${one ? 'it' : 'each of them'}, or the copy is the form it was copied from.`,\n },\n ],\n })\n}\n\n/**\n * What the form a short copy would have started from collects, as the build\n * that replaces the copy is told it (AGL-3143 §14): every field it asks for,\n * so the build keeps them beside what the plan adds. Empty when the form\n * cannot be read.\n */\nexport async function aiFormSourceLines(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; id: string; name: string },\n): Promise<string[]> {\n const snapshot = await firestore.collection('hosts').doc(input.hostId).collection('forms').doc(input.id).get()\n const fields = snapshot.exists ? ((snapshot.get('fields') ?? []) as unknown[]) : []\n const asked = fields.flatMap((field) => {\n if (!field || typeof field !== 'object') return []\n const row = field as Record<string, unknown>\n const name = String(row['fieldName'] ?? row['name'] ?? '').trim()\n if (!name) return []\n const fieldType = row['fieldType'] ?? row['type']\n const type = typeof fieldType === 'string' ? ` (${fieldType}${row['required'] ? ', required' : ''})` : ''\n return [`${name}${type}`]\n })\n if (!asked.length) return []\n return [\n `The plan starts from a copy of the form \"${input.name}\", which lacks part of what the plan gives it. Build from it: it collects ${asked.join(', ')}. Keep each of those, and add what the plan adds.`,\n ]\n}\n\nfunction listOf(items: readonly string[]): string {\n return items.length < 2\n ? items.join('')\n : `${items.slice(0, -1).join(', ')} and ${items[items.length - 1]}`\n}\n\n/**\n * What the person decides next, in words the step writes itself: a lead\n * routing to keep or switch off, the list a proposal could not store, and what\n * the brief asked for that a form cannot collect. `null` when there is none.\n */\nexport function aiFormOutputNote(answer: AiFormAnswer): string | null {\n const sentences: string[] = []\n if (answer.routing.kind === 'lead') {\n sentences.push(\n 'Each submission with an email address also files a lead in CRM → Leads; you can switch that off on the form’s page.',\n )\n }\n if (answer.routing.kind === 'list') {\n const list = answer.routing.list ? `your \"${answer.routing.list}\" list` : 'an email list'\n sentences.push(\n `Submissions arrive in the Inbox. To add the people who tick \"${MARKETING_CONSENT_FORM_FIELD.label}\" to ${list}, add an action on form submissions in Automation that enrolls them.`,\n )\n }\n if (answer.cannotCollect.length) {\n sentences.push(\n `Forms cannot collect ${listOf(answer.cannotCollect)} yet, so this form leaves ${\n answer.cannotCollect.length === 1 ? 'it' : 'them'\n } out.`,\n )\n }\n return sentences.length ? sentences.join(' ') : null\n}\n\n/**\n * The form job as the generation's user turn: the name, who fills the form\n * in, the brief, and the confirmed plan.\n *\n * Who the form is for (AGL-2918) is the answer a contact form's FIELDS turn\n * on — a trades business asks where the work is, a practice asks what the\n * appointment is about — and it is not something a brief about a whole site\n * says in a place the form can find. It rides here, in the turn, because it\n * is one site's own words.\n *\n * Where submissions go rides here too, and it is NOT a hint: the step binds\n * the routing to it whatever the model answers. It is stated so the model\n * does not spend its answer proposing something that will be replaced, and\n * so a form told its submissions are leads asks for the address that makes\n * one.\n */\nexport function aiJobFormPrompt(\n job: Pick<AiJob, 'brief'>,\n plan: AiJobPlan | null,\n name: string,\n context: { audience?: string; submissions?: AiSiteSubmissions | null } = {},\n): string {\n const audience = (context.audience ?? '').trim()\n return [\n `Form name: ${name}`,\n audience ? `The people who fill it in: ${audience}` : '',\n context.submissions ? `Where its submissions go: ${SUBMISSION_WORDS[context.submissions]}` : '',\n aiJobBriefLine(job),\n ...aiPlanReferenceLines(plan),\n ]\n .filter(Boolean)\n .join('\\n')\n}\n\n/** What each decided routing is, said to the model the way the tool says it. */\nconst SUBMISSION_WORDS: Record<AiSiteSubmissions, string> = {\n inbox: 'the Inbox, to be read — routing.kind is inbox',\n lead: 'the Inbox, and each one with an email address is a sales lead — routing.kind is lead',\n}\n\n/**\n * A form job is admitted for a site of the job's own org whose plan has\n * forms, with a form to spare, and confirmed only for a plan that creates\n * nothing but the form: a form places nothing another job builds\n * (AGL-3143 §15).\n */\nexport const aiFormJobAdmission: AiJobAdmission = (context) =>\n aiDraftAdmissionRefusal(context.firestore, {\n orgId: context.orgId,\n hostId: context.hostId,\n kind: 'form',\n org: context.org,\n ownCheck: (hostId) => aiJobPlanCreationsRefusal('form', context, hostId),\n })\n\nexport interface AiJobFormStepDeps {\n /** The inventory reader; specs hand in a fake. */\n readInventory?: typeof readSiteInventory\n /** The platform's duplicate module, for a plan that starts from a copy. */\n duplicate?: typeof duplicateResource\n}\n\nexport function createAiJobFormStep(deps: AiJobFormStepDeps = {}): AiJobStepRunner {\n const readInventory = deps.readInventory ?? readSiteInventory\n const duplicate = deps.duplicate ?? duplicateResource\n return async ({ job, now, signal, firestore, modelFor }) => {\n const hostId = job.hostId\n if (!hostId) throw new Error('a form job names no site, and its admission refuses one')\n const output = (\n draft: AiDraftRecord,\n extra: { load?: AiLoadEstimate | null; note?: string | null } = {},\n ): AiJobOutput => ({\n resource: 'form',\n id: draft.id,\n versionId: draft.versionId,\n hostId,\n hostSubdomain: draft.hostSubdomain,\n label: draft.name,\n ...(extra.load ? { load: extra.load } : {}),\n ...(extra.note ? { note: extra.note } : {}),\n })\n // The switch's answer for this job, else the routing table's (AGL-2942).\n const model = modelFor?.('job.form') ?? aiModelForStep('job.form')\n\n // A run cut off after its draft was written reports that draft, found by the id the job recorded.\n const draftId = aiJobDraftId(job, 'form')\n const written = await readAiDraft(firestore, { kind: 'form', hostId, id: draftId })\n if (written) return aiUnspentOutcome(model, { outputs: [output(written)] })\n\n const [inventory, orgSnapshot] = await Promise.all([\n readInventory(job.orgId, hostId, { firestore }),\n firestore.collection('orgs').doc(job.orgId).get(),\n ])\n const org = (orgSnapshot.data() ?? null) as Partial<AglynOrgBilling> | null\n const plan = aiConfirmedPlan(job)\n const creation = aiPlanCreation(plan, 'form')\n const name = creation?.name || AI_JOB_FORM_DEFAULT_NAME\n\n // The plan starts from a copy of a form the site has (rule 15): the copy\n // is the draft, and nothing is generated. A source gone since the plan\n // was made is built from the brief instead, and one that does not collect\n // a field the plan gives the form is built from the plan, told what the\n // source collects (AGL-3143 §14): copying it could only end in the review\n // below. A source this step cannot read still copies.\n const source = creation?.duplicateOf\n ? inventory.forms.find((form) => form.id === creation.duplicateOf)\n : undefined\n const sourceReview = source\n ? await aiCopiedFormReview(firestore, { hostId, formId: source.id, name: source.name, plan })\n : null\n const sourceShort = Boolean(sourceReview?.findings.length)\n const sourceLines =\n source && sourceShort ? await aiFormSourceLines(firestore, { hostId, id: source.id, name: source.name }) : []\n if (creation?.duplicateOf && source && !sourceShort) {\n const copy = await duplicate('form', {\n orgId: job.orgId,\n hostId,\n sourceId: creation.duplicateOf,\n name,\n uid: job.createdBy,\n org: org as Record<string, unknown> | null,\n // The same claim the page step makes (AGL-3024): a copy core mints is\n // written under core's own id, never `draftId`, so a step that runs\n // again — a pass resumed, a run cut off after the copy — would mint a\n // second. The job's recorded id makes core replay the one it made.\n attemptKey: draftId,\n })\n if (copy.ok) {\n // The copy is the whole output, so the contract is asked of it here\n // or nowhere: a copy still bound to its source is not a form this\n // job may report as built (AGL-3024).\n const unbound = await aiFormCopyUnboundFailure(firestore, { hostId, formId: copy.id })\n if (unbound) return { ...aiUnspentOutcome(model), failure: unbound }\n const hostSubdomain = await aiSiteSubdomain(firestore, hostId)\n // And the one duplication hides: what the plan promised of the copy,\n // read back (AGL-3024). This branch generates nothing, so nothing\n // else can answer for it.\n const review = await aiCopiedFormReview(firestore, {\n hostId,\n formId: copy.id,\n name: copy.name,\n plan,\n })\n if (review) return aiUnspentOutcome(model, { review })\n return aiUnspentOutcome(model, {\n outputs: [output({ id: copy.id, versionId: copy.versionId, name: copy.name, hostSubdomain })],\n })\n }\n if (copy.ok === false && copy.status === 403) {\n return aiUnspentOutcome(model, { review: aiLimitReview(copy.error) })\n }\n }\n\n const allowance = await aiDraftAllowanceRefusal(firestore, { kind: 'form', hostId, org })\n if (allowance) return aiUnspentOutcome(model, { review: aiLimitReview(allowance) })\n\n // What the person answered about this form, where they were asked\n // (AGL-2918): a scaffold's form unit carries the guided start's inputs.\n const decisions: AiFormDecisions = { submissions: aiSiteSubmissions(job.inputs) }\n const { audience } = aiSiteWords(job.inputs)\n const check = aiFormDraftCheck({ formId: draftId, name, decisions })\n const result = await runValidatedGeneration('form', {\n step: 'job.form',\n model,\n instructions: AI_JOB_FORM_INSTRUCTIONS,\n inventory,\n messages: [\n {\n role: 'user',\n content: [\n aiJobFormPrompt(job, plan, name, {\n audience,\n submissions: decisions.submissions,\n }),\n ...sourceLines,\n ].join('\\n'),\n },\n ],\n tool: AI_JOB_FORM_TOOL,\n maxTokens: AI_JOB_FORM_STEP_BUDGET.maxTokens(model),\n thinking: 'off',\n extend: check.extend,\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result) }\n\n const draft = check.draftFor(result.value)\n if (!draft) throw new Error('a form the doctrine admitted has no draft from its check')\n const write = await writeAiDraft(firestore, {\n kind: 'form',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: draft.nodes,\n form: {\n rootId: draft.rootId,\n formNodeId: draft.formNodeId,\n fields: draft.fields,\n consentFieldName: draft.consentFieldName,\n routing: draft.routing,\n },\n now,\n })\n if (write.ok === false) {\n if (write.status === 404) throw new Error(`site ${hostId} vanished while its form was generated`)\n return { ...spent, review: aiLimitReview(write.error) }\n }\n // What the person decides next, then the facts the brief did not give (AGL-3056).\n const note = [\n aiFormOutputNote(draft.answer),\n aiBracketedFactsNote({\n tree: { rootId: result.value.rootId, nodes: result.value.nodes as unknown as AiDoctrineTree['nodes'] },\n inventory,\n }),\n ]\n .filter(Boolean)\n .join(' ')\n return {\n ...spent,\n outputs: [output(write, { load: result.value.load, note })],\n }\n }\n}\n\nexport const runAiJobFormStep = createAiJobFormStep()\n\n/** Registers the form step and the check a form job passes before it is created or resumed. */\nexport function registerAiFormJob(): void {\n registerAiJobStep('form', runAiJobFormStep, { minimumMs: AI_JOB_FORM_STEP_MINIMUM_MS })\n registerAiJobAdmission('form', aiFormJobAdmission)\n}\n"],"names":["checkFormContract","formFieldsCanYieldAnEmail","collectFormFieldNodeIds","FORM_COMPONENT_ID","FORM_FIELD_COMPONENT_ID","FORM_ID_PROP","formFieldDeclsFromNodes","formNodeIdIn","isMarketingConsentFieldName","MARKETING_CONSENT_FORM_FIELD","decodeStoredNodes","CANVAS_ROOT_ELEMENT_ID","NodeType","duplicateResource","aiSiteSubmissions","aiSiteWords","AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","aiDoctrineTreeTool","runValidatedGeneration","AI_PALETTE","readSiteInventory","registerAiJobAdmission","aiJobPlanCreationsRefusal","aiJobDraftId","aiDraftAdmissionRefusal","aiDraftAllowanceRefusal","aiSiteSubdomain","readAiDraft","writeAiDraft","aiBracketedFactsNote","aiConfirmedPlan","aiDoctrineReview","aiGenerationSpent","aiJobBriefLine","aiLimitReview","aiModelNodeIds","aiPlanCreation","aiPlanReferenceLines","aiUnspentOutcome","aiJobStepBudget","registerAiJobStep","AI_JOB_FORM_MAX_TOKENS","maxTokens","AI_JOB_FORM_STEP_BUDGET","tier","AI_JOB_FORM_STEP_MINIMUM_MS","minimumMs","AI_JOB_FORM_DEFAULT_NAME","AI_FORM_ROUTING_KINDS","AI_FORM_CANNOT_COLLECT_MAX","CANNOT_COLLECT_MAX_CHARS","LIST_NAME_MAX_CHARS","AI_FORM_CONSENT_NODE_ID","AI_JOB_FORM_INSTRUCTIONS","text","join","TREE_TOOL","AI_JOB_FORM_TOOL","name","description","strict","inputSchema","type","additionalProperties","required","properties","tree","routing","kind","enum","list","anyOf","cannotCollect","items","isRecord","value","Array","isArray","oneLine","max","replace","trim","slice","parseAiFormAnswer","answer","decisions","submissions","proposed","includes","seen","Set","entry","has","toLowerCase","add","push","length","aiFormDraft","identity","parsed","formNodeId","rootId","nodes","id","node","Object","entries","props","form","consentFieldName","componentId","formId","fieldName","parent","String","parentId","filter","child","$id","NODE","pluginId","fields","lead","aiFormDraftViolations","draft","sourceIds","violations","map","violation","rule","code","message","nodeId","nodeIds","paths","aiFormDraftCheck","drafts","WeakMap","extend","set","draftFor","get","AI_FORM_COPY_UNBOUND_FAILURE","aiFormCopyUnboundFailure","firestore","copy","snapshot","collection","doc","hostId","data","unbound","find","aiCopiedFormReview","input","promised","plan","field","split","Boolean","exists","reason","findings","collected","missing","one","size","aiFormSourceLines","asked","flatMap","row","fieldType","listOf","aiFormOutputNote","sentences","label","aiJobFormPrompt","job","context","audience","SUBMISSION_WORDS","inbox","aiFormJobAdmission","orgId","org","ownCheck","createAiJobFormStep","deps","readInventory","duplicate","now","signal","modelFor","orgSnapshot","Error","output","extra","resource","versionId","hostSubdomain","load","note","model","draftId","written","outputs","inventory","Promise","all","creation","source","duplicateOf","forms","undefined","sourceReview","sourceShort","sourceLines","sourceId","uid","createdBy","attemptKey","ok","failure","review","status","error","allowance","inputs","check","result","step","instructions","messages","role","content","tool","thinking","spent","refused","write","runAiJobFormStep","registerAiFormJob"],"mappings":"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"}