@aglyn/plugins-ai 1.0.0-beta.269 → 1.0.0-beta.271

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 (237) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.d.ts +1 -1
  3. package/src/lib/activity/ai-activity-actions.js +1 -0
  4. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  5. package/src/lib/assets/site-styles/beauty.webp +0 -0
  6. package/src/lib/assets/site-styles/blog.webp +0 -0
  7. package/src/lib/assets/site-styles/business.webp +0 -0
  8. package/src/lib/assets/site-styles/coming-soon.webp +0 -0
  9. package/src/lib/assets/site-styles/education.webp +0 -0
  10. package/src/lib/assets/site-styles/events.webp +0 -0
  11. package/src/lib/assets/site-styles/fitness.webp +0 -0
  12. package/src/lib/assets/site-styles/landing.webp +0 -0
  13. package/src/lib/assets/site-styles/music.webp +0 -0
  14. package/src/lib/assets/site-styles/nonprofit.webp +0 -0
  15. package/src/lib/assets/site-styles/personal.webp +0 -0
  16. package/src/lib/assets/site-styles/photography.webp +0 -0
  17. package/src/lib/assets/site-styles/portfolio.webp +0 -0
  18. package/src/lib/assets/site-styles/professional.webp +0 -0
  19. package/src/lib/assets/site-styles/realestate.webp +0 -0
  20. package/src/lib/assets/site-styles/restaurant.webp +0 -0
  21. package/src/lib/assets/site-styles/store.webp +0 -0
  22. package/src/lib/assets/site-styles/studio.webp +0 -0
  23. package/src/lib/assets/site-styles/trades.webp +0 -0
  24. package/src/lib/assets/site-styles/wellness.webp +0 -0
  25. package/src/lib/assets/site-styles/yoga.webp +0 -0
  26. package/src/lib/components/ai-credits-card.component.d.ts +8 -6
  27. package/src/lib/components/ai-credits-card.component.js +39 -22
  28. package/src/lib/components/ai-credits-card.component.js.map +1 -1
  29. package/src/lib/components/ai-job-input-form.component.d.ts +36 -0
  30. package/src/lib/components/ai-job-input-form.component.js +100 -0
  31. package/src/lib/components/ai-job-input-form.component.js.map +1 -0
  32. package/src/lib/components/ai-job-links.d.ts +6 -0
  33. package/src/lib/components/ai-job-links.js +9 -0
  34. package/src/lib/components/ai-job-links.js.map +1 -1
  35. package/src/lib/components/ai-job-requests.d.ts +4 -0
  36. package/src/lib/components/ai-job-requests.js +4 -0
  37. package/src/lib/components/ai-job-requests.js.map +1 -1
  38. package/src/lib/components/ai-products-hub-card.component.d.ts +6 -0
  39. package/src/lib/components/ai-products-hub-card.component.js +21 -6
  40. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  41. package/src/lib/components/ai-site-build-page.component.js +3 -2
  42. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  43. package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
  44. package/src/lib/components/ai-site-start-card.component.js +99 -31
  45. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  46. package/src/lib/components/ai-site-start-steps.component.d.ts +43 -2
  47. package/src/lib/components/ai-site-start-steps.component.js +651 -15
  48. package/src/lib/components/ai-site-start-steps.component.js.map +1 -1
  49. package/src/lib/components/ai-site-start-tracking.d.ts +10 -0
  50. package/src/lib/components/ai-site-start-tracking.js +14 -0
  51. package/src/lib/components/ai-site-start-tracking.js.map +1 -1
  52. package/src/lib/components/ai-site-style-previews.js +26 -22
  53. package/src/lib/components/ai-site-style-previews.js.map +1 -1
  54. package/src/lib/components/ai-store-finish-card.component.d.ts +14 -3
  55. package/src/lib/components/ai-store-finish-card.component.js +67 -9
  56. package/src/lib/components/ai-store-finish-card.component.js.map +1 -1
  57. package/src/lib/components/ai-upsell-dialog.component.d.ts +7 -5
  58. package/src/lib/components/ai-upsell-dialog.component.js +8 -6
  59. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  60. package/src/lib/components/assist-build-card.component.js +14 -3
  61. package/src/lib/components/assist-build-card.component.js.map +1 -1
  62. package/src/lib/components/assist-jobs-drawer.component.js +9 -1
  63. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  64. package/src/lib/components/assist-panel.component.js +8 -4
  65. package/src/lib/components/assist-panel.component.js.map +1 -1
  66. package/src/lib/components/staff-org-ai-card.component.js +4 -7
  67. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  68. package/src/lib/declarations.d.ts +2 -2
  69. package/src/lib/declarations.js +9 -5
  70. package/src/lib/declarations.js.map +1 -1
  71. package/src/lib/jobs/ai-build-capabilities.js +3 -1
  72. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  73. package/src/lib/jobs/ai-business-profile-prefill.d.ts +1 -1
  74. package/src/lib/jobs/ai-business-profile-prefill.js +14 -3
  75. package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -1
  76. package/src/lib/jobs/ai-job-build-step.d.ts +3 -0
  77. package/src/lib/jobs/ai-job-build-step.js +13 -5
  78. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  79. package/src/lib/jobs/ai-job-form-step.d.ts +24 -1
  80. package/src/lib/jobs/ai-job-form-step.js +93 -7
  81. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  82. package/src/lib/jobs/ai-job-page-language.d.ts +35 -11
  83. package/src/lib/jobs/ai-job-page-language.js +68 -2
  84. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  85. package/src/lib/jobs/ai-job-page-step.d.ts +17 -0
  86. package/src/lib/jobs/ai-job-page-step.js +40 -17
  87. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  88. package/src/lib/jobs/ai-job-plan-step.d.ts +25 -2
  89. package/src/lib/jobs/ai-job-plan-step.js +103 -29
  90. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  91. package/src/lib/jobs/ai-job-settings-step.js +4 -3
  92. package/src/lib/jobs/ai-job-settings-step.js.map +1 -1
  93. package/src/lib/jobs/ai-job-site-datasets.d.ts +27 -0
  94. package/src/lib/jobs/ai-job-site-datasets.js +140 -14
  95. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  96. package/src/lib/jobs/ai-job-site-look.js +18 -5
  97. package/src/lib/jobs/ai-job-site-look.js.map +1 -1
  98. package/src/lib/jobs/ai-job-site-step.d.ts +16 -1
  99. package/src/lib/jobs/ai-job-site-step.js +95 -22
  100. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  101. package/src/lib/jobs/ai-job-text-step.d.ts +8 -1
  102. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  103. package/src/lib/jobs/ai-job-tracking-step.d.ts +14 -3
  104. package/src/lib/jobs/ai-job-tracking-step.js +28 -5
  105. package/src/lib/jobs/ai-job-tracking-step.js.map +1 -1
  106. package/src/lib/jobs/ai-jobs.d.ts +8 -1
  107. package/src/lib/jobs/ai-jobs.js +63 -8
  108. package/src/lib/jobs/ai-jobs.js.map +1 -1
  109. package/src/lib/jobs/ai-layout-owner-photos.d.ts +57 -0
  110. package/src/lib/jobs/ai-layout-owner-photos.js +110 -0
  111. package/src/lib/jobs/ai-layout-owner-photos.js.map +1 -0
  112. package/src/lib/jobs/ai-layout-stock-photos.d.ts +62 -4
  113. package/src/lib/jobs/ai-layout-stock-photos.js +523 -72
  114. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  115. package/src/lib/jobs/ai-site-inspiration-signals.d.ts +98 -0
  116. package/src/lib/jobs/ai-site-inspiration-signals.js +585 -0
  117. package/src/lib/jobs/ai-site-inspiration-signals.js.map +1 -0
  118. package/src/lib/jobs/ai-site-inspiration.d.ts +157 -0
  119. package/src/lib/jobs/ai-site-inspiration.js +346 -0
  120. package/src/lib/jobs/ai-site-inspiration.js.map +1 -0
  121. package/src/lib/jobs/ai-site-publish.d.ts +26 -1
  122. package/src/lib/jobs/ai-site-publish.js +162 -13
  123. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  124. package/src/lib/jobs/ai-theme-brand-inputs.d.ts +16 -0
  125. package/src/lib/jobs/ai-theme-brand-inputs.js +18 -4
  126. package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
  127. package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.d.ts +97 -0
  128. package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.js +137 -0
  129. package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.js.map +1 -0
  130. package/src/lib/jobs/fixtures/ai-site-inspiration-pages.d.ts +38 -0
  131. package/src/lib/jobs/fixtures/ai-site-inspiration-pages.js +103 -0
  132. package/src/lib/jobs/fixtures/ai-site-inspiration-pages.js.map +1 -0
  133. package/src/lib/jobs/fixtures/ai-stock-photo-agl3942-recordings.d.ts +29 -0
  134. package/src/lib/jobs/fixtures/ai-stock-photo-agl3942-recordings.js +291 -0
  135. package/src/lib/jobs/fixtures/ai-stock-photo-agl3942-recordings.js.map +1 -0
  136. package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.d.ts +25 -0
  137. package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.js +174 -0
  138. package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.js.map +1 -0
  139. package/src/lib/layout-language/ai-layout-compiler.d.ts +1 -1
  140. package/src/lib/layout-language/ai-layout-compiler.js +438 -25
  141. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  142. package/src/lib/layout-language/ai-layout-contact.d.ts +57 -0
  143. package/src/lib/layout-language/ai-layout-contact.js +137 -0
  144. package/src/lib/layout-language/ai-layout-contact.js.map +1 -0
  145. package/src/lib/layout-language/ai-layout-copy.d.ts +12 -0
  146. package/src/lib/layout-language/ai-layout-copy.js +34 -2
  147. package/src/lib/layout-language/ai-layout-copy.js.map +1 -1
  148. package/src/lib/layout-language/ai-layout-design.d.ts +65 -8
  149. package/src/lib/layout-language/ai-layout-design.js +411 -97
  150. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  151. package/src/lib/layout-language/ai-layout-frame.js +23 -0
  152. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  153. package/src/lib/layout-language/ai-layout-labels.d.ts +2 -0
  154. package/src/lib/layout-language/ai-layout-labels.js +12 -6
  155. package/src/lib/layout-language/ai-layout-labels.js.map +1 -1
  156. package/src/lib/layout-language/ai-layout-language.d.ts +3 -2
  157. package/src/lib/layout-language/ai-layout-language.js +4 -3
  158. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  159. package/src/lib/layout-language/ai-layout-links.d.ts +13 -0
  160. package/src/lib/layout-language/ai-layout-links.js +30 -0
  161. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  162. package/src/lib/layout-language/ai-layout-pictures.d.ts +49 -6
  163. package/src/lib/layout-language/ai-layout-pictures.js +145 -22
  164. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  165. package/src/lib/model/ai-build-job.d.ts +16 -1
  166. package/src/lib/model/ai-build-job.js +40 -0
  167. package/src/lib/model/ai-build-job.js.map +1 -1
  168. package/src/lib/model/ai-job-input.d.ts +33 -0
  169. package/src/lib/model/ai-job-input.js +59 -0
  170. package/src/lib/model/ai-job-input.js.map +1 -0
  171. package/src/lib/model/ai-job-notice.d.ts +4 -0
  172. package/src/lib/model/ai-job-notice.js +11 -1
  173. package/src/lib/model/ai-job-notice.js.map +1 -1
  174. package/src/lib/model/ai-jobs.types.d.ts +40 -1
  175. package/src/lib/model/ai-jobs.types.js.map +1 -1
  176. package/src/lib/model/ai-site-details.d.ts +122 -0
  177. package/src/lib/model/ai-site-details.js +300 -0
  178. package/src/lib/model/ai-site-details.js.map +1 -0
  179. package/src/lib/model/ai-site-entity.d.ts +181 -0
  180. package/src/lib/model/ai-site-entity.js +390 -0
  181. package/src/lib/model/ai-site-entity.js.map +1 -0
  182. package/src/lib/model/ai-site-inspiration.d.ts +72 -0
  183. package/src/lib/model/ai-site-inspiration.js +109 -0
  184. package/src/lib/model/ai-site-inspiration.js.map +1 -0
  185. package/src/lib/model/ai-site-job.d.ts +13 -1
  186. package/src/lib/model/ai-site-job.js +15 -2
  187. package/src/lib/model/ai-site-job.js.map +1 -1
  188. package/src/lib/model/ai-site-kinds.js +109 -21
  189. package/src/lib/model/ai-site-kinds.js.map +1 -1
  190. package/src/lib/model/ai-site-look.d.ts +10 -0
  191. package/src/lib/model/ai-site-look.js +24 -0
  192. package/src/lib/model/ai-site-look.js.map +1 -1
  193. package/src/lib/model/ai-site-start-seo.d.ts +19 -5
  194. package/src/lib/model/ai-site-start-seo.js +25 -7
  195. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  196. package/src/lib/model/ai-site-start-steps.d.ts +9 -3
  197. package/src/lib/model/ai-site-start-steps.js +67 -5
  198. package/src/lib/model/ai-site-start-steps.js.map +1 -1
  199. package/src/lib/model/ai-site-start.d.ts +68 -4
  200. package/src/lib/model/ai-site-start.js +166 -13
  201. package/src/lib/model/ai-site-start.js.map +1 -1
  202. package/src/lib/model/ai-upsell-copy.js +4 -1
  203. package/src/lib/model/ai-upsell-copy.js.map +1 -1
  204. package/src/lib/native-nav.d.ts +26 -0
  205. package/src/lib/native-nav.js +58 -0
  206. package/src/lib/native-nav.js.map +1 -0
  207. package/src/lib/plan-entitlements.d.ts +4 -3
  208. package/src/lib/plan-entitlements.js +3 -8
  209. package/src/lib/plan-entitlements.js.map +1 -1
  210. package/src/lib/runtime/ai-dataset-generation.d.ts +8 -1
  211. package/src/lib/runtime/ai-dataset-generation.js +12 -2
  212. package/src/lib/runtime/ai-dataset-generation.js.map +1 -1
  213. package/src/lib/runtime/ai-doctrine-validators.d.ts +26 -33
  214. package/src/lib/runtime/ai-doctrine-validators.js +54 -44
  215. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  216. package/src/lib/runtime/ai-doctrine.js +1 -1
  217. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  218. package/src/lib/runtime/ai-node-tree.d.ts +7 -0
  219. package/src/lib/runtime/ai-node-tree.js +9 -1
  220. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  221. package/src/lib/server/ai-create-route.d.ts +1 -1
  222. package/src/lib/server/ai-create-route.js +3 -4
  223. package/src/lib/server/ai-create-route.js.map +1 -1
  224. package/src/lib/server/ai-jobs-resume.js +27 -3
  225. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  226. package/src/lib/server/ai-jobs-route.js +7 -0
  227. package/src/lib/server/ai-jobs-route.js.map +1 -1
  228. package/src/lib/usage/assist-credits.d.ts +4 -4
  229. package/src/lib/usage/assist-credits.js +4 -4
  230. package/src/lib/usage/assist-credits.js.map +1 -1
  231. package/src/lib/usage/staff-org-ai-view.js +6 -3
  232. package/src/lib/usage/staff-org-ai-view.js.map +1 -1
  233. package/src/lib/usage/staff-org-ai.d.ts +4 -2
  234. package/src/lib/usage/staff-org-ai.js +7 -7
  235. package/src/lib/usage/staff-org-ai.js.map +1 -1
  236. package/src/lib/usage/staff-org-usage-ai.js +2 -2
  237. package/src/lib/usage/staff-org-usage-ai.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/plan-entitlements.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\n// Types only: the manifest generator loads this module on its own to compile\n// the plan figures, before the catalog file core reads them from exists in\n// its new shape, so nothing here may reach a core module at runtime.\nimport type { OrgPlan } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport type { PluginPlanEntitlementsDeclaration } from '@aglyn/aglyn/plugin-manager/plugin-plan-entitlements'\n\n/**\n * THE AI PLUGIN'S PLAN BAND (AGL-3080): how many AI credits each plan\n * includes a month. Compiled into core's `PLAN_ENTITLEMENTS` by the manifest\n * generator (`register.planEntitlements`), so `/pricing`, the plan comparison\n * and every gate read the same figure they always have; this is where it is\n * written, beside the reasoning that sized it.\n */\ndeclare module '@aglyn/aglyn/plugin-manager/plugin-entitlement-keys' {\n interface PluginEntitlementQuotas {\n /**\n * AI credits per calendar month — the band assist spends against.\n *\n * A CREDIT IS A UNIT OF MODEL COST, not a message. One assist action can\n * cost two orders of magnitude more than another: a question is a few\n * thousand tokens, while generating a screen carries the node tree, the\n * component catalog and the theme tokens in, structured markup out, and\n * iterates. A message allowance would price those the same, so one\n * workspace's ten screen builds would outspend another's thousand\n * questions and both would read as \"within allowance\".\n *\n * The unit is defined by `ASSIST_CREDIT_COST_USD` and the conversion\n * lives in `assist-credits.ts`. Only that module turns credits into\n * dollars; everything a customer sees counts credits, because the dollar\n * figure behind them is our provider bill and not a price.\n *\n * ## HOW THE BAND IS SIZED, and why it is not a share of the price\n *\n * A credit is a dollar of provider spend at `ASSIST_CREDIT_COST_USD`, so\n * the band IS a liability figure: the plan's whole assist give is\n * `assistCreditsPerMonth / 1000` dollars. Credits past it are not a give\n * — since AGL-2653 they are SOLD at `extraAssistCreditsUsdPer1k`, unless\n * the org's `assistOverage.hardCap` asks `reserveAssistMessage` to refuse\n * at the band instead. Sizing it as a share of the subscription price\n * says nothing about whether the tier can afford it, because the price is\n * also carrying storage, bandwidth, form submissions, dataset storage,\n * API requests, contacts and email.\n *\n * So each band is sized against what those SEVEN other terms leave.\n * Every paid band takes between a quarter and a third of that remainder,\n * which keeps the tier's worst case positive and keeps the ladder's\n * shape: the margin each tier holds at 100% of every band stays in\n * proportion to what it held before assist was sold at all.\n * `tier-margin-floor.spec.ts` is the model, and it pins both the rule and\n * the resulting figures.\n *\n * The Aglyn AI add-on (AGL-2896) adds `AI_ADDON_CREDITS_PER_MONTH[plan]`\n * to this band in `resolveOrgEntitlements` — one pool, widened, rather\n * than a second meter.\n */\n assistCreditsPerMonth?: number\n }\n}\n\n/**\n * AI credits an Enterprise agreement includes per month, before the contract\n * says otherwise.\n *\n * ## Never `UNLIMITED`, and the reason is not stylistic\n *\n * `UNLIMITED` is `Number.POSITIVE_INFINITY`, `JSON.stringify(Infinity)` is\n * `null`, and `Number(null)` is `0`. An unbounded assist band therefore\n * serialises to a band of ZERO through every route that does not also send\n * the explicit flag `restoreQuotaLimit` rebuilds from — which would hand the\n * only customers with a signed contract the one budget that refuses\n * everything. A finite number crosses the wire as itself.\n *\n * The band is also a real liability rather than a capacity we already own.\n * Storage and page views are infrastructure metered into the deal; assist is\n * a per-token charge from a third party, and generative building spends it in\n * units two orders of magnitude apart.\n *\n * ## Why this number\n *\n * Enterprise carries no list price, so the band cannot be sized the way every\n * self-serve tier's is — against what that tier's other cost terms leave out\n * of a known price. It is anchored to the top of the ladder instead: 116,000\n * credits is twice Agency's band, the rule every Enterprise fallback follows\n * since the 2026-09-07 pricing decision (see the `enterprise` row of\n * `PLAN_ENTITLEMENTS`). In cost that is $116 of provider spend a month, or\n * under 9% of any deal priced at or above the self-serve top. A deal below\n * that is sold as Agency, not written as an Enterprise agreement.\n *\n * ## It is a DEFAULT, and a contract raises it\n *\n * `resolveOrgEntitlements` applies a per-org\n * `entitlements.assistCreditsPerMonth` override ahead of this, so a deal that\n * buys more assist buys it on that org without moving the figure every other\n * agreement is measured against — the same mechanism the other contracted\n * bands use.\n */\nexport const ENTERPRISE_ASSIST_CREDITS_PER_MONTH = 116_000\n\n/**\n * The Free AI taste (AGL-2925): the credits a Free workspace draws on each\n * month, behind a hard wall.\n *\n * ## Why it exists\n *\n * So the AI landing pages can say \"generate your first page free\" and mean\n * it. Three hundred credits is one or two generated sections or a handful of\n * copy rewrites — enough to see what the assistant does, not enough to build\n * a site on. The band is a WALL: `PLAN_PRICING.free\n * .extraAssistCreditsUsdPer1k` stays `null`, so `assistBandRefuses` answers\n * true and the reservation refuses at 100% with no overage, no switch and no\n * invoice. Nothing about a Free workspace can produce a charge.\n *\n * ## What it costs, and why that is the number\n *\n * A credit is `ASSIST_CREDIT_COST_USD` of provider spend, so the band costs\n * at most **$0.30 per Free workspace per month** — asserted by\n * `apps/console/specs/tier-margin-floor.spec.ts`. It is the one AI band with\n * no invoice behind it, which is why the same issue closes every multiplier\n * a script could use before the flag flips:\n *\n * - it is metered per ACCOUNT as well as per workspace, so the three free\n * workspaces an account may hold (AGL-2265) share one 300-credit\n * allowance rather than tripling it (`reserveAssistMessage`);\n * - every free request is bounded per IP, per uid and per account per\n * day (a minimum account age, `AI_FREE_MIN_ACCOUNT_AGE_HOURS`, is\n * available and off by default — AGL-3591);\n * - a platform-wide daily ceiling on free spend pauses the taste for\n * everyone until the UTC day rolls, paid workspaces untouched.\n *\n * The account allowance reads THIS constant rather than the workspace's\n * resolved band, because a staff override that widens one workspace's band\n * is a decision about that workspace, not about how much one person may draw\n * across all of theirs.\n */\nexport const FREE_AI_TASTE_CREDITS_PER_MONTH = 300\n\n/** The AI credits each plan includes a month — see the key's own docblock. */\nexport const AI_CREDITS_BY_PLAN: Readonly<Record<OrgPlan, number>> = {\n // The Free AI taste (AGL-2925): a real band, and a wall. See the constant\n // for what it buys, what it costs and the precautions that keep one person\n // from drawing it three times over.\n free: FREE_AI_TASTE_CREDITS_PER_MONTH,\n // The first paid rung of the assist ladder (AGL-3203). Starter banded at 0\n // until 2026-09-20, which put a PAYING workspace below the Free taste's 300\n // on the one axis a visitor compares straight down the column — the\n // `/pricing` row read `300 / mo` for Free and `—` for Starter. A paid plan\n // that includes less of something than the free plan is not a packaging\n // subtlety; it is the comparison table arguing against the upgrade.\n //\n // 750 is deliberately small: 2.5x the taste, enough that the assistant is a\n // thing the tier HAS rather than a thing it is shown, and $0.75 of provider\n // spend a month at `ASSIST_CREDIT_COST_USD` against a $16 annual price — a\n // twentieth of the $8.81 the tier's other metered bands already cost, and\n // the widest margin on the ladder absorbs it with room left\n // (`tier-margin-floor.spec.ts` carries the arithmetic).\n //\n // It is NOT a wall, unlike Free's: `PLAN_PRICING.starter\n // .extraAssistCreditsUsdPer1k` is $3.00, so past 750 the tier meters and\n // bills like every paid plan above it. The two fields move together by rule\n // — a positive band with no rate beside it is usage past a bound that is\n // silently free, which `plan-entitlements.spec.ts` forbids.\n //\n // `features.aiAssist` is TRUE, and AGL-3203 shipping it false was the\n // defect AGL-3207 closes. The reasoning then was \"the band is credits, not\n // the guided rung, exactly as on Free\" — but on Free the band IS spendable,\n // through `aiGenerative`, which is the door the taste exists for. Starter\n // carried neither flag, so it was the only row on the ladder sold a band\n // with nothing to spend it through: 750 credits reachable only by buying\n // the $9 add-on that was also the only thing that made them usable. \"No\n // plan bands at zero any more\" was true of the number and false of the\n // capability.\n //\n // It opens `aiAssist` rather than `aiGenerative` because that is the shape\n // of every paid rung above it — the included band funds the guided\n // assistant and generation is the add-on. Opening the generative door here\n // instead would hand the FIRST paid rung a door Pro through Agency have to\n // buy.\n //\n // No cost moves: `tier-margin-floor.spec.ts` already prices this tier's\n // assist term at 100% burn of the 750 credits, 75¢/mo. A door does not\n // widen a band; it only makes spend that was already modeled reachable.\n //\n // The AI add-on adds `AI_ADDON_CREDITS_PER_MONTH.starter` on top and\n // switches `aiGenerative` on as well.\n starter: 750,\n // $2.75 of provider spend, against the $8.35 the tier's other seven cost\n // terms leave out of $56. See the key's docblock for why the remainder and\n // not the price is what sizes this.\n pro: 2_750,\n // $7.50, against the $22.69 the other seven terms leave out of $139.\n business: 7_500,\n // $10.00, against the $30.62 the other seven terms leave out of $249.\n scale: 10_000,\n // $13.00, against the $39.74 the other seven terms leave out of $399 — the\n // thinnest remainder on the ladder, and the tier that binds the whole\n // assist ladder's share of it.\n advanced: 13_000,\n // $58.00, against the $176.71 the other seven terms leave out of $1,299.\n agency: 58_000,\n enterprise: ENTERPRISE_ASSIST_CREDITS_PER_MONTH,\n}\n\n/** The AI plugin's plan figures, for the manifest generator. */\nexport function aiPlanEntitlements(): PluginPlanEntitlementsDeclaration {\n return {\n quotas: [\n {\n key: 'assistCreditsPerMonth',\n label: 'AI credits / mo',\n byPlan: AI_CREDITS_BY_PLAN,\n },\n ],\n }\n}\n"],"names":["ENTERPRISE_ASSIST_CREDITS_PER_MONTH","FREE_AI_TASTE_CREDITS_PER_MONTH","AI_CREDITS_BY_PLAN","free","starter","pro","business","scale","advanced","agency","enterprise","aiPlanEntitlements","quotas","key","label","byPlan"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,6EAA6E;AAC7E,2EAA2E;AAC3E,qEAAqE;AAyDrE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoCC,GACD,OAAO,MAAMA,sCAAsC,OAAO;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmCC,GACD,OAAO,MAAMC,kCAAkC,IAAG;AAElD,4EAA4E,GAC5E,OAAO,MAAMC,qBAAwD;IACnE,0EAA0E;IAC1E,2EAA2E;IAC3E,oCAAoC;IACpCC,MAAMF;IACN,2EAA2E;IAC3E,4EAA4E;IAC5E,oEAAoE;IACpE,2EAA2E;IAC3E,wEAAwE;IACxE,oEAAoE;IACpE,EAAE;IACF,4EAA4E;IAC5E,4EAA4E;IAC5E,2EAA2E;IAC3E,0EAA0E;IAC1E,4DAA4D;IAC5D,wDAAwD;IACxD,EAAE;IACF,yDAAyD;IACzD,yEAAyE;IACzE,4EAA4E;IAC5E,yEAAyE;IACzE,4DAA4D;IAC5D,EAAE;IACF,sEAAsE;IACtE,2EAA2E;IAC3E,4EAA4E;IAC5E,0EAA0E;IAC1E,yEAAyE;IACzE,yEAAyE;IACzE,wEAAwE;IACxE,uEAAuE;IACvE,cAAc;IACd,EAAE;IACF,2EAA2E;IAC3E,mEAAmE;IACnE,2EAA2E;IAC3E,2EAA2E;IAC3E,OAAO;IACP,EAAE;IACF,wEAAwE;IACxE,uEAAuE;IACvE,wEAAwE;IACxE,EAAE;IACF,qEAAqE;IACrE,sCAAsC;IACtCG,SAAS;IACT,yEAAyE;IACzE,2EAA2E;IAC3E,oCAAoC;IACpCC,KAAK;IACL,qEAAqE;IACrEC,UAAU;IACV,sEAAsE;IACtEC,OAAO;IACP,2EAA2E;IAC3E,sEAAsE;IACtE,+BAA+B;IAC/BC,UAAU;IACV,yEAAyE;IACzEC,QAAQ;IACRC,YAAYV;AACd,EAAC;AAED,8DAA8D,GAC9D,OAAO,SAASW;IACd,OAAO;QACLC,QAAQ;YACN;gBACEC,KAAK;gBACLC,OAAO;gBACPC,QAAQb;YACV;SACD;IACH;AACF"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/plan-entitlements.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\n// Types only: the manifest generator loads this module on its own to compile\n// the plan figures, before the catalog file core reads them from exists in\n// its new shape, so nothing here may reach a core module at runtime.\nimport type { OrgPlan } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport type { PluginPlanEntitlementsDeclaration } from '@aglyn/aglyn/plugin-manager/plugin-plan-entitlements'\n\n/**\n * THE AI PLUGIN'S PLAN BAND (AGL-3080): how many AI credits each plan\n * includes a month. Compiled into core's `PLAN_ENTITLEMENTS` by the manifest\n * generator (`register.planEntitlements`), so `/pricing`, the plan comparison\n * and every gate read the same figure they always have; this is where it is\n * written, beside the reasoning that sized it.\n */\ndeclare module '@aglyn/aglyn/plugin-manager/plugin-entitlement-keys' {\n interface PluginEntitlementQuotas {\n /**\n * AI credits per calendar month — the band assist spends against.\n *\n * A CREDIT IS A UNIT OF MODEL COST, not a message. One assist action can\n * cost two orders of magnitude more than another: a question is a few\n * thousand tokens, while generating a screen carries the node tree, the\n * component catalog and the theme tokens in, structured markup out, and\n * iterates. A message allowance would price those the same, so one\n * workspace's ten screen builds would outspend another's thousand\n * questions and both would read as \"within allowance\".\n *\n * The unit is defined by `ASSIST_CREDIT_COST_USD` and the conversion\n * lives in `assist-credits.ts`. Only that module turns credits into\n * dollars; everything a customer sees counts credits, because the dollar\n * figure behind them is our provider bill and not a price.\n *\n * ## HOW THE BAND IS SIZED, and why it is not a share of the price\n *\n * A credit is a dollar of provider spend at `ASSIST_CREDIT_COST_USD`, so\n * the band IS a liability figure: the plan's whole assist give is\n * `assistCreditsPerMonth / 1000` dollars. Credits past it are not a give\n * — since AGL-2653 they are SOLD at `extraAssistCreditsUsdPer1k`, unless\n * the org's `assistOverage.hardCap` asks `reserveAssistMessage` to refuse\n * at the band instead. Sizing it as a share of the subscription price\n * says nothing about whether the tier can afford it, because the price is\n * also carrying storage, bandwidth, form submissions, dataset storage,\n * API requests, contacts and email.\n *\n * So each band is sized against what those SEVEN other terms leave.\n * Every paid band takes between a quarter and a third of that remainder,\n * which keeps the tier's worst case positive and keeps the ladder's\n * shape: the margin each tier holds at 100% of every band stays in\n * proportion to what it held before assist was sold at all.\n * `tier-margin-floor.spec.ts` is the model, and it pins both the rule and\n * the resulting figures.\n *\n * ONE POOL FOR EVERYTHING (AGL-3965): this band pays for every Aglyn AI\n * action, generation included, on every plan. An Aglyn AI tier adds its\n * credits (`AI_TIERS[tier].credits`) to it in `resolveOrgEntitlements`\n * — one pool, widened, rather than a second meter.\n */\n assistCreditsPerMonth?: number\n }\n}\n\n/**\n * AI credits an Enterprise agreement includes per month, before the contract\n * says otherwise.\n *\n * ## Never `UNLIMITED`, and the reason is not stylistic\n *\n * `UNLIMITED` is `Number.POSITIVE_INFINITY`, `JSON.stringify(Infinity)` is\n * `null`, and `Number(null)` is `0`. An unbounded assist band therefore\n * serialises to a band of ZERO through every route that does not also send\n * the explicit flag `restoreQuotaLimit` rebuilds from — which would hand the\n * only customers with a signed contract the one budget that refuses\n * everything. A finite number crosses the wire as itself.\n *\n * The band is also a real liability rather than a capacity we already own.\n * Storage and page views are infrastructure metered into the deal; assist is\n * a per-token charge from a third party, and generative building spends it in\n * units two orders of magnitude apart.\n *\n * ## Why this number\n *\n * Enterprise carries no list price, so the band cannot be sized the way every\n * self-serve tier's is — against what that tier's other cost terms leave out\n * of a known price. It is anchored to the top of the ladder instead: 116,000\n * credits is twice Agency's band, the rule every Enterprise fallback follows\n * since the 2026-09-07 pricing decision (see the `enterprise` row of\n * `PLAN_ENTITLEMENTS`). In cost that is $116 of provider spend a month, or\n * under 9% of any deal priced at or above the self-serve top. A deal below\n * that is sold as Agency, not written as an Enterprise agreement.\n *\n * ## It is a DEFAULT, and a contract raises it\n *\n * `resolveOrgEntitlements` applies a per-org\n * `entitlements.assistCreditsPerMonth` override ahead of this, so a deal that\n * buys more assist buys it on that org without moving the figure every other\n * agreement is measured against — the same mechanism the other contracted\n * bands use.\n */\nexport const ENTERPRISE_ASSIST_CREDITS_PER_MONTH = 116_000\n\n/**\n * The Free AI taste (AGL-2925): the credits a Free workspace draws on each\n * month, behind a hard wall.\n *\n * ## Why it exists\n *\n * So the AI landing pages can say \"generate your first page free\" and mean\n * it. Three hundred credits is one or two generated sections or a handful of\n * copy rewrites — enough to see what the assistant does, not enough to build\n * a site on. The band is a WALL: `PLAN_PRICING.free\n * .extraAssistCreditsUsdPer1k` stays `null`, so `assistBandRefuses` answers\n * true and the reservation refuses at 100% with no overage, no switch and no\n * invoice. Nothing about a Free workspace can produce a charge.\n *\n * ## What it costs, and why that is the number\n *\n * A credit is `ASSIST_CREDIT_COST_USD` of provider spend, so the band costs\n * at most **$0.30 per Free workspace per month** — asserted by\n * `apps/console/specs/tier-margin-floor.spec.ts`. It is the one AI band with\n * no invoice behind it, which is why the same issue closes every multiplier\n * a script could use before the flag flips:\n *\n * - it is metered per ACCOUNT as well as per workspace, so the three free\n * workspaces an account may hold (AGL-2265) share one 300-credit\n * allowance rather than tripling it (`reserveAssistMessage`);\n * - every free request is bounded per IP, per uid and per account per\n * day (a minimum account age, `AI_FREE_MIN_ACCOUNT_AGE_HOURS`, is\n * available and off by default — AGL-3591);\n * - a platform-wide daily ceiling on free spend pauses the taste for\n * everyone until the UTC day rolls, paid workspaces untouched.\n *\n * The account allowance reads THIS constant rather than the workspace's\n * resolved band, because a staff override that widens one workspace's band\n * is a decision about that workspace, not about how much one person may draw\n * across all of theirs.\n */\nexport const FREE_AI_TASTE_CREDITS_PER_MONTH = 300\n\n/** The AI credits each plan includes a month — see the key's own docblock. */\nexport const AI_CREDITS_BY_PLAN: Readonly<Record<OrgPlan, number>> = {\n // The Free AI taste (AGL-2925): a real band, and a wall. See the constant\n // for what it buys, what it costs and the precautions that keep one person\n // from drawing it three times over.\n free: FREE_AI_TASTE_CREDITS_PER_MONTH,\n // The first paid rung of the assist ladder (AGL-3203). Starter banded at 0\n // until 2026-09-20, which put a PAYING workspace below the Free taste's 300\n // on the one axis a visitor compares straight down the column — the\n // `/pricing` row read `300 / mo` for Free and `—` for Starter. A paid plan\n // that includes less of something than the free plan is not a packaging\n // subtlety; it is the comparison table arguing against the upgrade.\n //\n // 750 is deliberately small: 2.5x the taste, enough that the assistant is a\n // thing the tier HAS rather than a thing it is shown, and $0.75 of provider\n // spend a month at `ASSIST_CREDIT_COST_USD` against a $16 annual price — a\n // twentieth of the $8.81 the tier's other metered bands already cost, and\n // the widest margin on the ladder absorbs it with room left\n // (`tier-margin-floor.spec.ts` carries the arithmetic).\n //\n // It is NOT a wall, unlike Free's: `PLAN_PRICING.starter\n // .extraAssistCreditsUsdPer1k` is $3.00, so past 750 the tier meters and\n // bills like every paid plan above it. The two fields move together by rule\n // — a positive band with no rate beside it is usage past a bound that is\n // silently free, which `plan-entitlements.spec.ts` forbids.\n //\n // `features.aiAssist` is TRUE, and AGL-3203 shipping it false was the\n // defect AGL-3207 closes. The reasoning then was \"the band is credits, not\n // the guided rung, exactly as on Free\" — but on Free the band IS spendable,\n // through `aiGenerative`, which is the door the taste exists for. Starter\n // carried neither flag, so it was the only row on the ladder sold a band\n // with nothing to spend it through: 750 credits reachable only by buying\n // the $9 add-on that was also the only thing that made them usable. \"No\n // plan bands at zero any more\" was true of the number and false of the\n // capability.\n //\n // Since AGL-3965 it opens `aiGenerative` too, as every paid rung does: the\n // included band pays for generation and assist alike (one pool), and an\n // Aglyn AI tier adds credits on top rather than opening a door.\n //\n // No cost moves: `tier-margin-floor.spec.ts` already prices this tier's\n // assist term at 100% burn of the 750 credits, 75¢/mo. A door does not\n // widen a band; it only makes spend that was already modeled reachable.\n starter: 750,\n // $2.75 of provider spend, against the $8.35 the tier's other seven cost\n // terms leave out of $56. See the key's docblock for why the remainder and\n // not the price is what sizes this.\n pro: 2_750,\n // $7.50, against the $22.69 the other seven terms leave out of $139.\n business: 7_500,\n // $10.00, against the $30.62 the other seven terms leave out of $249.\n scale: 10_000,\n // $13.00, against the $39.74 the other seven terms leave out of $399 — the\n // thinnest remainder on the ladder, and the tier that binds the whole\n // assist ladder's share of it.\n advanced: 13_000,\n // $58.00, against the $176.71 the other seven terms leave out of $1,299.\n agency: 58_000,\n enterprise: ENTERPRISE_ASSIST_CREDITS_PER_MONTH,\n}\n\n/** The AI plugin's plan figures, for the manifest generator. */\nexport function aiPlanEntitlements(): PluginPlanEntitlementsDeclaration {\n return {\n quotas: [\n {\n key: 'assistCreditsPerMonth',\n label: 'AI credits / mo',\n byPlan: AI_CREDITS_BY_PLAN,\n },\n ],\n }\n}\n"],"names":["ENTERPRISE_ASSIST_CREDITS_PER_MONTH","FREE_AI_TASTE_CREDITS_PER_MONTH","AI_CREDITS_BY_PLAN","free","starter","pro","business","scale","advanced","agency","enterprise","aiPlanEntitlements","quotas","key","label","byPlan"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,6EAA6E;AAC7E,2EAA2E;AAC3E,qEAAqE;AA0DrE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoCC,GACD,OAAO,MAAMA,sCAAsC,OAAO;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmCC,GACD,OAAO,MAAMC,kCAAkC,IAAG;AAElD,4EAA4E,GAC5E,OAAO,MAAMC,qBAAwD;IACnE,0EAA0E;IAC1E,2EAA2E;IAC3E,oCAAoC;IACpCC,MAAMF;IACN,2EAA2E;IAC3E,4EAA4E;IAC5E,oEAAoE;IACpE,2EAA2E;IAC3E,wEAAwE;IACxE,oEAAoE;IACpE,EAAE;IACF,4EAA4E;IAC5E,4EAA4E;IAC5E,2EAA2E;IAC3E,0EAA0E;IAC1E,4DAA4D;IAC5D,wDAAwD;IACxD,EAAE;IACF,yDAAyD;IACzD,yEAAyE;IACzE,4EAA4E;IAC5E,yEAAyE;IACzE,4DAA4D;IAC5D,EAAE;IACF,sEAAsE;IACtE,2EAA2E;IAC3E,4EAA4E;IAC5E,0EAA0E;IAC1E,yEAAyE;IACzE,yEAAyE;IACzE,wEAAwE;IACxE,uEAAuE;IACvE,cAAc;IACd,EAAE;IACF,2EAA2E;IAC3E,wEAAwE;IACxE,gEAAgE;IAChE,EAAE;IACF,wEAAwE;IACxE,uEAAuE;IACvE,wEAAwE;IACxEG,SAAS;IACT,yEAAyE;IACzE,2EAA2E;IAC3E,oCAAoC;IACpCC,KAAK;IACL,qEAAqE;IACrEC,UAAU;IACV,sEAAsE;IACtEC,OAAO;IACP,2EAA2E;IAC3E,sEAAsE;IACtE,+BAA+B;IAC/BC,UAAU;IACV,yEAAyE;IACzEC,QAAQ;IACRC,YAAYV;AACd,EAAC;AAED,8DAA8D,GAC9D,OAAO,SAASW;IACd,OAAO;QACLC,QAAQ;YACN;gBACEC,KAAK;gBACLC,OAAO;gBACPC,QAAQb;YACV;SACD;IACH;AACF"}
