@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
@@ -0,0 +1,194 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { _ as _extends } from "@swc/helpers/_/_extends";
17
+ import { aiCompileLayoutFrame } from "../layout-language/ai-layout-frame.js";
18
+ import { AI_LAYOUT_LANGUAGE_TEXT, aiReadLayoutFrame } from "../layout-language/ai-layout-language.js";
19
+ import { aiLayoutCopyCheck } from "../layout-language/ai-layout-gaps.js";
20
+ import { aiLayoutStoredTree } from "../layout-language/ai-layout-store.js";
21
+ import { AI_GENERATION_MAX_ATTEMPTS } from "../runtime/ai-generation-bounds.js";
22
+ import { aiHomeScreenIds } from "../model/ai-site-inventory.js";
23
+ import { AI_STEP_TIERS } from "../providers/catalog.js";
24
+ import { validateAiDoctrineTree } from "../runtime/ai-doctrine-validators.js";
25
+ import { aiJobStepBudget } from "./ai-job-budget.js";
26
+ import { aiJobBriefLine } from "./ai-job-generation.js";
27
+ import { AI_LAYOUT_SITE_PAGES_MAX, aiLayoutIsHomeSlug, aiLayoutSitePages } from "./ai-job-layout-site-pages.js";
28
+ import { aiLayoutPageTargets } from "./ai-job-page-language.js";
29
+ import { AI_SITE_NAME_TOKEN } from "./ai-layout-site-facts.js";
30
+ /**
31
+ * A site's layout built in the compact layout language (AGL-3660): the layout
32
+ * step's pass for a guided start's and a build's layout unit. The model
33
+ * designs the header and the footer — their bands, the call to action, the
34
+ * footer's columns and every word — in one short answer through
35
+ * `submit_frame`, and the compiler writes the layout around what the site
36
+ * owes it: its name as the brand, its pages as the navigation and the phone
37
+ * menu, the Layout Slot, and the footer at the bottom of a short page
38
+ * (`ai-layout-frame.ts`).
39
+ */ /** The most a frame's answer asks for: two sections of a few blocks each. */ export const AI_JOB_LAYOUT_LANGUAGE_TOKENS = 1200;
40
+ /** A language layout pass's time, on the tier the layout step is served from. */ export const AI_JOB_LAYOUT_LANGUAGE_BUDGET = aiJobStepBudget({
41
+ tier: AI_STEP_TIERS['job.layout'],
42
+ maxTokens: AI_JOB_LAYOUT_LANGUAGE_TOKENS
43
+ });
44
+ /** The generation kind a frame is asked under, which the doctrine scopes. */ export const AI_LAYOUT_FRAME_KIND = 'layout-frame';
45
+ /** The language, as the frame door's cached instructions. */ export const AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS = [
46
+ {
47
+ text: [
48
+ AI_LAYOUT_LANGUAGE_TEXT,
49
+ '',
50
+ "Now you design a site's header and footer, each one section of the language, answered with submit_frame.",
51
+ "- header: the platform writes the site's name and its navigation itself. Choose its band and align (start, or center for a centered navigation), and give it at most one button: the call to action every page shows.",
52
+ '- footer: a section like any other, usually a row of two or three columns: a few words about the business, the details the brief gives, and a list of links (items with to). Put no contact detail, opening hours or address in it that the brief does not give, and no gap in square brackets for one.'
53
+ ].join('\n'),
54
+ cacheBreakpoint: true
55
+ }
56
+ ];
57
+ /** The site's name as the brand: the name the job was given, else the token the site's own name fills. */ export function aiLayoutSiteName(job) {
58
+ var _job_inputs;
59
+ const given = (_job_inputs = job.inputs) == null ? void 0 : _job_inputs['businessName'];
60
+ return typeof given === 'string' && given.trim() ? given.trim() : AI_SITE_NAME_TOKEN;
61
+ }
62
+ /** The pages a frame's navigation links: the planned pages, else the site's own, in order. */ export function aiLayoutNavPages(job, inventory) {
63
+ var _ref;
64
+ // The header and the footer link the pages the job was told (AGL-3660),
65
+ // else the site's own, the home page always and first, labelled Home. The
66
+ // model draws neither list, so it cannot drop a page.
67
+ const planned = aiLayoutSitePages(job.inputs);
68
+ const own = ((_ref = inventory == null ? void 0 : inventory.screens) != null ? _ref : []).filter((row)=>!row.template).map((row)=>({
69
+ id: row.id,
70
+ label: row.name,
71
+ slug: row.slug
72
+ }));
73
+ const siteHome = planned.some((page)=>aiLayoutIsHomeSlug(page.slug)) ? [] : own.filter((page)=>aiLayoutIsHomeSlug(page.slug)).slice(0, 1);
74
+ const pages = [
75
+ ...siteHome,
76
+ ...planned.length ? planned : own
77
+ ].filter((page)=>page.label.trim());
78
+ const home = pages.find((page)=>aiLayoutIsHomeSlug(page.slug));
79
+ return [
80
+ ...home ? [
81
+ _extends({}, home, {
82
+ label: 'Home'
83
+ })
84
+ ] : [],
85
+ ...pages.filter((page)=>page !== home && !aiLayoutIsHomeSlug(page.slug))
86
+ ].slice(0, AI_LAYOUT_SITE_PAGES_MAX);
87
+ }
88
+ /** The home page the brand links: the planned home, else the site's. */ export function aiLayoutHomeId(pages, inventory) {
89
+ var _ref, _ref1;
90
+ const planned = pages.find((page)=>aiLayoutIsHomeSlug(page.slug));
91
+ return (_ref = (_ref1 = planned == null ? void 0 : planned.id) != null ? _ref1 : aiHomeScreenIds(inventory)[0]) != null ? _ref : null;
92
+ }
93
+ /** The frame's user turn: the site, its brief, its pages and its form. */ export function aiLayoutFramePrompt(input) {
94
+ const { job, pages, targets } = input;
95
+ return [
96
+ `Site: ${input.siteName === AI_SITE_NAME_TOKEN ? 'the business the brief describes' : `"${input.siteName}"`}`,
97
+ aiJobBriefLine(job),
98
+ pages.length ? `Its pages, which the navigation links: ${pages.map((page)=>`${page.label} (page:${page.id})`).join(', ')}.` : 'It has no pages yet.',
99
+ targets.forms.length || targets.formPageId ? 'A call to action that asks a visitor to get in touch goes to form.' : 'The site has no form yet.',
100
+ `Design seed: ${job.$id.slice(-6)}.`
101
+ ].join('\n');
102
+ }
103
+ /** The check a frame answer is held to: read, compiled, stored and checked as a layout, with the layout door's own checks. */ export function aiLayoutFrameCheck(input) {
104
+ let answers = 0;
105
+ return (answer)=>{
106
+ // The last answer a generation takes has its gaps taken out rather than asked about again.
107
+ answers += 1;
108
+ const last = answers >= AI_GENERATION_MAX_ATTEMPTS;
109
+ const frame = aiReadLayoutFrame(answer);
110
+ const compiled = aiCompileLayoutFrame(frame, {
111
+ siteName: input.siteName,
112
+ homeId: input.homeId,
113
+ navPages: input.pages
114
+ }, input.targets);
115
+ const context = {
116
+ screenIds: [
117
+ ...new Set([
118
+ ...input.targets.pages.map((page)=>page.id),
119
+ ...input.pages.map((page)=>page.id),
120
+ ...input.homeId ? [
121
+ input.homeId
122
+ ] : []
123
+ ])
124
+ ],
125
+ formIds: input.targets.forms.map((form)=>form.id),
126
+ homeScreenIds: input.targets.homeIds,
127
+ codeBuilt: true
128
+ };
129
+ const stored = aiLayoutStoredTree(compiled.tree, 'layout', context);
130
+ if (stored.ok === false) {
131
+ console.error('ai layout frame: a compiled layout did not store', {
132
+ error: stored.error
133
+ });
134
+ return {
135
+ value: null,
136
+ violations: [
137
+ {
138
+ rule: null,
139
+ code: 'layout-compile',
140
+ message: 'This layout could not be built. Try again.',
141
+ detail: stored.error
142
+ }
143
+ ]
144
+ };
145
+ }
146
+ // No gap and no internal reference in the words of a frame every published page shows.
147
+ const navPages = [
148
+ ...input.targets.pages,
149
+ ...input.pages
150
+ ];
151
+ const copy = aiLayoutCopyCheck(stored.nodes, stored.rootId, {
152
+ pages: navPages,
153
+ ids: [
154
+ ...navPages.map((page)=>page.id),
155
+ ...input.targets.forms.map((form)=>form.id)
156
+ ]
157
+ }, last);
158
+ const { nodes, dropped } = copy;
159
+ const report = validateAiDoctrineTree({
160
+ rootId: stored.rootId,
161
+ nodes
162
+ }, 'layout', context);
163
+ const violations = [
164
+ ...report.violations,
165
+ ...copy.violations,
166
+ ...input.extend({
167
+ rootId: stored.rootId,
168
+ nodes,
169
+ repairs: [],
170
+ sourceIds: {},
171
+ score: report.score,
172
+ load: report.load
173
+ })
174
+ ];
175
+ return {
176
+ value: violations.length ? null : {
177
+ rootId: stored.rootId,
178
+ nodes,
179
+ load: report.load,
180
+ dropped
181
+ },
182
+ violations
183
+ };
184
+ };
185
+ }
186
+ /** What a frame's links resolve against: the site's pages and its form, as a page's do. */ export function aiLayoutFrameTargets(job, inventory) {
187
+ return aiLayoutPageTargets({
188
+ job,
189
+ inventory,
190
+ own: []
191
+ });
192
+ }
193
+
194
+ //# sourceMappingURL=ai-job-layout-language.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-layout-language.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 { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { aiCompileLayoutFrame } from '../layout-language/ai-layout-frame'\nimport {\n AI_LAYOUT_FRAME_TOOL,\n AI_LAYOUT_LANGUAGE_TEXT,\n aiReadLayoutFrame,\n} from '../layout-language/ai-layout-language'\nimport type {\n AiLayoutPage,\n AiLayoutTargets,\n} from '../layout-language/ai-layout-links'\nimport { aiLayoutCopyCheck } from '../layout-language/ai-layout-gaps'\nimport { aiLayoutStoredTree } from '../layout-language/ai-layout-store'\nimport { AI_GENERATION_MAX_ATTEMPTS } from '../runtime/ai-generation-bounds'\nimport type { AiJob } from '../model/ai-jobs.types'\nimport {\n aiHomeScreenIds,\n type AiSiteInventory,\n} from '../model/ai-site-inventory'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport type { AiGenerationCheck, AiValidatedTree } from '../runtime/ai-doctrine'\nimport {\n validateAiDoctrineTree,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiJobBriefLine } from './ai-job-generation'\nimport {\n AI_LAYOUT_SITE_PAGES_MAX,\n aiLayoutIsHomeSlug,\n aiLayoutSitePages,\n} from './ai-job-layout-site-pages'\nimport { aiLayoutPageTargets } from './ai-job-page-language'\nimport { AI_SITE_NAME_TOKEN } from './ai-layout-site-facts'\n\n/**\n * A site's layout built in the compact layout language (AGL-3660): the layout\n * step's pass for a guided start's and a build's layout unit. The model\n * designs the header and the footer — their bands, the call to action, the\n * footer's columns and every word — in one short answer through\n * `submit_frame`, and the compiler writes the layout around what the site\n * owes it: its name as the brand, its pages as the navigation and the phone\n * menu, the Layout Slot, and the footer at the bottom of a short page\n * (`ai-layout-frame.ts`).\n */\n\n/** The most a frame's answer asks for: two sections of a few blocks each. */\nexport const AI_JOB_LAYOUT_LANGUAGE_TOKENS = 1_200\n\n/** A language layout pass's time, on the tier the layout step is served from. */\nexport const AI_JOB_LAYOUT_LANGUAGE_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.layout'],\n maxTokens: AI_JOB_LAYOUT_LANGUAGE_TOKENS,\n})\n\n/** The generation kind a frame is asked under, which the doctrine scopes. */\nexport const AI_LAYOUT_FRAME_KIND = 'layout-frame'\n\n/** The language, as the frame door's cached instructions. */\nexport const AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n AI_LAYOUT_LANGUAGE_TEXT,\n '',\n \"Now you design a site's header and footer, each one section of the language, answered with submit_frame.\",\n \"- header: the platform writes the site's name and its navigation itself. Choose its band and align (start, or center for a centered navigation), and give it at most one button: the call to action every page shows.\",\n '- footer: a section like any other, usually a row of two or three columns: a few words about the business, the details the brief gives, and a list of links (items with to). Put no contact detail, opening hours or address in it that the brief does not give, and no gap in square brackets for one.',\n ].join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\n/** The site's name as the brand: the name the job was given, else the token the site's own name fills. */\nexport function aiLayoutSiteName(job: Pick<AiJob, 'inputs'>): string {\n const given = job.inputs?.['businessName']\n return typeof given === 'string' && given.trim()\n ? given.trim()\n : AI_SITE_NAME_TOKEN\n}\n\n/** The pages a frame's navigation links: the planned pages, else the site's own, in order. */\nexport function aiLayoutNavPages(\n job: Pick<AiJob, 'inputs'>,\n inventory: AiSiteInventory | null,\n): AiLayoutPage[] {\n // The header and the footer link the pages the job was told (AGL-3660),\n // else the site's own, the home page always and first, labelled Home. The\n // model draws neither list, so it cannot drop a page.\n const planned = aiLayoutSitePages(job.inputs)\n const own = (inventory?.screens ?? [])\n .filter((row) => !row.template)\n .map((row) => ({ id: row.id, label: row.name, slug: row.slug }))\n const siteHome = planned.some((page) => aiLayoutIsHomeSlug(page.slug))\n ? []\n : own.filter((page) => aiLayoutIsHomeSlug(page.slug)).slice(0, 1)\n const pages = [...siteHome, ...(planned.length ? planned : own)].filter((page) => page.label.trim())\n const home = pages.find((page) => aiLayoutIsHomeSlug(page.slug))\n return [\n ...(home ? [{ ...home, label: 'Home' }] : []),\n ...pages.filter((page) => page !== home && !aiLayoutIsHomeSlug(page.slug)),\n ].slice(0, AI_LAYOUT_SITE_PAGES_MAX)\n}\n\n/** The home page the brand links: the planned home, else the site's. */\nexport function aiLayoutHomeId(\n pages: readonly AiLayoutPage[],\n inventory: AiSiteInventory | null,\n): string | null {\n const planned = pages.find((page) => aiLayoutIsHomeSlug(page.slug))\n return planned?.id ?? aiHomeScreenIds(inventory)[0] ?? null\n}\n\n/** The frame's user turn: the site, its brief, its pages and its form. */\nexport function aiLayoutFramePrompt(input: {\n job: Pick<AiJob, 'brief' | '$id'>\n siteName: string\n pages: readonly AiLayoutPage[]\n targets: AiLayoutTargets\n}): string {\n const { job, pages, targets } = input\n return [\n `Site: ${input.siteName === AI_SITE_NAME_TOKEN ? 'the business the brief describes' : `\"${input.siteName}\"`}`,\n aiJobBriefLine(job),\n pages.length\n ? `Its pages, which the navigation links: ${pages.map((page) => `${page.label} (page:${page.id})`).join(', ')}.`\n : 'It has no pages yet.',\n targets.forms.length || targets.formPageId\n ? 'A call to action that asks a visitor to get in touch goes to form.'\n : 'The site has no form yet.',\n `Design seed: ${job.$id.slice(-6)}.`,\n ].join('\\n')\n}\n\n/** A validated language layout. */\nexport interface AiLayoutFrameBuilt {\n rootId: string\n nodes: NodesMap\n load: AiLoadEstimate | null\n /** What the last answer's gaps took out of the frame (`ai-layout-gaps.ts`). */\n dropped: string[]\n}\n\n/** The check a frame answer is held to: read, compiled, stored and checked as a layout, with the layout door's own checks. */\nexport function aiLayoutFrameCheck(input: {\n siteName: string\n homeId: string | null\n pages: readonly AiLayoutPage[]\n targets: AiLayoutTargets\n extend: (tree: AiValidatedTree) => AiDoctrineViolation[]\n}): AiGenerationCheck<AiLayoutFrameBuilt> {\n let answers = 0\n return (answer) => {\n // The last answer a generation takes has its gaps taken out rather than asked about again.\n answers += 1\n const last = answers >= AI_GENERATION_MAX_ATTEMPTS\n const frame = aiReadLayoutFrame(answer)\n const compiled = aiCompileLayoutFrame(\n frame,\n { siteName: input.siteName, homeId: input.homeId, navPages: input.pages },\n input.targets,\n )\n const context = {\n screenIds: [\n ...new Set([\n ...input.targets.pages.map((page) => page.id),\n ...input.pages.map((page) => page.id),\n ...(input.homeId ? [input.homeId] : []),\n ]),\n ],\n formIds: input.targets.forms.map((form) => form.id),\n homeScreenIds: input.targets.homeIds,\n codeBuilt: true,\n }\n const stored = aiLayoutStoredTree(compiled.tree, 'layout', context)\n if (stored.ok === false) {\n console.error('ai layout frame: a compiled layout did not store', {\n error: stored.error,\n })\n return {\n value: null,\n violations: [\n {\n rule: null,\n code: 'layout-compile',\n message: 'This layout could not be built. Try again.',\n detail: stored.error,\n },\n ],\n }\n }\n // No gap and no internal reference in the words of a frame every published page shows.\n const navPages = [...input.targets.pages, ...input.pages]\n const copy = aiLayoutCopyCheck(\n stored.nodes,\n stored.rootId,\n {\n pages: navPages,\n ids: [...navPages.map((page) => page.id), ...input.targets.forms.map((form) => form.id)],\n },\n last,\n )\n const { nodes, dropped } = copy\n const report = validateAiDoctrineTree(\n { rootId: stored.rootId, nodes },\n 'layout',\n context,\n )\n const violations = [\n ...report.violations,\n ...copy.violations,\n ...input.extend({\n rootId: stored.rootId,\n nodes,\n repairs: [],\n sourceIds: {},\n score: report.score as AiValidatedTree['score'],\n load: report.load,\n }),\n ]\n return {\n value: violations.length\n ? null\n : { rootId: stored.rootId, nodes, load: report.load, dropped },\n violations,\n }\n }\n}\n\n/** What a frame's links resolve against: the site's pages and its form, as a page's do. */\nexport function aiLayoutFrameTargets(\n job: Pick<AiJob, 'brief' | 'inputs'>,\n inventory: AiSiteInventory | null,\n): AiLayoutTargets {\n return aiLayoutPageTargets({ job, inventory, own: [] })\n}\n"],"names":["aiCompileLayoutFrame","AI_LAYOUT_LANGUAGE_TEXT","aiReadLayoutFrame","aiLayoutCopyCheck","aiLayoutStoredTree","AI_GENERATION_MAX_ATTEMPTS","aiHomeScreenIds","AI_STEP_TIERS","validateAiDoctrineTree","aiJobStepBudget","aiJobBriefLine","AI_LAYOUT_SITE_PAGES_MAX","aiLayoutIsHomeSlug","aiLayoutSitePages","aiLayoutPageTargets","AI_SITE_NAME_TOKEN","AI_JOB_LAYOUT_LANGUAGE_TOKENS","AI_JOB_LAYOUT_LANGUAGE_BUDGET","tier","maxTokens","AI_LAYOUT_FRAME_KIND","AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS","text","join","cacheBreakpoint","aiLayoutSiteName","job","given","inputs","trim","aiLayoutNavPages","inventory","planned","own","screens","filter","row","template","map","id","label","name","slug","siteHome","some","page","slice","pages","length","home","find","aiLayoutHomeId","aiLayoutFramePrompt","input","targets","siteName","forms","formPageId","$id","aiLayoutFrameCheck","answers","answer","last","frame","compiled","homeId","navPages","context","screenIds","Set","formIds","form","homeScreenIds","homeIds","codeBuilt","stored","tree","ok","console","error","value","violations","rule","code","message","detail","copy","nodes","rootId","ids","dropped","report","extend","repairs","sourceIds","score","load","aiLayoutFrameTargets"],"mappings":"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"}
@@ -45,8 +45,19 @@ export interface AiLayoutSitePage {
45
45
  label: string;
46
46
  slug: string;
47
47
  }
