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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (340) hide show
  1. package/README.md +3 -3
  2. package/package.json +17 -17
  3. package/src/lib/activity/ai-activity-actions.js +7 -1
  4. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  5. package/src/lib/components/ai-assist-provider-on-host.component.d.ts +66 -0
  6. package/src/lib/components/ai-assist-provider-on-host.component.js +78 -0
  7. package/src/lib/components/ai-assist-provider-on-host.component.js.map +1 -0
  8. package/src/lib/components/ai-assist-provider.component.js +9 -10
  9. package/src/lib/components/ai-assist-provider.component.js.map +1 -1
  10. package/src/lib/components/ai-automation-zones.d.ts +38 -0
  11. package/src/lib/components/ai-automation-zones.js +5 -1
  12. package/src/lib/components/ai-automation-zones.js.map +1 -1
  13. package/src/lib/components/ai-brief-dialog.component.d.ts +20 -2
  14. package/src/lib/components/ai-brief-dialog.component.js +87 -8
  15. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  16. package/src/lib/components/ai-campaign-create.component.d.ts +54 -0
  17. package/src/lib/components/ai-campaign-create.component.js +270 -0
  18. package/src/lib/components/ai-campaign-create.component.js.map +1 -0
  19. package/src/lib/components/ai-describe-automation.component.d.ts +8 -3
  20. package/src/lib/components/ai-describe-automation.component.js +15 -6
  21. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  22. package/src/lib/components/ai-describe-button.component.d.ts +20 -8
  23. package/src/lib/components/ai-describe-button.component.js +19 -36
  24. package/src/lib/components/ai-describe-button.component.js.map +1 -1
  25. package/src/lib/components/ai-describe-email.component.d.ts +31 -0
  26. package/src/lib/components/ai-describe-email.component.js +22 -0
  27. package/src/lib/components/ai-describe-email.component.js.map +1 -0
  28. package/src/lib/components/ai-describe-org-automation.component.d.ts +36 -0
  29. package/src/lib/components/ai-describe-org-automation.component.js +238 -0
  30. package/src/lib/components/ai-describe-org-automation.component.js.map +1 -0
  31. package/src/lib/components/ai-experiment-cards.component.d.ts +9 -1
  32. package/src/lib/components/ai-experiment-cards.component.js +104 -18
  33. package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
  34. package/src/lib/components/ai-explain-automation.component.js +1 -5
  35. package/src/lib/components/ai-explain-automation.component.js.map +1 -1
  36. package/src/lib/components/ai-funnel-zones.component.d.ts +61 -0
  37. package/src/lib/components/ai-funnel-zones.component.js +178 -0
  38. package/src/lib/components/ai-funnel-zones.component.js.map +1 -0
  39. package/src/lib/components/ai-insight-card.component.d.ts +42 -0
  40. package/src/lib/components/ai-insight-card.component.js +137 -0
  41. package/src/lib/components/ai-insight-card.component.js.map +1 -0
  42. package/src/lib/components/ai-insight-dialog.component.d.ts +6 -0
  43. package/src/lib/components/ai-insight-dialog.component.js +9 -3
  44. package/src/lib/components/ai-insight-dialog.component.js.map +1 -1
  45. package/src/lib/components/ai-job-plan.component.d.ts +7 -2
  46. package/src/lib/components/ai-job-plan.component.js +38 -6
  47. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  48. package/src/lib/components/ai-job-requests.d.ts +17 -1
  49. package/src/lib/components/ai-job-requests.js +39 -3
  50. package/src/lib/components/ai-job-requests.js.map +1 -1
  51. package/src/lib/components/ai-logic-zones.d.ts +64 -0
  52. package/src/lib/components/ai-logic-zones.js +18 -0
  53. package/src/lib/components/ai-logic-zones.js.map +1 -0
  54. package/src/lib/components/ai-logic.component.d.ts +77 -0
  55. package/src/lib/components/ai-logic.component.js +553 -0
  56. package/src/lib/components/ai-logic.component.js.map +1 -0
  57. package/src/lib/components/ai-marketing-insight.component.d.ts +30 -0
  58. package/src/lib/components/ai-marketing-insight.component.js +80 -0
  59. package/src/lib/components/ai-marketing-insight.component.js.map +1 -0
  60. package/src/lib/components/ai-media-create.component.d.ts +55 -0
  61. package/src/lib/components/ai-media-create.component.js +433 -0
  62. package/src/lib/components/ai-media-create.component.js.map +1 -0
  63. package/src/lib/components/ai-overlay-cards.component.d.ts +83 -0
  64. package/src/lib/components/ai-overlay-cards.component.js +505 -0
  65. package/src/lib/components/ai-overlay-cards.component.js.map +1 -0
  66. package/src/lib/components/ai-permissions-on-host.component.d.ts +3 -46
  67. package/src/lib/components/ai-permissions-on-host.component.js +5 -53
  68. package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
  69. package/src/lib/components/ai-product-zones.d.ts +5 -0
  70. package/src/lib/components/ai-product-zones.js +1 -1
  71. package/src/lib/components/ai-product-zones.js.map +1 -1
  72. package/src/lib/components/ai-products-create-button.component.d.ts +42 -0
  73. package/src/lib/components/ai-products-create-button.component.js +67 -0
  74. package/src/lib/components/ai-products-create-button.component.js.map +1 -0
  75. package/src/lib/components/ai-products-hub-card.component.js +100 -81
  76. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  77. package/src/lib/components/ai-revise-automation.component.d.ts +16 -0
  78. package/src/lib/components/ai-revise-automation.component.js +207 -0
  79. package/src/lib/components/ai-revise-automation.component.js.map +1 -0
  80. package/src/lib/components/ai-site-build-page.component.js +160 -24
  81. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  82. package/src/lib/components/ai-upsell-dialog.component.d.ts +53 -0
  83. package/src/lib/components/ai-upsell-dialog.component.js +177 -0
  84. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -0
  85. package/src/lib/components/assist-build-card.component.d.ts +42 -0
  86. package/src/lib/components/assist-build-card.component.js +183 -0
  87. package/src/lib/components/assist-build-card.component.js.map +1 -0
  88. package/src/lib/components/assist-edit-canvas.d.ts +2 -0
  89. package/src/lib/components/assist-edit-canvas.js +56 -1
  90. package/src/lib/components/assist-edit-canvas.js.map +1 -1
  91. package/src/lib/components/assist-jobs-drawer.component.js +48 -3
  92. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  93. package/src/lib/components/assist-panel.component.js +51 -2
  94. package/src/lib/components/assist-panel.component.js.map +1 -1
  95. package/src/lib/components/use-ai-job-run.d.ts +3 -14
  96. package/src/lib/components/use-ai-job-run.js +0 -50
  97. package/src/lib/components/use-ai-job-run.js.map +1 -1
  98. package/src/lib/jobs/ai-build-capabilities.d.ts +58 -0
  99. package/src/lib/jobs/ai-build-capabilities.js +256 -0
  100. package/src/lib/jobs/ai-build-capabilities.js.map +1 -0
  101. package/src/lib/jobs/ai-build-unit-outcome.d.ts +52 -0
  102. package/src/lib/jobs/ai-build-unit-outcome.js +140 -0
  103. package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -0
  104. package/src/lib/jobs/ai-job-auto-confirm.d.ts +8 -0
  105. package/src/lib/jobs/ai-job-auto-confirm.js +30 -6
  106. package/src/lib/jobs/ai-job-auto-confirm.js.map +1 -1
  107. package/src/lib/jobs/ai-job-build-step.d.ts +144 -0
  108. package/src/lib/jobs/ai-job-build-step.js +668 -0
  109. package/src/lib/jobs/ai-job-build-step.js.map +1 -0
  110. package/src/lib/jobs/ai-job-draft-ids.js +9 -1
  111. package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
  112. package/src/lib/jobs/ai-job-drafts.d.ts +14 -4
  113. package/src/lib/jobs/ai-job-drafts.js +25 -4
  114. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  115. package/src/lib/jobs/ai-job-generation.d.ts +8 -0
  116. package/src/lib/jobs/ai-job-generation.js +13 -2
  117. package/src/lib/jobs/ai-job-generation.js.map +1 -1
  118. package/src/lib/jobs/ai-job-insight-step.js +8 -1
  119. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  120. package/src/lib/jobs/ai-job-layout-language.d.ts +73 -0
  121. package/src/lib/jobs/ai-job-layout-language.js +194 -0
  122. package/src/lib/jobs/ai-job-layout-language.js.map +1 -0
  123. package/src/lib/jobs/ai-job-layout-site-pages.d.ts +13 -2
  124. package/src/lib/jobs/ai-job-layout-site-pages.js +18 -2
  125. package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
  126. package/src/lib/jobs/ai-job-layout-step.d.ts +5 -0
  127. package/src/lib/jobs/ai-job-layout-step.js +105 -5
  128. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  129. package/src/lib/jobs/ai-job-logic-step.d.ts +107 -0
  130. package/src/lib/jobs/ai-job-logic-step.js +395 -0
  131. package/src/lib/jobs/ai-job-logic-step.js.map +1 -0
  132. package/src/lib/jobs/ai-job-overlay-copy.d.ts +70 -0
  133. package/src/lib/jobs/ai-job-overlay-copy.js +178 -0
  134. package/src/lib/jobs/ai-job-overlay-copy.js.map +1 -0
  135. package/src/lib/jobs/ai-job-page-language.d.ts +149 -0
  136. package/src/lib/jobs/ai-job-page-language.js +355 -0
  137. package/src/lib/jobs/ai-job-page-language.js.map +1 -0
  138. package/src/lib/jobs/ai-job-page-sections.d.ts +37 -1
  139. package/src/lib/jobs/ai-job-page-sections.js +93 -11
  140. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  141. package/src/lib/jobs/ai-job-page-step.d.ts +4 -4
  142. package/src/lib/jobs/ai-job-page-step.js +111 -20
  143. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  144. package/src/lib/jobs/ai-job-plan-step.d.ts +27 -2
  145. package/src/lib/jobs/ai-job-plan-step.js +87 -13
  146. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  147. package/src/lib/jobs/ai-job-site-step.d.ts +19 -1
  148. package/src/lib/jobs/ai-job-site-step.js +220 -61
  149. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  150. package/src/lib/jobs/ai-job-text-step.d.ts +22 -1
  151. package/src/lib/jobs/ai-job-text-step.js +12 -0
  152. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  153. package/src/lib/jobs/ai-job-workflow-step.d.ts +54 -4
  154. package/src/lib/jobs/ai-job-workflow-step.js +320 -15
  155. package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
  156. package/src/lib/jobs/ai-jobs.d.ts +80 -2
  157. package/src/lib/jobs/ai-jobs.js +391 -18
  158. package/src/lib/jobs/ai-jobs.js.map +1 -1
  159. package/src/lib/jobs/ai-layout-full-height.d.ts +40 -0
  160. package/src/lib/jobs/ai-layout-full-height.js +82 -0
  161. package/src/lib/jobs/ai-layout-full-height.js.map +1 -0
  162. package/src/lib/jobs/ai-layout-site-facts.d.ts +54 -0
  163. package/src/lib/jobs/ai-layout-site-facts.js +153 -0
  164. package/src/lib/jobs/ai-layout-site-facts.js.map +1 -0
  165. package/src/lib/jobs/ai-logic-records.d.ts +40 -0
  166. package/src/lib/jobs/ai-logic-records.js +55 -0
  167. package/src/lib/jobs/ai-logic-records.js.map +1 -0
  168. package/src/lib/jobs/ai-site-publish.d.ts +7 -0
  169. package/src/lib/jobs/ai-site-publish.js +9 -0
  170. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  171. package/src/lib/jobs/ai-workflow-records.d.ts +4 -0
  172. package/src/lib/jobs/ai-workflow-records.js +40 -1
  173. package/src/lib/jobs/ai-workflow-records.js.map +1 -1
  174. package/src/lib/layout-language/ai-layout-compiler.d.ts +145 -0
  175. package/src/lib/layout-language/ai-layout-compiler.js +1148 -0
  176. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -0
  177. package/src/lib/layout-language/ai-layout-copy.d.ts +73 -0
  178. package/src/lib/layout-language/ai-layout-copy.js +137 -0
  179. package/src/lib/layout-language/ai-layout-copy.js.map +1 -0
  180. package/src/lib/layout-language/ai-layout-frame.d.ts +52 -0
  181. package/src/lib/layout-language/ai-layout-frame.js +457 -0
  182. package/src/lib/layout-language/ai-layout-frame.js.map +1 -0
  183. package/src/lib/layout-language/ai-layout-gaps.d.ts +63 -0
  184. package/src/lib/layout-language/ai-layout-gaps.js +345 -0
  185. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -0
  186. package/src/lib/layout-language/ai-layout-language.d.ts +161 -0
  187. package/src/lib/layout-language/ai-layout-language.js +446 -0
  188. package/src/lib/layout-language/ai-layout-language.js.map +1 -0
  189. package/src/lib/layout-language/ai-layout-links.d.ts +80 -0
  190. package/src/lib/layout-language/ai-layout-links.js +109 -0
  191. package/src/lib/layout-language/ai-layout-links.js.map +1 -0
  192. package/src/lib/layout-language/ai-layout-store.d.ts +39 -0
  193. package/src/lib/layout-language/ai-layout-store.js +69 -0
  194. package/src/lib/layout-language/ai-layout-store.js.map +1 -0
  195. package/src/lib/layout-language/ai-layout-tree.d.ts +59 -0
  196. package/src/lib/layout-language/ai-layout-tree.js +84 -0
  197. package/src/lib/layout-language/ai-layout-tree.js.map +1 -0
  198. package/src/lib/lazy-widget.d.ts +45 -0
  199. package/src/lib/lazy-widget.js +53 -0
  200. package/src/lib/lazy-widget.js.map +1 -0
  201. package/src/lib/model/ai-automation-draft.d.ts +18 -0
  202. package/src/lib/model/ai-automation-draft.js +86 -0
  203. package/src/lib/model/ai-automation-draft.js.map +1 -1
  204. package/src/lib/model/ai-build-job.d.ts +201 -0
  205. package/src/lib/model/ai-build-job.js +472 -0
  206. package/src/lib/model/ai-build-job.js.map +1 -0
  207. package/src/lib/model/ai-build-plan.d.ts +61 -0
  208. package/src/lib/model/ai-build-plan.js +154 -9
  209. package/src/lib/model/ai-build-plan.js.map +1 -1
  210. package/src/lib/model/ai-build-progress.d.ts +73 -0
  211. package/src/lib/model/ai-build-progress.js +170 -0
  212. package/src/lib/model/ai-build-progress.js.map +1 -0
  213. package/src/lib/model/ai-host-publish-context.d.ts +51 -0
  214. package/src/lib/model/ai-host-publish-context.js +104 -0
  215. package/src/lib/model/ai-host-publish-context.js.map +1 -0
  216. package/src/lib/model/ai-insight.d.ts +20 -10
  217. package/src/lib/model/ai-insight.js +72 -9
  218. package/src/lib/model/ai-insight.js.map +1 -1
  219. package/src/lib/model/ai-job-activity.d.ts +22 -0
  220. package/src/lib/model/ai-job-activity.js +25 -2
  221. package/src/lib/model/ai-job-activity.js.map +1 -1
  222. package/src/lib/model/ai-job-failure-copy.d.ts +38 -1
  223. package/src/lib/model/ai-job-failure-copy.js +56 -1
  224. package/src/lib/model/ai-job-failure-copy.js.map +1 -1
  225. package/src/lib/model/ai-job-notice.d.ts +3 -1
  226. package/src/lib/model/ai-job-notice.js +31 -9
  227. package/src/lib/model/ai-job-notice.js.map +1 -1
  228. package/src/lib/model/ai-jobs.types.d.ts +99 -2
  229. package/src/lib/model/ai-jobs.types.js +3 -1
  230. package/src/lib/model/ai-jobs.types.js.map +1 -1
  231. package/src/lib/model/ai-logic-job.d.ts +108 -0
  232. package/src/lib/model/ai-logic-job.js +327 -0
  233. package/src/lib/model/ai-logic-job.js.map +1 -0
  234. package/src/lib/model/ai-media-credits.d.ts +52 -0
  235. package/src/lib/model/ai-media-credits.js +61 -0
  236. package/src/lib/model/ai-media-credits.js.map +1 -0
  237. package/src/lib/model/ai-overlay-copy.d.ts +114 -0
  238. package/src/lib/model/ai-overlay-copy.js +175 -0
  239. package/src/lib/model/ai-overlay-copy.js.map +1 -0
  240. package/src/lib/model/ai-site-build-progress.d.ts +25 -4
  241. package/src/lib/model/ai-site-build-progress.js +194 -11
  242. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  243. package/src/lib/model/ai-site-job.d.ts +8 -0
  244. package/src/lib/model/ai-site-job.js +9 -0
  245. package/src/lib/model/ai-site-job.js.map +1 -1
  246. package/src/lib/model/ai-site-start-seo.d.ts +1 -1
  247. package/src/lib/model/ai-site-start.d.ts +5 -2
  248. package/src/lib/model/ai-site-start.js +5 -2
  249. package/src/lib/model/ai-site-start.js.map +1 -1
  250. package/src/lib/model/ai-svg.d.ts +69 -0
  251. package/src/lib/model/ai-svg.js +265 -0
  252. package/src/lib/model/ai-svg.js.map +1 -0
  253. package/src/lib/model/ai-usage-by-user.d.ts +3 -2
  254. package/src/lib/model/ai-usage-by-user.js.map +1 -1
  255. package/src/lib/model/ai-workflow-job.d.ts +92 -4
  256. package/src/lib/model/ai-workflow-job.js +124 -7
  257. package/src/lib/model/ai-workflow-job.js.map +1 -1
  258. package/src/lib/model/assist-build.d.ts +58 -0
  259. package/src/lib/model/assist-build.js +52 -0
  260. package/src/lib/model/assist-build.js.map +1 -0
  261. package/src/lib/model/assist-edit.d.ts +48 -3
  262. package/src/lib/model/assist-edit.js +48 -4
  263. package/src/lib/model/assist-edit.js.map +1 -1
  264. package/src/lib/plugin-config.js +2 -0
  265. package/src/lib/plugin-config.js.map +1 -1
  266. package/src/lib/plugin.js +308 -47
  267. package/src/lib/plugin.js.map +1 -1
  268. package/src/lib/providers/catalog.d.ts +82 -5
  269. package/src/lib/providers/catalog.js +83 -12
  270. package/src/lib/providers/catalog.js.map +1 -1
  271. package/src/lib/providers/image-contract.d.ts +116 -0
  272. package/src/lib/providers/image-contract.js +74 -0
  273. package/src/lib/providers/image-contract.js.map +1 -0
  274. package/src/lib/providers/model-choice.js +11 -0
  275. package/src/lib/providers/model-choice.js.map +1 -1
  276. package/src/lib/providers/routing.js +18 -0
  277. package/src/lib/providers/routing.js.map +1 -1
  278. package/src/lib/providers/vertex-image.d.ts +121 -0
  279. package/src/lib/providers/vertex-image.js +312 -0
  280. package/src/lib/providers/vertex-image.js.map +1 -0
  281. package/src/lib/runtime/ai-doctrine-validators.d.ts +101 -0
  282. package/src/lib/runtime/ai-doctrine-validators.js +525 -4
  283. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  284. package/src/lib/runtime/ai-doctrine.js +21 -6
  285. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  286. package/src/lib/runtime/ai-eval-live.js +4 -0
  287. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  288. package/src/lib/runtime/ai-eval.d.ts +15 -1
  289. package/src/lib/runtime/ai-eval.js +52 -1
  290. package/src/lib/runtime/ai-eval.js.map +1 -1
  291. package/src/lib/runtime/ai-icon-library.d.ts +257 -0
  292. package/src/lib/runtime/ai-icon-library.js +254 -0
  293. package/src/lib/runtime/ai-icon-library.js.map +1 -0
  294. package/src/lib/runtime/ai-node-tree.d.ts +9 -0
  295. package/src/lib/runtime/ai-node-tree.js +30 -4
  296. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  297. package/src/lib/runtime/ai-palette.d.ts +6 -0
  298. package/src/lib/runtime/ai-palette.generated.js +70 -0
  299. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  300. package/src/lib/runtime/ai-palette.js.map +1 -1
  301. package/src/lib/runtime/experiment-variant-copy.d.ts +34 -0
  302. package/src/lib/runtime/experiment-variant-copy.js +102 -0
  303. package/src/lib/runtime/experiment-variant-copy.js.map +1 -0
  304. package/src/lib/server/ai-experiment-versions.d.ts +51 -0
  305. package/src/lib/server/ai-experiment-versions.js +270 -0
  306. package/src/lib/server/ai-experiment-versions.js.map +1 -0
  307. package/src/lib/server/ai-jobs-resume.js +74 -4
  308. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  309. package/src/lib/server/ai-media-image.d.ts +66 -0
  310. package/src/lib/server/ai-media-image.js +521 -0
  311. package/src/lib/server/ai-media-image.js.map +1 -0
  312. package/src/lib/server/ai-media-svg.d.ts +91 -0
  313. package/src/lib/server/ai-media-svg.js +186 -0
  314. package/src/lib/server/ai-media-svg.js.map +1 -0
  315. package/src/lib/server/assist-build.d.ts +54 -0
  316. package/src/lib/server/assist-build.js +121 -0
  317. package/src/lib/server/assist-build.js.map +1 -0
  318. package/src/lib/server/assist-chat.js +108 -12
  319. package/src/lib/server/assist-chat.js.map +1 -1
  320. package/src/lib/server/assist-edit.js +213 -3
  321. package/src/lib/server/assist-edit.js.map +1 -1
  322. package/src/lib/server/assist-view-context.js +242 -4
  323. package/src/lib/server/assist-view-context.js.map +1 -1
  324. package/src/lib/server.js +26 -0
  325. package/src/lib/server.js.map +1 -1
  326. package/src/lib/tools/ai-logic-tool.d.ts +27 -0
  327. package/src/lib/tools/ai-logic-tool.js +207 -0
  328. package/src/lib/tools/ai-logic-tool.js.map +1 -0
  329. package/src/lib/tools/ai-overlay-tool.d.ts +35 -0
  330. package/src/lib/tools/ai-overlay-tool.js +107 -0
  331. package/src/lib/tools/ai-overlay-tool.js.map +1 -0
  332. package/src/lib/tools/ai-workflow-tool.d.ts +17 -2
  333. package/src/lib/tools/ai-workflow-tool.js +47 -14
  334. package/src/lib/tools/ai-workflow-tool.js.map +1 -1
  335. package/src/lib/usage/assist-job-refund.d.ts +28 -1
  336. package/src/lib/usage/assist-job-refund.js +44 -9
  337. package/src/lib/usage/assist-job-refund.js.map +1 -1
  338. package/src/lib/usage/assist-usage.d.ts +5 -0
  339. package/src/lib/usage/assist-usage.js +2 -0
  340. package/src/lib/usage/assist-usage.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-site-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 { resolveEffectivePlan } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport {\n isAiPlanNewRef,\n type AiBuildPlanCreate,\n type AiBuildPlanCreateKind,\n type AiBuildPlanScreen,\n} from '../model/ai-build-plan'\nimport type {\n AiJob,\n AiJobKind,\n AiJobOutput,\n AiJobOutputResource,\n AiJobPlan,\n} from '../model/ai-jobs.types'\nimport {\n AI_SITE_EMAIL_TYPE,\n AI_SITE_MAX_SECTIONS,\n AI_SITE_PAGES,\n aiSitePagesRefusal,\n aiSitePlanRefusal,\n aiSiteSubmissions,\n aiSiteWords,\n parseAiSiteJobInputs,\n type AiSiteJobInputs,\n} from '../model/ai-site-job'\nimport {\n AI_SITE_SEO_OUTPUT_ID,\n aiSiteSeoProposalForInputs,\n} from '../model/ai-site-start-seo'\nimport { aiModelForStep } from '../providers/routing'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport { aiOriginJobId, aiRecordedJobDraftId } from './ai-job-draft-ids'\nimport { readAiDraftNodes } from './ai-job-drafts'\nimport { AI_LAYOUT_SITE_PAGES_INPUT, aiLayoutSitePagesOfPlan } from './ai-job-layout-site-pages'\nimport { aiPageSectionNodeId } from './ai-job-page-sections'\nimport { aiJobPublishesSite, aiPublishGuidedSite } from './ai-site-publish'\nimport { aiConfirmedPlan, aiUnspentOutcome } from './ai-job-generation'\nimport {\n AI_JOB_BRIEF_MAX_CHARS,\n type AiJobStepContext,\n type AiJobStepOutcome,\n type AiJobStepRunner,\n} from './ai-job-text-step'\nimport { AI_JOB_PAGE_STEP_MINIMUM_MS } from './ai-job-page-budget'\nimport {\n aiJobStepRunMinimumMs,\n aiJobStepRunnerFor,\n registerAiJobStep,\n registerAiJobStepPasses,\n} from './ai-jobs'\n\n/**\n * The site scaffold (AGL-2911): the generation step of a `site` job, run once\n * a member confirmed the plan their brief produced.\n *\n * ── It builds nothing itself ─────────────────────────────────────────────\n *\n * A scaffold is a whole site: a palette suggestion, the layout its pages\n * render inside, the contact form they place, four to eight pages with their\n * search listings, and a welcome email. Every one of those is already a job\n * kind with a step of its own, so this step generates nothing and asks no\n * model anything. It works through the confirmed plan one UNIT at a time and\n * hands each unit to the runner registered for the kind that owns it — the\n * theme step for the palette, the page step for a page — under a job of that\n * kind derived from this one.\n *\n * That is why the scaffold cannot drift from the thing it scaffolds: a page\n * it builds is a page job's page, held to the same doctrine, written by the\n * same draft writer, left unpublished by the same rule. A kind whose step\n * this deployment has not loaded is simply not among the units, so nothing\n * here knows which issues have landed.\n *\n * ── One unit a pass ──────────────────────────────────────────────────────\n *\n * The step runs ONE delegated pass and asks the machine to continue, so every\n * pass is one reservation, one provider exchange and one recorded spend, and\n * the org's monthly ceiling binds a scaffold exactly as it binds a chat turn.\n * Credits running out mid-scaffold is therefore the machine's own park: the\n * job waits as `needs_input` with the meter's words, and the beat resumes it\n * where it stopped once the workspace's standing has changed. A pass needs the\n * time its unit's own step needs (`aiSiteJobRunMinimumMs`), so the beat starts\n * a palette change only with a palette change's time left.\n *\n * Where it stopped is read from the job's OUTPUTS rather than kept anywhere:\n * each unit reports exactly one output when it completes, so the units\n * already represented there are the ones already built. A pass interrupted\n * between its work and its record repeats a unit that then finds its own\n * draft and reports it, which is what every step's draft id already gives.\n *\n * ── Never published ──────────────────────────────────────────────────────\n *\n * Nothing here publishes, routes or sends: each delegated step writes the\n * unpublished draft it already wrote for a job of its own kind, and the\n * palette and navigation travel as proposals a member applies.\n *\n * ── The page job builds its creations the same way (AGL-3031) ────────────\n *\n * A page job whose plan creates a layout, forms or components builds them\n * before its page through this same machinery: the units a creation implies\n * (`aiCreationUnit`), where the job stands read from its outputs\n * (`aiSitePendingUnits`), one delegated pass a unit (`aiRunJobUnit`), and the\n * `new:<name>` references resolved to what was built (`aiSiteBuiltRefs`,\n * `aiSiteUnitJob`). There is one loop, and it is the machine's.\n */\n\n/** What a scaffold's units are, in the order it builds them; a component is a page job's. */\nexport type AiSiteUnitKind = 'theme' | 'layout' | 'form' | 'component' | 'page' | 'email'\n\nexport interface AiSiteUnit {\n kind: AiSiteUnitKind\n /** The job kind whose registered runner builds it. */\n jobKind: AiJobKind\n /** The resource the unit reports when it completes. */\n resource: AiJobOutputResource\n /** Stable within the job: what the unit's derived job and draft are addressed by. */\n slot: string\n /** The plan screen a page unit builds. */\n screen?: AiBuildPlanScreen\n /** The creation a theme, layout or form unit builds. */\n creation?: AiBuildPlanCreate\n /** What the unit is, for the derived job's brief. */\n label: string\n}\n\n/** The job kind and output resource each unit is built through. */\nconst UNIT_KINDS: Record<\n AiSiteUnitKind,\n { jobKind: AiJobKind; resource: AiJobOutputResource }\n> = {\n theme: { jobKind: 'theme', resource: 'theme' },\n layout: { jobKind: 'layout', resource: 'layout' },\n form: { jobKind: 'form', resource: 'form' },\n component: { jobKind: 'component', resource: 'reusableComponent' },\n page: { jobKind: 'page', resource: 'screen' },\n email: { jobKind: 'email', resource: 'emailScreen' },\n}\n\n/** The unit kind a creation is built as, where a unit builds it. */\nconst CREATION_UNIT_KINDS: Partial<Record<AiBuildPlanCreateKind, AiSiteUnitKind>> = {\n 'theme-change': 'theme',\n layout: 'layout',\n form: 'form',\n component: 'component',\n}\n\n/**\n * The unit one creation is built as, under the slot its derived job and draft\n * are addressed by; `null` for a creation no unit builds.\n */\nexport function aiCreationUnit(creation: AiBuildPlanCreate, slot: string): AiSiteUnit | null {\n const kind = CREATION_UNIT_KINDS[creation.kind]\n if (!kind) return null\n return { kind, ...UNIT_KINDS[kind], slot, creation, label: creation.name }\n}\n\n/** The creation kinds a unit is built from, by unit kind. */\nconst CREATION_UNITS: Array<{\n unit: AiSiteUnitKind\n create: AiBuildPlanCreateKind\n}> = [\n { unit: 'theme', create: 'theme-change' },\n { unit: 'layout', create: 'layout' },\n { unit: 'form', create: 'form' },\n]\n\nexport const AI_SITE_NO_PLAN_COPY =\n 'This site job has no confirmed plan to build.'\n\n/** What a scaffold answers where the step that builds a page is not loaded. */\nexport const AI_SITE_NO_PAGE_STEP_COPY =\n 'Building a whole site is not available yet.'\n\n/** A unit that finished without reporting what it built; the scaffold cannot go on. */\nexport const AI_SITE_UNIT_EMPTY_COPY =\n 'Part of this site could not be built. Try the site brief again.'\n\n/**\n * The most passes a scaffold's step may take: the largest plan it admits —\n * eight pages of eight sections, its three creations and a welcome email —\n * with nothing to spare, so a runner that never finishes is still bounded\n * while a real site is not.\n */\nexport const AI_SITE_MAX_PASSES =\n AI_SITE_PAGES.max * (AI_SITE_MAX_SECTIONS + 1) + CREATION_UNITS.length + 1\n\n/**\n * The units a plan implies, in build order: the palette first, because a\n * member reads it while the pages are still building; then the layout every\n * page renders inside and the form they place, because a page binds both by\n * id and so needs them to exist; then the pages; then the welcome email,\n * which is about the site rather than part of it.\n */\nexport function aiSiteJobUnits(\n plan: Pick<AiJobPlan, 'create' | 'screens'>,\n options: { welcomeEmail?: boolean } = {},\n): AiSiteUnit[] {\n const units: AiSiteUnit[] = []\n for (const { unit, create } of CREATION_UNITS) {\n const creation = plan.create.find((entry) => entry.kind === create)\n if (!creation) continue\n units.push({\n kind: unit,\n ...UNIT_KINDS[unit],\n slot: unit[0],\n creation,\n label: creation.name,\n })\n }\n plan.screens.forEach((screen, index) => {\n units.push({\n kind: 'page',\n ...UNIT_KINDS.page,\n slot: `p${index}`,\n screen,\n label: screen.title,\n })\n })\n if (options.welcomeEmail) {\n units.push({\n kind: 'email',\n ...UNIT_KINDS.email,\n slot: 'e',\n label: 'Welcome email',\n })\n }\n return units\n}\n\n/**\n * The units a scaffold still owes after the outputs recorded so far. Each\n * unit reports one output of its own resource when it completes, so the\n * outputs already recorded are consumed against the units in build order.\n */\nexport function aiSitePendingUnits(\n units: readonly AiSiteUnit[],\n outputs: readonly Pick<AiJobOutput, 'resource'>[],\n): AiSiteUnit[] {\n const done = new Map<AiJobOutputResource, number>()\n for (const output of outputs) {\n done.set(output.resource, (done.get(output.resource) ?? 0) + 1)\n }\n return units.filter((unit) => {\n const left = done.get(unit.resource) ?? 0\n if (left <= 0) return true\n done.set(unit.resource, left - 1)\n return false\n })\n}\n\n/**\n * The site's own search listing as the scaffold reports it (AGL-2918), or\n * nothing when the job already reported one or its inputs describe no site.\n *\n * It costs nothing and asks no model: the values are arithmetic on the\n * answers (`aiSiteSeoProposal`), so they ride out on the first pass that\n * builds anything rather than waiting for a pass of their own. Its resource\n * is `seo`, which no unit reports, so it cannot be mistaken for a unit's\n * output by `aiSitePendingUnits` and cannot move where the scaffold thinks\n * it is.\n */\nexport function aiSiteSeoOutputs(job: AiJob): AiJobOutput[] {\n const outputs = job.outputs ?? []\n if (outputs.some((output) => output.resource === 'seo' && output.id === AI_SITE_SEO_OUTPUT_ID)) {\n return []\n }\n const proposal = aiSiteSeoProposalForInputs(job.inputs)\n if (!proposal) return []\n return [\n {\n resource: 'seo',\n id: AI_SITE_SEO_OUTPUT_ID,\n hostId: job.hostId ?? null,\n label: 'The site’s search title and description',\n proposal: proposal as unknown as Record<string, unknown>,\n },\n ]\n}\n\n/** A record an earlier unit created, as a later one references it. */\nexport interface AiSiteBuiltRef {\n id: string\n label: string\n /** Only what becomes a record a page can name; a palette change is not one. */\n kind: 'layout' | 'form' | 'component'\n}\n\n/**\n * What the scaffold has already created, as the plan's `new:<name>`\n * references resolve to: the id of the record each completed creation wrote.\n * A page unit is told about the layout and the form by id, exactly as a page\n * job is told about records the site already had.\n */\nexport function aiSiteBuiltRefs(\n units: readonly AiSiteUnit[],\n outputs: readonly Pick<AiJobOutput, 'resource' | 'id' | 'label'>[],\n): Map<string, AiSiteBuiltRef> {\n const byResource = new Map<\n AiJobOutputResource,\n Array<Pick<AiJobOutput, 'id' | 'label'>>\n >()\n for (const output of outputs) {\n const rows = byResource.get(output.resource) ?? []\n rows.push(output)\n byResource.set(output.resource, rows)\n }\n const built = new Map<string, AiSiteBuiltRef>()\n for (const unit of units) {\n const rows = byResource.get(unit.resource)\n const row = rows?.shift()\n if (!row || !unit.creation) continue\n // A theme change is a proposal with no record to reference; a layout, a\n // form and a component become ids a page can name.\n if (\n unit.creation.kind !== 'layout' &&\n unit.creation.kind !== 'form' &&\n unit.creation.kind !== 'component'\n ) {\n continue\n }\n built.set(unit.creation.name.toLowerCase(), {\n id: row.id,\n label: row.label || unit.creation.name,\n kind: unit.creation.kind,\n })\n }\n return built\n}\n\ntype BuiltRefs = ReturnType<typeof aiSiteBuiltRefs>\n\n/** The layout the scaffold built, when it built one. */\nfunction aiSiteBuiltLayoutId(built: BuiltRefs): string | null {\n for (const entry of built.values()) if (entry.kind === 'layout') return entry.id\n return null\n}\n\n/** A plan reference as the unit's own job reads it: an id the site now has, or nothing. */\nexport function aiSiteResolvedRef(ref: string | null, built: BuiltRefs): string | null {\n if (!ref) return null\n if (!isAiPlanNewRef(ref)) return ref\n return (\n built.get(\n ref\n .slice(ref.indexOf(':') + 1)\n .trim()\n .toLowerCase(),\n )?.id ?? null\n )\n}\n\n/**\n * The site's own words, as every unit's brief carries them.\n *\n * The audience is here rather than left to the brief's prose (AGL-2918). A\n * guided start writes it into the brief itself, but an agency batch's brief\n * is a member's own sentence and may never mention it, and a theme, a layout\n * or a form asked to serve nobody in particular serves nobody in particular.\n */\nexport function aiSiteBriefLines(\n brief: string,\n inputs: AiSiteJobInputs,\n): string[] {\n const lines = [brief.trim()]\n const site = [\n inputs.businessName ? `name: ${inputs.businessName}` : '',\n `business: ${inputs.businessType}`,\n inputs.audience ? `for: ${inputs.audience}` : '',\n inputs.city ? `city: ${inputs.city}` : '',\n inputs.brand ? `brand: ${inputs.brand}` : '',\n ].filter(Boolean)\n lines.push(`Site — ${site.join('; ')}.`)\n return lines\n}\n\n/**\n * What the welcome email is told (AGL-2918), beyond that it is a welcome\n * email.\n *\n * The scaffold's email unit used to carry one sentence, and a yes/no toggle\n * decided whether it ran — so the draft was a welcome email for a business\n * in general, to nobody in particular, about nothing that had happened. The\n * two answers it is missing are who wrote in and what now becomes of what\n * they wrote, and the second of those is the person's own routing answer, so\n * the email and the form it acknowledges cannot say different things.\n *\n * ⛔ A DRAFT either way. Nothing here sends, schedules or enrolls anybody:\n * the email step writes an unpublished email design and stops.\n */\nexport function aiSiteEmailBriefLines(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): string[] {\n const lines = [\n 'Write the welcome email this site sends someone who gets in touch.',\n ]\n const { audience } = aiSiteWords(inputs)\n if (audience) lines.push(`Write it to ${audience}.`)\n const submissions = aiSiteSubmissions(inputs)\n if (submissions === 'lead') {\n lines.push('Their message is a sales lead, so say that somebody will be in touch about it.')\n } else if (submissions === 'inbox') {\n lines.push('Their message has been read, so say it arrived and that a reply is coming.')\n }\n return lines\n}\n\n/**\n * The id a unit's job is named by, and so the id its draft is written under\n * (AGL-3079): the id the plan entry it builds recorded, or for the welcome\n * email the id recorded on this job's step. A unit that writes no draft — a\n * palette change — and a unit whose plan recorded no id are named by this\n * job's id and the unit's slot, the name every draft of such a plan has.\n */\nexport function aiSiteUnitJobId(job: Pick<AiJob, '$id' | 'steps'>, unit: AiSiteUnit): string {\n const recorded =\n unit.creation?.id ?? unit.screen?.id ?? (unit.kind === 'email' ? aiRecordedJobDraftId(job, 'email') : null)\n return recorded || `${job.$id}-${unit.slot}`\n}\n\n/**\n * The job a unit is built under: this job's org, site, creator and model,\n * the kind that owns the unit, and a plan narrowed to the unit alone.\n *\n * The derived job carries NO steps and NO outputs of the scaffold's: a page\n * job's step reads its own draft and its own plan, and what the other units\n * produced is not its business. Its `$id` is the id the unit's draft is\n * written under (`aiSiteUnitJobId`), which is what a step with no id of its\n * own recorded names its draft by, so a unit re-run after its write finds its\n * own draft rather than writing a second.\n *\n * A creation unit reuses what the plan reuses less what the plan's screens\n * place themselves (AGL-3031): a component a page's section places is the\n * page's to place, and a layout or a card told to place it would be refused\n * for leaving it out.\n */\nexport function aiSiteUnitJob(\n job: AiJob,\n unit: AiSiteUnit,\n built: BuiltRefs,\n): AiJob {\n const plan = job.plan as AiJobPlan\n const labels = { ...(plan.labels ?? {}) }\n for (const entry of built.values()) labels[entry.id] = entry.label\n const inputs = parseAiSiteJobInputs(job.inputs)\n const brief =\n typeof inputs === 'string'\n ? [job.brief]\n : aiSiteBriefLines(job.brief, inputs)\n const unitPlan: AiJobPlan = {\n ...plan,\n labels,\n reuse: [...plan.reuse],\n create: [],\n screens: [],\n }\n if (unit.creation) {\n const placedByScreens = new Set(\n plan.screens.flatMap((screen) => screen.sections.flatMap((section) => section.uses)),\n )\n unitPlan.reuse = plan.reuse.filter((entry) => !placedByScreens.has(entry.id))\n unitPlan.create = [\n {\n ...unit.creation,\n duplicateOf: aiSiteResolvedRef(unit.creation.duplicateOf, built),\n },\n ]\n brief.push(\n `Build the ${unit.creation.kind} “${unit.creation.name}”: ${unit.creation.why}`,\n )\n }\n if (unit.screen) {\n const screen = unit.screen\n // Everything the scaffold has already built is a record the site has, so\n // the page is planned against it the way a page job is planned against\n // what the site always had.\n for (const entry of built.values()) {\n unitPlan.reuse.push({\n kind: entry.kind,\n id: entry.id,\n purpose: `the ${entry.kind} this site’s pages are built on`,\n })\n }\n unitPlan.screens = [\n {\n ...screen,\n // A page the plan named no layout for renders inside the one the\n // scaffold built (AGL-3596); the page step falls back to the site's.\n layout: aiSiteResolvedRef(screen.layout, built) ?? aiSiteBuiltLayoutId(built),\n template: aiSiteResolvedRef(screen.template, built),\n // A scaffold GENERATES every page from its plan (AGL-3596). A page\n // copied from one the site has is not built: a plan that named the\n // starter home page as its home's start produced the starter again,\n // byte for byte, reported as written. A page job keeps rule 15.\n duplicateOf: job.kind === 'site' ? null : aiSiteResolvedRef(screen.duplicateOf, built),\n // A dataset the plan creates and the site has not built yet keeps its\n // name, which the page's draft still binds by (AGL-3475).\n record: screen.record\n ? {\n ...screen.record,\n dataset: aiSiteResolvedRef(screen.record.dataset, built) ?? screen.record.dataset,\n }\n : null,\n sections: screen.sections.map((section) => ({\n ...section,\n uses: section.uses\n .map((ref) => aiSiteResolvedRef(ref, built))\n .filter((ref): ref is string => !!ref),\n })),\n },\n ]\n brief.push(\n `Build the page “${screen.title}” of this site, at ${screen.slug}.`,\n )\n }\n if (unit.kind === 'email') {\n brief.push(...aiSiteEmailBriefLines(job.inputs))\n }\n // The job the member started travels with every unit, so what the unit\n // writes names it (AGL-3596).\n const unitInputs: Record<string, unknown> = { ...job.inputs, originJobId: aiOriginJobId(job) }\n // The layout is built before the pages, so it is told them (AGL-3596): their\n // ids are minted on the plan, and the platform writes the header's links.\n if (job.kind === 'site' && unit.kind === 'layout') {\n const pages = aiLayoutSitePagesOfPlan(plan.screens)\n if (pages.length) unitInputs[AI_LAYOUT_SITE_PAGES_INPUT] = pages\n }\n return {\n ...job,\n $id: aiSiteUnitJobId(job, unit),\n kind: unit.jobKind,\n steps: [],\n outputs: [],\n plan: unitPlan,\n inputs:\n // A theme change on a site with no theme of its own is a new palette.\n unit.kind === 'theme'\n ? { ...unitInputs, mode: 'create' }\n : // The email step reads the kind off the inputs it is handed, so a\n // job the scaffold composed says which kind it is exactly as a\n // member's own does.\n unit.kind === 'email'\n ? { ...unitInputs, emailType: AI_SITE_EMAIL_TYPE }\n : unitInputs,\n brief: brief.join('\\n').slice(0, AI_JOB_BRIEF_MAX_CHARS),\n }\n}\n\n/**\n * A scaffold is admitted with inputs that read, for a site of the job's own\n * org, and only where this deployment has loaded the step that builds a page:\n * a scaffold whose pages nothing can build is not a scaffold.\n */\nexport const aiSiteJobAdmission: AiJobAdmission = async (context) => {\n const inputs = parseAiSiteJobInputs(context.inputs)\n if (typeof inputs === 'string') return { status: 400, error: inputs }\n // The workspace's own page band (AGL-3594): one or two pages on the Free\n // taste, four to eight on a paid plan.\n const freeTaste = aiSiteFreeTaste(context.org)\n const pages = aiSitePagesRefusal(inputs.pages, freeTaste)\n if (pages) return { status: 400, error: pages }\n if (context.plan) {\n const shape = aiSitePlanRefusal(context.plan, { freeTaste })\n if (shape) return { status: 400, error: shape }\n }\n if (!context.hostId) {\n return {\n status: 400,\n error: 'Open the site the scaffold is for before starting the job',\n }\n }\n const owner = await resolveOrgIdForHost(context.hostId)\n if (!owner || owner !== context.orgId)\n return { status: 404, error: 'Unknown site' }\n if (!aiJobStepRunnerFor('page')) {\n return { status: 400, error: AI_SITE_NO_PAGE_STEP_COPY }\n }\n return null\n}\n\n/** Whether a scaffold's workspace spends the Free taste (AGL-3594); a missing org reads as paid, as the band has always been. */\nexport function aiSiteFreeTaste(org: object | null | undefined): boolean {\n return Boolean(org) && resolveEffectivePlan(org as never) === 'free'\n}\n\n/**\n * Whether a scaffold drafts its welcome email (AGL-3594): where it was asked\n * for, and never on the Free taste, whose credits its pages need.\n */\nexport function aiSiteWelcomeEmail(inputs: AiSiteJobInputs, freeTaste: boolean): boolean {\n return inputs.welcomeEmail && !freeTaste\n}\n\n/** What one delegated pass came to: the unit's spend as the job records it, and whether the unit is built. */\nexport interface AiJobUnitPass {\n outcome: AiJobStepOutcome\n /** The unit reported what it built and asked for no pass of its own. */\n built: boolean\n}\n\n/**\n * One delegated pass (AGL-2911, AGL-3031): the unit handed to the runner of\n * the kind that owns it, under the job derived for it, and its outcome as the\n * delegating job records it. A delegate proposes a plan for its own job,\n * which does not exist, so no plan is carried back. A unit that stopped for a\n * person, refused or failed is the delegating job's stop; a unit that asked\n * for another pass keeps its turn; and a unit that finished without reporting\n * what it built fails with `emptyCopy`, since another pass would ask the same\n * thing again. The tokens it spent are on the bill either way.\n */\nexport async function aiRunJobUnit(\n context: AiJobStepContext,\n input: {\n unit: AiSiteUnit\n units: readonly AiSiteUnit[]\n runner: AiJobStepRunner\n emptyCopy: string\n },\n): Promise<AiJobUnitPass> {\n const { job } = context\n const outputs = job.outputs ?? []\n const outcome = await input.runner({\n ...context,\n job: aiSiteUnitJob(job, input.unit, aiSiteBuiltRefs(input.units, outputs)),\n })\n const spent: AiJobStepOutcome = {\n outputs: outcome.outputs,\n usage: outcome.usage,\n estCostUsd: outcome.estCostUsd,\n model: outcome.model,\n stopReason: outcome.stopReason,\n ...(outcome.effort ? { effort: outcome.effort } : {}),\n ...(outcome.refused ? { refused: true } : {}),\n ...(outcome.failure ? { failure: outcome.failure } : {}),\n ...(outcome.review ? { review: outcome.review } : {}),\n }\n if (spent.review || spent.refused || spent.failure) return { outcome: spent, built: false }\n if (outcome.continue) return { outcome: { ...spent, continue: true }, built: false }\n if (!outcome.outputs.length) return { outcome: { ...spent, failure: input.emptyCopy }, built: false }\n return { outcome: spent, built: true }\n}\n\nexport interface AiJobSiteStepDeps {\n /** The runner registry; specs hand in a fake for a kind they drive. */\n runnerFor?: typeof aiJobStepRunnerFor\n /** The draft reader a built page is checked through; specs hand in a fake. */\n readNodes?: typeof readAiDraftNodes\n /** The guided start's publish; specs hand in a fake. */\n publish?: typeof aiPublishGuidedSite\n}\n\n/** A page the scaffold reported built that holds none of its plan's sections. */\nexport const AI_SITE_PAGE_NOT_WRITTEN_COPY =\n 'A page of this site was not written from its plan. Try the site brief again.'\n\n/**\n * Whether a page unit's draft holds every section its plan named, under the\n * ids the page step writes them by (AGL-3596). A page counts as written only\n * then: a run that reported a page and wrote none of its sections — a copy of\n * a page the site already had, byte for byte — is not a page the job built,\n * and is never reported done.\n */\nexport async function aiSitePageWritten(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; unitJobId: string; screen: AiBuildPlanScreen; draftId: string },\n readNodes: typeof readAiDraftNodes = readAiDraftNodes,\n): Promise<boolean> {\n if (!input.screen.sections.length) return false\n const stored = await readNodes(firestore, { kind: 'screen', hostId: input.hostId, id: input.draftId })\n if (!stored) return false\n return input.screen.sections.every((_, index) => aiPageSectionNodeId(input.unitJobId, index) in stored.nodes)\n}\n\nexport function createAiJobSiteStep(\n deps: AiJobSiteStepDeps = {},\n): AiJobStepRunner {\n const runnerFor = deps.runnerFor ?? aiJobStepRunnerFor\n const readNodes = deps.readNodes ?? readAiDraftNodes\n const publish = deps.publish ?? aiPublishGuidedSite\n /** A guided start's last pass puts what it built on the site, once (AGL-3596). */\n const published = async (\n context: AiJobStepContext,\n outcome: AiJobStepOutcome,\n ): Promise<AiJobStepOutcome> => {\n const { job } = context\n if (!aiJobPublishesSite(job) || job.sitePublish || !job.hostId) return outcome\n const sitePublish = await publish(context.firestore, {\n job,\n outputs: [...(job.outputs ?? []), ...outcome.outputs],\n now: context.now,\n }).catch((error: unknown) => {\n // The site is built either way; the pages stay drafts and say so.\n console.error('ai site publish threw', { orgId: job.orgId, jobId: job.$id, error })\n return null\n })\n return sitePublish ? { ...outcome, sitePublish } : outcome\n }\n return async (context): Promise<AiJobStepOutcome> => {\n const { job } = context\n // The scaffold asks no model of its own. What it names where it spends\n // nothing is the model its plan ran on, which is the one exchange this\n // job has already had of its own.\n const model = context.modelFor?.('job.plan') ?? aiModelForStep('job.plan')\n const plan = aiConfirmedPlan(job)\n if (!plan)\n return { ...aiUnspentOutcome(model), failure: AI_SITE_NO_PLAN_COPY }\n // The resume door refuses such a plan at confirmation once it can read\n // one; a plan confirmed before a site changed still stops here, unspent.\n const freeTaste = aiSiteFreeTaste(context.org)\n const refusal = aiSitePlanRefusal(plan, { freeTaste })\n if (refusal) return { ...aiUnspentOutcome(model), failure: refusal }\n const inputs = parseAiSiteJobInputs(job.inputs)\n if (typeof inputs === 'string')\n return { ...aiUnspentOutcome(model), failure: inputs }\n\n // A kind this deployment has not loaded is not among the units at all, so\n // a scaffold owes only what something can build.\n const units = aiSiteJobUnits(plan, {\n welcomeEmail: aiSiteWelcomeEmail(inputs, freeTaste),\n }).filter((unit) => runnerFor(unit.jobKind))\n const outputs = job.outputs ?? []\n const pending = aiSitePendingUnits(units, outputs)\n if (!pending.length) return published(context, aiUnspentOutcome(model))\n const unit = pending[0]\n const runner = runnerFor(unit.jobKind)\n if (!runner) return aiUnspentOutcome(model)\n\n const pass = await aiRunJobUnit(context, {\n unit,\n units,\n runner,\n emptyCopy: AI_SITE_UNIT_EMPTY_COPY,\n })\n // The site's own listing rides out beside the first unit's output, once\n // (AGL-2918): it is derived from the answers rather than generated, so it\n // is ready before anything is built and costs the pass nothing.\n const outcome: AiJobStepOutcome = {\n ...pass.outcome,\n outputs: [...aiSiteSeoOutputs(job), ...pass.outcome.outputs],\n }\n // A page counts as built only when its plan's sections are in it\n // (AGL-3596): otherwise the pass fails, which is our failure and gives\n // the job's credits back, and the page it reported is not reported.\n if (pass.built && unit.kind === 'page' && unit.screen && job.hostId) {\n const page = pass.outcome.outputs.find((output) => output.resource === 'screen')\n const written =\n page !== undefined &&\n (await aiSitePageWritten(\n context.firestore,\n { hostId: job.hostId, unitJobId: aiSiteUnitJobId(job, unit), screen: unit.screen, draftId: page.id },\n readNodes,\n ))\n if (!written) {\n return {\n ...outcome,\n outputs: outcome.outputs.filter((output) => output.resource !== 'screen'),\n failure: AI_SITE_PAGE_NOT_WRITTEN_COPY,\n }\n }\n }\n // A built unit continues the scaffold while units remain after it.\n if (pass.built && pending.length > 1) return { ...outcome, continue: true }\n return pass.built ? published(context, outcome) : outcome\n }\n}\n\nexport const runAiJobSiteStep = createAiJobSiteStep()\n\n/**\n * The least time a scaffold's next pass needs (AGL-3035). The scaffold asks no\n * model itself: each pass hands one unit to the step registered for its kind,\n * under the job derived for it, so it needs what that step registers for that\n * job — a palette change's or a layout's time for those units, a page pass's\n * for a page. A scaffold with nothing left to build, or with no plan it can\n * build, spends nothing, and a page pass's time covers it.\n */\nexport function aiSiteJobRunMinimumMs(job: AiJob): number {\n const plan = aiConfirmedPlan(job)\n const inputs = parseAiSiteJobInputs(job.inputs)\n if (!plan || typeof inputs === 'string') return AI_JOB_PAGE_STEP_MINIMUM_MS\n const units = aiSiteJobUnits(plan, { welcomeEmail: inputs.welcomeEmail }).filter((unit) =>\n aiJobStepRunnerFor(unit.jobKind),\n )\n const outputs = job.outputs ?? []\n const [unit] = aiSitePendingUnits(units, outputs)\n if (!unit) return AI_JOB_PAGE_STEP_MINIMUM_MS\n return aiJobStepRunMinimumMs(aiSiteUnitJob(job, unit, aiSiteBuiltRefs(units, outputs)))\n}\n\n/**\n * Registers the site scaffold with the least time a pass needs — every\n * scaffold builds pages, and a page pass needs the least of any unit — and the\n * time its next pass needs, the passes a whole site's plan may take, and the\n * check a site job passes before it is created or resumed.\n */\nexport function registerAiSiteJob(): void {\n registerAiJobStep('site', runAiJobSiteStep, {\n minimumMs: AI_JOB_PAGE_STEP_MINIMUM_MS,\n minimumMsFor: aiSiteJobRunMinimumMs,\n })\n registerAiJobStepPasses('site', AI_SITE_MAX_PASSES)\n registerAiJobAdmission('site', aiSiteJobAdmission)\n}\n"],"names":["resolveEffectivePlan","resolveOrgIdForHost","isAiPlanNewRef","AI_SITE_EMAIL_TYPE","AI_SITE_MAX_SECTIONS","AI_SITE_PAGES","aiSitePagesRefusal","aiSitePlanRefusal","aiSiteSubmissions","aiSiteWords","parseAiSiteJobInputs","AI_SITE_SEO_OUTPUT_ID","aiSiteSeoProposalForInputs","aiModelForStep","registerAiJobAdmission","aiOriginJobId","aiRecordedJobDraftId","readAiDraftNodes","AI_LAYOUT_SITE_PAGES_INPUT","aiLayoutSitePagesOfPlan","aiPageSectionNodeId","aiJobPublishesSite","aiPublishGuidedSite","aiConfirmedPlan","aiUnspentOutcome","AI_JOB_BRIEF_MAX_CHARS","AI_JOB_PAGE_STEP_MINIMUM_MS","aiJobStepRunMinimumMs","aiJobStepRunnerFor","registerAiJobStep","registerAiJobStepPasses","UNIT_KINDS","theme","jobKind","resource","layout","form","component","page","email","CREATION_UNIT_KINDS","aiCreationUnit","creation","slot","kind","label","name","CREATION_UNITS","unit","create","AI_SITE_NO_PLAN_COPY","AI_SITE_NO_PAGE_STEP_COPY","AI_SITE_UNIT_EMPTY_COPY","AI_SITE_MAX_PASSES","max","length","aiSiteJobUnits","plan","options","units","find","entry","push","screens","forEach","screen","index","title","welcomeEmail","aiSitePendingUnits","outputs","done","Map","output","set","get","filter","left","aiSiteSeoOutputs","job","some","id","proposal","inputs","hostId","aiSiteBuiltRefs","byResource","rows","built","row","shift","toLowerCase","aiSiteBuiltLayoutId","values","aiSiteResolvedRef","ref","slice","indexOf","trim","aiSiteBriefLines","brief","lines","site","businessName","businessType","audience","city","brand","Boolean","join","aiSiteEmailBriefLines","submissions","aiSiteUnitJobId","recorded","$id","aiSiteUnitJob","labels","unitPlan","reuse","placedByScreens","Set","flatMap","sections","section","uses","has","duplicateOf","why","purpose","template","record","dataset","map","slug","unitInputs","originJobId","pages","steps","mode","emailType","aiSiteJobAdmission","context","status","error","freeTaste","aiSiteFreeTaste","org","shape","owner","orgId","aiSiteWelcomeEmail","aiRunJobUnit","input","outcome","runner","spent","usage","estCostUsd","model","stopReason","effort","refused","failure","review","continue","emptyCopy","AI_SITE_PAGE_NOT_WRITTEN_COPY","aiSitePageWritten","firestore","readNodes","stored","draftId","every","_","unitJobId","nodes","createAiJobSiteStep","deps","runnerFor","publish","published","sitePublish","now","catch","console","jobId","modelFor","refusal","pending","pass","written","undefined","runAiJobSiteStep","aiSiteJobRunMinimumMs","registerAiSiteJob","minimumMs","minimumMsFor"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-site-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 { resolveEffectivePlan } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport {\n isAiPlanNewRef,\n type AiBuildPlanCreate,\n type AiBuildPlanCreateKind,\n type AiBuildPlanScreen,\n} from '../model/ai-build-plan'\nimport type {\n AiJob,\n AiJobItemLedger,\n AiJobKind,\n AiJobOutput,\n AiJobOutputResource,\n AiJobPlan,\n} from '../model/ai-jobs.types'\nimport {\n aiBuildDegradation,\n aiBuildInitialLedger,\n aiBuildItemDelivered,\n aiBuildItemOpen,\n aiBuildNextUnit,\n type AiBuildUnit,\n} from '../model/ai-build-job'\nimport { aiBuildBuiltRefs, aiUnitErrorRetryable, aiUnitFailure, aiUnitSpend } from './ai-build-unit-outcome'\nimport {\n AI_SITE_EMAIL_TYPE,\n AI_SITE_MAX_SECTIONS,\n AI_SITE_PAGES,\n aiSiteNameSentence,\n aiSitePagesRefusal,\n aiSitePlanRefusal,\n aiSiteSubmissions,\n aiSiteWords,\n parseAiSiteJobInputs,\n type AiSiteJobInputs,\n} from '../model/ai-site-job'\nimport {\n AI_SITE_SEO_OUTPUT_ID,\n aiSiteSeoProposalForInputs,\n} from '../model/ai-site-start-seo'\nimport { aiModelForStep } from '../providers/routing'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport { aiOriginJobId, aiRecordedJobDraftId } from './ai-job-draft-ids'\nimport { readAiDraftNodes } from './ai-job-drafts'\nimport { AI_LAYOUT_SITE_PAGES_INPUT, aiLayoutSitePagesOfPlan } from './ai-job-layout-site-pages'\nimport { aiPageSectionNodeId } from './ai-job-page-sections'\nimport { AI_LAYOUT_FORM_PAGE_INPUT, AI_LAYOUT_LANGUAGE_INPUT, aiLayoutFormPageOfPlan } from './ai-job-page-language'\nimport { aiJobPublishesSite, aiPublishGuidedSite } from './ai-site-publish'\nimport { aiConfirmedPlan, aiUnspentOutcome } from './ai-job-generation'\nimport {\n AI_JOB_BRIEF_MAX_CHARS,\n type AiJobItemOutcome,\n type AiJobStepContext,\n type AiJobStepOutcome,\n type AiJobStepRunner,\n} from './ai-job-text-step'\nimport { AI_JOB_PAGE_STEP_MINIMUM_MS } from './ai-job-page-budget'\nimport {\n aiJobStepRunMinimumMs,\n aiJobStepRunnerFor,\n registerAiJobStep,\n registerAiJobStepPasses,\n} from './ai-jobs'\n\n/**\n * The site scaffold (AGL-2911): the generation step of a `site` job, run once\n * a member confirmed the plan their brief produced.\n *\n * ── It builds nothing itself ─────────────────────────────────────────────\n *\n * A scaffold is a whole site: a palette suggestion, the layout its pages\n * render inside, the contact form they place, four to eight pages with their\n * search listings, and a welcome email. Every one of those is already a job\n * kind with a step of its own, so this step generates nothing and asks no\n * model anything. It works through the confirmed plan one UNIT at a time and\n * hands each unit to the runner registered for the kind that owns it — the\n * theme step for the palette, the page step for a page — under a job of that\n * kind derived from this one.\n *\n * That is why the scaffold cannot drift from the thing it scaffolds: a page\n * it builds is a page job's page, held to the same doctrine, written by the\n * same draft writer, left unpublished by the same rule. A kind whose step\n * this deployment has not loaded is simply not among the units, so nothing\n * here knows which issues have landed.\n *\n * ── One unit a pass ──────────────────────────────────────────────────────\n *\n * The step runs ONE delegated pass and asks the machine to continue, so every\n * pass is one reservation, one provider exchange and one recorded spend, and\n * the org's monthly ceiling binds a scaffold exactly as it binds a chat turn.\n * Credits running out mid-scaffold is therefore the machine's own park: the\n * job waits as `needs_input` with the meter's words, and the beat resumes it\n * where it stopped once the workspace's standing has changed. A pass needs the\n * time its unit's own step needs (`aiSiteJobRunMinimumMs`), so the beat starts\n * a palette change only with a palette change's time left.\n *\n * Where it stopped is read from the job's OUTPUTS rather than kept anywhere:\n * each unit reports exactly one output when it completes, so the units\n * already represented there are the ones already built. A pass interrupted\n * between its work and its record repeats a unit that then finds its own\n * draft and reports it, which is what every step's draft id already gives.\n *\n * ── Never published ──────────────────────────────────────────────────────\n *\n * Nothing here publishes, routes or sends: each delegated step writes the\n * unpublished draft it already wrote for a job of its own kind, and the\n * palette and navigation travel as proposals a member applies.\n *\n * ── The page job builds its creations the same way (AGL-3031) ────────────\n *\n * A page job whose plan creates a layout, forms or components builds them\n * before its page through this same machinery: the units a creation implies\n * (`aiCreationUnit`), where the job stands read from its outputs\n * (`aiSitePendingUnits`), one delegated pass a unit (`aiRunJobUnit`), and the\n * `new:<name>` references resolved to what was built (`aiSiteBuiltRefs`,\n * `aiSiteUnitJob`). There is one loop, and it is the machine's.\n */\n\n/** What a scaffold's units are, in the order it builds them; a component is a page job's. */\nexport type AiSiteUnitKind = 'theme' | 'layout' | 'form' | 'component' | 'page' | 'email'\n\nexport interface AiSiteUnit {\n kind: AiSiteUnitKind\n /** The job kind whose registered runner builds it. */\n jobKind: AiJobKind\n /** The resource the unit reports when it completes. */\n resource: AiJobOutputResource\n /** Stable within the job: what the unit's derived job and draft are addressed by. */\n slot: string\n /** The plan screen a page unit builds. */\n screen?: AiBuildPlanScreen\n /** The creation a theme, layout or form unit builds. */\n creation?: AiBuildPlanCreate\n /** What the unit is, for the derived job's brief. */\n label: string\n}\n\n/** The job kind and output resource each unit is built through. */\nconst UNIT_KINDS: Record<\n AiSiteUnitKind,\n { jobKind: AiJobKind; resource: AiJobOutputResource }\n> = {\n theme: { jobKind: 'theme', resource: 'theme' },\n layout: { jobKind: 'layout', resource: 'layout' },\n form: { jobKind: 'form', resource: 'form' },\n component: { jobKind: 'component', resource: 'reusableComponent' },\n page: { jobKind: 'page', resource: 'screen' },\n email: { jobKind: 'email', resource: 'emailScreen' },\n}\n\n/** The unit kind a creation is built as, where a unit builds it. */\nconst CREATION_UNIT_KINDS: Partial<Record<AiBuildPlanCreateKind, AiSiteUnitKind>> = {\n 'theme-change': 'theme',\n layout: 'layout',\n form: 'form',\n component: 'component',\n}\n\n/**\n * The unit one creation is built as, under the slot its derived job and draft\n * are addressed by; `null` for a creation no unit builds.\n */\nexport function aiCreationUnit(creation: AiBuildPlanCreate, slot: string): AiSiteUnit | null {\n const kind = CREATION_UNIT_KINDS[creation.kind]\n if (!kind) return null\n return { kind, ...UNIT_KINDS[kind], slot, creation, label: creation.name }\n}\n\n/** The creation kinds a unit is built from, by unit kind. */\nconst CREATION_UNITS: Array<{\n unit: AiSiteUnitKind\n create: AiBuildPlanCreateKind\n}> = [\n { unit: 'theme', create: 'theme-change' },\n { unit: 'layout', create: 'layout' },\n { unit: 'form', create: 'form' },\n]\n\nexport const AI_SITE_NO_PLAN_COPY =\n 'This site job has no confirmed plan to build.'\n\n/** What a scaffold answers where the step that builds a page is not loaded. */\nexport const AI_SITE_NO_PAGE_STEP_COPY =\n 'Building a whole site is not available yet.'\n\n/** A unit that finished without reporting what it built; the scaffold cannot go on. */\nexport const AI_SITE_UNIT_EMPTY_COPY =\n 'Part of this site could not be built. Try the site brief again.'\n\n/**\n * The most passes a scaffold's step may take: the largest plan it admits —\n * eight pages of eight sections, its three creations and a welcome email —\n * with nothing to spare, so a runner that never finishes is still bounded\n * while a real site is not.\n */\nexport const AI_SITE_MAX_PASSES =\n AI_SITE_PAGES.max * (AI_SITE_MAX_SECTIONS + 1) + CREATION_UNITS.length + 1\n\n/**\n * The units a plan implies, in build order: the palette first, because a\n * member reads it while the pages are still building; then the layout every\n * page renders inside and the form they place, because a page binds both by\n * id and so needs them to exist; then the pages; then the welcome email,\n * which is about the site rather than part of it.\n */\nexport function aiSiteJobUnits(\n plan: Pick<AiJobPlan, 'create' | 'screens'>,\n options: { welcomeEmail?: boolean } = {},\n): AiSiteUnit[] {\n const units: AiSiteUnit[] = []\n for (const { unit, create } of CREATION_UNITS) {\n const creation = plan.create.find((entry) => entry.kind === create)\n if (!creation) continue\n units.push({\n kind: unit,\n ...UNIT_KINDS[unit],\n slot: unit[0],\n creation,\n label: creation.name,\n })\n }\n plan.screens.forEach((screen, index) => {\n units.push({\n kind: 'page',\n ...UNIT_KINDS.page,\n slot: `p${index}`,\n screen,\n label: screen.title,\n })\n })\n if (options.welcomeEmail) {\n units.push({\n kind: 'email',\n ...UNIT_KINDS.email,\n slot: 'e',\n label: 'Welcome email',\n })\n }\n return units\n}\n\n/**\n * The units a scaffold still owes after the outputs recorded so far. Each\n * unit reports one output of its own resource when it completes, so the\n * outputs already recorded are consumed against the units in build order.\n */\nexport function aiSitePendingUnits(\n units: readonly AiSiteUnit[],\n outputs: readonly Pick<AiJobOutput, 'resource'>[],\n): AiSiteUnit[] {\n const done = new Map<AiJobOutputResource, number>()\n for (const output of outputs) {\n done.set(output.resource, (done.get(output.resource) ?? 0) + 1)\n }\n return units.filter((unit) => {\n const left = done.get(unit.resource) ?? 0\n if (left <= 0) return true\n done.set(unit.resource, left - 1)\n return false\n })\n}\n\n/**\n * The site's own search listing as the scaffold reports it (AGL-2918), or\n * nothing when the job already reported one or its inputs describe no site.\n *\n * It costs nothing and asks no model: the values are arithmetic on the\n * answers (`aiSiteSeoProposal`), so they ride out on the first pass that\n * builds anything rather than waiting for a pass of their own. Its resource\n * is `seo`, which no unit reports, so it cannot be mistaken for a unit's\n * output by `aiSitePendingUnits` and cannot move where the scaffold thinks\n * it is.\n */\nexport function aiSiteSeoOutputs(job: AiJob): AiJobOutput[] {\n const outputs = job.outputs ?? []\n if (outputs.some((output) => output.resource === 'seo' && output.id === AI_SITE_SEO_OUTPUT_ID)) {\n return []\n }\n const proposal = aiSiteSeoProposalForInputs(job.inputs)\n if (!proposal) return []\n return [\n {\n resource: 'seo',\n id: AI_SITE_SEO_OUTPUT_ID,\n hostId: job.hostId ?? null,\n label: 'The site’s search title and description',\n proposal: proposal as unknown as Record<string, unknown>,\n },\n ]\n}\n\n/** A record an earlier unit created, as a later one references it. */\nexport interface AiSiteBuiltRef {\n id: string\n label: string\n /** Only what becomes a record a page can name; a palette change is not one. */\n kind: 'layout' | 'form' | 'component'\n}\n\n/**\n * What the scaffold has already created, as the plan's `new:<name>`\n * references resolve to: the id of the record each completed creation wrote.\n * A page unit is told about the layout and the form by id, exactly as a page\n * job is told about records the site already had.\n */\nexport function aiSiteBuiltRefs(\n units: readonly AiSiteUnit[],\n outputs: readonly Pick<AiJobOutput, 'resource' | 'id' | 'label'>[],\n): Map<string, AiSiteBuiltRef> {\n const byResource = new Map<\n AiJobOutputResource,\n Array<Pick<AiJobOutput, 'id' | 'label'>>\n >()\n for (const output of outputs) {\n const rows = byResource.get(output.resource) ?? []\n rows.push(output)\n byResource.set(output.resource, rows)\n }\n const built = new Map<string, AiSiteBuiltRef>()\n for (const unit of units) {\n const rows = byResource.get(unit.resource)\n const row = rows?.shift()\n if (!row || !unit.creation) continue\n // A theme change is a proposal with no record to reference; a layout, a\n // form and a component become ids a page can name.\n if (\n unit.creation.kind !== 'layout' &&\n unit.creation.kind !== 'form' &&\n unit.creation.kind !== 'component'\n ) {\n continue\n }\n built.set(unit.creation.name.toLowerCase(), {\n id: row.id,\n label: row.label || unit.creation.name,\n kind: unit.creation.kind,\n })\n }\n return built\n}\n\ntype BuiltRefs = ReturnType<typeof aiSiteBuiltRefs>\n\n/** The layout the scaffold built, when it built one. */\nfunction aiSiteBuiltLayoutId(built: BuiltRefs): string | null {\n for (const entry of built.values()) if (entry.kind === 'layout') return entry.id\n return null\n}\n\n/** A plan reference as the unit's own job reads it: an id the site now has, or nothing. */\nexport function aiSiteResolvedRef(ref: string | null, built: BuiltRefs): string | null {\n if (!ref) return null\n if (!isAiPlanNewRef(ref)) return ref\n return (\n built.get(\n ref\n .slice(ref.indexOf(':') + 1)\n .trim()\n .toLowerCase(),\n )?.id ?? null\n )\n}\n\n/**\n * The site's own words, as every unit's brief carries them.\n *\n * The audience is here rather than left to the brief's prose (AGL-2918). A\n * guided start writes it into the brief itself, but an agency batch's brief\n * is a member's own sentence and may never mention it, and a theme, a layout\n * or a form asked to serve nobody in particular serves nobody in particular.\n *\n * The name is said twice (AGL-3596): as a field, and as the instruction to use\n * it as written. A layout told only \"a dog groomer in Austin\" put a business\n * name of its own in the header and the footer.\n */\nexport function aiSiteBriefLines(\n brief: string,\n inputs: AiSiteJobInputs,\n): string[] {\n const lines = [brief.trim()]\n const site = [\n inputs.businessName ? `name: ${inputs.businessName}` : '',\n `business: ${inputs.businessType}`,\n inputs.audience ? `for: ${inputs.audience}` : '',\n inputs.city ? `city: ${inputs.city}` : '',\n inputs.brand ? `brand: ${inputs.brand}` : '',\n ].filter(Boolean)\n lines.push(`Site — ${site.join('; ')}.`)\n if (inputs.businessName) lines.push(aiSiteNameSentence(inputs.businessName))\n return lines\n}\n\n/**\n * What the welcome email is told (AGL-2918), beyond that it is a welcome\n * email.\n *\n * The scaffold's email unit used to carry one sentence, and a yes/no toggle\n * decided whether it ran — so the draft was a welcome email for a business\n * in general, to nobody in particular, about nothing that had happened. The\n * two answers it is missing are who wrote in and what now becomes of what\n * they wrote, and the second of those is the person's own routing answer, so\n * the email and the form it acknowledges cannot say different things.\n *\n * ⛔ A DRAFT either way. Nothing here sends, schedules or enrolls anybody:\n * the email step writes an unpublished email design and stops.\n */\nexport function aiSiteEmailBriefLines(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): string[] {\n const lines = [\n 'Write the welcome email this site sends someone who gets in touch.',\n ]\n const { audience } = aiSiteWords(inputs)\n if (audience) lines.push(`Write it to ${audience}.`)\n const submissions = aiSiteSubmissions(inputs)\n if (submissions === 'lead') {\n lines.push('Their message is a sales lead, so say that somebody will be in touch about it.')\n } else if (submissions === 'inbox') {\n lines.push('Their message has been read, so say it arrived and that a reply is coming.')\n }\n return lines\n}\n\n/**\n * The id a unit's job is named by, and so the id its draft is written under\n * (AGL-3079): the id the plan entry it builds recorded, or for the welcome\n * email the id recorded on this job's step. A unit that writes no draft — a\n * palette change — and a unit whose plan recorded no id are named by this\n * job's id and the unit's slot, the name every draft of such a plan has.\n */\nexport function aiSiteUnitJobId(job: Pick<AiJob, '$id' | 'steps'>, unit: AiSiteUnit): string {\n const recorded =\n unit.creation?.id ?? unit.screen?.id ?? (unit.kind === 'email' ? aiRecordedJobDraftId(job, 'email') : null)\n return recorded || `${job.$id}-${unit.slot}`\n}\n\n/**\n * The job a unit is built under: this job's org, site, creator and model,\n * the kind that owns the unit, and a plan narrowed to the unit alone.\n *\n * The derived job carries NO steps and NO outputs of the scaffold's: a page\n * job's step reads its own draft and its own plan, and what the other units\n * produced is not its business. Its `$id` is the id the unit's draft is\n * written under (`aiSiteUnitJobId`), which is what a step with no id of its\n * own recorded names its draft by, so a unit re-run after its write finds its\n * own draft rather than writing a second.\n *\n * A creation unit reuses what the plan reuses less what the plan's screens\n * place themselves (AGL-3031): a component a page's section places is the\n * page's to place, and a layout or a card told to place it would be refused\n * for leaving it out.\n */\nexport function aiSiteUnitJob(\n job: AiJob,\n unit: AiSiteUnit,\n built: BuiltRefs,\n): AiJob {\n const plan = job.plan as AiJobPlan\n const labels = { ...(plan.labels ?? {}) }\n for (const entry of built.values()) labels[entry.id] = entry.label\n const inputs = parseAiSiteJobInputs(job.inputs)\n const brief =\n typeof inputs === 'string'\n ? [job.brief]\n : aiSiteBriefLines(job.brief, inputs)\n const unitPlan: AiJobPlan = {\n ...plan,\n labels,\n reuse: [...plan.reuse],\n create: [],\n screens: [],\n }\n if (unit.creation) {\n const placedByScreens = new Set(\n plan.screens.flatMap((screen) => screen.sections.flatMap((section) => section.uses)),\n )\n unitPlan.reuse = plan.reuse.filter((entry) => !placedByScreens.has(entry.id))\n unitPlan.create = [\n {\n ...unit.creation,\n duplicateOf: aiSiteResolvedRef(unit.creation.duplicateOf, built),\n },\n ]\n brief.push(\n `Build the ${unit.creation.kind} “${unit.creation.name}”: ${unit.creation.why}`,\n )\n }\n if (unit.screen) {\n const screen = unit.screen\n // Everything the scaffold has already built is a record the site has, so\n // the page is planned against it the way a page job is planned against\n // what the site always had.\n for (const entry of built.values()) {\n unitPlan.reuse.push({\n kind: entry.kind,\n id: entry.id,\n purpose: `the ${entry.kind} this site’s pages are built on`,\n })\n }\n unitPlan.screens = [\n {\n ...screen,\n // A page the plan named no layout for renders inside the one the\n // scaffold built (AGL-3596); the page step falls back to the site's.\n layout: aiSiteResolvedRef(screen.layout, built) ?? aiSiteBuiltLayoutId(built),\n template: aiSiteResolvedRef(screen.template, built),\n // A scaffold GENERATES every page from its plan (AGL-3596). A page\n // copied from one the site has is not built: a plan that named the\n // starter home page as its home's start produced the starter again,\n // byte for byte, reported as written. A page job keeps rule 15.\n duplicateOf: job.kind === 'site' ? null : aiSiteResolvedRef(screen.duplicateOf, built),\n // A dataset the plan creates and the site has not built yet keeps its\n // name, which the page's draft still binds by (AGL-3475).\n record: screen.record\n ? {\n ...screen.record,\n dataset: aiSiteResolvedRef(screen.record.dataset, built) ?? screen.record.dataset,\n }\n : null,\n sections: screen.sections.map((section) => ({\n ...section,\n uses: section.uses\n .map((ref) => aiSiteResolvedRef(ref, built))\n .filter((ref): ref is string => !!ref),\n })),\n },\n ]\n brief.push(\n `Build the page “${screen.title}” of this site, at ${screen.slug}.`,\n )\n }\n if (unit.kind === 'email') {\n brief.push(...aiSiteEmailBriefLines(job.inputs))\n }\n // The job the member started travels with every unit, so what the unit\n // writes names it (AGL-3596).\n const unitInputs: Record<string, unknown> = { ...job.inputs, originJobId: aiOriginJobId(job) }\n // The layout is built before the pages, so it is told them (AGL-3596): their\n // ids are minted on the plan, and the platform writes the header's links.\n // A page is told them too, so its buttons may go to a page built after it.\n if (job.kind === 'site' && (unit.kind === 'layout' || unit.kind === 'page')) {\n // A guided start links every page the person asked for (AGL-3660).\n const pages = aiLayoutSitePagesOfPlan(plan.screens, { guided: true })\n if (pages.length) unitInputs[AI_LAYOUT_SITE_PAGES_INPUT] = pages\n }\n // A site's pages and its layout are designed in the layout language and\n // compiled (AGL-3660), and a page is told which page places the site's form.\n if (unit.kind === 'layout' || unit.kind === 'page') {\n unitInputs[AI_LAYOUT_LANGUAGE_INPUT] = true\n const formPage = aiLayoutFormPageOfPlan(plan)\n if (formPage) unitInputs[AI_LAYOUT_FORM_PAGE_INPUT] = formPage\n }\n return {\n ...job,\n $id: aiSiteUnitJobId(job, unit),\n kind: unit.jobKind,\n steps: [],\n outputs: [],\n plan: unitPlan,\n inputs:\n // A theme change on a site with no theme of its own is a new palette.\n unit.kind === 'theme'\n ? { ...unitInputs, mode: 'create' }\n : // The email step reads the kind off the inputs it is handed, so a\n // job the scaffold composed says which kind it is exactly as a\n // member's own does.\n unit.kind === 'email'\n ? { ...unitInputs, emailType: AI_SITE_EMAIL_TYPE }\n : unitInputs,\n brief: brief.join('\\n').slice(0, AI_JOB_BRIEF_MAX_CHARS),\n }\n}\n\n/**\n * A scaffold is admitted with inputs that read, for a site of the job's own\n * org, and only where this deployment has loaded the step that builds a page:\n * a scaffold whose pages nothing can build is not a scaffold.\n */\nexport const aiSiteJobAdmission: AiJobAdmission = async (context) => {\n const inputs = parseAiSiteJobInputs(context.inputs)\n if (typeof inputs === 'string') return { status: 400, error: inputs }\n // The workspace's own page band (AGL-3594): one or two pages on the Free\n // taste, four to eight on a paid plan.\n const freeTaste = aiSiteFreeTaste(context.org)\n const pages = aiSitePagesRefusal(inputs.pages, freeTaste)\n if (pages) return { status: 400, error: pages }\n if (context.plan) {\n const shape = aiSitePlanRefusal(context.plan, { freeTaste })\n if (shape) return { status: 400, error: shape }\n }\n if (!context.hostId) {\n return {\n status: 400,\n error: 'Open the site the scaffold is for before starting the job',\n }\n }\n const owner = await resolveOrgIdForHost(context.hostId)\n if (!owner || owner !== context.orgId)\n return { status: 404, error: 'Unknown site' }\n if (!aiJobStepRunnerFor('page')) {\n return { status: 400, error: AI_SITE_NO_PAGE_STEP_COPY }\n }\n return null\n}\n\n/** Whether a scaffold's workspace spends the Free taste (AGL-3594); a missing org reads as paid, as the band has always been. */\nexport function aiSiteFreeTaste(org: object | null | undefined): boolean {\n return Boolean(org) && resolveEffectivePlan(org as never) === 'free'\n}\n\n/**\n * Whether a scaffold drafts its welcome email (AGL-3594): where it was asked\n * for, and never on the Free taste, whose credits its pages need.\n */\nexport function aiSiteWelcomeEmail(inputs: AiSiteJobInputs, freeTaste: boolean): boolean {\n return inputs.welcomeEmail && !freeTaste\n}\n\n/** What one delegated pass came to: the unit's spend as the job records it, and whether the unit is built. */\nexport interface AiJobUnitPass {\n outcome: AiJobStepOutcome\n /** The unit reported what it built and asked for no pass of its own. */\n built: boolean\n}\n\n/**\n * One delegated pass (AGL-2911, AGL-3031): the unit handed to the runner of\n * the kind that owns it, under the job derived for it, and its outcome as the\n * delegating job records it. A delegate proposes a plan for its own job,\n * which does not exist, so no plan is carried back. A unit that stopped for a\n * person, refused or failed is the delegating job's stop; a unit that asked\n * for another pass keeps its turn; and a unit that finished without reporting\n * what it built fails with `emptyCopy`, since another pass would ask the same\n * thing again. The tokens it spent are on the bill either way.\n */\nexport async function aiRunJobUnit(\n context: AiJobStepContext,\n input: {\n unit: AiSiteUnit\n units: readonly AiSiteUnit[]\n runner: AiJobStepRunner\n emptyCopy: string\n },\n): Promise<AiJobUnitPass> {\n const { job } = context\n const outputs = job.outputs ?? []\n const outcome = await input.runner({\n ...context,\n job: aiSiteUnitJob(job, input.unit, aiSiteBuiltRefs(input.units, outputs)),\n })\n const spent: AiJobStepOutcome = {\n outputs: outcome.outputs,\n usage: outcome.usage,\n estCostUsd: outcome.estCostUsd,\n model: outcome.model,\n stopReason: outcome.stopReason,\n ...(outcome.effort ? { effort: outcome.effort } : {}),\n ...(outcome.refused ? { refused: true } : {}),\n ...(outcome.failure ? { failure: outcome.failure } : {}),\n ...(outcome.review ? { review: outcome.review } : {}),\n }\n if (spent.review || spent.refused || spent.failure) return { outcome: spent, built: false }\n if (outcome.continue) return { outcome: { ...spent, continue: true }, built: false }\n if (!outcome.outputs.length) return { outcome: { ...spent, failure: input.emptyCopy }, built: false }\n return { outcome: spent, built: true }\n}\n\nexport interface AiJobSiteStepDeps {\n /** The runner registry; specs hand in a fake for a kind they drive. */\n runnerFor?: typeof aiJobStepRunnerFor\n /** The draft reader a built page is checked through; specs hand in a fake. */\n readNodes?: typeof readAiDraftNodes\n /** The guided start's publish; specs hand in a fake. */\n publish?: typeof aiPublishGuidedSite\n}\n\n/** A page the scaffold reported built that holds none of its plan's sections. */\nexport const AI_SITE_PAGE_NOT_WRITTEN_COPY =\n 'A page of this site was not written from its plan. Try the site brief again.'\n\n/**\n * Whether a page unit's draft holds every section its plan named, under the\n * ids the page step writes them by (AGL-3596). A page counts as written only\n * then: a run that reported a page and wrote none of its sections — a copy of\n * a page the site already had, byte for byte — is not a page the job built,\n * and is never reported done.\n */\nexport async function aiSitePageWritten(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; unitJobId: string; screen: AiBuildPlanScreen; draftId: string },\n readNodes: typeof readAiDraftNodes = readAiDraftNodes,\n): Promise<boolean> {\n if (!input.screen.sections.length) return false\n const stored = await readNodes(firestore, { kind: 'screen', hostId: input.hostId, id: input.draftId })\n if (!stored) return false\n return input.screen.sections.every((_, index) => aiPageSectionNodeId(input.unitJobId, index) in stored.nodes)\n}\n\n/**\n * A scaffold's units as a build's (AGL-3616): the same slots, each unit's\n * kind as its operation, and every page built after the layout and the form\n * it renders inside and places. What the build's ledger, degradation and\n * Try again read, so a site is settled item by item exactly as a build is.\n */\nexport function aiSiteLedgerUnits(units: readonly AiSiteUnit[]): AiBuildUnit[] {\n const creations = units.filter((unit) => unit.kind === 'layout' || unit.kind === 'form').map((unit) => unit.slot)\n return units.map((unit) => ({\n slot: unit.slot,\n op: unit.kind === 'theme' ? 'theme' : unit.jobKind,\n label: unit.label,\n ...(unit.creation ? { creation: unit.creation } : {}),\n ...(unit.screen ? { screen: unit.screen } : {}),\n deps: unit.kind === 'page' ? creations : [],\n }))\n}\n\n/**\n * A scaffold's ledger to start from (AGL-3616): every unit pending, except\n * the units a job already running when the ledger arrived had built, read\n * off its outputs as the scaffold always read them, which stand as built.\n */\nexport function aiSiteInitialLedger(\n units: readonly AiSiteUnit[],\n outputs: readonly AiJobOutput[],\n): AiJobItemLedger[] {\n const pending = new Set(aiSitePendingUnits(units, outputs).map((unit) => unit.slot))\n const byResource = new Map<AiJobOutputResource, string[]>()\n for (const output of outputs) byResource.set(output.resource, [...(byResource.get(output.resource) ?? []), output.id])\n return aiBuildInitialLedger(aiSiteLedgerUnits(units)).map((row, index) => {\n const unit = units[index]\n if (pending.has(unit.slot)) return row\n const id = byResource.get(unit.resource)?.shift()\n return { ...row, status: 'succeeded', outputs: id ? [id] : [] }\n })\n}\n\n/** The units a scaffold owes on this job: what its plan implies that this deployment can build. */\nfunction aiSiteOwedUnits(\n job: AiJob,\n plan: AiJobPlan,\n inputs: AiSiteJobInputs,\n freeTaste: boolean,\n runnerFor: typeof aiJobStepRunnerFor,\n): AiSiteUnit[] {\n return aiSiteJobUnits(plan, { welcomeEmail: aiSiteWelcomeEmail(inputs, freeTaste) }).filter((unit) =>\n runnerFor(unit.jobKind),\n )\n}\n\nexport function createAiJobSiteStep(\n deps: AiJobSiteStepDeps = {},\n): AiJobStepRunner {\n const runnerFor = deps.runnerFor ?? aiJobStepRunnerFor\n const readNodes = deps.readNodes ?? readAiDraftNodes\n const publish = deps.publish ?? aiPublishGuidedSite\n return async (context): Promise<AiJobStepOutcome> => {\n const { job } = context\n // The scaffold asks no model of its own. What it names where it spends\n // nothing is the model its plan ran on, which is the one exchange this\n // job has already had of its own.\n const model = context.modelFor?.('job.plan') ?? aiModelForStep('job.plan')\n const plan = aiConfirmedPlan(job)\n if (!plan)\n return { ...aiUnspentOutcome(model), failure: AI_SITE_NO_PLAN_COPY }\n // The resume door refuses such a plan at confirmation once it can read\n // one; a plan confirmed before a site changed still stops here, unspent.\n const freeTaste = aiSiteFreeTaste(context.org)\n const refusal = aiSitePlanRefusal(plan, { freeTaste })\n if (refusal) return { ...aiUnspentOutcome(model), failure: refusal }\n const inputs = parseAiSiteJobInputs(job.inputs)\n if (typeof inputs === 'string')\n return { ...aiUnspentOutcome(model), failure: inputs }\n\n // A kind this deployment has not loaded is not among the units at all, so\n // a scaffold owes only what something can build.\n const units = aiSiteOwedUnits(job, plan, inputs, freeTaste, runnerFor)\n const ledgerUnits = aiSiteLedgerUnits(units)\n // Settled item by item, like a build (AGL-3616): the ledger, not the\n // outputs, says where the scaffold stands once it has one.\n const starting = !job.items?.length\n const ledger = starting ? aiSiteInitialLedger(units, job.outputs ?? []) : (job.items as AiJobItemLedger[])\n const init = starting ? { items: ledger } : {}\n const rows = new Map(ledger.map((row) => [row.slot, row]))\n const next = aiBuildNextUnit(ledgerUnits, ledger)\n\n /** A guided start's last pass puts what it built on the site, once (AGL-3596): only its pages that were built. */\n const finish = async (outcome: AiJobStepOutcome, pages: readonly AiJobOutput[]): Promise<AiJobStepOutcome> => {\n if (!aiJobPublishesSite(job) || job.sitePublish || !job.hostId || !pages.length) return outcome\n const sitePublish = await publish(context.firestore, {\n job,\n outputs: pages,\n now: context.now,\n }).catch((error: unknown) => {\n // The site is built either way; the pages stay drafts and say so.\n console.error('ai site publish threw', { orgId: job.orgId, jobId: job.$id, error })\n return null\n })\n return sitePublish ? { ...outcome, sitePublish } : outcome\n }\n /** The pages built so far, as the job reported them. */\n const builtPages = (extra: readonly AiJobOutput[] = []) => {\n const ids = new Set(\n units\n .filter((unit) => unit.kind === 'page' && aiBuildItemDelivered(rows.get(unit.slot) ?? { status: 'pending' }))\n .flatMap((unit) => rows.get(unit.slot)?.outputs ?? []),\n )\n return [...(job.outputs ?? []).filter((output) => output.resource === 'screen' && ids.has(output.id)), ...extra]\n }\n\n if (!next) return finish({ ...aiUnspentOutcome(model), ...init }, builtPages())\n const unit = units.find((one) => one.slot === next.slot) as AiSiteUnit\n const runner = runnerFor(unit.jobKind)\n if (!runner) return { ...aiUnspentOutcome(model), ...init }\n const othersOpen = ledgerUnits.some(\n (one) => one.slot !== unit.slot && aiBuildItemOpen(rows.get(one.slot) ?? { status: 'pending' }),\n )\n // The site's own listing rides out beside the first unit's output, once\n // (AGL-2918): it is derived from the answers rather than generated, so it\n // is ready before anything is built and costs the pass nothing.\n const listing = aiSiteSeoOutputs(job)\n const settle = (item: AiJobItemOutcome, spent: AiJobStepOutcome = aiUnspentOutcome(model)): AiJobStepOutcome => ({\n ...spent,\n outputs: [...listing, ...spent.outputs],\n ...init,\n item,\n ...(item.status === 'running' || othersOpen ? { continue: true } : {}),\n })\n\n // A page whose layout or form failed is built without it, and says so.\n const degradation = aiBuildDegradation(next, { units: ledgerUnits, ledger })\n const degraded = degradation.degradedBy.length > 0\n const note = degradation.notes.length ? degradation.notes.join(' ') : null\n const built = aiBuildBuiltRefs(ledgerUnits, ledger, job.outputs ?? [])\n const derived = aiSiteUnitJob(job, unit, built)\n const handed: AiJob = degradation.briefLines.length\n ? { ...derived, brief: [derived.brief, ...degradation.briefLines].join('\\n').slice(0, AI_JOB_BRIEF_MAX_CHARS) }\n : derived\n\n let outcome: AiJobStepOutcome\n try {\n outcome = await runner({ ...context, job: handed })\n } catch (error) {\n if (aiUnitErrorRetryable(error)) throw error\n console.error('ai site unit threw', { orgId: job.orgId, jobId: job.$id, slot: unit.slot, error })\n return settle({ slot: unit.slot, status: 'failed', failure: { ours: true, reason: 'provider', message: AI_SITE_UNIT_EMPTY_COPY } })\n }\n const spent = aiUnitSpend(outcome)\n const stopped = aiUnitFailure(unit.slot, outcome)\n if (stopped) {\n return settle(\n stopped.status === 'failed' && stopped.failure && !outcome.failure && !outcome.refused && !outcome.review\n ? { ...stopped, failure: { ...stopped.failure, message: AI_SITE_UNIT_EMPTY_COPY } }\n : stopped,\n spent,\n )\n }\n // A page counts as built only when its plan's sections are in it\n // (AGL-3596): otherwise the page fails, on our side, and is not reported.\n if (unit.kind === 'page' && unit.screen && job.hostId) {\n const page = outcome.outputs.find((output) => output.resource === 'screen')\n const written =\n page !== undefined &&\n (await aiSitePageWritten(\n context.firestore,\n { hostId: job.hostId, unitJobId: aiSiteUnitJobId(job, unit), screen: unit.screen, draftId: page.id },\n readNodes,\n ))\n if (!written) {\n return settle(\n { slot: unit.slot, status: 'failed', failure: { ours: true, reason: 'step-failure', message: AI_SITE_PAGE_NOT_WRITTEN_COPY } },\n { ...spent, outputs: spent.outputs.filter((output) => output.resource !== 'screen') },\n )\n }\n }\n const done = settle(\n {\n slot: unit.slot,\n status: degraded ? 'degraded' : 'succeeded',\n outputs: outcome.outputs.map((output) => output.id),\n note,\n ...(degraded ? { degradedBy: degradation.degradedBy } : {}),\n },\n spent,\n )\n if (done.continue) return done\n rows.set(unit.slot, { ...(rows.get(unit.slot) as AiJobItemLedger), status: degraded ? 'degraded' : 'succeeded' })\n return finish(done, builtPages(unit.kind === 'page' ? outcome.outputs.filter((output) => output.resource === 'screen') : []))\n }\n}\n\nexport const runAiJobSiteStep = createAiJobSiteStep()\n\n/**\n * The least time a scaffold's next pass needs (AGL-3035). The scaffold asks no\n * model itself: each pass hands one unit to the step registered for its kind,\n * under the job derived for it, so it needs what that step registers for that\n * job — a palette change's or a layout's time for those units, a page pass's\n * for a page. A scaffold with nothing left to build, or with no plan it can\n * build, spends nothing, and a page pass's time covers it.\n */\nexport function aiSiteJobRunMinimumMs(job: AiJob): number {\n const plan = aiConfirmedPlan(job)\n const inputs = parseAiSiteJobInputs(job.inputs)\n if (!plan || typeof inputs === 'string') return AI_JOB_PAGE_STEP_MINIMUM_MS\n const units = aiSiteJobUnits(plan, { welcomeEmail: inputs.welcomeEmail }).filter((unit) =>\n aiJobStepRunnerFor(unit.jobKind),\n )\n const outputs = job.outputs ?? []\n const ledger = job.items?.length ? job.items : aiSiteInitialLedger(units, outputs)\n const next = aiBuildNextUnit(aiSiteLedgerUnits(units), ledger)\n const unit = next ? units.find((one) => one.slot === next.slot) : undefined\n if (!unit) return AI_JOB_PAGE_STEP_MINIMUM_MS\n return aiJobStepRunMinimumMs(aiSiteUnitJob(job, unit, aiBuildBuiltRefs(aiSiteLedgerUnits(units), ledger, outputs)))\n}\n\n/**\n * Registers the site scaffold with the least time a pass needs — every\n * scaffold builds pages, and a page pass needs the least of any unit — and the\n * time its next pass needs, the passes a whole site's plan may take, and the\n * check a site job passes before it is created or resumed.\n */\nexport function registerAiSiteJob(): void {\n registerAiJobStep('site', runAiJobSiteStep, {\n minimumMs: AI_JOB_PAGE_STEP_MINIMUM_MS,\n minimumMsFor: aiSiteJobRunMinimumMs,\n })\n registerAiJobStepPasses('site', AI_SITE_MAX_PASSES)\n registerAiJobAdmission('site', aiSiteJobAdmission)\n}\n"],"names":["resolveEffectivePlan","resolveOrgIdForHost","isAiPlanNewRef","aiBuildDegradation","aiBuildInitialLedger","aiBuildItemDelivered","aiBuildItemOpen","aiBuildNextUnit","aiBuildBuiltRefs","aiUnitErrorRetryable","aiUnitFailure","aiUnitSpend","AI_SITE_EMAIL_TYPE","AI_SITE_MAX_SECTIONS","AI_SITE_PAGES","aiSiteNameSentence","aiSitePagesRefusal","aiSitePlanRefusal","aiSiteSubmissions","aiSiteWords","parseAiSiteJobInputs","AI_SITE_SEO_OUTPUT_ID","aiSiteSeoProposalForInputs","aiModelForStep","registerAiJobAdmission","aiOriginJobId","aiRecordedJobDraftId","readAiDraftNodes","AI_LAYOUT_SITE_PAGES_INPUT","aiLayoutSitePagesOfPlan","aiPageSectionNodeId","AI_LAYOUT_FORM_PAGE_INPUT","AI_LAYOUT_LANGUAGE_INPUT","aiLayoutFormPageOfPlan","aiJobPublishesSite","aiPublishGuidedSite","aiConfirmedPlan","aiUnspentOutcome","AI_JOB_BRIEF_MAX_CHARS","AI_JOB_PAGE_STEP_MINIMUM_MS","aiJobStepRunMinimumMs","aiJobStepRunnerFor","registerAiJobStep","registerAiJobStepPasses","UNIT_KINDS","theme","jobKind","resource","layout","form","component","page","email","CREATION_UNIT_KINDS","aiCreationUnit","creation","slot","kind","label","name","CREATION_UNITS","unit","create","AI_SITE_NO_PLAN_COPY","AI_SITE_NO_PAGE_STEP_COPY","AI_SITE_UNIT_EMPTY_COPY","AI_SITE_MAX_PASSES","max","length","aiSiteJobUnits","plan","options","units","find","entry","push","screens","forEach","screen","index","title","welcomeEmail","aiSitePendingUnits","outputs","done","Map","output","set","get","filter","left","aiSiteSeoOutputs","job","some","id","proposal","inputs","hostId","aiSiteBuiltRefs","byResource","rows","built","row","shift","toLowerCase","aiSiteBuiltLayoutId","values","aiSiteResolvedRef","ref","slice","indexOf","trim","aiSiteBriefLines","brief","lines","site","businessName","businessType","audience","city","brand","Boolean","join","aiSiteEmailBriefLines","submissions","aiSiteUnitJobId","recorded","$id","aiSiteUnitJob","labels","unitPlan","reuse","placedByScreens","Set","flatMap","sections","section","uses","has","duplicateOf","why","purpose","template","record","dataset","map","slug","unitInputs","originJobId","pages","guided","formPage","steps","mode","emailType","aiSiteJobAdmission","context","status","error","freeTaste","aiSiteFreeTaste","org","shape","owner","orgId","aiSiteWelcomeEmail","aiRunJobUnit","input","outcome","runner","spent","usage","estCostUsd","model","stopReason","effort","refused","failure","review","continue","emptyCopy","AI_SITE_PAGE_NOT_WRITTEN_COPY","aiSitePageWritten","firestore","readNodes","stored","draftId","every","_","unitJobId","nodes","aiSiteLedgerUnits","creations","op","deps","aiSiteInitialLedger","pending","aiSiteOwedUnits","runnerFor","createAiJobSiteStep","publish","modelFor","refusal","ledgerUnits","starting","items","ledger","init","next","finish","sitePublish","now","catch","console","jobId","builtPages","extra","ids","one","othersOpen","listing","settle","item","degradation","degraded","degradedBy","note","notes","derived","handed","briefLines","ours","reason","message","stopped","written","undefined","runAiJobSiteStep","aiSiteJobRunMinimumMs","registerAiSiteJob","minimumMs","minimumMsFor"],"mappings":"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"}
@@ -15,7 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  import type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types';
18
- import type { AiJob, AiJobOutput, AiJobPlan, AiJobReview, AiJobSitePublish } from '../model/ai-jobs.types';
18
+ import type { AiJob, AiJobOutput, AiJobPlan, AiJobReview, AiJobItemFailure, AiJobItemLedger, AiJobItemStatus, AiJobSitePublish } from '../model/ai-jobs.types';
19
19
  import { type AiStepKind } from '../providers/catalog';
20
20
  import { type AiEffort, type AiSystemBlock } from '../runtime/ai-runtime';
21
21
  import type { AssistTokenUsage } from '../usage/assist-usage';
@@ -105,6 +105,27 @@ export interface AiJobStepOutcome {
105
105
  * writes the job document itself.
106
106
  */
107
107
  sitePublish?: AiJobSitePublish;
108
+ /**
109
+ * A `build` pass's unit and what it came to (AGL-3616), which the machine
110
+ * writes to the job's item ledger in the transaction that records the
111
+ * pass's spend. An item's failure is never the step's: it is a row, and
112
+ * the build goes on.
113
+ */
114
+ item?: AiJobItemOutcome;
115
+ /** A `build`'s ledger to start from, written by the machine in the same transaction (AGL-3616). */
116
+ items?: AiJobItemLedger[];
117
+ }
118
+ /** What one `build` pass came to for the unit it ran (AGL-3616). */
119
+ export interface AiJobItemOutcome {
120
+ slot: string;
121
+ /** `running` while the unit asks for another pass of its own. */
122
+ status: AiJobItemStatus;
123
+ failure?: AiJobItemFailure | null;
124
+ /** Customer-safe: what the person should know about the item. */
125
+ note?: string | null;
126
+ degradedBy?: string[];
127
+ /** The ids of the outputs this pass produced for the item. */
128
+ outputs?: string[];
108
129
  }
109
130
  export interface AiJobStepContext {
110
131
  job: AiJob;
@@ -18,6 +18,8 @@ import { AI_STEP_TIERS } from "../providers/catalog.js";
18
18
  import { AI_ROUTING_TABLE, aiModelForStep } from "../providers/routing.js";
19
19
  import { runAiRequest } from "../runtime/ai-runtime.js";
20
20
  import { aiJobStepBudget } from "./ai-job-budget.js";
21
+ import { aiOverlayCopyRequested } from "../model/ai-overlay-copy.js";
22
+ import { runAiOverlayCopy } from "./ai-job-overlay-copy.js";
21
23
  /**
22
24
  * The `text` step (AGL-2904): a brief in, a short piece of copy out.
23
25
  *
@@ -74,6 +76,16 @@ import { aiJobStepBudget } from "./ai-job-budget.js";
74
76
  export const runAiJobTextStep = async ({ job, signal, modelFor })=>{
75
77
  var _ref, _job_hostId;
76
78
  const model = (_ref = modelFor == null ? void 0 : modelFor('job.text')) != null ? _ref : aiJobTextModel();
79
+ // Overlay copy (AGL-3603): the same one request, answered as fields.
80
+ if (aiOverlayCopyRequested(job.inputs)) {
81
+ return runAiOverlayCopy(_extends({
82
+ job,
83
+ model,
84
+ maxTokens: AI_JOB_TEXT_STEP_BUDGET.maxTokens(model)
85
+ }, signal ? {
86
+ signal
87
+ } : {}));
88
+ }
77
89
  const result = await runAiRequest(_extends({
78
90
  model,
79
91
  system: AI_JOB_TEXT_SYSTEM,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-text-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 type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport type {\n AiJob,\n AiJobOutput,\n AiJobPlan,\n AiJobReview,\n AiJobSitePublish,\n} from '../model/ai-jobs.types'\nimport { AI_STEP_TIERS, type AiStepKind } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport { runAiRequest, type AiEffort, type AiSystemBlock } from '../runtime/ai-runtime'\nimport type { AssistTokenUsage } from '../usage/assist-usage'\nimport { aiJobStepBudget } from './ai-job-budget'\n\n/**\n * The `text` step (AGL-2904): a brief in, a short piece of copy out.\n *\n * The one step kind this issue ships for real, so the state machine — the\n * lease, the reservation, the record, the beat — is exercised end to end by\n * something that spends tokens, rather than by a stub that could pass with\n * the meter unplugged. Every other kind refuses until its own issue lands.\n *\n * The balanced tier with adaptive thinking: a brief is open-ended in a way the copy\n * assistant's element mode is not, and the cost is one call per job. The\n * static system block is cached; the brief rides in the user turn, so no\n * per-org byte sits inside the cached prefix (AGL-2352, enforced by the\n * runtime before the request leaves).\n */\n\n/** The model the text step runs on: the routing table's answer for `job.text`. */\nexport function aiJobTextModel(): string {\n return aiModelForStep('job.text')\n}\n\n/** The text step's answer ceiling, from the routing table. */\nexport const AI_JOB_TEXT_MAX_TOKENS = AI_ROUTING_TABLE['job.text'].maxTokens\n\n/**\n * The text step's time (AGL-3035): ONE request — no re-ask, and no site\n * inventory to look anything up in — at the routing table's ceiling on the\n * tier `job.text` is served from, with the step's reads and writes. That fits\n * an inline door's budget, so a door runs a text job where it is asked for,\n * and the least time is the helper's worst case rather than a figure anyone\n * chose; a slower tier asks less so its worst case still fits.\n */\nexport const AI_JOB_TEXT_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.text'],\n maxTokens: AI_JOB_TEXT_MAX_TOKENS,\n attempts: 1,\n lookups: 0,\n})\n\n/** The least time one text step needs before it starts. */\nexport const AI_JOB_TEXT_STEP_MINIMUM_MS = AI_JOB_TEXT_STEP_BUDGET.minimumMs\n\n/** How much of a brief is sent. Past this a brief is a document, not a brief. */\nexport const AI_JOB_BRIEF_MAX_CHARS = 4_000\n\n/**\n * What a step hands back to the machine. The machine meters `usage` at\n * `model`'s rates, records the step and appends the outputs; a step never\n * touches the job document or the meter itself.\n */\nexport interface AiJobStepOutcome {\n outputs: AiJobOutput[]\n /**\n * The step made progress and has more of the same work to do (AGL-2910):\n * a site audit works through its pages a batch at a time. The machine\n * records this pass — its spend and its outputs — and hands the SAME step\n * back to run again, so every pass is one reservation and one provider\n * exchange, and the job document says how far it got.\n */\n continue?: boolean\n usage: AssistTokenUsage\n estCostUsd: number\n model: string\n stopReason: string | null\n /**\n * The thinking effort the request asked for, recorded on the step\n * (AGL-2937); absent or `null` when the request named none.\n */\n effort?: AiEffort | null\n /** The model declined the brief (`stop_reason: 'refusal'`). Tokens were spent. */\n refused?: boolean\n /**\n * The model answered and nothing usable came of it — no structured answer\n * even after the step's own re-ask, say (AGL-2938). Tokens were spent, so\n * the machine meters the step before it fails the job with this sentence,\n * which is customer-safe and the only thing a customer reads.\n */\n failure?: string\n /** The plan the plan step proposed, which the machine keeps on the job (AGL-2935). */\n plan?: AiJobPlan\n /**\n * The step stopped for a person (AGL-2935). The machine records what it\n * spent and parks the job `needs_review`: a `plan` review completes the\n * step, so confirming runs the next one; a `doctrine` review hands the\n * step back, so trying again runs the same one.\n */\n review?: AiJobReview\n /**\n * The answer was refused by the platform's own checks, not the model's\n * (AGL-3594): the machine meters the step as any other and then gives the\n * job's credits back to the meters it was charged to (`refundJobCredits`),\n * a bounded number of times a day.\n */\n uncredited?: boolean\n /**\n * What a guided site start put live on its last pass (AGL-3596), kept on\n * the job by the machine in the write that records the pass: a step never\n * writes the job document itself.\n */\n sitePublish?: AiJobSitePublish\n}\n\nexport interface AiJobStepContext {\n job: AiJob\n stepIndex: number\n now: Date\n /** Aborts the provider call when the runner's budget ends. */\n signal?: AbortSignal\n /**\n * The Admin SDK handle the machine runs on, for a step that reads what it\n * builds from — a site's theme, say — or writes a draft. A step never\n * writes the job document, the meter or the lease through it.\n */\n firestore: FirebaseFirestore.Firestore\n /** The org document the machine read for the step's reservation. */\n org?: Partial<AglynOrgBilling> | null\n /**\n * The model a step of this kind runs on for THIS job (AGL-2942): the\n * creator's pick where the plan and the allotment allowlists allow it,\n * the routing table otherwise. Absent, a runner asks the table itself.\n */\n modelFor?: (kind: AiStepKind) => string | undefined\n}\n\nexport type AiJobStepRunner = (\n context: AiJobStepContext,\n) => Promise<AiJobStepOutcome>\n\n/**\n * The static block, byte-identical on every request so it caches. The\n * customer's brief and the org's inputs never enter it.\n */\nexport const AI_JOB_TEXT_SYSTEM: AiSystemBlock[] = [\n {\n text:\n 'You write short, publishable copy for a website builder. You are ' +\n 'given a brief from the person who owns the site. Answer with the ' +\n 'copy only: no preamble, no headings unless the brief asks for them, ' +\n 'no closing remarks, no markdown fences. Keep to the length the brief ' +\n 'implies; when it implies none, write under 150 words. Write in the ' +\n 'language of the brief. Never invent facts, prices, names or claims ' +\n 'the brief did not give you; where a detail is missing, leave a ' +\n 'clearly marked placeholder in square brackets.',\n cacheBreakpoint: true,\n },\n]\n\n/** The brief as the user turn, with the kind-specific inputs stated plainly. */\nexport function aiJobTextPrompt(job: Pick<AiJob, 'brief' | 'inputs'>): string {\n const brief = job.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)\n const tone = typeof job.inputs?.['tone'] === 'string' ? job.inputs['tone'] : ''\n const audience =\n typeof job.inputs?.['audience'] === 'string' ? job.inputs['audience'] : ''\n const lines = [`Brief: ${brief}`]\n if (tone) lines.push(`Tone: ${tone}`)\n if (audience) lines.push(`Audience: ${audience}`)\n return lines.join('\\n')\n}\n\nexport const runAiJobTextStep: AiJobStepRunner = async ({ job, signal, modelFor }) => {\n const model = modelFor?.('job.text') ?? aiJobTextModel()\n const result = await runAiRequest({\n model,\n system: AI_JOB_TEXT_SYSTEM,\n messages: [{ role: 'user', content: aiJobTextPrompt(job) }],\n // The routing ceiling, lowered on a tier too slow to answer it inside the\n // least time the step registered.\n maxTokens: AI_JOB_TEXT_STEP_BUDGET.maxTokens(model),\n ...(AI_ROUTING_TABLE['job.text'].thinking ? { thinking: AI_ROUTING_TABLE['job.text'].thinking } : {}),\n ...(AI_ROUTING_TABLE['job.text'].effort ? { effort: AI_ROUTING_TABLE['job.text'].effort } : {}),\n stream: false,\n ...(signal ? { signal } : {}),\n })\n if (result.kind === 'refusal') {\n return {\n outputs: [],\n usage: result.usage,\n estCostUsd: result.estCostUsd,\n model,\n stopReason: result.stopReason,\n refused: true,\n }\n }\n const text = result.text.trim()\n return {\n outputs: text\n ? [\n {\n resource: 'text',\n // A text output has no document of its own; the id names the\n // step within the job so a later step can address it.\n id: 'draft',\n hostId: job.hostId ?? null,\n label: 'Draft copy',\n text,\n },\n ]\n : [],\n usage: result.usage,\n estCostUsd: result.estCostUsd,\n model,\n stopReason: result.stopReason,\n }\n}\n"],"names":["AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","runAiRequest","aiJobStepBudget","aiJobTextModel","AI_JOB_TEXT_MAX_TOKENS","maxTokens","AI_JOB_TEXT_STEP_BUDGET","tier","attempts","lookups","AI_JOB_TEXT_STEP_MINIMUM_MS","minimumMs","AI_JOB_BRIEF_MAX_CHARS","AI_JOB_TEXT_SYSTEM","text","cacheBreakpoint","aiJobTextPrompt","job","brief","slice","tone","inputs","audience","lines","push","join","runAiJobTextStep","signal","modelFor","model","result","system","messages","role","content","thinking","effort","stream","kind","outputs","usage","estCostUsd","stopReason","refused","trim","resource","id","hostId","label"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-text-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 type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport type {\n AiJob,\n AiJobOutput,\n AiJobPlan,\n AiJobReview,\n AiJobItemFailure,\n AiJobItemLedger,\n AiJobItemStatus,\n AiJobSitePublish,\n} from '../model/ai-jobs.types'\nimport { AI_STEP_TIERS, type AiStepKind } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport { runAiRequest, type AiEffort, type AiSystemBlock } from '../runtime/ai-runtime'\nimport type { AssistTokenUsage } from '../usage/assist-usage'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiOverlayCopyRequested } from '../model/ai-overlay-copy'\nimport { runAiOverlayCopy } from './ai-job-overlay-copy'\n\n/**\n * The `text` step (AGL-2904): a brief in, a short piece of copy out.\n *\n * The one step kind this issue ships for real, so the state machine — the\n * lease, the reservation, the record, the beat — is exercised end to end by\n * something that spends tokens, rather than by a stub that could pass with\n * the meter unplugged. Every other kind refuses until its own issue lands.\n *\n * The balanced tier with adaptive thinking: a brief is open-ended in a way the copy\n * assistant's element mode is not, and the cost is one call per job. The\n * static system block is cached; the brief rides in the user turn, so no\n * per-org byte sits inside the cached prefix (AGL-2352, enforced by the\n * runtime before the request leaves).\n */\n\n/** The model the text step runs on: the routing table's answer for `job.text`. */\nexport function aiJobTextModel(): string {\n return aiModelForStep('job.text')\n}\n\n/** The text step's answer ceiling, from the routing table. */\nexport const AI_JOB_TEXT_MAX_TOKENS = AI_ROUTING_TABLE['job.text'].maxTokens\n\n/**\n * The text step's time (AGL-3035): ONE request — no re-ask, and no site\n * inventory to look anything up in — at the routing table's ceiling on the\n * tier `job.text` is served from, with the step's reads and writes. That fits\n * an inline door's budget, so a door runs a text job where it is asked for,\n * and the least time is the helper's worst case rather than a figure anyone\n * chose; a slower tier asks less so its worst case still fits.\n */\nexport const AI_JOB_TEXT_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.text'],\n maxTokens: AI_JOB_TEXT_MAX_TOKENS,\n attempts: 1,\n lookups: 0,\n})\n\n/** The least time one text step needs before it starts. */\nexport const AI_JOB_TEXT_STEP_MINIMUM_MS = AI_JOB_TEXT_STEP_BUDGET.minimumMs\n\n/** How much of a brief is sent. Past this a brief is a document, not a brief. */\nexport const AI_JOB_BRIEF_MAX_CHARS = 4_000\n\n/**\n * What a step hands back to the machine. The machine meters `usage` at\n * `model`'s rates, records the step and appends the outputs; a step never\n * touches the job document or the meter itself.\n */\nexport interface AiJobStepOutcome {\n outputs: AiJobOutput[]\n /**\n * The step made progress and has more of the same work to do (AGL-2910):\n * a site audit works through its pages a batch at a time. The machine\n * records this pass — its spend and its outputs — and hands the SAME step\n * back to run again, so every pass is one reservation and one provider\n * exchange, and the job document says how far it got.\n */\n continue?: boolean\n usage: AssistTokenUsage\n estCostUsd: number\n model: string\n stopReason: string | null\n /**\n * The thinking effort the request asked for, recorded on the step\n * (AGL-2937); absent or `null` when the request named none.\n */\n effort?: AiEffort | null\n /** The model declined the brief (`stop_reason: 'refusal'`). Tokens were spent. */\n refused?: boolean\n /**\n * The model answered and nothing usable came of it — no structured answer\n * even after the step's own re-ask, say (AGL-2938). Tokens were spent, so\n * the machine meters the step before it fails the job with this sentence,\n * which is customer-safe and the only thing a customer reads.\n */\n failure?: string\n /** The plan the plan step proposed, which the machine keeps on the job (AGL-2935). */\n plan?: AiJobPlan\n /**\n * The step stopped for a person (AGL-2935). The machine records what it\n * spent and parks the job `needs_review`: a `plan` review completes the\n * step, so confirming runs the next one; a `doctrine` review hands the\n * step back, so trying again runs the same one.\n */\n review?: AiJobReview\n /**\n * The answer was refused by the platform's own checks, not the model's\n * (AGL-3594): the machine meters the step as any other and then gives the\n * job's credits back to the meters it was charged to (`refundJobCredits`),\n * a bounded number of times a day.\n */\n uncredited?: boolean\n /**\n * What a guided site start put live on its last pass (AGL-3596), kept on\n * the job by the machine in the write that records the pass: a step never\n * writes the job document itself.\n */\n sitePublish?: AiJobSitePublish\n /**\n * A `build` pass's unit and what it came to (AGL-3616), which the machine\n * writes to the job's item ledger in the transaction that records the\n * pass's spend. An item's failure is never the step's: it is a row, and\n * the build goes on.\n */\n item?: AiJobItemOutcome\n /** A `build`'s ledger to start from, written by the machine in the same transaction (AGL-3616). */\n items?: AiJobItemLedger[]\n}\n\n/** What one `build` pass came to for the unit it ran (AGL-3616). */\nexport interface AiJobItemOutcome {\n slot: string\n /** `running` while the unit asks for another pass of its own. */\n status: AiJobItemStatus\n failure?: AiJobItemFailure | null\n /** Customer-safe: what the person should know about the item. */\n note?: string | null\n degradedBy?: string[]\n /** The ids of the outputs this pass produced for the item. */\n outputs?: string[]\n}\n\nexport interface AiJobStepContext {\n job: AiJob\n stepIndex: number\n now: Date\n /** Aborts the provider call when the runner's budget ends. */\n signal?: AbortSignal\n /**\n * The Admin SDK handle the machine runs on, for a step that reads what it\n * builds from — a site's theme, say — or writes a draft. A step never\n * writes the job document, the meter or the lease through it.\n */\n firestore: FirebaseFirestore.Firestore\n /** The org document the machine read for the step's reservation. */\n org?: Partial<AglynOrgBilling> | null\n /**\n * The model a step of this kind runs on for THIS job (AGL-2942): the\n * creator's pick where the plan and the allotment allowlists allow it,\n * the routing table otherwise. Absent, a runner asks the table itself.\n */\n modelFor?: (kind: AiStepKind) => string | undefined\n}\n\nexport type AiJobStepRunner = (\n context: AiJobStepContext,\n) => Promise<AiJobStepOutcome>\n\n/**\n * The static block, byte-identical on every request so it caches. The\n * customer's brief and the org's inputs never enter it.\n */\nexport const AI_JOB_TEXT_SYSTEM: AiSystemBlock[] = [\n {\n text:\n 'You write short, publishable copy for a website builder. You are ' +\n 'given a brief from the person who owns the site. Answer with the ' +\n 'copy only: no preamble, no headings unless the brief asks for them, ' +\n 'no closing remarks, no markdown fences. Keep to the length the brief ' +\n 'implies; when it implies none, write under 150 words. Write in the ' +\n 'language of the brief. Never invent facts, prices, names or claims ' +\n 'the brief did not give you; where a detail is missing, leave a ' +\n 'clearly marked placeholder in square brackets.',\n cacheBreakpoint: true,\n },\n]\n\n/** The brief as the user turn, with the kind-specific inputs stated plainly. */\nexport function aiJobTextPrompt(job: Pick<AiJob, 'brief' | 'inputs'>): string {\n const brief = job.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)\n const tone = typeof job.inputs?.['tone'] === 'string' ? job.inputs['tone'] : ''\n const audience =\n typeof job.inputs?.['audience'] === 'string' ? job.inputs['audience'] : ''\n const lines = [`Brief: ${brief}`]\n if (tone) lines.push(`Tone: ${tone}`)\n if (audience) lines.push(`Audience: ${audience}`)\n return lines.join('\\n')\n}\n\nexport const runAiJobTextStep: AiJobStepRunner = async ({ job, signal, modelFor }) => {\n const model = modelFor?.('job.text') ?? aiJobTextModel()\n // Overlay copy (AGL-3603): the same one request, answered as fields.\n if (aiOverlayCopyRequested(job.inputs)) {\n return runAiOverlayCopy({\n job,\n model,\n maxTokens: AI_JOB_TEXT_STEP_BUDGET.maxTokens(model),\n ...(signal ? { signal } : {}),\n })\n }\n const result = await runAiRequest({\n model,\n system: AI_JOB_TEXT_SYSTEM,\n messages: [{ role: 'user', content: aiJobTextPrompt(job) }],\n // The routing ceiling, lowered on a tier too slow to answer it inside the\n // least time the step registered.\n maxTokens: AI_JOB_TEXT_STEP_BUDGET.maxTokens(model),\n ...(AI_ROUTING_TABLE['job.text'].thinking ? { thinking: AI_ROUTING_TABLE['job.text'].thinking } : {}),\n ...(AI_ROUTING_TABLE['job.text'].effort ? { effort: AI_ROUTING_TABLE['job.text'].effort } : {}),\n stream: false,\n ...(signal ? { signal } : {}),\n })\n if (result.kind === 'refusal') {\n return {\n outputs: [],\n usage: result.usage,\n estCostUsd: result.estCostUsd,\n model,\n stopReason: result.stopReason,\n refused: true,\n }\n }\n const text = result.text.trim()\n return {\n outputs: text\n ? [\n {\n resource: 'text',\n // A text output has no document of its own; the id names the\n // step within the job so a later step can address it.\n id: 'draft',\n hostId: job.hostId ?? null,\n label: 'Draft copy',\n text,\n },\n ]\n : [],\n usage: result.usage,\n estCostUsd: result.estCostUsd,\n model,\n stopReason: result.stopReason,\n }\n}\n"],"names":["AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","runAiRequest","aiJobStepBudget","aiOverlayCopyRequested","runAiOverlayCopy","aiJobTextModel","AI_JOB_TEXT_MAX_TOKENS","maxTokens","AI_JOB_TEXT_STEP_BUDGET","tier","attempts","lookups","AI_JOB_TEXT_STEP_MINIMUM_MS","minimumMs","AI_JOB_BRIEF_MAX_CHARS","AI_JOB_TEXT_SYSTEM","text","cacheBreakpoint","aiJobTextPrompt","job","brief","slice","tone","inputs","audience","lines","push","join","runAiJobTextStep","signal","modelFor","model","result","system","messages","role","content","thinking","effort","stream","kind","outputs","usage","estCostUsd","stopReason","refused","trim","resource","id","hostId","label"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAaD,SAASA,aAAa,QAAyB,0BAAsB;AACrE,SAASC,gBAAgB,EAAEC,cAAc,QAAQ,0BAAsB;AACvE,SAASC,YAAY,QAA2C,2BAAuB;AAEvF,SAASC,eAAe,QAAQ,qBAAiB;AACjD,SAASC,sBAAsB,QAAQ,8BAA0B;AACjE,SAASC,gBAAgB,QAAQ,2BAAuB;AAExD;;;;;;;;;;;;;CAaC,GAED,gFAAgF,GAChF,OAAO,SAASC;IACd,OAAOL,eAAe;AACxB;AAEA,4DAA4D,GAC5D,OAAO,MAAMM,yBAAyBP,gBAAgB,CAAC,WAAW,CAACQ,SAAS,CAAA;AAE5E;;;;;;;CAOC,GACD,OAAO,MAAMC,0BAA0BN,gBAAgB;IACrDO,MAAMX,aAAa,CAAC,WAAW;IAC/BS,WAAWD;IACXI,UAAU;IACVC,SAAS;AACX,GAAE;AAEF,yDAAyD,GACzD,OAAO,MAAMC,8BAA8BJ,wBAAwBK,SAAS,CAAA;AAE5E,+EAA+E,GAC/E,OAAO,MAAMC,yBAAyB,KAAK;AA2G3C;;;CAGC,GACD,OAAO,MAAMC,qBAAsC;IACjD;QACEC,MACE,sEACA,sEACA,yEACA,0EACA,wEACA,wEACA,oEACA;QACFC,iBAAiB;IACnB;CACD,CAAA;AAED,8EAA8E,GAC9E,OAAO,SAASC,gBAAgBC,GAAoC;QAE9CA,aAEXA;IAHT,MAAMC,QAAQD,IAAIC,KAAK,CAACC,KAAK,CAAC,GAAGP;IACjC,MAAMQ,OAAO,SAAOH,cAAAA,IAAII,MAAM,qBAAVJ,WAAY,CAAC,OAAO,MAAK,WAAWA,IAAII,MAAM,CAAC,OAAO,GAAG;IAC7E,MAAMC,WACJ,SAAOL,eAAAA,IAAII,MAAM,qBAAVJ,YAAY,CAAC,WAAW,MAAK,WAAWA,IAAII,MAAM,CAAC,WAAW,GAAG;IAC1E,MAAME,QAAQ;QAAC,CAAC,OAAO,EAAEL,OAAO;KAAC;IACjC,IAAIE,MAAMG,MAAMC,IAAI,CAAC,CAAC,MAAM,EAAEJ,MAAM;IACpC,IAAIE,UAAUC,MAAMC,IAAI,CAAC,CAAC,UAAU,EAAEF,UAAU;IAChD,OAAOC,MAAME,IAAI,CAAC;AACpB;AAEA,OAAO,MAAMC,mBAAoC,OAAO,EAAET,GAAG,EAAEU,MAAM,EAAEC,QAAQ,EAAE;cA0C7DX;IAzClB,MAAMY,gBAAQD,4BAAAA,SAAW,8BAAezB;IACxC,qEAAqE;IACrE,IAAIF,uBAAuBgB,IAAII,MAAM,GAAG;QACtC,OAAOnB,iBAAiB;YACtBe;YACAY;YACAxB,WAAWC,wBAAwBD,SAAS,CAACwB;WACzCF,SAAS;YAAEA;QAAO,IAAI,CAAC;IAE/B;IACA,MAAMG,SAAS,MAAM/B,aAAa;QAChC8B;QACAE,QAAQlB;QACRmB,UAAU;YAAC;gBAAEC,MAAM;gBAAQC,SAASlB,gBAAgBC;YAAK;SAAE;QAC3D,0EAA0E;QAC1E,kCAAkC;QAClCZ,WAAWC,wBAAwBD,SAAS,CAACwB;OACzChC,gBAAgB,CAAC,WAAW,CAACsC,QAAQ,GAAG;QAAEA,UAAUtC,gBAAgB,CAAC,WAAW,CAACsC,QAAQ;IAAC,IAAI,CAAC,GAC/FtC,gBAAgB,CAAC,WAAW,CAACuC,MAAM,GAAG;QAAEA,QAAQvC,gBAAgB,CAAC,WAAW,CAACuC,MAAM;IAAC,IAAI,CAAC;QAC7FC,QAAQ;OACJV,SAAS;QAAEA;IAAO,IAAI,CAAC;IAE7B,IAAIG,OAAOQ,IAAI,KAAK,WAAW;QAC7B,OAAO;YACLC,SAAS,EAAE;YACXC,OAAOV,OAAOU,KAAK;YACnBC,YAAYX,OAAOW,UAAU;YAC7BZ;YACAa,YAAYZ,OAAOY,UAAU;YAC7BC,SAAS;QACX;IACF;IACA,MAAM7B,OAAOgB,OAAOhB,IAAI,CAAC8B,IAAI;IAC7B,OAAO;QACLL,SAASzB,OACL;YACE;gBACE+B,UAAU;gBACV,6DAA6D;gBAC7D,sDAAsD;gBACtDC,IAAI;gBACJC,MAAM,GAAE9B,cAAAA,IAAI8B,MAAM,YAAV9B,cAAc;gBACtB+B,OAAO;gBACPlC;YACF;SACD,GACD,EAAE;QACN0B,OAAOV,OAAOU,KAAK;QACnBC,YAAYX,OAAOW,UAAU;QAC7BZ;QACAa,YAAYZ,OAAOY,UAAU;IAC/B;AACF,EAAC"}
@@ -14,15 +14,16 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- import { type AiAutomationRecords } from '../model/ai-automation-draft';
18
- import { type AiAutomationCapabilities } from '../model/ai-workflow-job';
17
+ import { type AiAutomationDraft, type AiAutomationRecords } from '../model/ai-automation-draft';
18
+ import type { AiJobOutput } from '../model/ai-jobs.types';
19
+ import { type AiAutomationCapabilities, type AiOrgAutomationVocabulary } from '../model/ai-workflow-job';
19
20
  import { type AiCustomGenerationInput } from '../runtime/ai-doctrine';
20
21
  import type { AiSystemBlock } from '../runtime/ai-runtime';
21
22
  import { type AiAutomationAnswer, type AiWorkflowExplanation } from '../tools/ai-workflow-tool';
22
23
  import { type AiJobAdmission } from './ai-job-admission';
23
24
  import { type AiPluginDraftWriterLookup } from './ai-job-plugin-drafts';
24
25
  import { type AiJobStepRunner } from './ai-job-text-step';
25
- import { readAiAutomationRecords, readAiWorkflowFunctions, readAiWorkflowRun, readAiWorkflowTarget } from './ai-workflow-records';
26
+ import { readAiAutomationRecords, readAiOrgAutomationRecords, readAiWorkflowFunctions, readAiWorkflowRun, readAiWorkflowTarget } from './ai-workflow-records';
26
27
  /**
27
28
  * The `workflow` step (AGL-2919): an automation drafted from a description,
28
29
  * or a saved automation explained.
@@ -55,6 +56,29 @@ import { readAiAutomationRecords, readAiWorkflowFunctions, readAiWorkflowRun, re
55
56
  * never the stored documents, never an email address, never a run's event
56
57
  * payload. It answers through `submit_explanation`, which becomes a `text`
57
58
  * output. Nothing is changed.
59
+ *
60
+ * ## Revising (AGL-3603)
61
+ *
62
+ * A saved ACTION and a change asked of it. The model is sent the drafting
63
+ * request — the same cached vocabulary, the same tool, the same reach, forms
64
+ * and datasets — with the action's outline in the user turn and the change as
65
+ * the brief, and answers the whole action as it would be after the change.
66
+ * That answer is held to the vocabulary and matched to the site's records as
67
+ * a draft is, and written as a NEW draft, OFF, under the job's draft id: the
68
+ * saved action is read and never written. What changed is worked out in code
69
+ * from the two stored shapes. An action holding what the vocabulary cannot
70
+ * write is refused before the model is asked, so a revision never drops an
71
+ * on-page step the person did not ask to lose.
72
+ *
73
+ * ## A workspace's automation (AGL-3603)
74
+ *
75
+ * A draft with `scope: 'org'` names no site. It is asked as a draft is — the
76
+ * same cached vocabulary, the same tool — with the org automation's narrower
77
+ * vocabulary stated in the user turn and held by the check, and matched
78
+ * against the workspace's lists, campaigns, datasets and stages. Nothing is
79
+ * written: the automation rides on an `orgAutomation` output, which the Org
80
+ * automations editor opens unsaved, where its sites are picked and its save
81
+ * route — the workflows plugin's — holds it to that plugin's rules.
58
82
  */
59
83
  /** The longest an explanation or a draft's generation may take reading the site first. */
60
84
  export declare const AI_WORKFLOW_RECORDS_READ_MS = 4000;
@@ -82,11 +106,21 @@ export declare const AI_WORKFLOW_DATASETS_LISTED = 40;
82
106
  export declare const AI_JOB_WORKFLOW_DRAFT_INSTRUCTIONS: readonly AiSystemBlock[];
83
107
  /** The explaining instructions, byte-identical on every request so they cache. */
84
108
  export declare const AI_JOB_WORKFLOW_EXPLAIN_INSTRUCTIONS: readonly AiSystemBlock[];
85
- /** What a draft is asked from: the workspace's reach, the site's forms and datasets, and the brief. */
109
+ /**
110
+ * What a draft is asked from: the workspace's reach, the site's forms and
111
+ * datasets, and the brief. A revision adds the saved action's outline, and its
112
+ * brief is the change asked for.
113
+ */
86
114
  export declare function aiJobWorkflowDraftPrompt(input: {
87
115
  brief: string;
88
116
  capabilities: AiAutomationCapabilities;
89
117
  records: Pick<AiAutomationRecords, 'forms' | 'datasets'>;
118
+ /** The saved action a revision starts from, as its outline. */
119
+ revising?: {
120
+ outline: string;
121
+ } | null;
122
+ /** A workspace's automation (AGL-3603): what it may start on and do. */
123
+ org?: AiOrgAutomationVocabulary | null;
90
124
  }): string;
91
125
  /** What an explanation is asked from: the mode, the outlines, and the member's question. */
92
126
  export declare function aiJobWorkflowExplainPrompt(input: {
@@ -100,6 +134,10 @@ export declare function aiJobWorkflowDraftGeneration(request: {
100
134
  brief: string;
101
135
  capabilities: AiAutomationCapabilities;
102
136
  records: Pick<AiAutomationRecords, 'forms' | 'datasets'>;
137
+ revising?: {
138
+ outline: string;
139
+ } | null;
140
+ org?: AiOrgAutomationVocabulary | null;
103
141
  model: string;
104
142
  signal?: AbortSignal;
105
143
  }): AiCustomGenerationInput<AiAutomationAnswer>;
@@ -116,11 +154,23 @@ export interface AiJobWorkflowStepDeps {
116
154
  /** How the automation draft writer is found; the core's registry otherwise. */
117
155
  writerFor?: AiPluginDraftWriterLookup;
118
156
  readRecords?: typeof readAiAutomationRecords;
157
+ readOrgRecords?: typeof readAiOrgAutomationRecords;
119
158
  readTarget?: typeof readAiWorkflowTarget;
120
159
  readRun?: typeof readAiWorkflowRun;
121
160
  readFunctions?: typeof readAiWorkflowFunctions;
122
161
  }
123
162
  export declare function createAiJobWorkflowStep(deps?: AiJobWorkflowStepDeps): AiJobStepRunner;
163
+ /** What a person is told when a workspace's automation would need what only one site runs. */
164
+ export declare const AI_ORG_AUTOMATION_UNSUPPORTED_COPY = "An org automation cannot do what this description asks \u2014 it starts only on what the server sees and uses only the steps every site runs the same way \u2014 so nothing was drafted. Build it as an action on the site instead.";
165
+ /**
166
+ * A workspace's drafted automation as the output that proposes it
167
+ * (AGL-3603): the stored shape the Org automations editor opens — its name,
168
+ * trigger and steps, never a placement, which the person picks — and what
169
+ * to fill in before switching it on.
170
+ */
171
+ export declare function orgProposalOutput(draft: AiAutomationDraft, notes: readonly string[]): AiJobOutput;
172
+ /** The name a revised copy is saved under when the change asked for no other. */
173
+ export declare function aiRevisedName(name: string): string;
124
174
  export declare const runAiJobWorkflowStep: AiJobStepRunner;
125
175
  export interface AiWorkflowJobAdmissionDeps {
126
176
  readTarget?: typeof readAiWorkflowTarget;