@@ -71,7 +71,14 @@ export interface GenerateAiDatasetInput extends AiDatasetPromptInput {
71
71
  }
72
72
  /** Design one dataset. It writes nothing to the site. */
73
73
  export declare function generateAiDataset(input: GenerateAiDatasetInput): Promise<AiValidatedGeneration<AiDataset>>;
74
- /** A designed dataset as the data plugin's `dataset` writer takes it: values by field name. */
74
+ /**
75
+ * A designed dataset as the data plugin's `dataset` writer takes it: values by
76
+ * field name. Its words' "a" and "an" agree with what follows them in English
77
+ * (`aiLayoutArticles`, AGL-3988), as a page's do: the beta.270 Sugar Hollow
78
+ * packages said "A easygoing session" in their records and on every page
79
+ * that listed them.
80
+ */
75
81
  export declare function aiDatasetDraftContent(name: string, dataset: AiDataset, options: {
76
82
  recordPages: boolean;
83
+ language?: string | null;
77
84
  }): Record<string, unknown>;
@@ -17,6 +17,7 @@
17
17
  import { AI_ROUTING_TABLE } from "../providers/routing.js";
18
18
  import { AI_DATASET_LIMITS, AI_DATASET_LIST_SEPARATOR, AI_DATASET_TOOL, AI_DATASET_TOOL_NAME, checkAiDataset } from "../tools/ai-dataset-tool.js";