48
- /** The pages a scaffold's plan puts in the navigation, in plan order: ids minted, entries asked for, no record templates. */
49
- export declare function aiLayoutSitePagesOfPlan(screens: readonly AiBuildPlanScreen[]): AiLayoutSitePage[];
48
+ /** Whether a slug is the site's root, the home page. */
49
+ export declare function aiLayoutIsHomeSlug(slug: string): boolean;
50
+ /**
51
+ * The pages a plan puts in the navigation, record templates aside, the home
52
+ * page first whatever its `nav` flag says (AGL-3660): a guided start's plan
53
+ * marked its Home `nav: false`, and the header linked only Contact. Every
54
+ * other page keeps its `nav` flag, except in a guided start (`guided`), whose
55
+ * pages are the ones the person asked for: each is linked, but a
56
+ * confirmation page — a thank-you, a "submitted" — stays out.
57
+ */
58
+ export declare function aiLayoutSitePagesOfPlan(screens: readonly AiBuildPlanScreen[], options?: {
59
+ guided?: boolean;
60
+ }): AiLayoutSitePage[];
50
61
  /** The planned pages a layout unit's inputs carry; none for any other layout. */
51
62
  export declare function aiLayoutSitePages(inputs: Readonly<Record<string, unknown>> | null | undefined): AiLayoutSitePage[];
