@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/model/ai-site-build-progress.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 { aiJobKindNoun } from './ai-job-activity'\nimport { aiJobRefundCopy } from './ai-job-failure-copy'\nimport type { AiJobSummary } from './ai-jobs.types'\n\n/**\n * What the \"Building your site\" page shows of a site job (AGL-3594): one row\n * a stage — planning the pages, then each page — with its state, the state\n * the whole job is in, and the job's one price.\n *\n * Read off the job summary every surface already follows: the plan step's\n * status, the plan's pages, and the pages the build has reported as outputs.\n * A scaffold builds its units in plan order and reports each page when it is\n * written, so the pages written so far are the first pages of the plan.\n *\n * Pure: no React, no request. The page and its spec read the same rows.\n */\n\nexport type AiSiteBuildRowState = 'done' | 'active' | 'waiting' | 'failed'\n\nexport interface AiSiteBuildRow {\n id: string\n label: string\n state: AiSiteBuildRowState\n}\n\n/** Where the whole job stands, as the page's header and actions read it. */\nexport type AiSiteBuildPhase = 'working' | 'done' | 'stopped' | 'failed' | 'canceled'\n\nexport function aiSiteBuildPhase(job: Pick<AiJobSummary, 'status'>): AiSiteBuildPhase {\n switch (job.status) {\n case 'done':\n return 'done'\n case 'failed':\n return 'failed'\n case 'canceled':\n return 'canceled'\n case 'needs_review':\n case 'needs_input':\n return 'stopped'\n default:\n return 'working'\n }\n}\n\n/** The creations a scaffold builds before its pages, in its build order, and what each row says. */\nconst CREATION_ROWS: ReadonlyArray<{ kind: string; resource: string; label: (name: string) => string }> = [\n { kind: 'theme-change', resource: 'theme', label: () => 'Choosing your colors' },\n { kind: 'layout', resource: 'layout', label: (name) => `Building the header and footer: ${name}` },\n { kind: 'form', resource: 'form', label: (name) => `Building the form: ${name}` },\n]\n\n/**\n * One row a stage: planning, then each creation the plan builds before its\n * pages — the colors, the layout, the form — then each page (AGL-3596). A\n * scaffold builds them in that order and reports each as an output when it\n * is written, so the first row without its output is where the build is, and\n * where a failure stopped it: a layout that broke is the layout's row, never\n * the first page's.\n */\nexport function aiSiteBuildRows(\n job: Pick<AiJobSummary, 'status' | 'steps' | 'plan' | 'outputs' | 'review'>,\n): AiSiteBuildRow[] {\n const phase = aiSiteBuildPhase(job)\n const planStep = job.steps.find((step) => step.name === 'plan')\n const planDone = planStep?.status === 'done' || Boolean(job.plan)\n const stopped = phase === 'failed' || phase === 'stopped'\n const rows: AiSiteBuildRow[] = [\n {\n id: 'plan',\n label: 'Planning your pages',\n state: planDone ? 'done' : stopped ? 'failed' : phase === 'working' ? 'active' : 'waiting',\n },\n ]\n const built = new Map<string, number>()\n for (const output of job.outputs) built.set(output.resource, (built.get(output.resource) ?? 0) + 1)\n const take = (resource: string): boolean => {\n const left = built.get(resource) ?? 0\n if (left <= 0) return false\n built.set(resource, left - 1)\n return true\n }\n const stages: Array<{ id: string; label: string; resource: string }> = []\n for (const row of CREATION_ROWS) {\n const creation = job.plan?.create.find((entry) => entry.kind === row.kind)\n if (creation) stages.push({ id: row.resource, label: row.label(creation.name), resource: row.resource })\n }\n const pages = job.plan?.screens ?? []\n pages.forEach((page, index) => {\n stages.push({ id: `page-${index}`, label: `Writing page ${index + 1} of ${pages.length}: ${page.title}`, resource: 'screen' })\n })\n let reached = false\n for (const stage of stages) {\n const done = phase === 'done' || take(stage.resource)\n const current = !done && !reached && planDone\n if (current) reached = true\n rows.push({\n id: stage.id,\n label: stage.label,\n state: done ? 'done' : current && stopped ? 'failed' : current && phase === 'working' ? 'active' : 'waiting',\n })\n }\n return rows\n}\n\n/**\n * The job's one price (AGL-3594), never a plan's on its own: what it has used\n * so far while it works, what it used once done, and, on a failure we gave\n * back, that nothing was charged.\n */\nexport function aiSiteBuildCreditsLine(\n job: Pick<AiJobSummary, 'status' | 'creditsSpent' | 'refundedCredits'> & Partial<Pick<AiJobSummary, 'kind'>>,\n): string | null {\n const spent = Math.max(0, job.creditsSpent ?? 0)\n const refunded = Math.max(0, job.refundedCredits ?? 0)\n const net = Math.max(0, spent - refunded)\n const phase = aiSiteBuildPhase(job)\n // What the job made, by its kind; a job that names none is a site's.\n const subject = !job.kind || job.kind === 'site' ? 'This site' : aiJobKindNoun(job.kind) === 'AI job' ? 'This AI job' : `This ${aiJobKindNoun(job.kind)}`\n // A failure on our side says so in the words every surface uses (AGL-3596),\n // from the job's recorded give-back.\n const refund = phase === 'working' || phase === 'done' ? null : aiJobRefundCopy(job)\n if (refund && refunded > 0) return refund\n if (phase === 'done') return `${subject} used ${net} credits.`\n if (phase === 'working') return `Credits used so far: ${net}`\n if (refund) return `${subject} used ${net} credits. ${refund}`\n return net > 0 ? `${subject} used ${net} credits.` : null\n}\n\n/** The heading and the sentence under it, for one job's page in its current phase. */\nexport interface AiJobPageCopy {\n heading: string\n lede: string\n}\n\n/**\n * What one job's page says at the top (AGL-3594, AGL-3596). A site job reads\n * as \"Building your site\"; any other kind names what it makes — \"Building\n * your page\" — or, for a kind with no noun, the job itself.\n *\n * A guided site start publishes its pages when it finishes, and says so\n * (`sitePublish`): with a page live the heading is \"Your site is live\". A\n * done site job without that record — an older job, or one that was not a\n * guided start — built drafts, and says that instead.\n */\nexport function aiJobPageCopy(\n job: Pick<AiJobSummary, 'kind' | 'status' | 'review' | 'error'> & Partial<Pick<AiJobSummary, 'sitePublish'>>,\n brand: string,\n): AiJobPageCopy {\n const phase = aiSiteBuildPhase(job)\n const noun = job.kind === 'site' ? 'site' : aiJobKindNoun(job.kind)\n const generic = noun === 'AI job'\n // `products` is the one plural noun.\n const isAre = noun.endsWith('s') ? 'are' : 'is'\n const why = job.review?.message ?? job.error ?? null\n if (phase === 'working') {\n return {\n heading: generic ? 'Your AI job is running' : `Building your ${noun}`,\n lede:\n job.kind === 'site'\n ? `${brand} AI is planning your pages and writing each one. You can leave this page; it keeps going.`\n : `${brand} AI is working on it. You can leave this page; it keeps going.`,\n }\n }\n if (phase === 'done') {\n const published = job.sitePublish?.published.length ?? 0\n if (job.kind === 'site' && published > 0) {\n return {\n heading: 'Your site is live',\n lede: job.sitePublish?.drafts.length\n ? 'Your pages are published, except the ones listed below, which stayed drafts.'\n : 'Your pages are published, and anyone can visit your site now.',\n }\n }\n if (job.kind === 'site' && job.sitePublish) {\n return {\n heading: 'Your site is ready',\n lede: 'None of your new pages could be published, so they are drafts. The reasons are below.',\n }\n }\n return {\n heading: generic ? 'Your AI job finished' : `Your ${noun} ${isAre} ready`,\n lede:\n job.kind === 'site'\n ? 'Your new pages are drafts. Publish them when you’re happy.'\n : 'Everything it built is an unpublished draft until you publish it.',\n }\n }\n if (phase === 'canceled') {\n return { heading: generic ? 'Your AI job was canceled' : `Your ${noun} was not built`, lede: 'The job was canceled.' }\n }\n return {\n heading: generic ? 'Your AI job stopped' : `Your ${noun} ${noun.endsWith('s') ? 'were' : 'was'} not built`,\n lede: why ?? (generic ? 'Something went wrong running this job.' : `Something went wrong building your ${noun}.`),\n }\n}\n"],"names":["aiJobKindNoun","aiJobRefundCopy","aiSiteBuildPhase","job","status","CREATION_ROWS","kind","resource","label","name","aiSiteBuildRows","built","phase","planStep","steps","find","step","planDone","Boolean","plan","stopped","rows","id","state","Map","output","outputs","set","get","take","left","stages","row","creation","create","entry","push","pages","screens","forEach","page","index","length","title","reached","stage","done","current","aiSiteBuildCreditsLine","spent","Math","max","creditsSpent","refunded","refundedCredits","net","subject","refund","aiJobPageCopy","brand","noun","generic","isAre","endsWith","why","review","message","error","heading","lede","published","sitePublish","drafts"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-build-progress.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 { aiJobConfirmingOwnPlan, aiJobKindNoun } from './ai-job-activity'\nimport { aiJobRefundCopy } from './ai-job-failure-copy'\nimport { aiBuildItemRows, aiBuildOutcomeLine, aiSitePartialCopy } from './ai-build-progress'\nimport type { AiJobSummary } from './ai-jobs.types'\n\n/**\n * What the \"Building your site\" page shows of a site job (AGL-3594): one row\n * a stage — planning the pages, then each page — with its state, the state\n * the whole job is in, and the job's one price.\n *\n * Read off the job summary every surface already follows: the plan step's\n * status, the plan's pages, and the pages the build has reported as outputs.\n * A scaffold builds its units in plan order and reports each page when it is\n * written, so the pages written so far are the first pages of the plan.\n *\n * Pure: no React, no request. The page and its spec read the same rows.\n */\n\nexport type AiSiteBuildRowState = 'done' | 'active' | 'waiting' | 'failed' | 'skipped'\n\nexport interface AiSiteBuildRow {\n id: string\n label: string\n state: AiSiteBuildRowState\n /** What the person should know about this row (AGL-3616): a build item's failure, note or refund. */\n detail?: string | null\n /** What the row is doing while it is the active one, in a sentence. */\n hint?: string | null\n /** A page's planned sections, in order, so the person sees what is being written. */\n sections?: string[]\n /** The credits this row's work used, once it has finished; absent while it runs. */\n credits?: number\n /** When this row's work started, for the elapsed time beside the active row. */\n startedAt?: string | null\n}\n\n/** Where the whole job stands, as the page's header and actions read it. */\nexport type AiSiteBuildPhase = 'working' | 'done' | 'stopped' | 'failed' | 'canceled'\n\nexport function aiSiteBuildPhase(\n job: Pick<AiJobSummary, 'status'> & Partial<Pick<AiJobSummary, 'review' | 'autoConfirm' | 'updatedAt'>>,\n): AiSiteBuildPhase {\n // A guided start's plan parked for the instant its confirmation takes is\n // the build going on (AGL-3596), never a stop.\n if (aiJobConfirmingOwnPlan(job)) return 'working'\n switch (job.status) {\n case 'done':\n return 'done'\n case 'failed':\n return 'failed'\n case 'canceled':\n return 'canceled'\n case 'needs_review':\n case 'needs_input':\n return 'stopped'\n default:\n return 'working'\n }\n}\n\n/** The creations a scaffold builds before its pages, in its build order, and what each row says. */\nconst CREATION_ROWS: ReadonlyArray<{ kind: string; resource: string; label: (name: string) => string }> = [\n { kind: 'theme-change', resource: 'theme', label: () => 'Choosing your colors' },\n { kind: 'layout', resource: 'layout', label: (name) => `Building the header and footer: ${name}` },\n { kind: 'form', resource: 'form', label: (name) => `Building the form: ${name}` },\n]\n\n/**\n * One row a stage: planning, then each creation the plan builds before its\n * pages — the colors, the layout, the form — then each page (AGL-3596). A\n * scaffold builds them in that order and reports each as an output when it\n * is written, so the first row without its output is where the build is, and\n * where a failure stopped it: a layout that broke is the layout's row, never\n * the first page's.\n */\nexport function aiSiteBuildRows(\n job: Pick<AiJobSummary, 'status' | 'steps' | 'plan' | 'outputs' | 'review'> &\n Partial<Pick<AiJobSummary, 'items' | 'kind' | 'siteInputs' | 'sitePublish'>>,\n): AiSiteBuildRow[] {\n const phase = aiSiteBuildPhase(job)\n // A build is read off its item ledger (AGL-3616): one row an item, each with\n // its own state and what became of its credits.\n if (job.items?.length) {\n const site = job.kind !== 'build'\n const pages = (job.items ?? []).filter((row) => row.op === 'page')\n return [\n { id: 'plan', label: site ? 'Planning your pages' : 'Planning what to build', state: 'done' },\n ...aiBuildItemRows(job).map((row, index) => {\n const ledger = (job.items ?? [])[index]\n // A site keeps the scaffold's own words for each stage (AGL-3596).\n const label =\n !site || !ledger\n ? row.label\n : ledger.op === 'theme'\n ? 'Choosing your colors'\n : ledger.op === 'layout'\n ? `Building the header and footer: ${ledger.label}`\n : ledger.op === 'form'\n ? `Building the form: ${ledger.label}`\n : ledger.op === 'page'\n ? `Writing page ${pages.indexOf(ledger) + 1} of ${pages.length}: ${ledger.label}`\n : ledger.op === 'email'\n ? 'Writing your welcome email'\n : row.label\n const screen = ledger?.op === 'page' ? aiPlanScreenFor(job.plan, ledger.label, pages.indexOf(ledger)) : null\n const finished = row.state === 'done' || row.state === 'failed'\n const net = ledger ? Math.max(0, Math.floor((ledger.creditsSpent ?? 0) - (ledger.creditsRefunded ?? 0))) : 0\n return {\n id: row.slot,\n label,\n state: row.state,\n // A part that failed while the rest is still being built says the\n // build goes on (AGL-3596): read alone, it looked like the site had.\n detail:\n row.state === 'failed' && phase === 'working'\n ? [row.detail, site ? 'The rest of your site keeps building.' : 'The rest keeps building.'].filter(Boolean).join(' ')\n : row.detail,\n ...(row.state === 'active'\n ? { startedAt: row.startedAt ?? null, hint: ledger?.op === 'page' ? AI_SITE_PAGE_HINT : AI_SITE_ITEM_HINT }\n : {}),\n ...(screen?.sections.length ? { sections: screen.sections.map((section) => section.name) } : {}),\n ...(finished && ledger ? { credits: net } : {}),\n }\n }),\n ...aiSitePublishRow(job, phase),\n ]\n }\n const planStep = job.steps.find((step) => step.name === 'plan')\n const planDone = planStep?.status === 'done' || Boolean(job.plan)\n const stopped = phase === 'failed' || phase === 'stopped'\n const planState: AiSiteBuildRowState = planDone ? 'done' : stopped ? 'failed' : phase === 'working' ? 'active' : 'waiting'\n const rows: AiSiteBuildRow[] = [\n {\n id: 'plan',\n label: 'Planning your pages',\n state: planState,\n startedAt: planStep?.startedAt ?? null,\n hint: planState === 'active' ? AI_SITE_PLAN_HINT : null,\n ...(planDone && planStep ? { credits: Math.max(0, Math.floor(planStep.creditsSpent ?? 0)) } : {}),\n },\n ]\n // Before there is a plan, the stages a guided start always goes through\n // are named from its answers, so the page shows the whole way from the\n // start rather than one row.\n if (!job.plan && job.kind !== 'build') {\n const pagesAsked = typeof job.siteInputs?.['pages'] === 'number' ? Math.round(job.siteInputs['pages'] as number) : 0\n if (job.siteInputs) {\n rows.push({ id: 'layout', label: 'Building the header and footer', state: 'waiting' })\n // A guided start that says where its contact form's submissions go\n // plans that form, built after the layout and before the pages (AGL-3596).\n const submissions = job.siteInputs['submissions']\n if (submissions === 'inbox' || submissions === 'lead') {\n rows.push({ id: 'form', label: 'Building your contact form', state: 'waiting' })\n }\n rows.push({\n id: 'pages',\n label: pagesAsked > 1 ? `Writing your ${pagesAsked} pages` : 'Writing your pages',\n state: 'waiting',\n })\n rows.push(...aiSitePublishRow(job, phase))\n }\n return rows\n }\n const built = new Map<string, number>()\n for (const output of job.outputs) built.set(output.resource, (built.get(output.resource) ?? 0) + 1)\n const take = (resource: string): boolean => {\n const left = built.get(resource) ?? 0\n if (left <= 0) return false\n built.set(resource, left - 1)\n return true\n }\n const stages: Array<{ id: string; label: string; resource: string; sections?: string[] }> = []\n for (const row of CREATION_ROWS) {\n const creation = job.plan?.create.find((entry) => entry.kind === row.kind)\n if (creation) stages.push({ id: row.resource, label: row.label(creation.name), resource: row.resource })\n }\n const pages = job.plan?.screens ?? []\n pages.forEach((page, index) => {\n stages.push({\n id: `page-${index}`,\n label: `Writing page ${index + 1} of ${pages.length}: ${page.title}`,\n resource: 'screen',\n sections: page.sections.map((section) => section.name),\n })\n })\n let reached = false\n for (const stage of stages) {\n const done = phase === 'done' || take(stage.resource)\n const current = !done && !reached && planDone\n if (current) reached = true\n rows.push({\n id: stage.id,\n label: stage.label,\n state: done ? 'done' : current && stopped ? 'failed' : current && phase === 'working' ? 'active' : 'waiting',\n ...(stage.sections?.length ? { sections: stage.sections } : {}),\n })\n }\n rows.push(...aiSitePublishRow(job, phase))\n return rows\n}\n\n/** What the planning row says while it runs. */\nexport const AI_SITE_PLAN_HINT =\n 'Reading your answers and choosing your pages, what each one says, and the forms and layout they need. This usually takes under a minute.'\n\n/** What the page being written says while it is the active row (AGL-3596). */\nexport const AI_SITE_PAGE_HINT =\n 'Writing this page’s sections from your plan. It is done once every section is in.'\n\n/** What any other part being built says while it is the active row. */\nexport const AI_SITE_ITEM_HINT = 'Building this now. The next step starts when it is done.'\n\n/** The plan's page an item builds: by its title, else by its place among the pages. */\nfunction aiPlanScreenFor(\n plan: AiJobSummary['plan'],\n title: string,\n index: number,\n): NonNullable<AiJobSummary['plan']>['screens'][number] | null {\n const screens = plan?.screens ?? []\n return screens.find((screen) => screen.title === title) ?? (index >= 0 ? (screens[index] ?? null) : null)\n}\n\n/**\n * A guided start ends by putting its pages live (AGL-3596): the last row, so\n * the person sees that step coming and when it is done. Only a site job\n * publishes on its own; a build publishes when asked, and says so in its plan.\n */\nfunction aiSitePublishRow(\n job: Pick<AiJobSummary, 'status'> & Partial<Pick<AiJobSummary, 'kind' | 'sitePublish'>>,\n phase: AiSiteBuildPhase,\n): AiSiteBuildRow[] {\n if (job.kind !== 'site') return []\n const published = (job.sitePublish?.published.length ?? 0) > 0\n return [\n {\n id: 'publish',\n label: 'Publishing your site',\n // A job that stopped or failed publishes nothing, so the row is passed over.\n state: phase === 'done' ? (published ? 'done' : 'skipped') : phase === 'working' ? 'waiting' : 'skipped',\n },\n ]\n}\n\n/**\n * How far the job is, from 0 to 1, by its rows: a finished row counts whole\n * and the active one half. `null` before there is more than one row to count.\n */\nexport function aiSiteBuildFraction(rows: readonly AiSiteBuildRow[]): number | null {\n if (rows.length < 2) return null\n const counted = rows.reduce(\n (sum, row) => sum + (row.state === 'active' ? 0.5 : row.state === 'waiting' ? 0 : 1),\n 0,\n )\n return Math.min(1, counted / rows.length)\n}\n\n/**\n * The job's one price (AGL-3594), never a plan's on its own: what it has used\n * so far while it works, what it used once done, and, on a failure we gave\n * back, that nothing was charged.\n */\nexport function aiSiteBuildCreditsLine(\n job: Pick<AiJobSummary, 'status' | 'creditsSpent' | 'refundedCredits'> & Partial<Pick<AiJobSummary, 'kind'>>,\n): string | null {\n const spent = Math.max(0, job.creditsSpent ?? 0)\n const refunded = Math.max(0, job.refundedCredits ?? 0)\n const net = Math.max(0, spent - refunded)\n const phase = aiSiteBuildPhase(job)\n // What the job made, by its kind; a job that names none is a site's.\n const subject = !job.kind || job.kind === 'site' ? 'This site' : aiJobKindNoun(job.kind) === 'AI job' ? 'This AI job' : `This ${aiJobKindNoun(job.kind)}`\n // A failure on our side says so in the words every surface uses (AGL-3596),\n // from the job's recorded give-back.\n const refund = phase === 'working' || phase === 'done' ? null : aiJobRefundCopy(job)\n if (refund && refunded > 0) return refund\n if (phase === 'done') return `${subject} used ${net} credits.`\n if (phase === 'working') return `Credits used so far: ${net}`\n if (refund) return `${subject} used ${net} credits. ${refund}`\n return net > 0 ? `${subject} used ${net} credits.` : null\n}\n\n/** The heading and the sentence under it, for one job's page in its current phase. */\nexport interface AiJobPageCopy {\n heading: string\n lede: string\n}\n\n/**\n * What one job's page says at the top (AGL-3594, AGL-3596). A site job reads\n * as \"Building your site\"; any other kind names what it makes — \"Building\n * your page\" — or, for a kind with no noun, the job itself.\n *\n * A guided site start publishes its pages when it finishes, and says so\n * (`sitePublish`): with a page live the heading is \"Your site is live\". A\n * done site job without that record — an older job, or one that was not a\n * guided start — built drafts, and says that instead.\n */\nexport function aiJobPageCopy(\n job: Pick<AiJobSummary, 'kind' | 'status' | 'review' | 'error'> & Partial<Pick<AiJobSummary, 'sitePublish' | 'items'>>,\n brand: string,\n): AiJobPageCopy {\n const phase = aiSiteBuildPhase(job)\n // A build is what the person asked for, item by item (AGL-3616).\n if (job.kind === 'build') return aiBuildPageCopy(job, brand)\n const noun = job.kind === 'site' ? 'site' : aiJobKindNoun(job.kind)\n const generic = noun === 'AI job'\n // `products` is the one plural noun.\n const isAre = noun.endsWith('s') ? 'are' : 'is'\n const why = job.review?.message ?? job.error ?? null\n if (phase === 'working') {\n return {\n heading: generic ? 'Your AI job is running' : `Building your ${noun}`,\n lede:\n job.kind === 'site'\n ? `${brand} AI is planning your pages and writing each one. You can leave this page; it keeps going.`\n : `${brand} AI is working on it. You can leave this page; it keeps going.`,\n }\n }\n if (phase === 'done') {\n const published = job.sitePublish?.published.length ?? 0\n // Part of the site was built (AGL-3616): what was, what was not, and its credits.\n const partial = job.kind === 'site' ? aiSitePartialCopy({ items: job.items, status: job.status }) : null\n if (job.kind === 'site' && published > 0) {\n return {\n heading: 'Your site is live',\n lede: [\n partial,\n job.sitePublish?.drafts.length\n ? 'Your pages are published, except the ones listed below, which stayed drafts.'\n : 'Your pages are published, and anyone can visit your site now.',\n ]\n .filter(Boolean)\n .join(' '),\n }\n }\n if (partial) return { heading: 'Your site is ready', lede: `${partial} Your new pages are drafts. Publish them when you’re happy.` }\n if (job.kind === 'site' && job.sitePublish) {\n return {\n heading: 'Your site is ready',\n lede: 'None of your new pages could be published, so they are drafts. The reasons are below.',\n }\n }\n return {\n heading: generic ? 'Your AI job finished' : `Your ${noun} ${isAre} ready`,\n lede:\n job.kind === 'site'\n ? 'Your new pages are drafts. Publish them when you’re happy.'\n : 'Everything it built is an unpublished draft until you publish it.',\n }\n }\n if (phase === 'canceled') {\n return { heading: generic ? 'Your AI job was canceled' : `Your ${noun} was not built`, lede: 'The job was canceled.' }\n }\n return {\n heading: generic ? 'Your AI job stopped' : `Your ${noun} ${noun.endsWith('s') ? 'were' : 'was'} not built`,\n lede: why ?? (generic ? 'Something went wrong running this job.' : `Something went wrong building your ${noun}.`),\n }\n}\n\n/** A build's page heading and sentence (AGL-3616). */\nfunction aiBuildPageCopy(\n job: Pick<AiJobSummary, 'status' | 'review' | 'error'> & Partial<Pick<AiJobSummary, 'items' | 'sitePublish'>>,\n brand: string,\n): AiJobPageCopy {\n const phase = aiSiteBuildPhase(job)\n const outcome = aiBuildOutcomeLine({ items: job.items ?? [], status: job.status })\n if (phase === 'working') {\n return {\n heading: 'Building what you asked for',\n lede: `${brand} AI is building each part in turn. You can leave this page; it keeps going.`,\n }\n }\n if (phase === 'stopped') {\n return {\n heading: job.review?.reason === 'plan' ? 'Your plan is ready' : 'Your build needs you',\n lede: job.review?.message ?? job.error ?? 'It waits for your decision.',\n }\n }\n if (phase === 'done') {\n const failed = (job.items ?? []).some((row) => row.status === 'failed' || row.status === 'skipped')\n const live = job.sitePublish?.published.length ?? 0\n return {\n heading: failed ? 'Most of it is ready' : 'Everything you asked for is ready',\n lede: [\n outcome,\n live ? 'Your new pages are published.' : 'Everything it built is an unpublished draft until you publish it.',\n failed ? 'Try again builds only what failed.' : '',\n ]\n .filter(Boolean)\n .join(' '),\n }\n }\n if (phase === 'canceled') return { heading: 'Your build was canceled', lede: 'The job was canceled.' }\n return {\n heading: 'Nothing could be built',\n lede: [job.error ?? 'Something went wrong building what you asked for.', outcome].filter(Boolean).join(' '),\n }\n}\n"],"names":["aiJobConfirmingOwnPlan","aiJobKindNoun","aiJobRefundCopy","aiBuildItemRows","aiBuildOutcomeLine","aiSitePartialCopy","aiSiteBuildPhase","job","status","CREATION_ROWS","kind","resource","label","name","aiSiteBuildRows","planStep","built","phase","items","length","site","pages","filter","row","op","id","state","map","index","ledger","indexOf","screen","aiPlanScreenFor","plan","finished","net","Math","max","floor","creditsSpent","creditsRefunded","slot","detail","Boolean","join","startedAt","hint","AI_SITE_PAGE_HINT","AI_SITE_ITEM_HINT","sections","section","credits","aiSitePublishRow","steps","find","step","planDone","stopped","planState","rows","AI_SITE_PLAN_HINT","pagesAsked","siteInputs","round","push","submissions","Map","output","outputs","set","get","take","left","stages","creation","create","entry","screens","forEach","page","title","reached","stage","done","current","published","sitePublish","aiSiteBuildFraction","counted","reduce","sum","min","aiSiteBuildCreditsLine","spent","refunded","refundedCredits","subject","refund","aiJobPageCopy","brand","aiBuildPageCopy","noun","generic","isAre","endsWith","why","review","message","error","heading","lede","partial","drafts","outcome","reason","failed","some","live"],"mappings":"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"}
@@ -236,6 +236,14 @@ export declare const AI_SITE_SUBMISSION_CHOICES: ReadonlyArray<{
236
236
  * because it is the first thing the model is told.
237
237
  */
238
238
  export declare const AI_SITE_EMAIL_TYPE = "reply";
239
+ /**
240
+ * What a site job's plan and every unit it builds are told about the
241
+ * business's name (AGL-3596): that it is the name, used as written. Said on
242
+ * the job's own turn, never in a cached system block, because it is one
243
+ * site's. A model handed only what the business does named it itself —
244
+ * "Hillside Dog Grooming" was built as "Austin Paws Grooming".
245
+ */
246
+ export declare function aiSiteNameSentence(name: string): string;
239
247
  /** Where a job's inputs say submissions go, or `null` where they do not say. */
240
248
  export declare function aiSiteSubmissions(inputs: Readonly<Record<string, unknown>> | null | undefined): AiSiteSubmissions | null;
241
249
  /** A scaffold's inputs, or the sentence the door refuses them with. */
@@ -179,6 +179,15 @@ import { AI_PAGE_CREATE_KINDS } from "./ai-page-job.js";
179
179
  * up, and a kind that misdescribes the email is worse than an absent one
180
180
  * because it is the first thing the model is told.
181
181
  */ export const AI_SITE_EMAIL_TYPE = 'reply';
182
+ /**
183
+ * What a site job's plan and every unit it builds are told about the
184
+ * business's name (AGL-3596): that it is the name, used as written. Said on
185
+ * the job's own turn, never in a cached system block, because it is one
186
+ * site's. A model handed only what the business does named it itself —
187
+ * "Hillside Dog Grooming" was built as "Austin Paws Grooming".
188
+ */ export function aiSiteNameSentence(name) {
189
+ return `The business is named “${name}”: the site names it exactly that way, in the header, the footer, the copy and the search listing, and never by any other name.`;
190
+ }
182
191
  /** Where a job's inputs say submissions go, or `null` where they do not say. */ export function aiSiteSubmissions(inputs) {
183
192
  const value = inputs == null ? void 0 : inputs['submissions'];
184
193
  return AI_SITE_SUBMISSIONS.includes(value) ? value : null;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-job.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AI_BUILD_PLAN_CREATION_NOUNS,\n aiPlanUndeclaredRefs,\n type AiBuildPlan,\n type AiBuildPlanCreateKind,\n} from './ai-build-plan'\nimport { AI_JOB_CREATE_KINDS } from './ai-job-creations'\nimport { AI_PAGE_CREATE_KINDS } from './ai-page-job'\n\n/**\n * What a site scaffold is (AGL-2911): the inputs a `site` job is admitted\n * with, the plans a scaffold can build, and what one is estimated to cost\n * before it starts.\n *\n * A scaffold is the one job kind that builds a whole site rather than one\n * record: a plan of four to eight screens, the layout they render inside,\n * the contact form they place, a palette suggestion, and — where the email\n * step is loaded — a welcome email. It builds each of those through the job\n * kind that already owns it, so this module describes what a scaffold may\n * ask for and refuses the rest at confirmation, before a credit is spent,\n * naming what to make first.\n *\n * ── The estimate is the guard rail ───────────────────────────────────────\n *\n * A scaffold is tens of model calls, where every other kind is a few, so the\n * member reads what it will cost before they confirm the plan that spends\n * it. The figure is the machine's own nominal credits per step multiplied by\n * the passes the plan implies — an estimate, and said to be one wherever it\n * is shown.\n *\n * This module imports nothing at runtime, so the doors, the step, the batch\n * card and the plan proposal read one vocabulary.\n */\n\n/** How many pages a scaffold builds: enough to be a site, few enough to read. */\nexport const AI_SITE_PAGES = { min: 4, max: 8 } as const\n\n/**\n * How many pages a Free workspace's scaffold builds (AGL-3594): its home page\n * and the one page the brief most needs. A product decision, not a figure the\n * wall derives — the wall's proof (`jobs/ai-job-free-site.spec.ts`) shows a\n * plan and a build of this many pages fit the Free taste with room for one\n * retried section.\n */\nexport const AI_SITE_FREE_PAGES = { min: 1, max: 2 } as const\n\n/** The pages a scaffold builds for a workspace on the Free taste or on a paid plan. */\nexport function aiSitePagesBand(\n freeTaste: boolean | null | undefined,\n): { readonly min: number; readonly max: number } {\n return freeTaste ? AI_SITE_FREE_PAGES : AI_SITE_PAGES\n}\n\n/**\n * The job input a guided site start sets so its plan is confirmed for it\n * (AGL-3594); `jobs/ai-job-auto-confirm.ts` says where it is honored.\n */\nexport const AI_JOB_AUTO_CONFIRM_INPUT = 'autoConfirm'\n\n/** What a Free site start's page selector says under it (AGL-3594). */\nexport const AI_SITE_FREE_PAGES_NOTE = 'Paid plans can generate more pages.'\n\n/**\n * The plan's answer ceiling for a scaffold (AGL-3594), Free and paid. A plan\n * is an outline — each page's title, address, search listing and sections by\n * name — and the build steps write everything else, so a scaffold plans with\n * no thinking and at a ceiling sized to the outline of its largest band with\n * room to spare: about 300 tokens a page. The 13,491-token plan a Free site\n * spent 227 of its 300 credits on was its thinking, at the routing table's\n * 8,000 a call.\n */\nexport const AI_SITE_PLAN_MAX_TOKENS = { free: 2_000, paid: 4_000 } as const\n\n/** What each exchange of a Free site job comes to at its worst, in credits. */\nexport interface AiFreeSiteWorstCase {\n /** The site plan: its answer and its one re-ask, each at its ceiling. */\n plan: number\n /** The one layout a Free plan includes, built first on a site with none. */\n layout: number\n /** A page's first section pass, which writes the cached prefix. */\n firstSection: number\n /** Each later section pass. */\n laterSection: number\n /** A page's listing. */\n listing: number\n}\n\n/**\n * The Free site start's wall at its worst (AGL-3594): the site plan as\n * `jobs/ai-job-free-site.spec.ts` derives it from the plan request as it\n * stands, on the model a Free site plans with, every answer at its ceiling;\n * the build's exchanges are the Free page's (`AI_FREE_PAGE_WORST_CASE_CREDITS`),\n * which the same spec holds these to. The spec fails when a figure it derives\n * moves and this does not.\n */\nexport const AI_FREE_SITE_WORST_CASE_CREDITS: Readonly<AiFreeSiteWorstCase> = {\n plan: 35,\n layout: 64,\n firstSection: 44,\n laterSection: 20,\n listing: 3,\n}\n\n/**\n * The most sections a Free site's plan fits in the Free taste at its worst,\n * across all its pages: the plan, the layouts it builds first, each page's\n * listing and first pass, and ROOM FOR ONE RETRIED SECTION, then as many later\n * passes as the rest pays for; none when that is already past the wall. Every\n * page's first pass is counted as a cache write, which errs dear.\n */\nexport function aiFreeSiteSectionsWithin(\n creations: { layouts: number; pages: number },\n taste: number,\n credits: Readonly<AiFreeSiteWorstCase> = AI_FREE_SITE_WORST_CASE_CREDITS,\n): number {\n const pages = Math.max(1, Math.floor(creations.pages))\n const before =\n credits.plan +\n creations.layouts * credits.layout +\n pages * (credits.listing + credits.firstSection) +\n credits.firstSection\n if (before > taste) return 0\n return pages + Math.floor((taste - before) / credits.laterSection)\n}\n\n/** Sections a Free site's page is assumed to hold before its plan names them. */\nexport const AI_FREE_SITE_NOMINAL_SECTIONS = 3\n\n/**\n * About what a Free site start of this many pages costs at its worst, in\n * credits, for the dialog that asks for one: the site plan, each page's\n * listing and its sections at the nominal count, from the figures the wall is\n * proven with rather than the nominal credits a paid estimate counts.\n */\nexport function aiFreeSiteCreditEstimate(\n pages: number,\n credits: Readonly<AiFreeSiteWorstCase> = AI_FREE_SITE_WORST_CASE_CREDITS,\n): number {\n const count = Math.max(1, Math.floor(pages))\n return (\n credits.plan +\n count * (credits.listing + credits.firstSection) +\n count * (AI_FREE_SITE_NOMINAL_SECTIONS - 1) * credits.laterSection\n )\n}\n\n/**\n * The most sections one scaffolded page may hold. The plan model allows more\n * for a page job, which builds one page; a scaffold builds eight, and each\n * section is a pass of its own.\n */\nexport const AI_SITE_MAX_SECTIONS = 8\n\n/**\n * Sections a page is assumed to hold before a plan names them, for the\n * estimate a member reads when they ask for a scaffold. A declared\n * assumption, not a measurement: the plan's own section counts replace it\n * the moment the plan exists.\n */\nexport const AI_SITE_NOMINAL_SECTIONS = 5\n\n/**\n * The credits one pass is held against: the machine's\n * `AI_JOB_STEP_RESERVE_CREDITS`, declared here so the estimate stays a pure\n * number the console computes without the Admin SDK. `ai-site-job.spec.ts`\n * holds the two equal.\n */\nexport const AI_SITE_PASS_CREDITS = 50\n\n/** The longest a scaffold's scalar inputs may run; they are names, not copy. */\nexport const AI_SITE_INPUT_MAX_CHARS = 120\n\n/** The sites one agency batch may generate for in one request. */\nexport const AI_SITE_BATCH_MAX = 25\n\n/**\n * The sites a plan must hold before the org Sites page offers the batch at\n * all: the Agency and Advanced bands, which the plan's `hostLimit` is what\n * distinguishes.\n */\nexport const AI_SITE_BATCH_MIN_HOST_LIMIT = 25\n\nexport interface AiSiteJobInputs {\n /** What the business is, in a few words: the brief's subject. */\n businessType: string\n /**\n * Who the site is for, in a few words (AGL-2918); empty when nobody said.\n * The plan step puts it in front of the model on a line of its own, as it\n * does every other scalar here, so it narrows the pages a plan proposes\n * without a prompt of its own.\n */\n audience: string\n /**\n * The starter site whose shape the person liked (AGL-2918), by its\n * `STARTER_TEMPLATES` id; empty when they picked none.\n *\n * A hint carried as the id rather than the starter, because an id is what\n * the guided start's list, this door and the plan's line all agree on\n * without any of them loading a catalog of node maps. Unrecognized ids are\n * admitted for the same reason an unrecognized business type is: it is a\n * few words in a brief, not a lookup.\n */\n starter: string\n /** How many pages the member asked for. */\n pages: number\n /** The per-site variables an agency batch varies; empty when none was given. */\n businessName: string\n city: string\n brand: string\n /** Whether the scaffold also drafts a welcome email. */\n welcomeEmail: boolean\n /**\n * Where the contact form's submissions go (AGL-2918); `null` where nobody\n * said, which is every job created before the question existed and every\n * door that does not ask it.\n *\n * The one setting a new site owner actually needs made, and the one thing\n * about a contact form a model cannot know: whether a submission is a note\n * to read or a lead to chase is a decision about how the business runs. So\n * an answer BINDS the form step rather than hinting to it, and `null`\n * leaves the model's own proposal standing, exactly as it stood before.\n */\n submissions: AiSiteSubmissions | null\n /** The batch this job was created under (AGL-2911); `null` for a single site. */\n batchId: string | null\n}\n\n/**\n * Where a site's contact form sends what a visitor writes.\n *\n * Two, not three. The form step's own vocabulary has a third — a mailing\n * list — which it can only answer with a note, because the stored routing has\n * no place for a list; offering it as a question would be asking somebody to\n * pick an outcome the platform then explains it cannot store.\n */\nexport const AI_SITE_SUBMISSIONS = ['inbox', 'lead'] as const\n\nexport type AiSiteSubmissions = (typeof AI_SITE_SUBMISSIONS)[number]\n\n/**\n * What each answer to \"where do submissions go\" is called, and what it means.\n *\n * Here rather than beside the guided start's own questions, because it is not\n * only the guided start that asks: the agency batch asks the same thing of a\n * whole run (AGL-2918), and two surfaces wording one decision two ways is two\n * decisions. `ai-site-job.spec.ts` holds the list to {@link AI_SITE_SUBMISSIONS}.\n */\nexport const AI_SITE_SUBMISSION_CHOICES: ReadonlyArray<{\n id: AiSiteSubmissions\n label: string\n blurb: string\n}> = [\n {\n id: 'inbox',\n label: 'The Inbox',\n blurb: 'Every message is one to read and reply to.',\n },\n {\n id: 'lead',\n label: 'The Inbox, and CRM as a lead',\n blurb: 'Every message with an email address is also somebody to follow up.',\n },\n]\n\n/**\n * Which kind of email a scaffold's email unit is (AGL-2918), carried on the\n * derived job as `inputs.emailType`. The email step names the kind on the one\n * line it puts above the brief, and a scaffold was naming none.\n *\n * A key of the email step's own `AI_EMAIL_TYPES`, held here because neither\n * step may import the other: a scaffold delegates by job kind, and a\n * deployment that scaffolds is not necessarily one that has loaded the email\n * step. `ai-job-email-step.spec.ts` holds this key to that catalog.\n *\n * `reply` and not `welcome`. The five kinds a member picks are campaigns\n * somebody sends to an audience, and this is an answer to one person who\n * wrote in through the site; the nearest, `welcome`, is a label about signing\n * up, and a kind that misdescribes the email is worse than an absent one\n * because it is the first thing the model is told.\n */\nexport const AI_SITE_EMAIL_TYPE = 'reply'\n\n/** Where a job's inputs say submissions go, or `null` where they do not say. */\nexport function aiSiteSubmissions(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): AiSiteSubmissions | null {\n const value = inputs?.['submissions']\n return (AI_SITE_SUBMISSIONS as readonly unknown[]).includes(value)\n ? (value as AiSiteSubmissions)\n : null\n}\n\nconst ID_CHARS = /^[A-Za-z0-9_-]{1,64}$/\n\n/** A scaffold's inputs, or the sentence the door refuses them with. */\nexport function parseAiSiteJobInputs(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): AiSiteJobInputs | string {\n const text = (key: string): string | null => {\n const value = inputs?.[key]\n if (value === undefined || value === null || value === '') return ''\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length > AI_SITE_INPUT_MAX_CHARS ? null : trimmed\n }\n const businessType = text('businessType')\n if (businessType === null) {\n return `businessType must be text under ${AI_SITE_INPUT_MAX_CHARS} characters`\n }\n if (!businessType) return 'Say what kind of business the site is for'\n const businessName = text('businessName')\n const city = text('city')\n const brand = text('brand')\n const audience = text('audience')\n const starter = text('starter')\n for (const [key, value] of [\n ['businessName', businessName],\n ['city', city],\n ['brand', brand],\n ['audience', audience],\n ['starter', starter],\n ] as const) {\n if (value === null)\n return `${key} must be text under ${AI_SITE_INPUT_MAX_CHARS} characters`\n }\n const rawPages = inputs?.['pages']\n const pages =\n typeof rawPages === 'number' ? rawPages : Number(rawPages ?? NaN)\n // Read here across both bands; the door holds a workspace to its own\n // (`aiSitePagesRefusal`), since only it knows the workspace's plan.\n if (\n !Number.isInteger(pages) ||\n pages < AI_SITE_FREE_PAGES.min ||\n pages > AI_SITE_PAGES.max\n ) {\n return `pages must be a whole number from ${AI_SITE_FREE_PAGES.min} to ${AI_SITE_PAGES.max}`\n }\n // Where submissions go is admitted only as one of the two the form step\n // can actually bind; anything else is nobody having said, and the model\n // proposes as it always did rather than the door refusing the whole job.\n const submissions = aiSiteSubmissions(inputs)\n const rawBatch = inputs?.['batchId']\n const batchId =\n rawBatch === undefined || rawBatch === null || rawBatch === ''\n ? null\n : typeof rawBatch === 'string' && ID_CHARS.test(rawBatch)\n ? rawBatch\n : undefined\n if (batchId === undefined) return 'batchId is not a batch id'\n return {\n businessType,\n audience: audience as string,\n starter: starter as string,\n pages,\n businessName: businessName as string,\n city: city as string,\n brand: brand as string,\n welcomeEmail: inputs?.['welcomeEmail'] !== false,\n submissions,\n batchId,\n }\n}\n\n/**\n * Why a workspace cannot ask for this many pages (AGL-3594), in a sentence a\n * member reads; `null` when it can. A Free workspace builds one or two, and a\n * paid one four to eight, as it always has. Refused rather than clamped: the\n * guided start offers only the band, so a request outside it was not made\n * there, and a job quietly building fewer pages than it was asked for is a\n * worse answer than a sentence saying why.\n */\nexport function aiSitePagesRefusal(pages: number, freeTaste: boolean | null | undefined): string | null {\n const band = aiSitePagesBand(freeTaste)\n if (pages >= band.min && pages <= band.max) return null\n return freeTaste\n ? `A Free workspace's AI site start builds ${AI_SITE_FREE_PAGES.min} or ${AI_SITE_FREE_PAGES.max} pages. ${AI_SITE_FREE_PAGES_NOTE}`\n : `A site is planned with ${band.min} to ${band.max} pages.`\n}\n\n/**\n * What the site itself is, as a step that is NOT the scaffold reads it off a\n * job's inputs (AGL-2918).\n *\n * A scaffold delegates unit by unit under a job derived from its own — a page\n * job, a form job — and the derived job carries the scaffold's inputs. So the\n * two answers that describe the site rather than one record of it are\n * readable by every delegated step, and a step that wants them does not have\n * to know it was delegated to.\n *\n * Lenient where {@link parseAiSiteJobInputs} is strict, because it reads the\n * inputs of jobs that are not scaffolds: a page job started from the Screens\n * page has no `businessType` and no `pages`, and that is not an error here —\n * it is a site nobody described, and the answer is two empty strings.\n */\nexport interface AiSiteWords {\n /** What kind of site it is; empty when the job's inputs do not say. */\n about: string\n /** Who it is for; empty when the job's inputs do not say. */\n audience: string\n}\n\n/** Whether either half of {@link AiSiteWords} says anything. */\nexport function aiSiteWordsSaidAnything(words: AiSiteWords): boolean {\n return Boolean(words.about || words.audience)\n}\n\n/** What a job's inputs say the site is, read defensively and trimmed to the input ceiling. */\nexport function aiSiteWords(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): AiSiteWords {\n const read = (key: string): string => {\n const value = inputs?.[key]\n if (typeof value !== 'string') return ''\n return value.replace(/\\s+/g, ' ').trim().slice(0, AI_SITE_INPUT_MAX_CHARS).trim()\n }\n return { about: read('businessType'), audience: read('audience') }\n}\n\n/**\n * What a scaffold builds for itself: the layout its pages render inside, the\n * form they place, and the palette suggestion a member applies in the Theme\n * section. Anything else a plan asks to create is a job of its own.\n */\nexport const AI_SITE_CREATE_KINDS: readonly AiBuildPlanCreateKind[] = [\n 'layout',\n 'form',\n 'theme-change',\n]\n\n/** The creations a scaffold cannot build, in the order the plan lists them, each named once. */\nexport function aiSitePlanPrerequisites(\n plan: AiBuildPlan,\n): Array<{ kind: AiBuildPlanCreateKind; name: string }> {\n const seen = new Set<string>()\n const prerequisites: Array<{ kind: AiBuildPlanCreateKind; name: string }> = []\n const add = (kind: AiBuildPlanCreateKind, name: string) => {\n const key = `${kind}:${name.toLowerCase()}`\n if (seen.has(key)) return\n seen.add(key)\n prerequisites.push({ kind, name })\n }\n for (const entry of plan.create) {\n if (!AI_SITE_CREATE_KINDS.includes(entry.kind)) add(entry.kind, entry.name)\n }\n // A reference the create list does not carry is refused by the plan rules\n // before a plan is kept: rule 2 for a screen's layout, rule 7 for anything\n // else (`plan-creation-undeclared`, AGL-3040). A plan confirmed before that\n // rule existed can still carry one. It is named as a component: the\n // reference carries no kind, and the refusal must say where one is made.\n for (const { name } of aiPlanUndeclaredRefs(plan)) add('component', name)\n return prerequisites\n}\n\n/**\n * Why a scaffold cannot build a plan of this SHAPE — the page band, a page's\n * sections, its addresses, its navigation — in a sentence a member reads;\n * `null` when the shape is one it builds. The plan step holds a plan to it\n * with a re-ask (AGL-3030), and the doors hold a confirmed plan to it again\n * through `aiSitePlanRefusal`.\n */\nexport function aiSitePlanShapeRefusal(\n plan: AiBuildPlan,\n options: { freeTaste?: boolean } = {},\n): string | null {\n const { screens } = plan\n const band = aiSitePagesBand(options.freeTaste)\n if (screens.length < band.min || screens.length > band.max) {\n return `This plan builds ${screens.length} ${screens.length === 1 ? 'page' : 'pages'}, and ${\n options.freeTaste ? \"a Free workspace's site scaffold\" : 'a site scaffold'\n } builds ${band.min} to ${band.max}. Describe the site again.`\n }\n const slugs = new Set<string>()\n for (const screen of screens) {\n if (!screen.sections.length) {\n return `The page “${screen.title}” has no sections to build. Describe the site again.`\n }\n if (screen.sections.length > AI_SITE_MAX_SECTIONS) {\n return `The page “${screen.title}” has ${screen.sections.length} sections, and a scaffolded page holds ${AI_SITE_MAX_SECTIONS}. Describe the site again.`\n }\n const slug = screen.slug.trim().toLowerCase()\n if (slugs.has(slug)) {\n return `Two pages in this plan share the address ${screen.slug}. Describe the site again.`\n }\n slugs.add(slug)\n }\n if (!screens.some((screen) => screen.nav)) {\n return 'No page in this plan is in the site navigation. Describe the site again.'\n }\n return null\n}\n\n/**\n * Why a scaffold cannot build this plan, in a sentence a member reads when\n * confirming it; `null` when it can.\n */\nexport function aiSitePlanRefusal(\n plan: AiBuildPlan,\n options: { freeTaste?: boolean } = {},\n): string | null {\n const shape = aiSitePlanShapeRefusal(plan, options)\n if (shape) return shape\n const prerequisites = aiSitePlanPrerequisites(plan)\n if (!prerequisites.length) return null\n const parts = prerequisites.map(\n ({ kind, name }) =>\n `the ${AI_BUILD_PLAN_CREATION_NOUNS[kind].noun} “${name}” ${AI_BUILD_PLAN_CREATION_NOUNS[kind].where}`,\n )\n const listed =\n parts.length === 1\n ? parts[0]\n : `${parts.slice(0, -1).join(', ')} and ${parts[parts.length - 1]}`\n return `This site needs what the workspace does not have yet. Create ${listed}, then describe the site again.`\n}\n\n/** What the estimate counts a plan in: whether a welcome email follows, and the creations the job builds. */\nexport interface AiPlanPassOptions {\n welcomeEmail?: boolean\n /** The creation kinds the job builds itself; a scaffold's when absent. */\n creates?: readonly AiBuildPlanCreateKind[]\n}\n\n/**\n * The passes a plan implies: one per section of every screen, one more per\n * screen for its search listing and its draft, and one for each thing the\n * plan creates that the job builds. What the estimate is counted in.\n */\nexport function aiPlanPasses(plan: AiBuildPlan, options: AiPlanPassOptions = {}): number {\n const screens = plan.screens.reduce(\n (total, screen) => total + screen.sections.length + 1,\n 0,\n )\n const creates = options.creates ?? AI_SITE_CREATE_KINDS\n const creations = plan.create.filter((entry) => creates.includes(entry.kind)).length\n return screens + creations + (options.welcomeEmail ? 1 : 0)\n}\n\n/**\n * About what a plan costs to build, in credits. An ESTIMATE: the nominal\n * credits the machine holds per step, times the passes the plan implies.\n * What each pass actually costs is its model's tokens, recorded on the job\n * as it runs.\n */\nexport function aiPlanCreditEstimate(plan: AiBuildPlan, options: AiPlanPassOptions = {}): number {\n return aiPlanPasses(plan, options) * AI_SITE_PASS_CREDITS\n}\n\n/**\n * About what a job of this kind costs to build from its plan (AGL-3031): a\n * page job counts the layout, forms and components it builds before its\n * page; a template, layout, component, form or email job counts its own\n * record and every creation it builds before it (AGL-3143 §15); and a\n * scaffold counts what it builds. The figure the proposal shows beside\n * Confirm, and the hold a confirmation takes.\n */\nexport function aiJobPlanCreditEstimate(kind: string, plan: AiBuildPlan): number {\n const creates =\n kind === 'page' ? AI_PAGE_CREATE_KINDS : AI_JOB_CREATE_KINDS[kind as keyof typeof AI_JOB_CREATE_KINDS]\n return aiPlanCreditEstimate(plan, creates ? { creates } : {})\n}\n\n/**\n * About what a scaffold of this many pages costs, before a plan names its\n * sections: the same arithmetic on the nominal section count, plus the plan\n * step that proposes it. Shown where a member asks for a scaffold.\n */\nexport function aiSiteCreditEstimate(\n pages: number,\n options: { welcomeEmail?: boolean } = {},\n): number {\n const screens = Math.max(0, Math.floor(pages))\n const passes =\n 1 +\n screens * (AI_SITE_NOMINAL_SECTIONS + 1) +\n 2 +\n (options.welcomeEmail ? 1 : 0)\n return passes * AI_SITE_PASS_CREDITS\n}\n"],"names":["AI_BUILD_PLAN_CREATION_NOUNS","aiPlanUndeclaredRefs","AI_JOB_CREATE_KINDS","AI_PAGE_CREATE_KINDS","AI_SITE_PAGES","min","max","AI_SITE_FREE_PAGES","aiSitePagesBand","freeTaste","AI_JOB_AUTO_CONFIRM_INPUT","AI_SITE_FREE_PAGES_NOTE","AI_SITE_PLAN_MAX_TOKENS","free","paid","AI_FREE_SITE_WORST_CASE_CREDITS","plan","layout","firstSection","laterSection","listing","aiFreeSiteSectionsWithin","creations","taste","credits","pages","Math","floor","before","layouts","AI_FREE_SITE_NOMINAL_SECTIONS","aiFreeSiteCreditEstimate","count","AI_SITE_MAX_SECTIONS","AI_SITE_NOMINAL_SECTIONS","AI_SITE_PASS_CREDITS","AI_SITE_INPUT_MAX_CHARS","AI_SITE_BATCH_MAX","AI_SITE_BATCH_MIN_HOST_LIMIT","AI_SITE_SUBMISSIONS","AI_SITE_SUBMISSION_CHOICES","id","label","blurb","AI_SITE_EMAIL_TYPE","aiSiteSubmissions","inputs","value","includes","ID_CHARS","parseAiSiteJobInputs","text","key","undefined","trimmed","trim","length","businessType","businessName","city","brand","audience","starter","rawPages","Number","NaN","isInteger","submissions","rawBatch","batchId","test","welcomeEmail","aiSitePagesRefusal","band","aiSiteWordsSaidAnything","words","Boolean","about","aiSiteWords","read","replace","slice","AI_SITE_CREATE_KINDS","aiSitePlanPrerequisites","seen","Set","prerequisites","add","kind","name","toLowerCase","has","push","entry","create","aiSitePlanShapeRefusal","options","screens","slugs","screen","sections","title","slug","some","nav","aiSitePlanRefusal","shape","parts","map","noun","where","listed","join","aiPlanPasses","reduce","total","creates","filter","aiPlanCreditEstimate","aiJobPlanCreditEstimate","aiSiteCreditEstimate","passes"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-job.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AI_BUILD_PLAN_CREATION_NOUNS,\n aiPlanUndeclaredRefs,\n type AiBuildPlan,\n type AiBuildPlanCreateKind,\n} from './ai-build-plan'\nimport { AI_JOB_CREATE_KINDS } from './ai-job-creations'\nimport { AI_PAGE_CREATE_KINDS } from './ai-page-job'\n\n/**\n * What a site scaffold is (AGL-2911): the inputs a `site` job is admitted\n * with, the plans a scaffold can build, and what one is estimated to cost\n * before it starts.\n *\n * A scaffold is the one job kind that builds a whole site rather than one\n * record: a plan of four to eight screens, the layout they render inside,\n * the contact form they place, a palette suggestion, and — where the email\n * step is loaded — a welcome email. It builds each of those through the job\n * kind that already owns it, so this module describes what a scaffold may\n * ask for and refuses the rest at confirmation, before a credit is spent,\n * naming what to make first.\n *\n * ── The estimate is the guard rail ───────────────────────────────────────\n *\n * A scaffold is tens of model calls, where every other kind is a few, so the\n * member reads what it will cost before they confirm the plan that spends\n * it. The figure is the machine's own nominal credits per step multiplied by\n * the passes the plan implies — an estimate, and said to be one wherever it\n * is shown.\n *\n * This module imports nothing at runtime, so the doors, the step, the batch\n * card and the plan proposal read one vocabulary.\n */\n\n/** How many pages a scaffold builds: enough to be a site, few enough to read. */\nexport const AI_SITE_PAGES = { min: 4, max: 8 } as const\n\n/**\n * How many pages a Free workspace's scaffold builds (AGL-3594): its home page\n * and the one page the brief most needs. A product decision, not a figure the\n * wall derives — the wall's proof (`jobs/ai-job-free-site.spec.ts`) shows a\n * plan and a build of this many pages fit the Free taste with room for one\n * retried section.\n */\nexport const AI_SITE_FREE_PAGES = { min: 1, max: 2 } as const\n\n/** The pages a scaffold builds for a workspace on the Free taste or on a paid plan. */\nexport function aiSitePagesBand(\n freeTaste: boolean | null | undefined,\n): { readonly min: number; readonly max: number } {\n return freeTaste ? AI_SITE_FREE_PAGES : AI_SITE_PAGES\n}\n\n/**\n * The job input a guided site start sets so its plan is confirmed for it\n * (AGL-3594); `jobs/ai-job-auto-confirm.ts` says where it is honored.\n */\nexport const AI_JOB_AUTO_CONFIRM_INPUT = 'autoConfirm'\n\n/** What a Free site start's page selector says under it (AGL-3594). */\nexport const AI_SITE_FREE_PAGES_NOTE = 'Paid plans can generate more pages.'\n\n/**\n * The plan's answer ceiling for a scaffold (AGL-3594), Free and paid. A plan\n * is an outline — each page's title, address, search listing and sections by\n * name — and the build steps write everything else, so a scaffold plans with\n * no thinking and at a ceiling sized to the outline of its largest band with\n * room to spare: about 300 tokens a page. The 13,491-token plan a Free site\n * spent 227 of its 300 credits on was its thinking, at the routing table's\n * 8,000 a call.\n */\nexport const AI_SITE_PLAN_MAX_TOKENS = { free: 2_000, paid: 4_000 } as const\n\n/** What each exchange of a Free site job comes to at its worst, in credits. */\nexport interface AiFreeSiteWorstCase {\n /** The site plan: its answer and its one re-ask, each at its ceiling. */\n plan: number\n /** The one layout a Free plan includes, built first on a site with none. */\n layout: number\n /** A page's first section pass, which writes the cached prefix. */\n firstSection: number\n /** Each later section pass. */\n laterSection: number\n /** A page's listing. */\n listing: number\n}\n\n/**\n * The Free site start's wall at its worst (AGL-3594): the site plan as\n * `jobs/ai-job-free-site.spec.ts` derives it from the plan request as it\n * stands, on the model a Free site plans with, every answer at its ceiling;\n * the build's exchanges are the Free page's (`AI_FREE_PAGE_WORST_CASE_CREDITS`),\n * which the same spec holds these to. The spec fails when a figure it derives\n * moves and this does not.\n */\nexport const AI_FREE_SITE_WORST_CASE_CREDITS: Readonly<AiFreeSiteWorstCase> = {\n plan: 35,\n layout: 64,\n firstSection: 44,\n laterSection: 20,\n listing: 3,\n}\n\n/**\n * The most sections a Free site's plan fits in the Free taste at its worst,\n * across all its pages: the plan, the layouts it builds first, each page's\n * listing and first pass, and ROOM FOR ONE RETRIED SECTION, then as many later\n * passes as the rest pays for; none when that is already past the wall. Every\n * page's first pass is counted as a cache write, which errs dear.\n */\nexport function aiFreeSiteSectionsWithin(\n creations: { layouts: number; pages: number },\n taste: number,\n credits: Readonly<AiFreeSiteWorstCase> = AI_FREE_SITE_WORST_CASE_CREDITS,\n): number {\n const pages = Math.max(1, Math.floor(creations.pages))\n const before =\n credits.plan +\n creations.layouts * credits.layout +\n pages * (credits.listing + credits.firstSection) +\n credits.firstSection\n if (before > taste) return 0\n return pages + Math.floor((taste - before) / credits.laterSection)\n}\n\n/** Sections a Free site's page is assumed to hold before its plan names them. */\nexport const AI_FREE_SITE_NOMINAL_SECTIONS = 3\n\n/**\n * About what a Free site start of this many pages costs at its worst, in\n * credits, for the dialog that asks for one: the site plan, each page's\n * listing and its sections at the nominal count, from the figures the wall is\n * proven with rather than the nominal credits a paid estimate counts.\n */\nexport function aiFreeSiteCreditEstimate(\n pages: number,\n credits: Readonly<AiFreeSiteWorstCase> = AI_FREE_SITE_WORST_CASE_CREDITS,\n): number {\n const count = Math.max(1, Math.floor(pages))\n return (\n credits.plan +\n count * (credits.listing + credits.firstSection) +\n count * (AI_FREE_SITE_NOMINAL_SECTIONS - 1) * credits.laterSection\n )\n}\n\n/**\n * The most sections one scaffolded page may hold. The plan model allows more\n * for a page job, which builds one page; a scaffold builds eight, and each\n * section is a pass of its own.\n */\nexport const AI_SITE_MAX_SECTIONS = 8\n\n/**\n * Sections a page is assumed to hold before a plan names them, for the\n * estimate a member reads when they ask for a scaffold. A declared\n * assumption, not a measurement: the plan's own section counts replace it\n * the moment the plan exists.\n */\nexport const AI_SITE_NOMINAL_SECTIONS = 5\n\n/**\n * The credits one pass is held against: the machine's\n * `AI_JOB_STEP_RESERVE_CREDITS`, declared here so the estimate stays a pure\n * number the console computes without the Admin SDK. `ai-site-job.spec.ts`\n * holds the two equal.\n */\nexport const AI_SITE_PASS_CREDITS = 50\n\n/** The longest a scaffold's scalar inputs may run; they are names, not copy. */\nexport const AI_SITE_INPUT_MAX_CHARS = 120\n\n/** The sites one agency batch may generate for in one request. */\nexport const AI_SITE_BATCH_MAX = 25\n\n/**\n * The sites a plan must hold before the org Sites page offers the batch at\n * all: the Agency and Advanced bands, which the plan's `hostLimit` is what\n * distinguishes.\n */\nexport const AI_SITE_BATCH_MIN_HOST_LIMIT = 25\n\nexport interface AiSiteJobInputs {\n /** What the business is, in a few words: the brief's subject. */\n businessType: string\n /**\n * Who the site is for, in a few words (AGL-2918); empty when nobody said.\n * The plan step puts it in front of the model on a line of its own, as it\n * does every other scalar here, so it narrows the pages a plan proposes\n * without a prompt of its own.\n */\n audience: string\n /**\n * The starter site whose shape the person liked (AGL-2918), by its\n * `STARTER_TEMPLATES` id; empty when they picked none.\n *\n * A hint carried as the id rather than the starter, because an id is what\n * the guided start's list, this door and the plan's line all agree on\n * without any of them loading a catalog of node maps. Unrecognized ids are\n * admitted for the same reason an unrecognized business type is: it is a\n * few words in a brief, not a lookup.\n */\n starter: string\n /** How many pages the member asked for. */\n pages: number\n /** The per-site variables an agency batch varies; empty when none was given. */\n businessName: string\n city: string\n brand: string\n /** Whether the scaffold also drafts a welcome email. */\n welcomeEmail: boolean\n /**\n * Where the contact form's submissions go (AGL-2918); `null` where nobody\n * said, which is every job created before the question existed and every\n * door that does not ask it.\n *\n * The one setting a new site owner actually needs made, and the one thing\n * about a contact form a model cannot know: whether a submission is a note\n * to read or a lead to chase is a decision about how the business runs. So\n * an answer BINDS the form step rather than hinting to it, and `null`\n * leaves the model's own proposal standing, exactly as it stood before.\n */\n submissions: AiSiteSubmissions | null\n /** The batch this job was created under (AGL-2911); `null` for a single site. */\n batchId: string | null\n}\n\n/**\n * Where a site's contact form sends what a visitor writes.\n *\n * Two, not three. The form step's own vocabulary has a third — a mailing\n * list — which it can only answer with a note, because the stored routing has\n * no place for a list; offering it as a question would be asking somebody to\n * pick an outcome the platform then explains it cannot store.\n */\nexport const AI_SITE_SUBMISSIONS = ['inbox', 'lead'] as const\n\nexport type AiSiteSubmissions = (typeof AI_SITE_SUBMISSIONS)[number]\n\n/**\n * What each answer to \"where do submissions go\" is called, and what it means.\n *\n * Here rather than beside the guided start's own questions, because it is not\n * only the guided start that asks: the agency batch asks the same thing of a\n * whole run (AGL-2918), and two surfaces wording one decision two ways is two\n * decisions. `ai-site-job.spec.ts` holds the list to {@link AI_SITE_SUBMISSIONS}.\n */\nexport const AI_SITE_SUBMISSION_CHOICES: ReadonlyArray<{\n id: AiSiteSubmissions\n label: string\n blurb: string\n}> = [\n {\n id: 'inbox',\n label: 'The Inbox',\n blurb: 'Every message is one to read and reply to.',\n },\n {\n id: 'lead',\n label: 'The Inbox, and CRM as a lead',\n blurb: 'Every message with an email address is also somebody to follow up.',\n },\n]\n\n/**\n * Which kind of email a scaffold's email unit is (AGL-2918), carried on the\n * derived job as `inputs.emailType`. The email step names the kind on the one\n * line it puts above the brief, and a scaffold was naming none.\n *\n * A key of the email step's own `AI_EMAIL_TYPES`, held here because neither\n * step may import the other: a scaffold delegates by job kind, and a\n * deployment that scaffolds is not necessarily one that has loaded the email\n * step. `ai-job-email-step.spec.ts` holds this key to that catalog.\n *\n * `reply` and not `welcome`. The five kinds a member picks are campaigns\n * somebody sends to an audience, and this is an answer to one person who\n * wrote in through the site; the nearest, `welcome`, is a label about signing\n * up, and a kind that misdescribes the email is worse than an absent one\n * because it is the first thing the model is told.\n */\nexport const AI_SITE_EMAIL_TYPE = 'reply'\n\n/**\n * What a site job's plan and every unit it builds are told about the\n * business's name (AGL-3596): that it is the name, used as written. Said on\n * the job's own turn, never in a cached system block, because it is one\n * site's. A model handed only what the business does named it itself —\n * \"Hillside Dog Grooming\" was built as \"Austin Paws Grooming\".\n */\nexport function aiSiteNameSentence(name: string): string {\n return `The business is named “${name}”: the site names it exactly that way, in the header, the footer, the copy and the search listing, and never by any other name.`\n}\n\n/** Where a job's inputs say submissions go, or `null` where they do not say. */\nexport function aiSiteSubmissions(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): AiSiteSubmissions | null {\n const value = inputs?.['submissions']\n return (AI_SITE_SUBMISSIONS as readonly unknown[]).includes(value)\n ? (value as AiSiteSubmissions)\n : null\n}\n\nconst ID_CHARS = /^[A-Za-z0-9_-]{1,64}$/\n\n/** A scaffold's inputs, or the sentence the door refuses them with. */\nexport function parseAiSiteJobInputs(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): AiSiteJobInputs | string {\n const text = (key: string): string | null => {\n const value = inputs?.[key]\n if (value === undefined || value === null || value === '') return ''\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length > AI_SITE_INPUT_MAX_CHARS ? null : trimmed\n }\n const businessType = text('businessType')\n if (businessType === null) {\n return `businessType must be text under ${AI_SITE_INPUT_MAX_CHARS} characters`\n }\n if (!businessType) return 'Say what kind of business the site is for'\n const businessName = text('businessName')\n const city = text('city')\n const brand = text('brand')\n const audience = text('audience')\n const starter = text('starter')\n for (const [key, value] of [\n ['businessName', businessName],\n ['city', city],\n ['brand', brand],\n ['audience', audience],\n ['starter', starter],\n ] as const) {\n if (value === null)\n return `${key} must be text under ${AI_SITE_INPUT_MAX_CHARS} characters`\n }\n const rawPages = inputs?.['pages']\n const pages =\n typeof rawPages === 'number' ? rawPages : Number(rawPages ?? NaN)\n // Read here across both bands; the door holds a workspace to its own\n // (`aiSitePagesRefusal`), since only it knows the workspace's plan.\n if (\n !Number.isInteger(pages) ||\n pages < AI_SITE_FREE_PAGES.min ||\n pages > AI_SITE_PAGES.max\n ) {\n return `pages must be a whole number from ${AI_SITE_FREE_PAGES.min} to ${AI_SITE_PAGES.max}`\n }\n // Where submissions go is admitted only as one of the two the form step\n // can actually bind; anything else is nobody having said, and the model\n // proposes as it always did rather than the door refusing the whole job.\n const submissions = aiSiteSubmissions(inputs)\n const rawBatch = inputs?.['batchId']\n const batchId =\n rawBatch === undefined || rawBatch === null || rawBatch === ''\n ? null\n : typeof rawBatch === 'string' && ID_CHARS.test(rawBatch)\n ? rawBatch\n : undefined\n if (batchId === undefined) return 'batchId is not a batch id'\n return {\n businessType,\n audience: audience as string,\n starter: starter as string,\n pages,\n businessName: businessName as string,\n city: city as string,\n brand: brand as string,\n welcomeEmail: inputs?.['welcomeEmail'] !== false,\n submissions,\n batchId,\n }\n}\n\n/**\n * Why a workspace cannot ask for this many pages (AGL-3594), in a sentence a\n * member reads; `null` when it can. A Free workspace builds one or two, and a\n * paid one four to eight, as it always has. Refused rather than clamped: the\n * guided start offers only the band, so a request outside it was not made\n * there, and a job quietly building fewer pages than it was asked for is a\n * worse answer than a sentence saying why.\n */\nexport function aiSitePagesRefusal(pages: number, freeTaste: boolean | null | undefined): string | null {\n const band = aiSitePagesBand(freeTaste)\n if (pages >= band.min && pages <= band.max) return null\n return freeTaste\n ? `A Free workspace's AI site start builds ${AI_SITE_FREE_PAGES.min} or ${AI_SITE_FREE_PAGES.max} pages. ${AI_SITE_FREE_PAGES_NOTE}`\n : `A site is planned with ${band.min} to ${band.max} pages.`\n}\n\n/**\n * What the site itself is, as a step that is NOT the scaffold reads it off a\n * job's inputs (AGL-2918).\n *\n * A scaffold delegates unit by unit under a job derived from its own — a page\n * job, a form job — and the derived job carries the scaffold's inputs. So the\n * two answers that describe the site rather than one record of it are\n * readable by every delegated step, and a step that wants them does not have\n * to know it was delegated to.\n *\n * Lenient where {@link parseAiSiteJobInputs} is strict, because it reads the\n * inputs of jobs that are not scaffolds: a page job started from the Screens\n * page has no `businessType` and no `pages`, and that is not an error here —\n * it is a site nobody described, and the answer is two empty strings.\n */\nexport interface AiSiteWords {\n /** What kind of site it is; empty when the job's inputs do not say. */\n about: string\n /** Who it is for; empty when the job's inputs do not say. */\n audience: string\n}\n\n/** Whether either half of {@link AiSiteWords} says anything. */\nexport function aiSiteWordsSaidAnything(words: AiSiteWords): boolean {\n return Boolean(words.about || words.audience)\n}\n\n/** What a job's inputs say the site is, read defensively and trimmed to the input ceiling. */\nexport function aiSiteWords(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): AiSiteWords {\n const read = (key: string): string => {\n const value = inputs?.[key]\n if (typeof value !== 'string') return ''\n return value.replace(/\\s+/g, ' ').trim().slice(0, AI_SITE_INPUT_MAX_CHARS).trim()\n }\n return { about: read('businessType'), audience: read('audience') }\n}\n\n/**\n * What a scaffold builds for itself: the layout its pages render inside, the\n * form they place, and the palette suggestion a member applies in the Theme\n * section. Anything else a plan asks to create is a job of its own.\n */\nexport const AI_SITE_CREATE_KINDS: readonly AiBuildPlanCreateKind[] = [\n 'layout',\n 'form',\n 'theme-change',\n]\n\n/** The creations a scaffold cannot build, in the order the plan lists them, each named once. */\nexport function aiSitePlanPrerequisites(\n plan: AiBuildPlan,\n): Array<{ kind: AiBuildPlanCreateKind; name: string }> {\n const seen = new Set<string>()\n const prerequisites: Array<{ kind: AiBuildPlanCreateKind; name: string }> = []\n const add = (kind: AiBuildPlanCreateKind, name: string) => {\n const key = `${kind}:${name.toLowerCase()}`\n if (seen.has(key)) return\n seen.add(key)\n prerequisites.push({ kind, name })\n }\n for (const entry of plan.create) {\n if (!AI_SITE_CREATE_KINDS.includes(entry.kind)) add(entry.kind, entry.name)\n }\n // A reference the create list does not carry is refused by the plan rules\n // before a plan is kept: rule 2 for a screen's layout, rule 7 for anything\n // else (`plan-creation-undeclared`, AGL-3040). A plan confirmed before that\n // rule existed can still carry one. It is named as a component: the\n // reference carries no kind, and the refusal must say where one is made.\n for (const { name } of aiPlanUndeclaredRefs(plan)) add('component', name)\n return prerequisites\n}\n\n/**\n * Why a scaffold cannot build a plan of this SHAPE — the page band, a page's\n * sections, its addresses, its navigation — in a sentence a member reads;\n * `null` when the shape is one it builds. The plan step holds a plan to it\n * with a re-ask (AGL-3030), and the doors hold a confirmed plan to it again\n * through `aiSitePlanRefusal`.\n */\nexport function aiSitePlanShapeRefusal(\n plan: AiBuildPlan,\n options: { freeTaste?: boolean } = {},\n): string | null {\n const { screens } = plan\n const band = aiSitePagesBand(options.freeTaste)\n if (screens.length < band.min || screens.length > band.max) {\n return `This plan builds ${screens.length} ${screens.length === 1 ? 'page' : 'pages'}, and ${\n options.freeTaste ? \"a Free workspace's site scaffold\" : 'a site scaffold'\n } builds ${band.min} to ${band.max}. Describe the site again.`\n }\n const slugs = new Set<string>()\n for (const screen of screens) {\n if (!screen.sections.length) {\n return `The page “${screen.title}” has no sections to build. Describe the site again.`\n }\n if (screen.sections.length > AI_SITE_MAX_SECTIONS) {\n return `The page “${screen.title}” has ${screen.sections.length} sections, and a scaffolded page holds ${AI_SITE_MAX_SECTIONS}. Describe the site again.`\n }\n const slug = screen.slug.trim().toLowerCase()\n if (slugs.has(slug)) {\n return `Two pages in this plan share the address ${screen.slug}. Describe the site again.`\n }\n slugs.add(slug)\n }\n if (!screens.some((screen) => screen.nav)) {\n return 'No page in this plan is in the site navigation. Describe the site again.'\n }\n return null\n}\n\n/**\n * Why a scaffold cannot build this plan, in a sentence a member reads when\n * confirming it; `null` when it can.\n */\nexport function aiSitePlanRefusal(\n plan: AiBuildPlan,\n options: { freeTaste?: boolean } = {},\n): string | null {\n const shape = aiSitePlanShapeRefusal(plan, options)\n if (shape) return shape\n const prerequisites = aiSitePlanPrerequisites(plan)\n if (!prerequisites.length) return null\n const parts = prerequisites.map(\n ({ kind, name }) =>\n `the ${AI_BUILD_PLAN_CREATION_NOUNS[kind].noun} “${name}” ${AI_BUILD_PLAN_CREATION_NOUNS[kind].where}`,\n )\n const listed =\n parts.length === 1\n ? parts[0]\n : `${parts.slice(0, -1).join(', ')} and ${parts[parts.length - 1]}`\n return `This site needs what the workspace does not have yet. Create ${listed}, then describe the site again.`\n}\n\n/** What the estimate counts a plan in: whether a welcome email follows, and the creations the job builds. */\nexport interface AiPlanPassOptions {\n welcomeEmail?: boolean\n /** The creation kinds the job builds itself; a scaffold's when absent. */\n creates?: readonly AiBuildPlanCreateKind[]\n}\n\n/**\n * The passes a plan implies: one per section of every screen, one more per\n * screen for its search listing and its draft, and one for each thing the\n * plan creates that the job builds. What the estimate is counted in.\n */\nexport function aiPlanPasses(plan: AiBuildPlan, options: AiPlanPassOptions = {}): number {\n const screens = plan.screens.reduce(\n (total, screen) => total + screen.sections.length + 1,\n 0,\n )\n const creates = options.creates ?? AI_SITE_CREATE_KINDS\n const creations = plan.create.filter((entry) => creates.includes(entry.kind)).length\n return screens + creations + (options.welcomeEmail ? 1 : 0)\n}\n\n/**\n * About what a plan costs to build, in credits. An ESTIMATE: the nominal\n * credits the machine holds per step, times the passes the plan implies.\n * What each pass actually costs is its model's tokens, recorded on the job\n * as it runs.\n */\nexport function aiPlanCreditEstimate(plan: AiBuildPlan, options: AiPlanPassOptions = {}): number {\n return aiPlanPasses(plan, options) * AI_SITE_PASS_CREDITS\n}\n\n/**\n * About what a job of this kind costs to build from its plan (AGL-3031): a\n * page job counts the layout, forms and components it builds before its\n * page; a template, layout, component, form or email job counts its own\n * record and every creation it builds before it (AGL-3143 §15); and a\n * scaffold counts what it builds. The figure the proposal shows beside\n * Confirm, and the hold a confirmation takes.\n */\nexport function aiJobPlanCreditEstimate(kind: string, plan: AiBuildPlan): number {\n const creates =\n kind === 'page' ? AI_PAGE_CREATE_KINDS : AI_JOB_CREATE_KINDS[kind as keyof typeof AI_JOB_CREATE_KINDS]\n return aiPlanCreditEstimate(plan, creates ? { creates } : {})\n}\n\n/**\n * About what a scaffold of this many pages costs, before a plan names its\n * sections: the same arithmetic on the nominal section count, plus the plan\n * step that proposes it. Shown where a member asks for a scaffold.\n */\nexport function aiSiteCreditEstimate(\n pages: number,\n options: { welcomeEmail?: boolean } = {},\n): number {\n const screens = Math.max(0, Math.floor(pages))\n const passes =\n 1 +\n screens * (AI_SITE_NOMINAL_SECTIONS + 1) +\n 2 +\n (options.welcomeEmail ? 1 : 0)\n return passes * AI_SITE_PASS_CREDITS\n}\n"],"names":["AI_BUILD_PLAN_CREATION_NOUNS","aiPlanUndeclaredRefs","AI_JOB_CREATE_KINDS","AI_PAGE_CREATE_KINDS","AI_SITE_PAGES","min","max","AI_SITE_FREE_PAGES","aiSitePagesBand","freeTaste","AI_JOB_AUTO_CONFIRM_INPUT","AI_SITE_FREE_PAGES_NOTE","AI_SITE_PLAN_MAX_TOKENS","free","paid","AI_FREE_SITE_WORST_CASE_CREDITS","plan","layout","firstSection","laterSection","listing","aiFreeSiteSectionsWithin","creations","taste","credits","pages","Math","floor","before","layouts","AI_FREE_SITE_NOMINAL_SECTIONS","aiFreeSiteCreditEstimate","count","AI_SITE_MAX_SECTIONS","AI_SITE_NOMINAL_SECTIONS","AI_SITE_PASS_CREDITS","AI_SITE_INPUT_MAX_CHARS","AI_SITE_BATCH_MAX","AI_SITE_BATCH_MIN_HOST_LIMIT","AI_SITE_SUBMISSIONS","AI_SITE_SUBMISSION_CHOICES","id","label","blurb","AI_SITE_EMAIL_TYPE","aiSiteNameSentence","name","aiSiteSubmissions","inputs","value","includes","ID_CHARS","parseAiSiteJobInputs","text","key","undefined","trimmed","trim","length","businessType","businessName","city","brand","audience","starter","rawPages","Number","NaN","isInteger","submissions","rawBatch","batchId","test","welcomeEmail","aiSitePagesRefusal","band","aiSiteWordsSaidAnything","words","Boolean","about","aiSiteWords","read","replace","slice","AI_SITE_CREATE_KINDS","aiSitePlanPrerequisites","seen","Set","prerequisites","add","kind","toLowerCase","has","push","entry","create","aiSitePlanShapeRefusal","options","screens","slugs","screen","sections","title","slug","some","nav","aiSitePlanRefusal","shape","parts","map","noun","where","listed","join","aiPlanPasses","reduce","total","creates","filter","aiPlanCreditEstimate","aiJobPlanCreditEstimate","aiSiteCreditEstimate","passes"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,4BAA4B,EAC5BC,oBAAoB,QAGf,qBAAiB;AACxB,SAASC,mBAAmB,QAAQ,wBAAoB;AACxD,SAASC,oBAAoB,QAAQ,mBAAe;AAEpD;;;;;;;;;;;;;;;;;;;;;;;CAuBC,GAED,+EAA+E,GAC/E,OAAO,MAAMC,gBAAgB;IAAEC,KAAK;IAAGC,KAAK;AAAE,EAAU;AAExD;;;;;;CAMC,GACD,OAAO,MAAMC,qBAAqB;IAAEF,KAAK;IAAGC,KAAK;AAAE,EAAU;AAE7D,qFAAqF,GACrF,OAAO,SAASE,gBACdC,SAAqC;IAErC,OAAOA,YAAYF,qBAAqBH;AAC1C;AAEA;;;CAGC,GACD,OAAO,MAAMM,4BAA4B,cAAa;AAEtD,qEAAqE,GACrE,OAAO,MAAMC,0BAA0B,sCAAqC;AAE5E;;;;;;;;CAQC,GACD,OAAO,MAAMC,0BAA0B;IAAEC,MAAM;IAAOC,MAAM;AAAM,EAAU;AAgB5E;;;;;;;CAOC,GACD,OAAO,MAAMC,kCAAiE;IAC5EC,MAAM;IACNC,QAAQ;IACRC,cAAc;IACdC,cAAc;IACdC,SAAS;AACX,EAAC;AAED;;;;;;CAMC,GACD,OAAO,SAASC,yBACdC,SAA6C,EAC7CC,KAAa,EACbC,UAAyCT,+BAA+B;IAExE,MAAMU,QAAQC,KAAKpB,GAAG,CAAC,GAAGoB,KAAKC,KAAK,CAACL,UAAUG,KAAK;IACpD,MAAMG,SACJJ,QAAQR,IAAI,GACZM,UAAUO,OAAO,GAAGL,QAAQP,MAAM,GAClCQ,QAASD,CAAAA,QAAQJ,OAAO,GAAGI,QAAQN,YAAY,AAAD,IAC9CM,QAAQN,YAAY;IACtB,IAAIU,SAASL,OAAO,OAAO;IAC3B,OAAOE,QAAQC,KAAKC,KAAK,CAAC,AAACJ,CAAAA,QAAQK,MAAK,IAAKJ,QAAQL,YAAY;AACnE;AAEA,+EAA+E,GAC/E,OAAO,MAAMW,gCAAgC,EAAC;AAE9C;;;;;CAKC,GACD,OAAO,SAASC,yBACdN,KAAa,EACbD,UAAyCT,+BAA+B;IAExE,MAAMiB,QAAQN,KAAKpB,GAAG,CAAC,GAAGoB,KAAKC,KAAK,CAACF;IACrC,OACED,QAAQR,IAAI,GACZgB,QAASR,CAAAA,QAAQJ,OAAO,GAAGI,QAAQN,YAAY,AAAD,IAC9Cc,QAASF,CAAAA,gCAAgC,CAAA,IAAKN,QAAQL,YAAY;AAEtE;AAEA;;;;CAIC,GACD,OAAO,MAAMc,uBAAuB,EAAC;AAErC;;;;;CAKC,GACD,OAAO,MAAMC,2BAA2B,EAAC;AAEzC;;;;;CAKC,GACD,OAAO,MAAMC,uBAAuB,GAAE;AAEtC,8EAA8E,GAC9E,OAAO,MAAMC,0BAA0B,IAAG;AAE1C,gEAAgE,GAChE,OAAO,MAAMC,oBAAoB,GAAE;AAEnC;;;;CAIC,GACD,OAAO,MAAMC,+BAA+B,GAAE;AA+C9C;;;;;;;CAOC,GACD,OAAO,MAAMC,sBAAsB;IAAC;IAAS;CAAO,CAAS;AAI7D;;;;;;;CAOC,GACD,OAAO,MAAMC,6BAIR;IACH;QACEC,IAAI;QACJC,OAAO;QACPC,OAAO;IACT;IACA;QACEF,IAAI;QACJC,OAAO;QACPC,OAAO;IACT;CACD,CAAA;AAED;;;;;;;;;;;;;;;CAeC,GACD,OAAO,MAAMC,qBAAqB,QAAO;AAEzC;;;;;;CAMC,GACD,OAAO,SAASC,mBAAmBC,IAAY;IAC7C,OAAO,CAAC,uBAAuB,EAAEA,KAAK,+HAA+H,CAAC;AACxK;AAEA,8EAA8E,GAC9E,OAAO,SAASC,kBACdC,MAA4D;IAE5D,MAAMC,QAAQD,0BAAAA,MAAQ,CAAC,cAAc;IACrC,OAAO,AAACT,oBAA2CW,QAAQ,CAACD,SACvDA,QACD;AACN;AAEA,MAAME,WAAW;AAEjB,qEAAqE,GACrE,OAAO,SAASC,qBACdJ,MAA4D;IAE5D,MAAMK,OAAO,CAACC;QACZ,MAAML,QAAQD,0BAAAA,MAAQ,CAACM,IAAI;QAC3B,IAAIL,UAAUM,aAAaN,UAAU,QAAQA,UAAU,IAAI,OAAO;QAClE,IAAI,OAAOA,UAAU,UAAU,OAAO;QACtC,MAAMO,UAAUP,MAAMQ,IAAI;QAC1B,OAAOD,QAAQE,MAAM,GAAGtB,0BAA0B,OAAOoB;IAC3D;IACA,MAAMG,eAAeN,KAAK;IAC1B,IAAIM,iBAAiB,MAAM;QACzB,OAAO,CAAC,gCAAgC,EAAEvB,wBAAwB,WAAW,CAAC;IAChF;IACA,IAAI,CAACuB,cAAc,OAAO;IAC1B,MAAMC,eAAeP,KAAK;IAC1B,MAAMQ,OAAOR,KAAK;IAClB,MAAMS,QAAQT,KAAK;IACnB,MAAMU,WAAWV,KAAK;IACtB,MAAMW,UAAUX,KAAK;IACrB,KAAK,MAAM,CAACC,KAAKL,MAAM,IAAI;QACzB;YAAC;YAAgBW;SAAa;QAC9B;YAAC;YAAQC;SAAK;QACd;YAAC;YAASC;SAAM;QAChB;YAAC;YAAYC;SAAS;QACtB;YAAC;YAAWC;SAAQ;KACrB,CAAW;QACV,IAAIf,UAAU,MACZ,OAAO,GAAGK,IAAI,oBAAoB,EAAElB,wBAAwB,WAAW,CAAC;IAC5E;IACA,MAAM6B,WAAWjB,0BAAAA,MAAQ,CAAC,QAAQ;IAClC,MAAMvB,QACJ,OAAOwC,aAAa,WAAWA,WAAWC,OAAOD,mBAAAA,WAAYE;IAC/D,qEAAqE;IACrE,oEAAoE;IACpE,IACE,CAACD,OAAOE,SAAS,CAAC3C,UAClBA,QAAQlB,mBAAmBF,GAAG,IAC9BoB,QAAQrB,cAAcE,GAAG,EACzB;QACA,OAAO,CAAC,kCAAkC,EAAEC,mBAAmBF,GAAG,CAAC,IAAI,EAAED,cAAcE,GAAG,EAAE;IAC9F;IACA,wEAAwE;IACxE,wEAAwE;IACxE,yEAAyE;IACzE,MAAM+D,cAActB,kBAAkBC;IACtC,MAAMsB,WAAWtB,0BAAAA,MAAQ,CAAC,UAAU;IACpC,MAAMuB,UACJD,aAAaf,aAAae,aAAa,QAAQA,aAAa,KACxD,OACA,OAAOA,aAAa,YAAYnB,SAASqB,IAAI,CAACF,YAC5CA,WACAf;IACR,IAAIgB,YAAYhB,WAAW,OAAO;IAClC,OAAO;QACLI;QACAI,UAAUA;QACVC,SAASA;QACTvC;QACAmC,cAAcA;QACdC,MAAMA;QACNC,OAAOA;QACPW,cAAczB,CAAAA,0BAAAA,MAAQ,CAAC,eAAe,MAAK;QAC3CqB;QACAE;IACF;AACF;AAEA;;;;;;;CAOC,GACD,OAAO,SAASG,mBAAmBjD,KAAa,EAAEhB,SAAqC;IACrF,MAAMkE,OAAOnE,gBAAgBC;IAC7B,IAAIgB,SAASkD,KAAKtE,GAAG,IAAIoB,SAASkD,KAAKrE,GAAG,EAAE,OAAO;IACnD,OAAOG,YACH,CAAC,wCAAwC,EAAEF,mBAAmBF,GAAG,CAAC,IAAI,EAAEE,mBAAmBD,GAAG,CAAC,QAAQ,EAAEK,yBAAyB,GAClI,CAAC,uBAAuB,EAAEgE,KAAKtE,GAAG,CAAC,IAAI,EAAEsE,KAAKrE,GAAG,CAAC,OAAO,CAAC;AAChE;AAwBA,8DAA8D,GAC9D,OAAO,SAASsE,wBAAwBC,KAAkB;IACxD,OAAOC,QAAQD,MAAME,KAAK,IAAIF,MAAMd,QAAQ;AAC9C;AAEA,4FAA4F,GAC5F,OAAO,SAASiB,YACdhC,MAA4D;IAE5D,MAAMiC,OAAO,CAAC3B;QACZ,MAAML,QAAQD,0BAAAA,MAAQ,CAACM,IAAI;QAC3B,IAAI,OAAOL,UAAU,UAAU,OAAO;QACtC,OAAOA,MAAMiC,OAAO,CAAC,QAAQ,KAAKzB,IAAI,GAAG0B,KAAK,CAAC,GAAG/C,yBAAyBqB,IAAI;IACjF;IACA,OAAO;QAAEsB,OAAOE,KAAK;QAAiBlB,UAAUkB,KAAK;IAAY;AACnE;AAEA;;;;CAIC,GACD,OAAO,MAAMG,uBAAyD;IACpE;IACA;IACA;CACD,CAAA;AAED,8FAA8F,GAC9F,OAAO,SAASC,wBACdrE,IAAiB;IAEjB,MAAMsE,OAAO,IAAIC;IACjB,MAAMC,gBAAsE,EAAE;IAC9E,MAAMC,MAAM,CAACC,MAA6B5C;QACxC,MAAMQ,MAAM,GAAGoC,KAAK,CAAC,EAAE5C,KAAK6C,WAAW,IAAI;QAC3C,IAAIL,KAAKM,GAAG,CAACtC,MAAM;QACnBgC,KAAKG,GAAG,CAACnC;QACTkC,cAAcK,IAAI,CAAC;YAAEH;YAAM5C;QAAK;IAClC;IACA,KAAK,MAAMgD,SAAS9E,KAAK+E,MAAM,CAAE;QAC/B,IAAI,CAACX,qBAAqBlC,QAAQ,CAAC4C,MAAMJ,IAAI,GAAGD,IAAIK,MAAMJ,IAAI,EAAEI,MAAMhD,IAAI;IAC5E;IACA,0EAA0E;IAC1E,2EAA2E;IAC3E,4EAA4E;IAC5E,oEAAoE;IACpE,yEAAyE;IACzE,KAAK,MAAM,EAAEA,IAAI,EAAE,IAAI7C,qBAAqBe,MAAOyE,IAAI,aAAa3C;IACpE,OAAO0C;AACT;AAEA;;;;;;CAMC,GACD,OAAO,SAASQ,uBACdhF,IAAiB,EACjBiF,UAAmC,CAAC,CAAC;IAErC,MAAM,EAAEC,OAAO,EAAE,GAAGlF;IACpB,MAAM2D,OAAOnE,gBAAgByF,QAAQxF,SAAS;IAC9C,IAAIyF,QAAQxC,MAAM,GAAGiB,KAAKtE,GAAG,IAAI6F,QAAQxC,MAAM,GAAGiB,KAAKrE,GAAG,EAAE;QAC1D,OAAO,CAAC,iBAAiB,EAAE4F,QAAQxC,MAAM,CAAC,CAAC,EAAEwC,QAAQxC,MAAM,KAAK,IAAI,SAAS,QAAQ,MAAM,EACzFuC,QAAQxF,SAAS,GAAG,qCAAqC,kBAC1D,QAAQ,EAAEkE,KAAKtE,GAAG,CAAC,IAAI,EAAEsE,KAAKrE,GAAG,CAAC,0BAA0B,CAAC;IAChE;IACA,MAAM6F,QAAQ,IAAIZ;IAClB,KAAK,MAAMa,UAAUF,QAAS;QAC5B,IAAI,CAACE,OAAOC,QAAQ,CAAC3C,MAAM,EAAE;YAC3B,OAAO,CAAC,UAAU,EAAE0C,OAAOE,KAAK,CAAC,oDAAoD,CAAC;QACxF;QACA,IAAIF,OAAOC,QAAQ,CAAC3C,MAAM,GAAGzB,sBAAsB;YACjD,OAAO,CAAC,UAAU,EAAEmE,OAAOE,KAAK,CAAC,MAAM,EAAEF,OAAOC,QAAQ,CAAC3C,MAAM,CAAC,uCAAuC,EAAEzB,qBAAqB,0BAA0B,CAAC;QAC3J;QACA,MAAMsE,OAAOH,OAAOG,IAAI,CAAC9C,IAAI,GAAGkC,WAAW;QAC3C,IAAIQ,MAAMP,GAAG,CAACW,OAAO;YACnB,OAAO,CAAC,yCAAyC,EAAEH,OAAOG,IAAI,CAAC,0BAA0B,CAAC;QAC5F;QACAJ,MAAMV,GAAG,CAACc;IACZ;IACA,IAAI,CAACL,QAAQM,IAAI,CAAC,CAACJ,SAAWA,OAAOK,GAAG,GAAG;QACzC,OAAO;IACT;IACA,OAAO;AACT;AAEA;;;CAGC,GACD,OAAO,SAASC,kBACd1F,IAAiB,EACjBiF,UAAmC,CAAC,CAAC;IAErC,MAAMU,QAAQX,uBAAuBhF,MAAMiF;IAC3C,IAAIU,OAAO,OAAOA;IAClB,MAAMnB,gBAAgBH,wBAAwBrE;IAC9C,IAAI,CAACwE,cAAc9B,MAAM,EAAE,OAAO;IAClC,MAAMkD,QAAQpB,cAAcqB,GAAG,CAC7B,CAAC,EAAEnB,IAAI,EAAE5C,IAAI,EAAE,GACb,CAAC,IAAI,EAAE9C,4BAA4B,CAAC0F,KAAK,CAACoB,IAAI,CAAC,EAAE,EAAEhE,KAAK,EAAE,EAAE9C,4BAA4B,CAAC0F,KAAK,CAACqB,KAAK,EAAE;IAE1G,MAAMC,SACJJ,MAAMlD,MAAM,KAAK,IACbkD,KAAK,CAAC,EAAE,GACR,GAAGA,MAAMzB,KAAK,CAAC,GAAG,CAAC,GAAG8B,IAAI,CAAC,MAAM,KAAK,EAAEL,KAAK,CAACA,MAAMlD,MAAM,GAAG,EAAE,EAAE;IACvE,OAAO,CAAC,6DAA6D,EAAEsD,OAAO,+BAA+B,CAAC;AAChH;AASA;;;;CAIC,GACD,OAAO,SAASE,aAAalG,IAAiB,EAAEiF,UAA6B,CAAC,CAAC;QAK7DA;IAJhB,MAAMC,UAAUlF,KAAKkF,OAAO,CAACiB,MAAM,CACjC,CAACC,OAAOhB,SAAWgB,QAAQhB,OAAOC,QAAQ,CAAC3C,MAAM,GAAG,GACpD;IAEF,MAAM2D,WAAUpB,mBAAAA,QAAQoB,OAAO,YAAfpB,mBAAmBb;IACnC,MAAM9D,YAAYN,KAAK+E,MAAM,CAACuB,MAAM,CAAC,CAACxB,QAAUuB,QAAQnE,QAAQ,CAAC4C,MAAMJ,IAAI,GAAGhC,MAAM;IACpF,OAAOwC,UAAU5E,YAAa2E,CAAAA,QAAQxB,YAAY,GAAG,IAAI,CAAA;AAC3D;AAEA;;;;;CAKC,GACD,OAAO,SAAS8C,qBAAqBvG,IAAiB,EAAEiF,UAA6B,CAAC,CAAC;IACrF,OAAOiB,aAAalG,MAAMiF,WAAW9D;AACvC;AAEA;;;;;;;CAOC,GACD,OAAO,SAASqF,wBAAwB9B,IAAY,EAAE1E,IAAiB;IACrE,MAAMqG,UACJ3B,SAAS,SAASvF,uBAAuBD,mBAAmB,CAACwF,KAAyC;IACxG,OAAO6B,qBAAqBvG,MAAMqG,UAAU;QAAEA;IAAQ,IAAI,CAAC;AAC7D;AAEA;;;;CAIC,GACD,OAAO,SAASI,qBACdhG,KAAa,EACbwE,UAAsC,CAAC,CAAC;IAExC,MAAMC,UAAUxE,KAAKpB,GAAG,CAAC,GAAGoB,KAAKC,KAAK,CAACF;IACvC,MAAMiG,SACJ,IACAxB,UAAWhE,CAAAA,2BAA2B,CAAA,IACtC,IACC+D,CAAAA,QAAQxB,YAAY,GAAG,IAAI,CAAA;IAC9B,OAAOiD,SAASvF;AAClB"}
@@ -63,7 +63,7 @@ export type AiSiteSeoFormField = (typeof AI_SITE_SEO_FORM_FIELDS)[keyof typeof A
63
63
  * What each value is held to: the lengths the SEO editors guide a listing to,
64
64
  * which are the same two the host settings form counts down from.
65
65
  */
66
- export declare const AI_SITE_SEO_LIMITS: Readonly<Record<"title" | "description", number>>;
66
+ export declare const AI_SITE_SEO_LIMITS: Readonly<Record<"description" | "title", number>>;
67
67
  /** The kind an `seo` output carries when it is a guided start's site listing. */
68
68
  export declare const AI_SITE_SEO_PROPOSAL_KIND = "site-listing";
69
69
  /** The id the scaffold reports its one site listing under. */
@@ -118,8 +118,11 @@ export declare function aiSiteStartBrief(answers: AiSiteStartAnswers): string;
118
118
  * without a prompt of their own.
119
119
  *
120
120
  * The per-site variables an agency batch fills — the business name, the city,
121
- * the brand — are left empty: this is one person's own site, and its name is
122
- * on the site already.
121
+ * the brand — are left empty here: the questions never ask them. The name is
122
+ * the site's own, and the create door fills it from the site
123
+ * (`aiJobAdmittedInputs`, AGL-3596) when the job is started — a job that
124
+ * reached the model with no name had the model invent one for its header and
125
+ * footer.
123
126
  */
124
127
  export declare function aiSiteStartInputs(answers: AiSiteStartAnswers): Omit<AiSiteJobInputs, 'batchId'>;
125
128
  /** The site inputs a guided start's answers become, by name: what a summary carries back (AGL-3596). */
@@ -140,8 +140,11 @@
140
140
  * without a prompt of their own.
141
141
  *
142
142
  * The per-site variables an agency batch fills — the business name, the city,
143
- * the brand — are left empty: this is one person's own site, and its name is
144
- * on the site already.
143
+ * the brand — are left empty here: the questions never ask them. The name is
144
+ * the site's own, and the create door fills it from the site
145
+ * (`aiJobAdmittedInputs`, AGL-3596) when the job is started — a job that
146
+ * reached the model with no name had the model invent one for its header and
147
+ * footer.
145
148
  */ export function aiSiteStartInputs(answers) {
146
149
  var _ref;
147
150
  const example = aiSiteStartExample(answers);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-start.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AI_SITE_INPUT_MAX_CHARS,\n AI_SITE_SUBMISSIONS,\n aiSitePagesBand,\n type AiSiteJobInputs,\n type AiSiteSubmissions,\n} from './ai-site-job'\n\n/**\n * The guided start (AGL-2918): the questions a person answers on a site they\n * have just made, and the site job those answers become.\n *\n * A scaffold already exists — the `site` job plans four to eight pages, a\n * layout, a contact form, a palette and a welcome email, and builds every one\n * of them as a draft. What it had no way in through was a brief: somebody had\n * to know what to write, on a site where nothing has happened yet. So this\n * module is the QUESTIONS, and nothing else: what is asked, what an answer may\n * be, and how a set of answers becomes the brief and the scalar inputs the\n * `site` door already takes.\n *\n * ── Answering is never the way out ───────────────────────────────────────\n *\n * There is no answer here that means \"not this\". The way past the questions\n * is the `hostFirstRun` zone's own `startBlank`, which is drawn beside them\n * from the first one and creates nothing at all — see the zone in\n * `CONSOLE_WIDGET_SLOTS`. Putting the exit inside the question set would make\n * leaving a thing you answer your way to, and it is not.\n *\n * ── Every answer but the first is optional ───────────────────────────────\n *\n * Only what a site is FOR has no default worth guessing. The audience narrows\n * the copy, the example steers the shape, the page count and the welcome\n * email are defaults a person may change — so a person who types six words\n * and confirms gets a planned site, and the rest of the questions are there\n * for the person who wants to answer them.\n *\n * Pure: shapes, vocabularies and two string builders. No React, no network,\n * and no import of the starter catalog — {@link AI_SITE_START_EXAMPLES} names\n * the starters rather than reading them, so the console does not carry a\n * catalog of node maps to draw five cards. `ai-site-start.spec.ts` holds the\n * two lists to each other.\n */\n\n/** A worked example a person picks to steer what the site should look like. */\nexport interface AiSiteStartExample {\n /** A `STARTER_TEMPLATES` id, which is a persisted identifier. */\n id: string\n /** The starter as its card names it. */\n label: string\n /** The starter's category, which is how the examples group. */\n category: string\n /** What the example is, in one line a person reads before picking it. */\n blurb: string\n}\n\n/**\n * The examples the start offers, in the order they are shown. The platform's\n * own starter sites: a person who has no words for what they want has seen\n * these five shapes before, and picking one says more than a sentence would.\n */\nexport const AI_SITE_START_EXAMPLES: readonly AiSiteStartExample[] = [\n {\n id: 'business',\n label: 'Business',\n category: 'Business',\n blurb: 'A home page led by what you do, an about page and a contact page',\n },\n {\n id: 'landing',\n label: 'Landing Page',\n category: 'Marketing',\n blurb: 'One page that leads to one action, with the reasons to take it',\n },\n {\n id: 'portfolio',\n label: 'Portfolio',\n category: 'Personal',\n blurb: 'An introduction, a grid of work and a way to get in touch',\n },\n {\n id: 'physical-shop',\n label: 'Shop (physical products)',\n category: 'Commerce',\n blurb: 'Featured products, a shop to browse, and a cart',\n },\n {\n id: 'digital-shop',\n label: 'Shop (digital products)',\n category: 'Commerce',\n blurb: 'Downloads up front, a shop to browse, and a cart',\n },\n]\n\n/**\n * Kinds of site offered beside the first question's box, so answering it can\n * be a tap. They fill the box rather than replacing it: what somebody is\n * building is theirs to say, and a list of eight is a list somebody is not on.\n */\nexport const AI_SITE_START_TYPES: readonly string[] = [\n 'a local service business',\n 'a restaurant or cafe',\n 'a professional practice',\n 'a trades business',\n 'a shop',\n 'a nonprofit',\n 'a personal portfolio',\n 'an event',\n]\n\n/** What a person answered; the shape the card holds and the brief is built from. */\nexport interface AiSiteStartAnswers {\n /** What kind of site this is. The one answer with no default. */\n siteType: string\n /** Who the site is for; empty when they did not say. */\n audience: string\n /** The example they liked, by {@link AiSiteStartExample.id}; `null` for none. */\n example: string | null\n /** How many pages to plan. */\n pages: number\n /** Where the contact form's submissions go. */\n submissions: AiSiteSubmissions\n /** Whether the scaffold also drafts a welcome email. */\n welcomeEmail: boolean\n}\n\n/**\n * What the questions start on. Five pages because it is the middle of the\n * band the scaffold builds, a welcome email because the scaffold drafts one\n * unless it is told not to, and the Inbox because it is what every submission\n * does anyway — filing a lead as well is the addition, and an addition is the\n * thing a person should have to choose.\n */\nexport const AI_SITE_START_ANSWERS: AiSiteStartAnswers = {\n siteType: '',\n audience: '',\n example: null,\n pages: 5,\n submissions: 'inbox',\n welcomeEmail: true,\n}\n\n/** The example an answer names, or `null` when it names none this list knows. */\nexport function aiSiteStartExample(\n answers: AiSiteStartAnswers,\n): AiSiteStartExample | null {\n if (!answers.example) return null\n return AI_SITE_START_EXAMPLES.find((entry) => entry.id === answers.example) ?? null\n}\n\n/**\n * Why these answers cannot start a site, in a sentence a person reads; `null`\n * when they can. The door's own parser is what actually admits the job — this\n * is what the card says before it asks for one.\n */\nexport function aiSiteStartRefusal(\n answers: AiSiteStartAnswers,\n options: { freeTaste?: boolean } = {},\n): string | null {\n // The workspace's own band (AGL-3594): one or two pages on the Free taste.\n const band = aiSitePagesBand(options.freeTaste)\n const siteType = answers.siteType.trim()\n if (!siteType) return 'Say what kind of site this is.'\n if (siteType.length > AI_SITE_INPUT_MAX_CHARS) {\n return `Keep what kind of site this is under ${AI_SITE_INPUT_MAX_CHARS} characters.`\n }\n if (answers.audience.trim().length > AI_SITE_INPUT_MAX_CHARS) {\n return `Keep who the site is for under ${AI_SITE_INPUT_MAX_CHARS} characters.`\n }\n if (!Number.isInteger(answers.pages) || answers.pages < band.min || answers.pages > band.max) {\n return `A site is planned with ${band.min} to ${band.max} pages.`\n }\n if (answers.example !== null && !aiSiteStartExample(answers)) {\n return 'Pick one of the examples, or none of them.'\n }\n if (!(AI_SITE_SUBMISSIONS as readonly string[]).includes(answers.submissions)) {\n return 'Say where the contact form’s submissions go.'\n }\n return null\n}\n\n/**\n * The answers as the brief the `site` job is created with: the plan step reads\n * it as the person's own words, so it says what they said and asks for nothing\n * they did not.\n */\nexport function aiSiteStartBrief(answers: AiSiteStartAnswers): string {\n const siteType = answers.siteType.trim()\n const audience = answers.audience.trim()\n const example = aiSiteStartExample(answers)\n const sentences = [\n `A ${answers.pages}-page website for ${siteType}.`,\n ]\n if (audience) sentences.push(`It is for ${audience}.`)\n if (example) {\n sentences.push(\n `Follow the shape of the ${example.label} starter: ${example.blurb.toLowerCase()}.`,\n )\n }\n sentences.push(\n 'Plan a page for each thing a visitor comes to do, and a contact form on the page that asks to be contacted.',\n )\n if (audience) {\n // Said of the FORM as well as of the copy: who fills a form in is what\n // decides which fields it asks for, and a brief that named the audience\n // once, in a sentence about the pages, left the form to guess.\n sentences.push(`Ask the contact form for what ${audience} would be asked for.`)\n }\n return sentences.join(' ')\n}\n\n/**\n * The answers as the `site` job's scalar inputs. Read by the door's\n * {@link parseAiSiteJobInputs}, and put in front of the model one per line by\n * the plan step, which is how the audience and the example reach the plan\n * without a prompt of their own.\n *\n * The per-site variables an agency batch fills — the business name, the city,\n * the brand — are left empty: this is one person's own site, and its name is\n * on the site already.\n */\nexport function aiSiteStartInputs(\n answers: AiSiteStartAnswers,\n): Omit<AiSiteJobInputs, 'batchId'> {\n const example = aiSiteStartExample(answers)\n return {\n businessType: answers.siteType.trim(),\n audience: answers.audience.trim(),\n starter: example?.id ?? '',\n pages: answers.pages,\n businessName: '',\n city: '',\n brand: '',\n submissions: answers.submissions,\n welcomeEmail: answers.welcomeEmail,\n }\n}\n\n/** The site inputs a guided start's answers become, by name: what a summary carries back (AGL-3596). */\nexport const AI_SITE_START_INPUT_KEYS = [\n 'businessType',\n 'audience',\n 'starter',\n 'pages',\n 'businessName',\n 'city',\n 'brand',\n 'submissions',\n 'welcomeEmail',\n] as const\n\n/** A site job's scalar inputs, and nothing else its inputs carry; `null` when it has none. */\nexport function aiSiteStartInputsOf(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): Record<string, unknown> | null {\n if (!inputs) return null\n const kept: Record<string, unknown> = {}\n for (const key of AI_SITE_START_INPUT_KEYS) {\n const value = inputs[key]\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') kept[key] = value\n }\n return Object.keys(kept).length ? kept : null\n}\n\n/**\n * A failed start's answers, back as the questions hold them (AGL-3596), so\n * Try again reopens the guided start filled in for the person to adjust:\n * the inverse of {@link aiSiteStartInputs}. Read defensively — an answer the\n * questions no longer offer goes back to its default — and held to the\n * workspace's page band. `null` when the inputs say no kind of site at all.\n */\nexport function aiSiteStartAnswersFromInputs(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n options: { freeTaste?: boolean } = {},\n): AiSiteStartAnswers | null {\n const text = (key: string) => (typeof inputs?.[key] === 'string' ? (inputs[key] as string).trim() : '')\n const siteType = text('businessType')\n if (!siteType) return null\n const band = aiSitePagesBand(options.freeTaste)\n const pages = typeof inputs?.['pages'] === 'number' ? Math.round(inputs['pages'] as number) : AI_SITE_START_ANSWERS.pages\n const starter = text('starter')\n const submissions = text('submissions')\n return {\n siteType,\n audience: text('audience'),\n example: AI_SITE_START_EXAMPLES.some((entry) => entry.id === starter) ? starter : null,\n pages: Math.min(band.max, Math.max(band.min, pages)),\n submissions: (AI_SITE_SUBMISSIONS as readonly string[]).includes(submissions)\n ? (submissions as AiSiteSubmissions)\n : AI_SITE_START_ANSWERS.submissions,\n welcomeEmail: options.freeTaste ? false : inputs?.['welcomeEmail'] !== false,\n }\n}\n"],"names":["AI_SITE_INPUT_MAX_CHARS","AI_SITE_SUBMISSIONS","aiSitePagesBand","AI_SITE_START_EXAMPLES","id","label","category","blurb","AI_SITE_START_TYPES","AI_SITE_START_ANSWERS","siteType","audience","example","pages","submissions","welcomeEmail","aiSiteStartExample","answers","find","entry","aiSiteStartRefusal","options","band","freeTaste","trim","length","Number","isInteger","min","max","includes","aiSiteStartBrief","sentences","push","toLowerCase","join","aiSiteStartInputs","businessType","starter","businessName","city","brand","AI_SITE_START_INPUT_KEYS","aiSiteStartInputsOf","inputs","kept","key","value","Object","keys","aiSiteStartAnswersFromInputs","text","Math","round","some"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-start.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AI_SITE_INPUT_MAX_CHARS,\n AI_SITE_SUBMISSIONS,\n aiSitePagesBand,\n type AiSiteJobInputs,\n type AiSiteSubmissions,\n} from './ai-site-job'\n\n/**\n * The guided start (AGL-2918): the questions a person answers on a site they\n * have just made, and the site job those answers become.\n *\n * A scaffold already exists — the `site` job plans four to eight pages, a\n * layout, a contact form, a palette and a welcome email, and builds every one\n * of them as a draft. What it had no way in through was a brief: somebody had\n * to know what to write, on a site where nothing has happened yet. So this\n * module is the QUESTIONS, and nothing else: what is asked, what an answer may\n * be, and how a set of answers becomes the brief and the scalar inputs the\n * `site` door already takes.\n *\n * ── Answering is never the way out ───────────────────────────────────────\n *\n * There is no answer here that means \"not this\". The way past the questions\n * is the `hostFirstRun` zone's own `startBlank`, which is drawn beside them\n * from the first one and creates nothing at all — see the zone in\n * `CONSOLE_WIDGET_SLOTS`. Putting the exit inside the question set would make\n * leaving a thing you answer your way to, and it is not.\n *\n * ── Every answer but the first is optional ───────────────────────────────\n *\n * Only what a site is FOR has no default worth guessing. The audience narrows\n * the copy, the example steers the shape, the page count and the welcome\n * email are defaults a person may change — so a person who types six words\n * and confirms gets a planned site, and the rest of the questions are there\n * for the person who wants to answer them.\n *\n * Pure: shapes, vocabularies and two string builders. No React, no network,\n * and no import of the starter catalog — {@link AI_SITE_START_EXAMPLES} names\n * the starters rather than reading them, so the console does not carry a\n * catalog of node maps to draw five cards. `ai-site-start.spec.ts` holds the\n * two lists to each other.\n */\n\n/** A worked example a person picks to steer what the site should look like. */\nexport interface AiSiteStartExample {\n /** A `STARTER_TEMPLATES` id, which is a persisted identifier. */\n id: string\n /** The starter as its card names it. */\n label: string\n /** The starter's category, which is how the examples group. */\n category: string\n /** What the example is, in one line a person reads before picking it. */\n blurb: string\n}\n\n/**\n * The examples the start offers, in the order they are shown. The platform's\n * own starter sites: a person who has no words for what they want has seen\n * these five shapes before, and picking one says more than a sentence would.\n */\nexport const AI_SITE_START_EXAMPLES: readonly AiSiteStartExample[] = [\n {\n id: 'business',\n label: 'Business',\n category: 'Business',\n blurb: 'A home page led by what you do, an about page and a contact page',\n },\n {\n id: 'landing',\n label: 'Landing Page',\n category: 'Marketing',\n blurb: 'One page that leads to one action, with the reasons to take it',\n },\n {\n id: 'portfolio',\n label: 'Portfolio',\n category: 'Personal',\n blurb: 'An introduction, a grid of work and a way to get in touch',\n },\n {\n id: 'physical-shop',\n label: 'Shop (physical products)',\n category: 'Commerce',\n blurb: 'Featured products, a shop to browse, and a cart',\n },\n {\n id: 'digital-shop',\n label: 'Shop (digital products)',\n category: 'Commerce',\n blurb: 'Downloads up front, a shop to browse, and a cart',\n },\n]\n\n/**\n * Kinds of site offered beside the first question's box, so answering it can\n * be a tap. They fill the box rather than replacing it: what somebody is\n * building is theirs to say, and a list of eight is a list somebody is not on.\n */\nexport const AI_SITE_START_TYPES: readonly string[] = [\n 'a local service business',\n 'a restaurant or cafe',\n 'a professional practice',\n 'a trades business',\n 'a shop',\n 'a nonprofit',\n 'a personal portfolio',\n 'an event',\n]\n\n/** What a person answered; the shape the card holds and the brief is built from. */\nexport interface AiSiteStartAnswers {\n /** What kind of site this is. The one answer with no default. */\n siteType: string\n /** Who the site is for; empty when they did not say. */\n audience: string\n /** The example they liked, by {@link AiSiteStartExample.id}; `null` for none. */\n example: string | null\n /** How many pages to plan. */\n pages: number\n /** Where the contact form's submissions go. */\n submissions: AiSiteSubmissions\n /** Whether the scaffold also drafts a welcome email. */\n welcomeEmail: boolean\n}\n\n/**\n * What the questions start on. Five pages because it is the middle of the\n * band the scaffold builds, a welcome email because the scaffold drafts one\n * unless it is told not to, and the Inbox because it is what every submission\n * does anyway — filing a lead as well is the addition, and an addition is the\n * thing a person should have to choose.\n */\nexport const AI_SITE_START_ANSWERS: AiSiteStartAnswers = {\n siteType: '',\n audience: '',\n example: null,\n pages: 5,\n submissions: 'inbox',\n welcomeEmail: true,\n}\n\n/** The example an answer names, or `null` when it names none this list knows. */\nexport function aiSiteStartExample(\n answers: AiSiteStartAnswers,\n): AiSiteStartExample | null {\n if (!answers.example) return null\n return AI_SITE_START_EXAMPLES.find((entry) => entry.id === answers.example) ?? null\n}\n\n/**\n * Why these answers cannot start a site, in a sentence a person reads; `null`\n * when they can. The door's own parser is what actually admits the job — this\n * is what the card says before it asks for one.\n */\nexport function aiSiteStartRefusal(\n answers: AiSiteStartAnswers,\n options: { freeTaste?: boolean } = {},\n): string | null {\n // The workspace's own band (AGL-3594): one or two pages on the Free taste.\n const band = aiSitePagesBand(options.freeTaste)\n const siteType = answers.siteType.trim()\n if (!siteType) return 'Say what kind of site this is.'\n if (siteType.length > AI_SITE_INPUT_MAX_CHARS) {\n return `Keep what kind of site this is under ${AI_SITE_INPUT_MAX_CHARS} characters.`\n }\n if (answers.audience.trim().length > AI_SITE_INPUT_MAX_CHARS) {\n return `Keep who the site is for under ${AI_SITE_INPUT_MAX_CHARS} characters.`\n }\n if (!Number.isInteger(answers.pages) || answers.pages < band.min || answers.pages > band.max) {\n return `A site is planned with ${band.min} to ${band.max} pages.`\n }\n if (answers.example !== null && !aiSiteStartExample(answers)) {\n return 'Pick one of the examples, or none of them.'\n }\n if (!(AI_SITE_SUBMISSIONS as readonly string[]).includes(answers.submissions)) {\n return 'Say where the contact form’s submissions go.'\n }\n return null\n}\n\n/**\n * The answers as the brief the `site` job is created with: the plan step reads\n * it as the person's own words, so it says what they said and asks for nothing\n * they did not.\n */\nexport function aiSiteStartBrief(answers: AiSiteStartAnswers): string {\n const siteType = answers.siteType.trim()\n const audience = answers.audience.trim()\n const example = aiSiteStartExample(answers)\n const sentences = [\n `A ${answers.pages}-page website for ${siteType}.`,\n ]\n if (audience) sentences.push(`It is for ${audience}.`)\n if (example) {\n sentences.push(\n `Follow the shape of the ${example.label} starter: ${example.blurb.toLowerCase()}.`,\n )\n }\n sentences.push(\n 'Plan a page for each thing a visitor comes to do, and a contact form on the page that asks to be contacted.',\n )\n if (audience) {\n // Said of the FORM as well as of the copy: who fills a form in is what\n // decides which fields it asks for, and a brief that named the audience\n // once, in a sentence about the pages, left the form to guess.\n sentences.push(`Ask the contact form for what ${audience} would be asked for.`)\n }\n return sentences.join(' ')\n}\n\n/**\n * The answers as the `site` job's scalar inputs. Read by the door's\n * {@link parseAiSiteJobInputs}, and put in front of the model one per line by\n * the plan step, which is how the audience and the example reach the plan\n * without a prompt of their own.\n *\n * The per-site variables an agency batch fills — the business name, the city,\n * the brand — are left empty here: the questions never ask them. The name is\n * the site's own, and the create door fills it from the site\n * (`aiJobAdmittedInputs`, AGL-3596) when the job is started — a job that\n * reached the model with no name had the model invent one for its header and\n * footer.\n */\nexport function aiSiteStartInputs(\n answers: AiSiteStartAnswers,\n): Omit<AiSiteJobInputs, 'batchId'> {\n const example = aiSiteStartExample(answers)\n return {\n businessType: answers.siteType.trim(),\n audience: answers.audience.trim(),\n starter: example?.id ?? '',\n pages: answers.pages,\n businessName: '',\n city: '',\n brand: '',\n submissions: answers.submissions,\n welcomeEmail: answers.welcomeEmail,\n }\n}\n\n/** The site inputs a guided start's answers become, by name: what a summary carries back (AGL-3596). */\nexport const AI_SITE_START_INPUT_KEYS = [\n 'businessType',\n 'audience',\n 'starter',\n 'pages',\n 'businessName',\n 'city',\n 'brand',\n 'submissions',\n 'welcomeEmail',\n] as const\n\n/** A site job's scalar inputs, and nothing else its inputs carry; `null` when it has none. */\nexport function aiSiteStartInputsOf(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): Record<string, unknown> | null {\n if (!inputs) return null\n const kept: Record<string, unknown> = {}\n for (const key of AI_SITE_START_INPUT_KEYS) {\n const value = inputs[key]\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') kept[key] = value\n }\n return Object.keys(kept).length ? kept : null\n}\n\n/**\n * A failed start's answers, back as the questions hold them (AGL-3596), so\n * Try again reopens the guided start filled in for the person to adjust:\n * the inverse of {@link aiSiteStartInputs}. Read defensively — an answer the\n * questions no longer offer goes back to its default — and held to the\n * workspace's page band. `null` when the inputs say no kind of site at all.\n */\nexport function aiSiteStartAnswersFromInputs(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n options: { freeTaste?: boolean } = {},\n): AiSiteStartAnswers | null {\n const text = (key: string) => (typeof inputs?.[key] === 'string' ? (inputs[key] as string).trim() : '')\n const siteType = text('businessType')\n if (!siteType) return null\n const band = aiSitePagesBand(options.freeTaste)\n const pages = typeof inputs?.['pages'] === 'number' ? Math.round(inputs['pages'] as number) : AI_SITE_START_ANSWERS.pages\n const starter = text('starter')\n const submissions = text('submissions')\n return {\n siteType,\n audience: text('audience'),\n example: AI_SITE_START_EXAMPLES.some((entry) => entry.id === starter) ? starter : null,\n pages: Math.min(band.max, Math.max(band.min, pages)),\n submissions: (AI_SITE_SUBMISSIONS as readonly string[]).includes(submissions)\n ? (submissions as AiSiteSubmissions)\n : AI_SITE_START_ANSWERS.submissions,\n welcomeEmail: options.freeTaste ? false : inputs?.['welcomeEmail'] !== false,\n }\n}\n"],"names":["AI_SITE_INPUT_MAX_CHARS","AI_SITE_SUBMISSIONS","aiSitePagesBand","AI_SITE_START_EXAMPLES","id","label","category","blurb","AI_SITE_START_TYPES","AI_SITE_START_ANSWERS","siteType","audience","example","pages","submissions","welcomeEmail","aiSiteStartExample","answers","find","entry","aiSiteStartRefusal","options","band","freeTaste","trim","length","Number","isInteger","min","max","includes","aiSiteStartBrief","sentences","push","toLowerCase","join","aiSiteStartInputs","businessType","starter","businessName","city","brand","AI_SITE_START_INPUT_KEYS","aiSiteStartInputsOf","inputs","kept","key","value","Object","keys","aiSiteStartAnswersFromInputs","text","Math","round","some"],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { AiImageAspectRatio, AiSvgStyle } from '../providers/image-contract';
18
+ /**
19
+ * The SVG an illustration arrives as (AGL-3602), and the one gate it passes
20
+ * before it is stored.
21
+ *
22
+ * A model writes SVG as text, and SVG is a document: it can carry script,
23
+ * event handlers, links out and HTML. The upload route strips all of that
24
+ * from anything it stores (`sanitize-svg`), so a dangerous answer could never
25
+ * be SERVED — but a stripped answer is no longer the picture the model meant,
26
+ * so this does not quietly accept one. Anything the sanitizer would have to
27
+ * remove, and anything a self-contained static picture has no use for, is a
28
+ * problem the model is told about and asked to fix once; a second answer with
29
+ * a problem is not stored at all.
30
+ *
31
+ * A picture that passes is:
32
+ *
33
+ * - one `<svg>` root in the SVG namespace, with a `viewBox` in the shape asked
34
+ * for, and balanced tags;
35
+ * - at most `AI_SVG_MAX_BYTES`;
36
+ * - self-contained: no `<image>`, no `<foreignObject>`, no `<script>`, no
37
+ * `<a>`, no event handler, no link or `url(…)` to anything but a `#fragment`
38
+ * in the same document, no `@import` and no `@font-face`;
39
+ * - static: no `<animate>`, `<set>` or other animation element;
40
+ * - for a logo mark, free of `<text>`: a mark, never a wordmark;
41
+ * - unchanged by `sanitizeSvg`.
42
+ */
43
+ /** The largest illustration stored, in bytes of UTF-8. */
44
+ export declare const AI_SVG_MAX_BYTES: number;
45
+ /** The `viewBox` each shape is drawn in. */
46
+ export declare const AI_SVG_VIEWBOX: Readonly<Record<AiImageAspectRatio, string>>;
47
+ export interface AiSvgProblem {
48
+ /** Stable per finding, for specs and logs. */
49
+ code: string;
50
+ /** What the model is told to fix. */
51
+ detail: string;
52
+ }
53
+ export interface AiSvgCheck {
54
+ /** The document to store, or `null` when it has a problem. */
55
+ svg: string | null;
56
+ problems: AiSvgProblem[];
57
+ }
58
+ /**
59
+ * The gate. `markup` is the model's answer; the result is the document to
60
+ * store, or the problems to send back.
61
+ */
62
+ export declare function checkAiSvgMarkup(markup: unknown, aspectRatio: AiImageAspectRatio, style: AiSvgStyle): AiSvgCheck;
63
+ /** A color a person may name: `#rgb` or `#rrggbb`. */
64
+ export declare function isAiSvgColor(value: unknown): value is string;
65
+ /**
66
+ * The colors a site's theme names, in the order a picture reaches for them:
67
+ * the primary and secondary colors first, then the backgrounds and text.
68
+ */
69
+ export declare function aiSvgThemePalette(colors: Readonly<Record<string, string>> | null | undefined): string[];