19
19
  import { runValidatedGeneration } from "./ai-doctrine.js";
20
+ import { aiLayoutArticles } from "../layout-language/ai-layout-copy.js";
20
21
  /**
21
22
  * A SITE'S DATASETS (AGL-3616): the typed fields and first records of one
22
23
  * dataset a site plan created, through the doctrine's generation call and the
@@ -95,7 +96,14 @@ import { runValidatedGeneration } from "./ai-doctrine.js";
95
96
  })
96
97
  }));
97
98
  }
98
- /** A designed dataset as the data plugin's `dataset` writer takes it: values by field name. */ export function aiDatasetDraftContent(name, dataset, options) {
99
+ /**
100
+ * A designed dataset as the data plugin's `dataset` writer takes it: values by
101
+ * field name. Its words' "a" and "an" agree with what follows them in English
102
+ * (`aiLayoutArticles`, AGL-3988), as a page's do: the beta.270 Sugar Hollow
103
+ * packages said "A easygoing session" in their records and on every page
104
+ * that listed them.
105
+ */ export function aiDatasetDraftContent(name, dataset, options) {
106
+ const said = (value)=>aiLayoutArticles(value, options.language);
99
107
  const records = dataset.records.map((values)=>{
100
108
  const record = {};
101
109
  dataset.fields.forEach((field, column)=>{
@@ -103,9 +111,11 @@ import { runValidatedGeneration } from "./ai-doctrine.js";
103
111
  const value = ((_values_column = values[column]) != null ? _values_column : '').trim();
104
112
  if (!value) return;
105
113
  if (field.type === 'list') {
106
- record[field.name] = value.split(AI_DATASET_LIST_SEPARATOR.trim()).map((part)=>part.trim()).filter(Boolean);
114
+ record[field.name] = value.split(AI_DATASET_LIST_SEPARATOR.trim()).map((part)=>said(part.trim())).filter(Boolean);
107
115
  } else if (field.type === 'number' || field.type === 'integer') {
108
116
  record[field.name] = value.replace(/,/g, '');
117
+ } else if (field.type === 'text') {
118
+ record[field.name] = said(value);
109
119
  } else {
110
120
  record[field.name] = value;
111
121
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/runtime/ai-dataset-generation.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AiStepKind } from '../providers/catalog'\nimport type { AiProvider } from '../providers/contract'\nimport { AI_ROUTING_TABLE, type AiPluginSettings } from '../providers/routing'\nimport {\n AI_DATASET_LIMITS,\n AI_DATASET_LIST_SEPARATOR,\n AI_DATASET_TOOL,\n AI_DATASET_TOOL_NAME,\n checkAiDataset,\n type AiDataset,\n} from '../tools/ai-dataset-tool'\nimport { runValidatedGeneration, type AiValidatedGeneration } from './ai-doctrine'\nimport type { AiSystemBlock } from './ai-runtime'\n\n/**\n * A SITE'S DATASETS (AGL-3616): the typed fields and first records of one\n * dataset a site plan created, through the doctrine's generation call and the\n * strict `submit_dataset` tool. The data plugin's writer stores what it\n * answers; nothing here writes.\n *\n * Routed as `job.products`, the row a store's first products are proposed on\n * (no thinking, a structured list from a brief, held to the storefront claim\n * rules): one dataset of a dozen records is the same work. Its answer is held\n * to a ceiling of its own under that row's. The rules are cached and\n * byte-identical; the site's words, the dataset and the pages that show it\n * ride in the user turn.\n */\n\n/** The step a dataset is routed as. */\nexport const AI_DATASET_STEP: AiStepKind = 'job.products'\n\n/**\n * One dataset's answer ceiling: twelve fields and twelve records of a few\n * sentences each, written as JSON, about 9,000 characters at three a token\n * with room; under the routing row's.\n */\nexport const AI_DATASET_MAX_TOKENS = Math.min(4_000, AI_ROUTING_TABLE[AI_DATASET_STEP].maxTokens)\n\n/** The generation kind a dataset is asked under, which the doctrine scopes. */\nexport const AI_DATASET_KIND = 'dataset'\n\n/** The rules for one dataset. Byte-identical on every request. */\nexport const AI_DATASET_INSTRUCTIONS: AiSystemBlock[] = [\n {\n text:\n 'You design one dataset of a new website: a list of like things — a menu, a team, services, portfolio pieces, events, questions and answers — kept as records the owner edits in one place, which the site’s pages list.\\n\\n' +\n `Answer by calling ${AI_DATASET_TOOL_NAME} exactly once. A reply in prose cannot be used.\\n\\n` +\n 'Rules:\\n' +\n [\n 'Keep the planned fields, in order and named exactly as planned; the pages around the dataset already name them. Add a field only where the records clearly need it.',\n 'Type each field: text for words, including a date or a time written the way a visitor reads it; number or integer only for a figure a visitor compares; boolean for yes or no; list for a few short tags. The first field names each record and is text.',\n `Write ${AI_DATASET_LIMITS.recordsMin} to ${AI_DATASET_LIMITS.recordsMax} records, as many as the brief supports: what the business actually offers, in its own words where the brief gives them, otherwise plain and general enough to be true of it.`,\n 'Never invent a fact: no price, date, address, phone number, award, credential, client or number the brief does not give, and no review, testimonial, rating or quote from anyone. Leave a value \"\" rather than make one up.',\n 'Never give a person a name the brief does not give: a team member the brief does not name is their role, such as \"Head chef\".',\n 'Never say anything cures, treats, heals or prevents anything, and never promise a health, financial or legal result.',\n `Values are plain text with no links, markup, emoji or square brackets, at most ${AI_DATASET_LIMITS.valueMax} characters each; a description is one to three sentences.`,\n 'Write in the language of the brief.',\n ]\n .map((rule) => `- ${rule}`)\n .join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\nexport interface AiDatasetPromptInput {\n /** The site's words: the brief and its answers, as a unit's brief carries them. */\n brief: string\n /** The dataset as the plan named it, and why it has one. */\n name: string\n why: string\n /** The field names the plan gave it. */\n fields: readonly string[]\n /** The sections that list it, by page: \"Menu › Our dishes (12 items)\". */\n shownIn: readonly string[]\n /** Whether each record also gets a page of its own. */\n recordPages: boolean\n}\n\n/** The user turn: the site's words, the dataset, and where it is shown. */\nexport function aiDatasetPrompt(input: AiDatasetPromptInput): string {\n return [\n 'Brief:',\n input.brief.trim(),\n `Dataset: “${input.name}” — ${input.why.trim() || 'the site’s structured content'}`,\n `Planned fields: ${input.fields.length ? input.fields.map((field) => `“${field}”`).join(', ') : '(none: choose them)'}`,\n input.shownIn.length ? `Listed on: ${input.shownIn.join('; ')}.` : 'Listed on the site’s pages.',\n input.recordPages\n ? 'Each record also gets a page of its own, so give each one enough to read: a sentence or two describing it.'\n : 'Each record is shown as a card, so keep its values short.',\n ].join('\\n')\n}\n\nexport interface GenerateAiDatasetInput extends AiDatasetPromptInput {\n /** What a claim or a price may be quoted from: the brief and the site's answers. */\n merchantWords: string\n settings?: AiPluginSettings\n model?: string\n maxTokens?: number\n signal?: AbortSignal\n provider?: AiProvider\n}\n\n/** Design one dataset. It writes nothing to the site. */\nexport function generateAiDataset(input: GenerateAiDatasetInput): Promise<AiValidatedGeneration<AiDataset>> {\n const row = AI_ROUTING_TABLE[AI_DATASET_STEP]\n return runValidatedGeneration(AI_DATASET_KIND, {\n step: AI_DATASET_STEP,\n ...(input.settings ? { settings: input.settings } : {}),\n ...(input.model ? { model: input.model } : {}),\n instructions: AI_DATASET_INSTRUCTIONS,\n messages: [{ role: 'user', content: aiDatasetPrompt(input) }],\n tool: AI_DATASET_TOOL,\n maxTokens: input.maxTokens ?? AI_DATASET_MAX_TOKENS,\n ...(row.thinking ? { thinking: row.thinking } : {}),\n ...(row.effort ? { effort: row.effort } : {}),\n ...(input.signal ? { signal: input.signal } : {}),\n ...(input.provider ? { provider: input.provider } : {}),\n check: (answer) => checkAiDataset(answer, { planned: input.fields, merchantWords: input.merchantWords }),\n }) as Promise<AiValidatedGeneration<AiDataset>>\n}\n\n/** A designed dataset as the data plugin's `dataset` writer takes it: values by field name. */\nexport function aiDatasetDraftContent(\n name: string,\n dataset: AiDataset,\n options: { recordPages: boolean },\n): Record<string, unknown> {\n const records = dataset.records.map((values) => {\n const record: Record<string, unknown> = {}\n dataset.fields.forEach((field, column) => {\n const value = (values[column] ?? '').trim()\n if (!value) return\n if (field.type === 'list') {\n record[field.name] = value.split(AI_DATASET_LIST_SEPARATOR.trim()).map((part) => part.trim()).filter(Boolean)\n } else if (field.type === 'number' || field.type === 'integer') {\n record[field.name] = value.replace(/,/g, '')\n } else {\n record[field.name] = value\n }\n })\n return record\n })\n return {\n name,\n fields: dataset.fields.map((field) => ({ name: field.name, type: field.type })),\n records,\n // A record page shows each record at an address made from its name.\n ...(options.recordPages && dataset.fields[0] ? { pageAddressFrom: dataset.fields[0].name } : {}),\n }\n}\n"],"names":["AI_ROUTING_TABLE","AI_DATASET_LIMITS","AI_DATASET_LIST_SEPARATOR","AI_DATASET_TOOL","AI_DATASET_TOOL_NAME","checkAiDataset","runValidatedGeneration","AI_DATASET_STEP","AI_DATASET_MAX_TOKENS","Math","min","maxTokens","AI_DATASET_KIND","AI_DATASET_INSTRUCTIONS","text","recordsMin","recordsMax","valueMax","map","rule","join","cacheBreakpoint","aiDatasetPrompt","input","brief","trim","name","why","fields","length","field","shownIn","recordPages","generateAiDataset","row","step","settings","model","instructions","messages","role","content","tool","thinking","effort","signal","provider","check","answer","planned","merchantWords","aiDatasetDraftContent","dataset","options","records","values","record","forEach","column","value","type","split","part","filter","Boolean","replace","pageAddressFrom"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/runtime/ai-dataset-generation.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AiStepKind } from '../providers/catalog'\nimport type { AiProvider } from '../providers/contract'\nimport { AI_ROUTING_TABLE, type AiPluginSettings } from '../providers/routing'\nimport {\n AI_DATASET_LIMITS,\n AI_DATASET_LIST_SEPARATOR,\n AI_DATASET_TOOL,\n AI_DATASET_TOOL_NAME,\n checkAiDataset,\n type AiDataset,\n} from '../tools/ai-dataset-tool'\nimport { runValidatedGeneration, type AiValidatedGeneration } from './ai-doctrine'\nimport { aiLayoutArticles } from '../layout-language/ai-layout-copy'\nimport type { AiSystemBlock } from './ai-runtime'\n\n/**\n * A SITE'S DATASETS (AGL-3616): the typed fields and first records of one\n * dataset a site plan created, through the doctrine's generation call and the\n * strict `submit_dataset` tool. The data plugin's writer stores what it\n * answers; nothing here writes.\n *\n * Routed as `job.products`, the row a store's first products are proposed on\n * (no thinking, a structured list from a brief, held to the storefront claim\n * rules): one dataset of a dozen records is the same work. Its answer is held\n * to a ceiling of its own under that row's. The rules are cached and\n * byte-identical; the site's words, the dataset and the pages that show it\n * ride in the user turn.\n */\n\n/** The step a dataset is routed as. */\nexport const AI_DATASET_STEP: AiStepKind = 'job.products'\n\n/**\n * One dataset's answer ceiling: twelve fields and twelve records of a few\n * sentences each, written as JSON, about 9,000 characters at three a token\n * with room; under the routing row's.\n */\nexport const AI_DATASET_MAX_TOKENS = Math.min(4_000, AI_ROUTING_TABLE[AI_DATASET_STEP].maxTokens)\n\n/** The generation kind a dataset is asked under, which the doctrine scopes. */\nexport const AI_DATASET_KIND = 'dataset'\n\n/** The rules for one dataset. Byte-identical on every request. */\nexport const AI_DATASET_INSTRUCTIONS: AiSystemBlock[] = [\n {\n text:\n 'You design one dataset of a new website: a list of like things — a menu, a team, services, portfolio pieces, events, questions and answers — kept as records the owner edits in one place, which the site’s pages list.\\n\\n' +\n `Answer by calling ${AI_DATASET_TOOL_NAME} exactly once. A reply in prose cannot be used.\\n\\n` +\n 'Rules:\\n' +\n [\n 'Keep the planned fields, in order and named exactly as planned; the pages around the dataset already name them. Add a field only where the records clearly need it.',\n 'Type each field: text for words, including a date or a time written the way a visitor reads it; number or integer only for a figure a visitor compares; boolean for yes or no; list for a few short tags. The first field names each record and is text.',\n `Write ${AI_DATASET_LIMITS.recordsMin} to ${AI_DATASET_LIMITS.recordsMax} records, as many as the brief supports: what the business actually offers, in its own words where the brief gives them, otherwise plain and general enough to be true of it.`,\n 'Never invent a fact: no price, date, address, phone number, award, credential, client or number the brief does not give, and no review, testimonial, rating or quote from anyone. Leave a value \"\" rather than make one up.',\n 'Never give a person a name the brief does not give: a team member the brief does not name is their role, such as \"Head chef\".',\n 'Never say anything cures, treats, heals or prevents anything, and never promise a health, financial or legal result.',\n `Values are plain text with no links, markup, emoji or square brackets, at most ${AI_DATASET_LIMITS.valueMax} characters each; a description is one to three sentences.`,\n 'Write in the language of the brief.',\n ]\n .map((rule) => `- ${rule}`)\n .join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\nexport interface AiDatasetPromptInput {\n /** The site's words: the brief and its answers, as a unit's brief carries them. */\n brief: string\n /** The dataset as the plan named it, and why it has one. */\n name: string\n why: string\n /** The field names the plan gave it. */\n fields: readonly string[]\n /** The sections that list it, by page: \"Menu › Our dishes (12 items)\". */\n shownIn: readonly string[]\n /** Whether each record also gets a page of its own. */\n recordPages: boolean\n}\n\n/** The user turn: the site's words, the dataset, and where it is shown. */\nexport function aiDatasetPrompt(input: AiDatasetPromptInput): string {\n return [\n 'Brief:',\n input.brief.trim(),\n `Dataset: “${input.name}” — ${input.why.trim() || 'the site’s structured content'}`,\n `Planned fields: ${input.fields.length ? input.fields.map((field) => `“${field}”`).join(', ') : '(none: choose them)'}`,\n input.shownIn.length ? `Listed on: ${input.shownIn.join('; ')}.` : 'Listed on the site’s pages.',\n input.recordPages\n ? 'Each record also gets a page of its own, so give each one enough to read: a sentence or two describing it.'\n : 'Each record is shown as a card, so keep its values short.',\n ].join('\\n')\n}\n\nexport interface GenerateAiDatasetInput extends AiDatasetPromptInput {\n /** What a claim or a price may be quoted from: the brief and the site's answers. */\n merchantWords: string\n settings?: AiPluginSettings\n model?: string\n maxTokens?: number\n signal?: AbortSignal\n provider?: AiProvider\n}\n\n/** Design one dataset. It writes nothing to the site. */\nexport function generateAiDataset(input: GenerateAiDatasetInput): Promise<AiValidatedGeneration<AiDataset>> {\n const row = AI_ROUTING_TABLE[AI_DATASET_STEP]\n return runValidatedGeneration(AI_DATASET_KIND, {\n step: AI_DATASET_STEP,\n ...(input.settings ? { settings: input.settings } : {}),\n ...(input.model ? { model: input.model } : {}),\n instructions: AI_DATASET_INSTRUCTIONS,\n messages: [{ role: 'user', content: aiDatasetPrompt(input) }],\n tool: AI_DATASET_TOOL,\n maxTokens: input.maxTokens ?? AI_DATASET_MAX_TOKENS,\n ...(row.thinking ? { thinking: row.thinking } : {}),\n ...(row.effort ? { effort: row.effort } : {}),\n ...(input.signal ? { signal: input.signal } : {}),\n ...(input.provider ? { provider: input.provider } : {}),\n check: (answer) => checkAiDataset(answer, { planned: input.fields, merchantWords: input.merchantWords }),\n }) as Promise<AiValidatedGeneration<AiDataset>>\n}\n\n/**\n * A designed dataset as the data plugin's `dataset` writer takes it: values by\n * field name. Its words' \"a\" and \"an\" agree with what follows them in English\n * (`aiLayoutArticles`, AGL-3988), as a page's do: the beta.270 Sugar Hollow\n * packages said \"A easygoing session\" in their records and on every page\n * that listed them.\n */\nexport function aiDatasetDraftContent(\n name: string,\n dataset: AiDataset,\n options: { recordPages: boolean; language?: string | null },\n): Record<string, unknown> {\n const said = (value: string) => aiLayoutArticles(value, options.language)\n const records = dataset.records.map((values) => {\n const record: Record<string, unknown> = {}\n dataset.fields.forEach((field, column) => {\n const value = (values[column] ?? '').trim()\n if (!value) return\n if (field.type === 'list') {\n record[field.name] = value.split(AI_DATASET_LIST_SEPARATOR.trim()).map((part) => said(part.trim())).filter(Boolean)\n } else if (field.type === 'number' || field.type === 'integer') {\n record[field.name] = value.replace(/,/g, '')\n } else if (field.type === 'text') {\n record[field.name] = said(value)\n } else {\n record[field.name] = value\n }\n })\n return record\n })\n return {\n name,\n fields: dataset.fields.map((field) => ({ name: field.name, type: field.type })),\n records,\n // A record page shows each record at an address made from its name.\n ...(options.recordPages && dataset.fields[0] ? { pageAddressFrom: dataset.fields[0].name } : {}),\n }\n}\n"],"names":["AI_ROUTING_TABLE","AI_DATASET_LIMITS","AI_DATASET_LIST_SEPARATOR","AI_DATASET_TOOL","AI_DATASET_TOOL_NAME","checkAiDataset","runValidatedGeneration","aiLayoutArticles","AI_DATASET_STEP","AI_DATASET_MAX_TOKENS","Math","min","maxTokens","AI_DATASET_KIND","AI_DATASET_INSTRUCTIONS","text","recordsMin","recordsMax","valueMax","map","rule","join","cacheBreakpoint","aiDatasetPrompt","input","brief","trim","name","why","fields","length","field","shownIn","recordPages","generateAiDataset","row","step","settings","model","instructions","messages","role","content","tool","thinking","effort","signal","provider","check","answer","planned","merchantWords","aiDatasetDraftContent","dataset","options","said","value","language","records","values","record","forEach","column","type","split","part","filter","Boolean","replace","pageAddressFrom"],"mappings":"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"}
@@ -1242,39 +1242,32 @@ export declare function aiFreePageSectionsWithin(creations: {
1242
1242
  pages?: number;
1243
1243
  }, credits?: Readonly<AiFreePageWorstCase>): number;
1244
1244
  /**
1245
- * The most sections past the Free wall a plan is trimmed by rather than
1246
- * re-asked (AGL-3737). The live candle-shop start of 2026-10-12 planned 9
1247
- * sections against a wall of 8 on all three answers, each with a home of 7:
1248
- * the model reaching for the budget the site redo names ("plan 7 to 8") and
1249
- * landing one band past it, which no re-ask cured. Within two, the plan's
1250
- * shape is right and only a band or two the brief needs least is extra, so
1251
- * taking those from the end loses nothing the plan depends on. Further over,
1252
- * the shape itself is off — a list planned as sections, a page the wall
1253
- * cannot pay for — and cutting more would drop content the model meant, so
1254
- * that plan is still re-asked.
1255
- */
1256
- export declare const AI_FREE_WALL_SETTLE_MAX_OVER = 2;
1257
- /**
1258
- * A Free plan a section or two past the wall is trimmed to fit before the
1259
- * rules read the plan (AGL-3737), rather than re-asked: which sections go has
1260
- * one answer, so the re-ask only costs the member a stopped start. The live
1261
- * candle-shop start of 2026-10-12 asked for a home of 7 and a second page of
1262
- * 2 against a wall of 8, three answers running, and stopped needing input.
1263
- *
1264
- * One section goes at a time, the last of its page that `freeWallKeeps`
1265
- * does not hold, from the page furthest past its share: the home at / past
1266
- * its fewest (`aiSiteHomeMinSections`, five on a site start whose wall pays
1267
- * for a full home), any other page past two; on a tie, the longer page, then
1268
- * the home. No home goes under its fewest and no page under one section. The
1269
- * shares are what a site start is told to plan (a home of five or six, the
1270
- * other page two or three): the candle shop's 7 + 2 becomes 6 + 2, never
1271
- * 7 + 1, and the recorded yoga answer's 6 + 4 becomes 5 + 3, the shape its
1272
- * own redo chose.
1273
- *
1274
- * A plan more than `AI_FREE_WALL_SETTLE_MAX_OVER` past the wall, a site
1275
- * start past its page cap, a plan with a list split into sections, and a
1276
- * plan the trim cannot fit without a section it keeps are returned
1277
- * unchanged, for the wall to re-ask.
1245
+ * A Free plan past the wall is trimmed to the wall before the rules read the
1246
+ * plan (AGL-3737, AGL-3969), rather than re-asked: a plan over its budget is
1247
+ * the model's mistake, never the member's, and which sections go has one
1248
+ * answer. The live candle-shop start of 2026-10-12 asked for a home of 7 and
1249
+ * a second page of 2 against a wall of 8, three answers running, and stopped
1250
+ * needing input; the Free candle shop of the beta.265 eval asked for 11, and
1251
+ * its re-ask still for more than 8, and was refused (AGL-3969).
1252
+ *
1253
+ * One section goes at a time, exactly as many as the plan is over, so the
1254
+ * plan keeps the whole budget. Each goes from the lowest tier any page still
1255
+ * has: first a section `freeWallKeeps` does not hold; then one it holds only
1256
+ * by its name (a contact, a call to action, a storefront's sign-up); last one
1257
+ * that places something every use of which another section still places, so
1258
+ * no creation is left unplaced. Within a tier it is the last such section of
1259
+ * the page furthest past its share: the home at / past its fewest
1260
+ * (`aiSiteHomeMinSections`, five on a site start whose wall pays for a full
1261
+ * home), any other page past two; on a tie, the longer page, then the home.
1262
+ * No home goes under its fewest, no page under one section, and no page loses
1263
+ * its opening or a Shop page its product grid. The shares are what a site
1264
+ * start is told to plan (a home of five or six, the other page two or three):
1265
+ * the candle shop's 7 + 2 becomes 6 + 2, never 7 + 1, and the recorded yoga
1266
+ * answer's 6 + 4 becomes 5 + 3, the shape its own redo chose.
1267
+ *
1268
+ * A site start past its page cap and a plan with a list split into sections
1269
+ * are returned unchanged, for their re-ask; so is the rare plan whose every
1270
+ * section past the wall is one no tier may take.
1278
1271
  */
1279
1272
  export declare function aiSettlePlanFreeWall(plan: AiBuildPlan, inventory: AiSiteInventory | null, capabilities?: AiPlanCapabilities | null): AiBuildPlan;
1280
1273
  /**
@@ -5263,24 +5263,13 @@ function slugKey(slug) {
5263
5263
  homeMin
5264
5264
  };
5265
5265
  }
5266
- /**
5267
- * The most sections past the Free wall a plan is trimmed by rather than
5268
- * re-asked (AGL-3737). The live candle-shop start of 2026-10-12 planned 9
5269
- * sections against a wall of 8 on all three answers, each with a home of 7:
5270
- * the model reaching for the budget the site redo names ("plan 7 to 8") and
5271
- * landing one band past it, which no re-ask cured. Within two, the plan's
5272
- * shape is right and only a band or two the brief needs least is extra, so
5273
- * taking those from the end loses nothing the plan depends on. Further over,
5274
- * the shape itself is off — a list planned as sections, a page the wall
5275
- * cannot pay for — and cutting more would drop content the model meant, so
5276
- * that plan is still re-asked.
5277
- */ export const AI_FREE_WALL_SETTLE_MAX_OVER = 2;
5278
5266
  /** A section through which a visitor reaches the owner: contact, a visit, a booking. */ const REACH_SECTION_NAME = /\b(?:contact|get in touch|visit(?: us)?|find us|locations?|hours|book(?:ing)?|reserve|reservations?)\b/i;
5279
5267
  /** A section that asks a visitor to act: a call to action, "Start your practice", "Get started". */ const CALL_TO_ACTION_NAME = /\b(?:call to action|cta|get started|start (?:your|a|here|now|today)|join (?:us|now|today|the)|ready to)\b/i;
5280
5268
  /**
5281
- * Whether the Free wall's trim keeps a section of a page (AGL-3737): the
5282
- * page's opening, any section that places something (a dataset it lists, a
5283
- * form, a component — dropping it would leave a creation unplaced), one that
5269
+ * Whether the Free wall's trim keeps a section of a page while the plan has
5270
+ * any other to take (AGL-3737, AGL-3969): the page's opening, any section
5271
+ * that places something (a dataset it lists, a form, a component — dropping
5272
+ * it would leave a creation unplaced), one that
5284
5273
  * collects answers, says how to reach the owner or asks a visitor to act
5285
5274
  * (AGL-3899: a home's call to action is its way to the form), and a storefront's
5286
5275
  * listing parts — the bestsellers, the collections, the sign-up, and a Shop
@@ -5297,31 +5286,37 @@ function slugKey(slug) {
5297
5286
  return aiLayoutIsShopPage(screen) && aiLayoutShopGridSection(screen.sections) === index;
5298
5287
  }
5299
5288
  /**
5300
- * A Free plan a section or two past the wall is trimmed to fit before the
5301
- * rules read the plan (AGL-3737), rather than re-asked: which sections go has
5302
- * one answer, so the re-ask only costs the member a stopped start. The live
5303
- * candle-shop start of 2026-10-12 asked for a home of 7 and a second page of
5304
- * 2 against a wall of 8, three answers running, and stopped needing input.
5289
+ * A Free plan past the wall is trimmed to the wall before the rules read the
5290
+ * plan (AGL-3737, AGL-3969), rather than re-asked: a plan over its budget is
5291
+ * the model's mistake, never the member's, and which sections go has one
5292
+ * answer. The live candle-shop start of 2026-10-12 asked for a home of 7 and
5293
+ * a second page of 2 against a wall of 8, three answers running, and stopped
5294
+ * needing input; the Free candle shop of the beta.265 eval asked for 11, and
5295
+ * its re-ask still for more than 8, and was refused (AGL-3969).
5305
5296
  *
5306
- * One section goes at a time, the last of its page that `freeWallKeeps`
5307
- * does not hold, from the page furthest past its share: the home at / past
5308
- * its fewest (`aiSiteHomeMinSections`, five on a site start whose wall pays
5309
- * for a full home), any other page past two; on a tie, the longer page, then
5310
- * the home. No home goes under its fewest and no page under one section. The
5311
- * shares are what a site start is told to plan (a home of five or six, the
5312
- * other page two or three): the candle shop's 7 + 2 becomes 6 + 2, never
5313
- * 7 + 1, and the recorded yoga answer's 6 + 4 becomes 5 + 3, the shape its
5314
- * own redo chose.
5297
+ * One section goes at a time, exactly as many as the plan is over, so the
5298
+ * plan keeps the whole budget. Each goes from the lowest tier any page still
5299
+ * has: first a section `freeWallKeeps` does not hold; then one it holds only
5300
+ * by its name (a contact, a call to action, a storefront's sign-up); last one
5301
+ * that places something every use of which another section still places, so
5302
+ * no creation is left unplaced. Within a tier it is the last such section of
5303
+ * the page furthest past its share: the home at / past its fewest
5304
+ * (`aiSiteHomeMinSections`, five on a site start whose wall pays for a full
5305
+ * home), any other page past two; on a tie, the longer page, then the home.
5306
+ * No home goes under its fewest, no page under one section, and no page loses
5307
+ * its opening or a Shop page its product grid. The shares are what a site
5308
+ * start is told to plan (a home of five or six, the other page two or three):
5309
+ * the candle shop's 7 + 2 becomes 6 + 2, never 7 + 1, and the recorded yoga
5310
+ * answer's 6 + 4 becomes 5 + 3, the shape its own redo chose.
5315
5311
  *
5316
- * A plan more than `AI_FREE_WALL_SETTLE_MAX_OVER` past the wall, a site
5317
- * start past its page cap, a plan with a list split into sections, and a
5318
- * plan the trim cannot fit without a section it keeps are returned
5319
- * unchanged, for the wall to re-ask.
5312
+ * A site start past its page cap and a plan with a list split into sections
5313
+ * are returned unchanged, for their re-ask; so is the rare plan whose every
5314
+ * section past the wall is one no tier may take.
5320
5315
  */ export function aiSettlePlanFreeWall(plan, inventory, capabilities = null) {
5321
5316
  const wall = freePlanWall(plan, inventory, capabilities);
5322
5317
  if (!wall || wall.overPages) return plan;
5323
5318
  const over = wall.asked - wall.fits;
5324
- if (over <= 0 || over > AI_FREE_WALL_SETTLE_MAX_OVER) return plan;
5319
+ if (over <= 0) return plan;
5325
5320
  // A list split into sections is joined by its re-ask, which frees the room;
5326
5321
  // a trim would cut one of its items and hide the split from the rule.
5327
5322
  if (detectPlanSplitLists(plan, inventory, capabilities).length) return plan;
@@ -5332,27 +5327,42 @@ function slugKey(slug) {
5332
5327
  const home = plan.screens.findIndex(aiSitePlanIsHome);
5333
5328
  const floor = (screenIndex)=>screenIndex === home ? Math.max(wall.homeMin, 1) : 1;
5334
5329
  const target = (screenIndex)=>screenIndex === home ? Math.max(wall.homeMin, 1) : 2;
5335
- // The last section of a page the trim may take, or -1.
5336
- const last = (screenIndex)=>{
5330
+ // Whether another kept section places every use of this one.
5331
+ const placedElsewhere = (screenIndex, at)=>kept[screenIndex][at].section.uses.every((use)=>kept.some((entries, other)=>entries.some((entry, place)=>(other !== screenIndex || place !== at) && entry.section.uses.includes(use))));
5332
+ // Whether the trim may take a page's section on a tier: 0 one the page does
5333
+ // not hold, 1 one it holds by name only, 2 a placement another still makes.
5334
+ const takes = (screenIndex, at, tier)=>{
5335
+ const screen = plan.screens[screenIndex];
5336
+ const { section, index } = kept[screenIndex][at];
5337
+ if (!freeWallKeeps(screen, index)) return true;
5338
+ if (tier === 0 || aiLayoutIsShopPage(screen) && aiLayoutShopGridSection(screen.sections) === index) return false;
5339
+ return section.uses.length === 0 || tier === 2 && placedElsewhere(screenIndex, at);
5340
+ };
5341
+ // The last section of a page the trim may take on a tier, or -1.
5342
+ const last = (screenIndex, tier)=>{
5337
5343
  const entries = kept[screenIndex];
5338
5344
  if (entries.length <= floor(screenIndex)) return -1;
5339
5345
  for(let at = entries.length - 1; at > 0; at -= 1){
5340
- if (!freeWallKeeps(plan.screens[screenIndex], entries[at].index)) return at;
5346
+ if (takes(screenIndex, at, tier)) return at;
5341
5347
  }
5342
5348
  return -1;
5343
5349
  };
5344
5350
  for(let left = over; left > 0; left -= 1){
5345
5351
  let pick = -1;
5346
- for(let index = 0; index < kept.length; index += 1){
5347
- if (last(index) === -1) continue;
5348
- const room = kept[index].length - target(index);
5349
- const best = pick === -1 ? null : kept[pick].length - target(pick);
5350
- if (best === null || room > best || room === best && (kept[index].length > kept[pick].length || kept[index].length === kept[pick].length && index === home)) {
5351
- pick = index;
5352
+ let tier = -1;
5353
+ while(pick === -1 && tier < 2){
5354
+ tier += 1;
5355
+ for(let index = 0; index < kept.length; index += 1){
5356
+ if (last(index, tier) === -1) continue;
5357
+ const room = kept[index].length - target(index);
5358
+ const best = pick === -1 ? null : kept[pick].length - target(pick);
5359
+ if (best === null || room > best || room === best && (kept[index].length > kept[pick].length || kept[index].length === kept[pick].length && index === home)) {
5360
+ pick = index;
5361
+ }
5352
5362
  }
5353
5363
  }
5354
5364
  if (pick === -1) return plan;
5355
- kept[pick].splice(last(pick), 1);
5365
+ kept[pick].splice(last(pick, tier), 1);
5356
5366
  }
5357
5367
  return _extends({}, plan, {
5358
5368
  screens: plan.screens.map((screen, index)=>kept[index].length === screen.sections.length ? screen : _extends({}, screen, {