52
63
  /**
@@ -36,12 +36,28 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
36
36
  * adds only the entries a header does not already carry, adds nothing twice.
37
37
  */ /** The unit input a site layout's planned pages travel under. */ export const AI_LAYOUT_SITE_PAGES_INPUT = 'sitePages';
38
38
  /** The most pages the header links, as the layout instructions cap it. */ export const AI_LAYOUT_SITE_PAGES_MAX = 7;
39
- /** The pages a scaffold's plan puts in the navigation, in plan order: ids minted, entries asked for, no record templates. */ export function aiLayoutSitePagesOfPlan(screens) {
40
- return screens.filter((screen)=>screen.nav && !screen.record && typeof screen.id === 'string' && screen.id).slice(0, AI_LAYOUT_SITE_PAGES_MAX).map((screen)=>({
39
+ /** Whether a slug is the site's root, the home page. */ export function aiLayoutIsHomeSlug(slug) {
40
+ return slug.replace(/^\/+|\/+$/g, '') === '';
41
+ }
42
+ /** A page that confirms what a visitor just did, which no navigation links. */ const CONFIRMATION = /thank|confirm|success|submitted/i;
43
+ /**
44
+ * The pages a plan puts in the navigation, record templates aside, the home
45
+ * page first whatever its `nav` flag says (AGL-3660): a guided start's plan
46
+ * marked its Home `nav: false`, and the header linked only Contact. Every
47
+ * other page keeps its `nav` flag, except in a guided start (`guided`), whose
48
+ * pages are the ones the person asked for: each is linked, but a
49
+ * confirmation page — a thank-you, a "submitted" — stays out.
50
+ */ export function aiLayoutSitePagesOfPlan(screens, options = {}) {
51
+ const linked = (screen)=>aiLayoutIsHomeSlug(screen.slug) || (options.guided ? !CONFIRMATION.test(`${screen.slug} ${screen.title}`) : !!screen.nav);
52
+ const pages = screens.filter((screen)=>!screen.record && typeof screen.id === 'string' && screen.id && linked(screen)).map((screen)=>({
41
53
  id: screen.id,
42
54
  label: screen.title.trim(),
43
55
  slug: screen.slug
44
56
  })).filter((page)=>page.label);
57
+ return [
58
+ ...pages.filter((page)=>aiLayoutIsHomeSlug(page.slug)),
59
+ ...pages.filter((page)=>!aiLayoutIsHomeSlug(page.slug))
60
+ ].slice(0, AI_LAYOUT_SITE_PAGES_MAX);
45
61
  }
46
62
  /** The planned pages a layout unit's inputs carry; none for any other layout. */ export function aiLayoutSitePages(inputs) {
47
63
  const raw = inputs == null ? void 0 : inputs[AI_LAYOUT_SITE_PAGES_INPUT];
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-layout-site-pages.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 { normalizeScreenSlug, SCREEN_ROOT_PATH } from '@aglyn/aglyn/app-utils/screen-route'\nimport type { AiBuildPlanScreen } from '../model/ai-build-plan'\n\n/**\n * The navigation of a layout a site scaffold builds before its pages (AGL-3596).\n *\n * A scaffold builds its layout FIRST, because every page renders inside it,\n * so when the layout is generated none of the site's pages exists yet: the\n * model cannot link them, and the layout instructions forbid inventing a page\n * id. A plan that gave the layout a nav region then left the model one way to\n * keep the promise — a nav element with nothing in it — which rule 16 refuses\n * as an empty container, twice, and the guided start stopped there.\n *\n * The scaffold does know the pages: their ids are minted when the plan is\n * kept (`AiBuildPlanScreen.id`), and each page unit writes its draft under\n * that id. So the layout unit is handed them (`sitePages`), and the header's\n * navigation is written by the platform rather than asked of the model: one\n * Page Link per planned page, inside the nav element the model left (filled,\n * never left empty) or a nav row the platform adds to the Toolbar Content.\n * The completed tree is what the doctrine checks and what is written, so rule\n * 16 holds unchanged — nothing it sees is empty — and the publish step, which\n * adds only the entries a header does not already carry, adds nothing twice.\n */\n\n/** The unit input a site layout's planned pages travel under. */\nexport const AI_LAYOUT_SITE_PAGES_INPUT = 'sitePages'\n\n/** The most pages the header links, as the layout instructions cap it. */\nexport const AI_LAYOUT_SITE_PAGES_MAX = 7\n\n/** A page the scaffold builds after the layout, as the layout links it. */\nexport interface AiLayoutSitePage {\n id: string\n label: string\n slug: string\n}\n\n/** The pages a scaffold's plan puts in the navigation, in plan order: ids minted, entries asked for, no record templates. */\nexport function aiLayoutSitePagesOfPlan(screens: readonly AiBuildPlanScreen[]): AiLayoutSitePage[] {\n return screens\n .filter((screen) => screen.nav && !screen.record && typeof screen.id === 'string' && screen.id)\n .slice(0, AI_LAYOUT_SITE_PAGES_MAX)\n .map((screen) => ({ id: screen.id as string, label: screen.title.trim(), slug: screen.slug }))\n .filter((page) => page.label)\n}\n\n/** The planned pages a layout unit's inputs carry; none for any other layout. */\nexport function aiLayoutSitePages(inputs: Readonly<Record<string, unknown>> | null | undefined): AiLayoutSitePage[] {\n const raw = inputs?.[AI_LAYOUT_SITE_PAGES_INPUT]\n if (!Array.isArray(raw)) return []\n const pages: AiLayoutSitePage[] = []\n for (const entry of raw) {\n if (!entry || typeof entry !== 'object') continue\n const { id, label, slug } = entry as Record<string, unknown>\n if (typeof id !== 'string' || !id || typeof label !== 'string' || !label.trim()) continue\n if (pages.some((page) => page.id === id)) continue\n pages.push({ id, label: label.trim(), slug: typeof slug === 'string' ? slug : '' })\n }\n return pages.slice(0, AI_LAYOUT_SITE_PAGES_MAX)\n}\n\n/**\n * What the layout's user turn says about those pages: that the platform\n * writes the navigation, so the model builds none and leaves no empty element\n * for it. The same promise the completion below keeps.\n */\nexport function aiLayoutSitePagesLines(pages: readonly AiLayoutSitePage[]): string[] {\n if (!pages.length) return []\n return [\n `This site’s pages are built after this layout: ${pages\n .map((page) => `${page.label} (${screenPath(page.slug)})`)\n .join(', ')}.`,\n 'The platform writes the header’s navigation to those pages itself, as a nav row of Page Links inside the Toolbar Content. Build no navigation links to them and no nav element for them: leave no empty element where the links will go.',\n ]\n}\n\nfunction screenPath(slug: string): string {\n const normalized = normalizeScreenSlug(slug)\n if (!normalized || normalized === SCREEN_ROOT_PATH) return '/'\n return `/${normalized.replace(/^\\/+/, '')}`\n}\n\ntype RawNode = { componentId?: unknown; props?: Record<string, unknown>; nodes?: unknown; [key: string]: unknown }\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n Boolean(value) && typeof value === 'object' && !Array.isArray(value)\n\nconst childrenOf = (node: RawNode | undefined): string[] =>\n Array.isArray(node?.nodes) ? (node.nodes as unknown[]).filter((id): id is string => typeof id === 'string') : []\n\nconst elementOf = (node: RawNode | undefined): string => {\n const value = node?.props?.['component'] ?? node?.props?.['element']\n return typeof value === 'string' ? value : ''\n}\n\nconst LINK_COMPONENTS = new Set(['muiScreenLink', 'muiButton'])\n\n/**\n * The model's layout with the header's navigation written in: a Page Link to\n * every planned page the header does not already link, placed in the first\n * nav element above the Layout Slot, else in a nav row the platform appends\n * to the Toolbar Content; and every OTHER nav element the model left empty\n * given the same links, so no nav is ever empty. A link to the site's current\n * home page is pointed at the planned home, which replaces it when the site\n * is published. Anything that is not a node map, or has no planned pages, is\n * returned as it came, for the doctrine to judge.\n */\nexport function aiLayoutWithSitePages(\n tree: unknown,\n pages: readonly AiLayoutSitePage[],\n options: { homeScreenIds?: readonly string[] } = {},\n): unknown {\n if (!pages.length || !isRecord(tree) || !isRecord(tree['nodes'])) return tree\n const rootId = typeof tree['rootId'] === 'string' ? tree['rootId'] : null\n const source = tree['nodes'] as Record<string, unknown>\n if (!rootId || !isRecord(source[rootId])) return tree\n const nodes: Record<string, RawNode> = {}\n for (const [id, node] of Object.entries(source)) {\n if (!isRecord(node)) continue\n nodes[id] = {\n ...(node as RawNode),\n ...(isRecord(node['props']) ? { props: { ...(node['props'] as Record<string, unknown>) } } : {}),\n ...(Array.isArray(node['nodes']) ? { nodes: [...(node['nodes'] as unknown[])] } : {}),\n }\n }\n\n // Document order, and where the header ends: the Layout Slot.\n const order: string[] = []\n const seen = new Set<string>()\n const visit = (id: string) => {\n if (seen.has(id) || !nodes[id]) return\n seen.add(id)\n order.push(id)\n for (const child of childrenOf(nodes[id])) visit(child)\n }\n visit(rootId)\n const slotAt = order.findIndex((id) => nodes[id].componentId === 'layoutSlot')\n const header = slotAt === -1 ? order : order.slice(0, slotAt)\n\n // The site's current home is replaced by the planned one at publish.\n const plannedHome = pages.find((page) => screenPath(page.slug) === '/')\n const oldHomes = new Set(options.homeScreenIds ?? [])\n let changed = false\n if (plannedHome) {\n for (const id of order) {\n const node = nodes[id]\n const screenId = node.props?.['screenId']\n if (LINK_COMPONENTS.has(String(node.componentId)) && typeof screenId === 'string' && oldHomes.has(screenId)) {\n node.props = { ...node.props, screenId: plannedHome.id }\n changed = true\n }\n }\n }\n\n const used = new Set(Object.keys(nodes))\n const freshId = (base: string): string => {\n let id = base\n for (let n = 2; used.has(id); n += 1) id = `${base}${n}`\n used.add(id)\n return id\n }\n const linkNodes = (entries: readonly AiLayoutSitePage[]): string[] =>\n entries.map((page) => {\n const id = freshId(`aiNavLink_${page.id.replace(/[^A-Za-z0-9_]/g, '')}`)\n nodes[id] = {\n componentId: 'muiScreenLink',\n props: { children: page.label.slice(0, 40), screenId: page.id, renderAs: 'link', color: 'inherit' },\n }\n return id\n })\n\n const linkedInHeader = new Set(\n header\n .map((id) => nodes[id])\n .filter((node) => LINK_COMPONENTS.has(String(node.componentId)))\n .map((node) => node.props?.['screenId'])\n .filter((value): value is string => typeof value === 'string'),\n )\n const missing = pages.filter((page) => !linkedInHeader.has(page.id))\n const navs = order.filter((id) => elementOf(nodes[id]) === 'nav')\n const headerNav = navs.find((id) => header.includes(id)) ?? null\n\n if (missing.length) {\n if (headerNav) {\n nodes[headerNav].nodes = [...childrenOf(nodes[headerNav]), ...linkNodes(missing)]\n changed = true\n } else {\n const toolbar = header.find((id) => nodes[id].componentId === 'muiToolbar')\n if (toolbar) {\n const navId = freshId('aiSiteNav')\n nodes[navId] = {\n componentId: 'muiStack',\n props: { component: 'nav', direction: 'row', alignItems: 'center' },\n sx: { gap: 2 },\n nodes: linkNodes(missing),\n }\n nodes[toolbar].nodes = [...childrenOf(nodes[toolbar]), navId]\n changed = true\n }\n }\n }\n // A nav element the model left empty anywhere else — a footer's, say — links the same pages.\n for (const id of navs) {\n if (id === headerNav && missing.length) continue\n if (childrenOf(nodes[id]).some((child) => nodes[child])) continue\n nodes[id].nodes = linkNodes(pages)\n changed = true\n }\n return changed ? { ...tree, nodes } : tree\n}\n"],"names":["normalizeScreenSlug","SCREEN_ROOT_PATH","AI_LAYOUT_SITE_PAGES_INPUT","AI_LAYOUT_SITE_PAGES_MAX","aiLayoutSitePagesOfPlan","screens","filter","screen","nav","record","id","slice","map","label","title","trim","slug","page","aiLayoutSitePages","inputs","raw","Array","isArray","pages","entry","some","push","aiLayoutSitePagesLines","length","screenPath","join","normalized","replace","isRecord","value","Boolean","childrenOf","node","nodes","elementOf","props","LINK_COMPONENTS","Set","aiLayoutWithSitePages","tree","options","navs","rootId","source","Object","entries","order","seen","visit","has","add","child","slotAt","findIndex","componentId","header","plannedHome","find","oldHomes","homeScreenIds","changed","screenId","String","used","keys","freshId","base","n","linkNodes","children","renderAs","color","linkedInHeader","missing","headerNav","includes","toolbar","navId","component","direction","alignItems","sx","gap"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-layout-site-pages.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 { normalizeScreenSlug, SCREEN_ROOT_PATH } from '@aglyn/aglyn/app-utils/screen-route'\nimport type { AiBuildPlanScreen } from '../model/ai-build-plan'\n\n/**\n * The navigation of a layout a site scaffold builds before its pages (AGL-3596).\n *\n * A scaffold builds its layout FIRST, because every page renders inside it,\n * so when the layout is generated none of the site's pages exists yet: the\n * model cannot link them, and the layout instructions forbid inventing a page\n * id. A plan that gave the layout a nav region then left the model one way to\n * keep the promise — a nav element with nothing in it — which rule 16 refuses\n * as an empty container, twice, and the guided start stopped there.\n *\n * The scaffold does know the pages: their ids are minted when the plan is\n * kept (`AiBuildPlanScreen.id`), and each page unit writes its draft under\n * that id. So the layout unit is handed them (`sitePages`), and the header's\n * navigation is written by the platform rather than asked of the model: one\n * Page Link per planned page, inside the nav element the model left (filled,\n * never left empty) or a nav row the platform adds to the Toolbar Content.\n * The completed tree is what the doctrine checks and what is written, so rule\n * 16 holds unchanged — nothing it sees is empty — and the publish step, which\n * adds only the entries a header does not already carry, adds nothing twice.\n */\n\n/** The unit input a site layout's planned pages travel under. */\nexport const AI_LAYOUT_SITE_PAGES_INPUT = 'sitePages'\n\n/** The most pages the header links, as the layout instructions cap it. */\nexport const AI_LAYOUT_SITE_PAGES_MAX = 7\n\n/** A page the scaffold builds after the layout, as the layout links it. */\nexport interface AiLayoutSitePage {\n id: string\n label: string\n slug: string\n}\n\n/** Whether a slug is the site's root, the home page. */\nexport function aiLayoutIsHomeSlug(slug: string): boolean {\n return slug.replace(/^\\/+|\\/+$/g, '') === ''\n}\n\n/** A page that confirms what a visitor just did, which no navigation links. */\nconst CONFIRMATION = /thank|confirm|success|submitted/i\n\n/**\n * The pages a plan puts in the navigation, record templates aside, the home\n * page first whatever its `nav` flag says (AGL-3660): a guided start's plan\n * marked its Home `nav: false`, and the header linked only Contact. Every\n * other page keeps its `nav` flag, except in a guided start (`guided`), whose\n * pages are the ones the person asked for: each is linked, but a\n * confirmation page — a thank-you, a \"submitted\" — stays out.\n */\nexport function aiLayoutSitePagesOfPlan(\n screens: readonly AiBuildPlanScreen[],\n options: { guided?: boolean } = {},\n): AiLayoutSitePage[] {\n const linked = (screen: AiBuildPlanScreen) =>\n aiLayoutIsHomeSlug(screen.slug) ||\n (options.guided ? !CONFIRMATION.test(`${screen.slug} ${screen.title}`) : !!screen.nav)\n const pages = screens\n .filter((screen) => !screen.record && typeof screen.id === 'string' && screen.id && linked(screen))\n .map((screen) => ({ id: screen.id as string, label: screen.title.trim(), slug: screen.slug }))\n .filter((page) => page.label)\n return [...pages.filter((page) => aiLayoutIsHomeSlug(page.slug)), ...pages.filter((page) => !aiLayoutIsHomeSlug(page.slug))].slice(\n 0,\n AI_LAYOUT_SITE_PAGES_MAX,\n )\n}\n\n/** The planned pages a layout unit's inputs carry; none for any other layout. */\nexport function aiLayoutSitePages(inputs: Readonly<Record<string, unknown>> | null | undefined): AiLayoutSitePage[] {\n const raw = inputs?.[AI_LAYOUT_SITE_PAGES_INPUT]\n if (!Array.isArray(raw)) return []\n const pages: AiLayoutSitePage[] = []\n for (const entry of raw) {\n if (!entry || typeof entry !== 'object') continue\n const { id, label, slug } = entry as Record<string, unknown>\n if (typeof id !== 'string' || !id || typeof label !== 'string' || !label.trim()) continue\n if (pages.some((page) => page.id === id)) continue\n pages.push({ id, label: label.trim(), slug: typeof slug === 'string' ? slug : '' })\n }\n return pages.slice(0, AI_LAYOUT_SITE_PAGES_MAX)\n}\n\n/**\n * What the layout's user turn says about those pages: that the platform\n * writes the navigation, so the model builds none and leaves no empty element\n * for it. The same promise the completion below keeps.\n */\nexport function aiLayoutSitePagesLines(pages: readonly AiLayoutSitePage[]): string[] {\n if (!pages.length) return []\n return [\n `This site’s pages are built after this layout: ${pages\n .map((page) => `${page.label} (${screenPath(page.slug)})`)\n .join(', ')}.`,\n 'The platform writes the header’s navigation to those pages itself, as a nav row of Page Links inside the Toolbar Content. Build no navigation links to them and no nav element for them: leave no empty element where the links will go.',\n ]\n}\n\nfunction screenPath(slug: string): string {\n const normalized = normalizeScreenSlug(slug)\n if (!normalized || normalized === SCREEN_ROOT_PATH) return '/'\n return `/${normalized.replace(/^\\/+/, '')}`\n}\n\ntype RawNode = { componentId?: unknown; props?: Record<string, unknown>; nodes?: unknown; [key: string]: unknown }\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n Boolean(value) && typeof value === 'object' && !Array.isArray(value)\n\nconst childrenOf = (node: RawNode | undefined): string[] =>\n Array.isArray(node?.nodes) ? (node.nodes as unknown[]).filter((id): id is string => typeof id === 'string') : []\n\nconst elementOf = (node: RawNode | undefined): string => {\n const value = node?.props?.['component'] ?? node?.props?.['element']\n return typeof value === 'string' ? value : ''\n}\n\nconst LINK_COMPONENTS = new Set(['muiScreenLink', 'muiButton'])\n\n/**\n * The model's layout with the header's navigation written in: a Page Link to\n * every planned page the header does not already link, placed in the first\n * nav element above the Layout Slot, else in a nav row the platform appends\n * to the Toolbar Content; and every OTHER nav element the model left empty\n * given the same links, so no nav is ever empty. A link to the site's current\n * home page is pointed at the planned home, which replaces it when the site\n * is published. Anything that is not a node map, or has no planned pages, is\n * returned as it came, for the doctrine to judge.\n */\nexport function aiLayoutWithSitePages(\n tree: unknown,\n pages: readonly AiLayoutSitePage[],\n options: { homeScreenIds?: readonly string[] } = {},\n): unknown {\n if (!pages.length || !isRecord(tree) || !isRecord(tree['nodes'])) return tree\n const rootId = typeof tree['rootId'] === 'string' ? tree['rootId'] : null\n const source = tree['nodes'] as Record<string, unknown>\n if (!rootId || !isRecord(source[rootId])) return tree\n const nodes: Record<string, RawNode> = {}\n for (const [id, node] of Object.entries(source)) {\n if (!isRecord(node)) continue\n nodes[id] = {\n ...(node as RawNode),\n ...(isRecord(node['props']) ? { props: { ...(node['props'] as Record<string, unknown>) } } : {}),\n ...(Array.isArray(node['nodes']) ? { nodes: [...(node['nodes'] as unknown[])] } : {}),\n }\n }\n\n // Document order, and where the header ends: the Layout Slot.\n const order: string[] = []\n const seen = new Set<string>()\n const visit = (id: string) => {\n if (seen.has(id) || !nodes[id]) return\n seen.add(id)\n order.push(id)\n for (const child of childrenOf(nodes[id])) visit(child)\n }\n visit(rootId)\n const slotAt = order.findIndex((id) => nodes[id].componentId === 'layoutSlot')\n const header = slotAt === -1 ? order : order.slice(0, slotAt)\n\n // The site's current home is replaced by the planned one at publish.\n const plannedHome = pages.find((page) => screenPath(page.slug) === '/')\n const oldHomes = new Set(options.homeScreenIds ?? [])\n let changed = false\n if (plannedHome) {\n for (const id of order) {\n const node = nodes[id]\n const screenId = node.props?.['screenId']\n if (LINK_COMPONENTS.has(String(node.componentId)) && typeof screenId === 'string' && oldHomes.has(screenId)) {\n node.props = { ...node.props, screenId: plannedHome.id }\n changed = true\n }\n }\n }\n\n const used = new Set(Object.keys(nodes))\n const freshId = (base: string): string => {\n let id = base\n for (let n = 2; used.has(id); n += 1) id = `${base}${n}`\n used.add(id)\n return id\n }\n const linkNodes = (entries: readonly AiLayoutSitePage[]): string[] =>\n entries.map((page) => {\n const id = freshId(`aiNavLink_${page.id.replace(/[^A-Za-z0-9_]/g, '')}`)\n nodes[id] = {\n componentId: 'muiScreenLink',\n props: { children: page.label.slice(0, 40), screenId: page.id, renderAs: 'link', color: 'inherit' },\n }\n return id\n })\n\n const linkedInHeader = new Set(\n header\n .map((id) => nodes[id])\n .filter((node) => LINK_COMPONENTS.has(String(node.componentId)))\n .map((node) => node.props?.['screenId'])\n .filter((value): value is string => typeof value === 'string'),\n )\n const missing = pages.filter((page) => !linkedInHeader.has(page.id))\n const navs = order.filter((id) => elementOf(nodes[id]) === 'nav')\n const headerNav = navs.find((id) => header.includes(id)) ?? null\n\n if (missing.length) {\n if (headerNav) {\n nodes[headerNav].nodes = [...childrenOf(nodes[headerNav]), ...linkNodes(missing)]\n changed = true\n } else {\n const toolbar = header.find((id) => nodes[id].componentId === 'muiToolbar')\n if (toolbar) {\n const navId = freshId('aiSiteNav')\n nodes[navId] = {\n componentId: 'muiStack',\n props: { component: 'nav', direction: 'row', alignItems: 'center' },\n sx: { gap: 2 },\n nodes: linkNodes(missing),\n }\n nodes[toolbar].nodes = [...childrenOf(nodes[toolbar]), navId]\n changed = true\n }\n }\n }\n // A nav element the model left empty anywhere else — a footer's, say — links the same pages.\n for (const id of navs) {\n if (id === headerNav && missing.length) continue\n if (childrenOf(nodes[id]).some((child) => nodes[child])) continue\n nodes[id].nodes = linkNodes(pages)\n changed = true\n }\n return changed ? { ...tree, nodes } : tree\n}\n"],"names":["normalizeScreenSlug","SCREEN_ROOT_PATH","AI_LAYOUT_SITE_PAGES_INPUT","AI_LAYOUT_SITE_PAGES_MAX","aiLayoutIsHomeSlug","slug","replace","CONFIRMATION","aiLayoutSitePagesOfPlan","screens","options","linked","screen","guided","test","title","nav","pages","filter","record","id","map","label","trim","page","slice","aiLayoutSitePages","inputs","raw","Array","isArray","entry","some","push","aiLayoutSitePagesLines","length","screenPath","join","normalized","isRecord","value","Boolean","childrenOf","node","nodes","elementOf","props","LINK_COMPONENTS","Set","aiLayoutWithSitePages","tree","navs","rootId","source","Object","entries","order","seen","visit","has","add","child","slotAt","findIndex","componentId","header","plannedHome","find","oldHomes","homeScreenIds","changed","screenId","String","used","keys","freshId","base","n","linkNodes","children","renderAs","color","linkedInHeader","missing","headerNav","includes","toolbar","navId","component","direction","alignItems","sx","gap"],"mappings":"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"}
@@ -78,6 +78,11 @@ export declare function aiJobLayoutPrompt(job: Pick<AiJob, 'brief'> & Partial<Pi
78
78
  * component is placed rather than drawn again in the header.
79
79
  */
80
80
  export declare function aiLayoutReuseCheck(inventory: AiSiteInventory | null, plan: AiJobPlan | null): (tree: AiValidatedTree) => AiDoctrineViolation[];
81
+ /**
82
+ * Every check the layout door adds to the doctrine's: the reuse and region
83
+ * checks above, and no contact detail the brief does not give (AGL-3596).
84
+ */
85
+ export declare function aiLayoutChecks(inventory: AiSiteInventory | null, plan: AiJobPlan | null, brief: string): (tree: AiValidatedTree) => AiDoctrineViolation[];
81
86
  /**
82
87
  * The regions the confirmed plan gives the layout, against the layout a COPY
83
88
  * actually produced (AGL-3024); `null` when the copy keeps the plan, or when
@@ -29,6 +29,11 @@ import { aiDraftAdmissionRefusal, aiDraftAllowanceRefusal, aiSiteSubdomain, read
29
29
  import { aiBracketedFactsNote, aiCopySourceLines, aiConfirmedPlan, aiDoctrineReview, aiGenerationSpent, aiJobBriefLine, aiLimitReview, aiModelNodeIds, aiPlacedComponentIds, aiPlanCreation, aiPlanReferenceLines, aiPlanReuseViolations, aiUnspentOutcome } from "./ai-job-generation.js";
30
30
  import { aiPlanRegionViolations, aiPlannedLayoutRegions } from "./ai-job-plan-conformance.js";
31
31
  import { aiLayoutSitePages, aiLayoutSitePagesLines, aiLayoutWithSitePages } from "./ai-job-layout-site-pages.js";
32
+ import { aiLayoutInventedContactViolations, aiLayoutWithSiteName } from "./ai-layout-site-facts.js";
33
+ import { aiLayoutWithFullHeight } from "./ai-layout-full-height.js";
34
+ import { AI_LAYOUT_FRAME_TOOL } from "../layout-language/ai-layout-language.js";
35
+ import { AI_JOB_LAYOUT_LANGUAGE_BUDGET, AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS, AI_LAYOUT_FRAME_KIND, aiLayoutFrameCheck, aiLayoutFramePrompt, aiLayoutFrameTargets, aiLayoutHomeId, aiLayoutNavPages, aiLayoutSiteName } from "./ai-job-layout-language.js";
36
+ import { aiJobUsesLayoutLanguage } from "./ai-job-page-language.js";
32
37
  import { aiJobStepBudget } from "./ai-job-budget.js";
33
38
  import { registerAiJobStep } from "./ai-jobs.js";
34
39
  /**
@@ -139,6 +144,24 @@ const FOOTER_NAME = /\bfooter\b/i;
139
144
  return violations;
140
145
  };
141
146
  }
147
+ /**
148
+ * Every check the layout door adds to the doctrine's: the reuse and region
149
+ * checks above, and no contact detail the brief does not give (AGL-3596).
150
+ */ export function aiLayoutChecks(inventory, plan, brief) {
151
+ const reuse = aiLayoutReuseCheck(inventory, plan);
152
+ return (tree)=>[
153
+ ...reuse(tree),
154
+ ...aiLayoutInventedContactViolations({
155
+ rootId: tree.rootId,
156
+ nodes: tree.nodes
157
+ }, brief).map((violation)=>{
158
+ var _violation_nodeIds;
159
+ return _extends({}, violation, {
160
+ nodeIds: aiModelNodeIds((_violation_nodeIds = violation.nodeIds) != null ? _violation_nodeIds : [], tree.sourceIds)
161
+ });
162
+ })
163
+ ];
164
+ }
142
165
  /**
143
166
  * The regions the confirmed plan gives the layout, against the layout a COPY
144
167
  * actually produced (AGL-3024); `null` when the copy keeps the plan, or when
@@ -310,6 +333,77 @@ export function createAiJobLayoutStep(deps = {}) {
310
333
  if (allowance) return aiUnspentOutcome(model, {
311
334
  review: aiLimitReview(allowance)
312
335
  });
336
+ if (aiJobUsesLayoutLanguage(job)) {
337
+ // ── The header and footer designed in the layout language (AGL-3660) ──
338
+ const siteName = aiLayoutSiteName(job);
339
+ const pages = aiLayoutNavPages(job, inventory);
340
+ const targets = aiLayoutFrameTargets(job, inventory);
341
+ const result = await runValidatedGeneration(AI_LAYOUT_FRAME_KIND, _extends({
342
+ step: 'job.layout',
343
+ model,
344
+ instructions: AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS,
345
+ inventory,
346
+ messages: [
347
+ {
348
+ role: 'user',
349
+ content: aiLayoutFramePrompt({
350
+ job,
351
+ siteName,
352
+ pages,
353
+ targets
354
+ })
355
+ }
356
+ ],
357
+ tool: AI_LAYOUT_FRAME_TOOL,
358
+ maxTokens: AI_JOB_LAYOUT_LANGUAGE_BUDGET.maxTokens(model),
359
+ thinking: 'off',
360
+ check: aiLayoutFrameCheck({
361
+ siteName,
362
+ homeId: aiLayoutHomeId(pages, inventory),
363
+ pages,
364
+ targets,
365
+ extend: aiLayoutChecks(inventory, plan, job.brief)
366
+ })
367
+ }, signal ? {
368
+ signal
369
+ } : {}));
370
+ const spent = aiGenerationSpent(result);
371
+ if (result.status === 'refused') return _extends({}, spent, {
372
+ refused: true
373
+ });
374
+ if (result.status === 'needs_input') return _extends({}, spent, {
375
+ review: aiDoctrineReview(result)
376
+ });
377
+ const draft = await writeAiDraft(firestore, {
378
+ kind: 'layout',
379
+ hostId,
380
+ id: draftId,
381
+ uid: job.createdBy,
382
+ org,
383
+ name,
384
+ nodes: result.value.nodes,
385
+ aiJobId: aiOriginJobId(job),
386
+ now
387
+ });
388
+ if (draft.ok === false) {
389
+ if (draft.status === 404) throw new Error(`site ${hostId} vanished while its layout was generated`);
390
+ return _extends({}, spent, {
391
+ review: aiLimitReview(draft.error)
392
+ });
393
+ }
394
+ const note = aiBracketedFactsNote({
395
+ tree: {
396
+ rootId: result.value.rootId,
397
+ nodes: result.value.nodes
398
+ },
399
+ inventory
400
+ });
401
+ return _extends({}, spent, {
402
+ outputs: [
403
+ output(draft, result.value.load, note)
404
+ ]
405
+ });
406
+ }
313
407
  // The pages a site scaffold builds after this layout (AGL-3596): their
314
408
  // ids are the plan's, so the header links them by id, written by the
315
409
  // platform before the doctrine checks the tree.
@@ -332,17 +426,23 @@ export function createAiJobLayoutStep(deps = {}) {
332
426
  } : {}, AI_ROUTING_TABLE['job.layout'].effort ? {
333
427
  effort: AI_ROUTING_TABLE['job.layout'].effort
334
428
  } : {}, {
335
- extend: aiLayoutReuseCheck(inventory, plan)
429
+ extend: aiLayoutChecks(inventory, plan, job.brief),
430
+ // The site's name is written as the token that reads it, and the
431
+ // layout fills the window so a short page keeps its footer at the
432
+ // bottom (AGL-3596).
433
+ complete: (tree)=>{
434
+ var _job_inputs;
435
+ return aiLayoutWithFullHeight(aiLayoutWithSiteName(sitePages.length ? aiLayoutWithSitePages(tree, sitePages, {
436
+ homeScreenIds: aiHomeScreenIds(inventory)
437
+ }) : tree, typeof ((_job_inputs = job.inputs) == null ? void 0 : _job_inputs['businessName']) === 'string' ? job.inputs['businessName'] : null));
438
+ }
336
439
  }, sitePages.length ? {
337
440
  context: {
338
441
  screenIds: [
339
442
  ...inventory.screens.map((screen)=>screen.id),
340
443
  ...sitePages.map((page)=>page.id)
341
444
  ]
342
- },
343
- complete: (tree)=>aiLayoutWithSitePages(tree, sitePages, {
344
- homeScreenIds: aiHomeScreenIds(inventory)
345
- })
445
+ }
346
446
  } : {}, signal ? {
347
447
  signal
348
448
  } : {}));