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

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 (220) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.js +8 -4
  3. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  4. package/src/lib/components/ai-job-follow.component.js +1 -0
  5. package/src/lib/components/ai-job-follow.component.js.map +1 -1
  6. package/src/lib/components/ai-job-links.js +13 -0
  7. package/src/lib/components/ai-job-links.js.map +1 -1
  8. package/src/lib/components/ai-jobs-list-page.component.js +1 -0
  9. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  10. package/src/lib/components/ai-media-create.component.d.ts +20 -7
  11. package/src/lib/components/ai-media-create.component.js +107 -55
  12. package/src/lib/components/ai-media-create.component.js.map +1 -1
  13. package/src/lib/components/ai-seo-fields-card.component.js +1 -0
  14. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  15. package/src/lib/components/ai-site-memory-card.component.d.ts +32 -0
  16. package/src/lib/components/ai-site-memory-card.component.js +187 -0
  17. package/src/lib/components/ai-site-memory-card.component.js.map +1 -0
  18. package/src/lib/components/ai-site-start-card.component.js +80 -77
  19. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  20. package/src/lib/components/ai-theme-proposal-card.component.js +4 -0
  21. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  22. package/src/lib/components/ai-usage-strip.component.js +1 -0
  23. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  24. package/src/lib/components/assist-panel.component.d.ts +10 -0
  25. package/src/lib/components/assist-panel.component.js +119 -9
  26. package/src/lib/components/assist-panel.component.js.map +1 -1
  27. package/src/lib/components/assist-signals-page.component.js +2 -0
  28. package/src/lib/components/assist-signals-page.component.js.map +1 -1
  29. package/src/lib/components/billing-ai-allotments.component.js +3 -0
  30. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  31. package/src/lib/components/billing-ai-top-users.component.js +1 -0
  32. package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
  33. package/src/lib/components/billing-assist-overage-card.component.js +2 -0
  34. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  35. package/src/lib/components/member-ai-usage-card.component.js +10 -0
  36. package/src/lib/components/member-ai-usage-card.component.js.map +1 -1
  37. package/src/lib/components/staff-org-ai-card.component.js +1 -0
  38. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  39. package/src/lib/components/staff-org-ai-conversations.component.d.ts +23 -0
  40. package/src/lib/components/staff-org-ai-conversations.component.js +346 -0
  41. package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -0
  42. package/src/lib/jobs/ai-build-capabilities.d.ts +13 -1
  43. package/src/lib/jobs/ai-build-capabilities.js +101 -7
  44. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  45. package/src/lib/jobs/ai-business-profile-prefill.d.ts +29 -0
  46. package/src/lib/jobs/ai-business-profile-prefill.js +85 -0
  47. package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -0
  48. package/src/lib/jobs/ai-job-build-step.js +3 -1
  49. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  50. package/src/lib/jobs/ai-job-edit-step.d.ts +225 -0
  51. package/src/lib/jobs/ai-job-edit-step.js +631 -0
  52. package/src/lib/jobs/ai-job-edit-step.js.map +1 -0
  53. package/src/lib/jobs/ai-job-insight-step.js +21 -4
  54. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  55. package/src/lib/jobs/ai-job-layout-language.d.ts +8 -3
  56. package/src/lib/jobs/ai-job-layout-language.js +8 -2
  57. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  58. package/src/lib/jobs/ai-job-layout-step.js +7 -6
  59. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  60. package/src/lib/jobs/ai-job-logic-step.d.ts +20 -0
  61. package/src/lib/jobs/ai-job-logic-step.js +87 -6
  62. package/src/lib/jobs/ai-job-logic-step.js.map +1 -1
  63. package/src/lib/jobs/ai-job-page-language.d.ts +52 -7
  64. package/src/lib/jobs/ai-job-page-language.js +72 -9
  65. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  66. package/src/lib/jobs/ai-job-page-step.js +12 -2
  67. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  68. package/src/lib/jobs/ai-job-plan-step.d.ts +19 -5
  69. package/src/lib/jobs/ai-job-plan-step.js +54 -20
  70. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  71. package/src/lib/jobs/ai-job-site-content.d.ts +163 -0
  72. package/src/lib/jobs/ai-job-site-content.js +443 -0
  73. package/src/lib/jobs/ai-job-site-content.js.map +1 -0
  74. package/src/lib/jobs/ai-job-site-look.d.ts +112 -0
  75. package/src/lib/jobs/ai-job-site-look.js +322 -0
  76. package/src/lib/jobs/ai-job-site-look.js.map +1 -0
  77. package/src/lib/jobs/ai-job-site-step.d.ts +26 -5
  78. package/src/lib/jobs/ai-job-site-step.js +163 -24
  79. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  80. package/src/lib/jobs/ai-jobs-notify.d.ts +6 -1
  81. package/src/lib/jobs/ai-jobs-notify.js +15 -3
  82. package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
  83. package/src/lib/layout-language/ai-layout-compiler.d.ts +9 -0
  84. package/src/lib/layout-language/ai-layout-compiler.js +120 -33
  85. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  86. package/src/lib/layout-language/ai-layout-frame.js +5 -4
  87. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  88. package/src/lib/layout-language/ai-layout-language.d.ts +12 -5
  89. package/src/lib/layout-language/ai-layout-language.js +92 -9
  90. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  91. package/src/lib/layout-language/ai-layout-pictures.d.ts +144 -0
  92. package/src/lib/layout-language/ai-layout-pictures.js +214 -0
  93. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -0
  94. package/src/lib/layout-language/ai-layout-store.d.ts +2 -0
  95. package/src/lib/layout-language/ai-layout-store.js +7 -1
  96. package/src/lib/layout-language/ai-layout-store.js.map +1 -1
  97. package/src/lib/model/ai-build-progress.js +4 -1
  98. package/src/lib/model/ai-build-progress.js.map +1 -1
  99. package/src/lib/model/ai-edit-job.d.ts +109 -0
  100. package/src/lib/model/ai-edit-job.js +157 -0
  101. package/src/lib/model/ai-edit-job.js.map +1 -0
  102. package/src/lib/model/ai-job-activity.js +1 -0
  103. package/src/lib/model/ai-job-activity.js.map +1 -1
  104. package/src/lib/model/ai-jobs.types.d.ts +3 -1
  105. package/src/lib/model/ai-jobs.types.js.map +1 -1
  106. package/src/lib/model/ai-logic-job.d.ts +18 -1
  107. package/src/lib/model/ai-logic-job.js +24 -3
  108. package/src/lib/model/ai-logic-job.js.map +1 -1
  109. package/src/lib/model/ai-media-credits.d.ts +8 -4
  110. package/src/lib/model/ai-media-credits.js +11 -4
  111. package/src/lib/model/ai-media-credits.js.map +1 -1
  112. package/src/lib/model/ai-media-kinds.d.ts +68 -0
  113. package/src/lib/model/ai-media-kinds.js +98 -0
  114. package/src/lib/model/ai-media-kinds.js.map +1 -0
  115. package/src/lib/model/ai-site-build-progress.d.ts +4 -0
  116. package/src/lib/model/ai-site-build-progress.js +28 -11
  117. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  118. package/src/lib/model/ai-site-context.d.ts +111 -0
  119. package/src/lib/model/ai-site-context.js +222 -0
  120. package/src/lib/model/ai-site-context.js.map +1 -0
  121. package/src/lib/model/ai-site-job.d.ts +10 -2
  122. package/src/lib/model/ai-site-job.js +12 -5
  123. package/src/lib/model/ai-site-job.js.map +1 -1
  124. package/src/lib/model/ai-site-kinds.d.ts +164 -0
  125. package/src/lib/model/ai-site-kinds.js +1942 -0
  126. package/src/lib/model/ai-site-kinds.js.map +1 -0
  127. package/src/lib/model/ai-site-look.d.ts +150 -0
  128. package/src/lib/model/ai-site-look.js +1236 -0
  129. package/src/lib/model/ai-site-look.js.map +1 -0
  130. package/src/lib/model/ai-site-memory.d.ts +80 -0
  131. package/src/lib/model/ai-site-memory.js +171 -0
  132. package/src/lib/model/ai-site-memory.js.map +1 -0
  133. package/src/lib/model/ai-site-start.d.ts +10 -1
  134. package/src/lib/model/ai-site-start.js +16 -2
  135. package/src/lib/model/ai-site-start.js.map +1 -1
  136. package/src/lib/model/assist-follow-up.d.ts +92 -0
  137. package/src/lib/model/assist-follow-up.js +128 -0
  138. package/src/lib/model/assist-follow-up.js.map +1 -0
  139. package/src/lib/plugin-config.js +2 -0
  140. package/src/lib/plugin-config.js.map +1 -1
  141. package/src/lib/plugin.js +20 -1
  142. package/src/lib/plugin.js.map +1 -1
  143. package/src/lib/providers/anthropic.d.ts +9 -1
  144. package/src/lib/providers/anthropic.js +143 -9
  145. package/src/lib/providers/anthropic.js.map +1 -1
  146. package/src/lib/providers/catalog.d.ts +37 -7
  147. package/src/lib/providers/catalog.js +103 -11
  148. package/src/lib/providers/catalog.js.map +1 -1
  149. package/src/lib/providers/contract.d.ts +29 -0
  150. package/src/lib/providers/contract.js.map +1 -1
  151. package/src/lib/providers/image-contract.d.ts +22 -1
  152. package/src/lib/providers/image-contract.js +40 -1
  153. package/src/lib/providers/image-contract.js.map +1 -1
  154. package/src/lib/providers/image-endpoints.d.ts +24 -0
  155. package/src/lib/providers/image-endpoints.js +28 -0
  156. package/src/lib/providers/image-endpoints.js.map +1 -0
  157. package/src/lib/providers/model-choice.js +23 -2
  158. package/src/lib/providers/model-choice.js.map +1 -1
  159. package/src/lib/providers/routing.js +18 -0
  160. package/src/lib/providers/routing.js.map +1 -1
  161. package/src/lib/providers/vertex-image-endpoint.d.ts +48 -0
  162. package/src/lib/providers/vertex-image-endpoint.js +80 -0
  163. package/src/lib/providers/vertex-image-endpoint.js.map +1 -0
  164. package/src/lib/providers/vertex-image.d.ts +13 -27
  165. package/src/lib/providers/vertex-image.js +15 -42
  166. package/src/lib/providers/vertex-image.js.map +1 -1
  167. package/src/lib/runtime/ai-blog-post-generation.d.ts +62 -0
  168. package/src/lib/runtime/ai-blog-post-generation.js +89 -0
  169. package/src/lib/runtime/ai-blog-post-generation.js.map +1 -0
  170. package/src/lib/runtime/ai-dev-env.d.ts +53 -0
  171. package/src/lib/runtime/ai-dev-env.js +72 -0
  172. package/src/lib/runtime/ai-dev-env.js.map +1 -0
  173. package/src/lib/runtime/ai-dev-replay.d.ts +116 -0
  174. package/src/lib/runtime/ai-dev-replay.js +313 -0
  175. package/src/lib/runtime/ai-dev-replay.js.map +1 -0
  176. package/src/lib/runtime/ai-doctrine.js +5 -1
  177. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  178. package/src/lib/runtime/ai-eval-live.js +4 -0
  179. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  180. package/src/lib/runtime/ai-eval.d.ts +13 -1
  181. package/src/lib/runtime/ai-eval.js +84 -1
  182. package/src/lib/runtime/ai-eval.js.map +1 -1
  183. package/src/lib/runtime/ai-live-batch.d.ts +45 -0
  184. package/src/lib/runtime/ai-live-batch.js +89 -0
  185. package/src/lib/runtime/ai-live-batch.js.map +1 -0
  186. package/src/lib/runtime/ai-runtime.js +46 -2
  187. package/src/lib/runtime/ai-runtime.js.map +1 -1
  188. package/src/lib/runtime/site-context.d.ts +58 -0
  189. package/src/lib/runtime/site-context.js +127 -0
  190. package/src/lib/runtime/site-context.js.map +1 -0
  191. package/src/lib/server/ai-admin-conversations.d.ts +22 -0
  192. package/src/lib/server/ai-admin-conversations.js +197 -0
  193. package/src/lib/server/ai-admin-conversations.js.map +1 -0
  194. package/src/lib/server/ai-media-image.d.ts +5 -5
  195. package/src/lib/server/ai-media-image.js +25 -11
  196. package/src/lib/server/ai-media-image.js.map +1 -1
  197. package/src/lib/server/ai-media-raster-prompt.d.ts +34 -0
  198. package/src/lib/server/ai-media-raster-prompt.js +53 -0
  199. package/src/lib/server/ai-media-raster-prompt.js.map +1 -0
  200. package/src/lib/server/assist-chat.d.ts +11 -0
  201. package/src/lib/server/assist-chat.js +57 -18
  202. package/src/lib/server/assist-chat.js.map +1 -1
  203. package/src/lib/server/assist-edit-applied.js +16 -3
  204. package/src/lib/server/assist-edit-applied.js.map +1 -1
  205. package/src/lib/server/assist-view-context.d.ts +19 -1
  206. package/src/lib/server/assist-view-context.js +38 -1
  207. package/src/lib/server/assist-view-context.js.map +1 -1
  208. package/src/lib/server.js +10 -0
  209. package/src/lib/server.js.map +1 -1
  210. package/src/lib/subprocessors.js +17 -1
  211. package/src/lib/subprocessors.js.map +1 -1
  212. package/src/lib/tools/ai-blog-post-tool.d.ts +60 -0
  213. package/src/lib/tools/ai-blog-post-tool.js +175 -0
  214. package/src/lib/tools/ai-blog-post-tool.js.map +1 -0
  215. package/src/lib/usage/assist-usage.d.ts +3 -0
  216. package/src/lib/usage/assist-usage.js +4 -2
  217. package/src/lib/usage/assist-usage.js.map +1 -1
  218. package/src/lib/usage/staff-org-ai-conversations.d.ts +103 -0
  219. package/src/lib/usage/staff-org-ai-conversations.js +152 -0
  220. package/src/lib/usage/staff-org-ai-conversations.js.map +1 -0
@@ -21,7 +21,7 @@ import { type AiSiteInventory } from '../model/ai-site-inventory';
21
21
  import { type AiLayoutSection, type AiLayoutSettlement } from '../layout-language/ai-layout-language';
22
22
  import type { AiLayoutTargets } from '../layout-language/ai-layout-links';
23
23
  import { type AiGenerationCheck, type AiValidatedGeneration } from '../runtime/ai-doctrine';
24
- import { type AiDoctrineTreeContext } from '../runtime/ai-doctrine-validators';
24
+ import { type AiDoctrineTreeContext, type AiDoctrineViolation } from '../runtime/ai-doctrine-validators';
25
25
  import type { AiLoadEstimate } from '../runtime/ai-palette';
26
26
  import type { AiSystemBlock } from '../runtime/ai-runtime';
27
27
  /**
@@ -46,18 +46,46 @@ export declare const AI_LAYOUT_FORM_PAGE_INPUT = "layoutFormPage";
46
46
  /** Whether this job builds its page or its layout in the layout language. */
47
47
  export declare function aiJobUsesLayoutLanguage(job: Pick<AiJob, 'inputs'>): boolean;
48
48
  /**
49
- * The most one page's answer asks for. A section in the language is about
50
- * 120 to 250 tokens with its copy, so the largest page a plan admits — eight
51
- * sections — fits with room; the compiler, not the ceiling, keeps a page small.
49
+ * How the language doors think (AGL-3660): adaptively, at an effort named
50
+ * rather than left to the model's default. Zach, 2026-10-07: quality first —
51
+ * a page and a site's frame are designed, not transcribed, and the caching
52
+ * work pays for the thinking. Medium is where Claude Sonnet 5.5, the balanced
53
+ * default, is tuned to start for multistep design work.
52
54
  */
53
- export declare const AI_JOB_PAGE_LANGUAGE_TOKENS = 3000;
55
+ export declare const AI_LAYOUT_LANGUAGE_THINKING: {
56
+ readonly thinking: "adaptive";
57
+ readonly effort: "medium";
58
+ };
59
+ /**
60
+ * Room for the thinking a page's answer does before it writes, inside the
61
+ * same `max_tokens` as the answer: thinking is drawn from the ceiling first,
62
+ * so a ceiling sized for the answer alone would cut a thoughtful answer off.
63
+ *
64
+ * Bounded by TIME, not by money: the page step's answer, its re-ask and the
65
+ * follow-up must fit the least time a beat gives a step
66
+ * (`AI_JOB_STEP_MAX_MINIMUM_MS`), and `aiJobStepBudget` quietly lowers every
67
+ * ceiling to what fits. 2,000 here, with the follow-up's 500, is the most
68
+ * that fits whole on the balanced tier; at 5,000 the budget cut a page's
69
+ * ceiling to 879 tokens, under the answer itself.
70
+ */
71
+ export declare const AI_LAYOUT_PAGE_THINKING_TOKENS = 2000;
72
+ /** The same room for a site's header and footer, whose step has the time for more. */
73
+ export declare const AI_LAYOUT_FRAME_THINKING_TOKENS = 4000;
74
+ /**
75
+ * The most one page's answer asks for: the answer and the thinking before it.
76
+ * A section in the language is about 120 to 250 tokens with its copy, so the
77
+ * largest page a plan admits — eight sections — fits in 3,000 with room (the
78
+ * live runs of 2026-10-07 wrote 683 to 1,745); the compiler, not the ceiling,
79
+ * keeps a page small.
80
+ */
81
+ export declare const AI_JOB_PAGE_LANGUAGE_TOKENS: number;
54
82
  /**
55
83
  * The time a language page pass needs: its lookup rounds, its answer and its
56
84
  * re-ask at `AI_JOB_PAGE_LANGUAGE_TOKENS` on the tier the page step is served
57
85
  * from, with the step's reads and writes.
58
86
  */
59
- /** The most the follow-up asking only for the sections an answer left out may run to. */
60
- export declare const AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS = 1500;
87
+ /** The most the follow-up asking only for the sections an answer left out may run to, its thinking included. */
88
+ export declare const AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS: number;
61
89
  export declare const AI_JOB_PAGE_LANGUAGE_BUDGET: import("./ai-job-budget").AiJobStepBudget;
62
90
  /** The doctrine scope the language doors are told: values the compiler checks itself. */
63
91
  export declare const AI_LAYOUT_PAGE_KIND = "layout-page";
@@ -90,6 +118,8 @@ export interface AiLayoutPageBuilt {
90
118
  settled: AiLayoutSettlement[];
91
119
  /** What the last answer's gaps took out of the page (`ai-layout-gaps.ts`). */
92
120
  dropped: string[];
121
+ /** How many repeated items each plan section shows, as built (AGL-3660). */
122
+ items: number[];
93
123
  }
94
124
  export interface AiLayoutPageCheckInput {
95
125
  screen: AiBuildPlanScreen;
@@ -119,6 +149,21 @@ export declare function aiLayoutMissingSectionsPrompt(input: {
119
149
  export declare function aiLayoutMissingSections(kept: ReadonlyArray<AiLayoutSection | null>): number[];
120
150
  /** The violation a planned section the answer gave nothing usable for is named by. */
121
151
  export declare const AI_LAYOUT_SECTION_MISSING_CODE = "layout-section-missing";
152
+ /** The code a section planned with items that shows none is refused under (AGL-3660). */
153
+ export declare const AI_LAYOUT_SECTION_EMPTY_CODE = "layout-section-no-items";
154
+ /**
155
+ * How many repeated items each section shows once stored (AGL-3660): the
156
+ * elements the compiler drew for them, under the ids they are stored by,
157
+ * that are still in the page after anything later took words out.
158
+ */
159
+ export declare function aiLayoutShownItems(itemIds: readonly (readonly string[])[], storedIds: Readonly<Record<string, string>>, nodes: Readonly<Record<string, unknown>>): number[];
160
+ /**
161
+ * The sections a page planned with repeated items — cards, steps, a list —
162
+ * that show none, or too few, once the page is built (AGL-3660). A heading
163
+ * and an intro over nothing is a section a visitor reads as broken, so it is
164
+ * asked for again.
165
+ */
166
+ export declare function aiLayoutEmptyItemSections(screen: Pick<AiBuildPlanScreen, 'sections'>, items: readonly number[]): AiDoctrineViolation[];
122
167
  /**
123
168
  * The check a language page answer is held to: each planned section read,
124
169
  * the page compiled, stored and checked whole. Sections that read are kept
@@ -25,6 +25,7 @@ import { AI_GENERATION_MAX_ATTEMPTS } from "../runtime/ai-generation-bounds.js";
25
25
  import { AI_STEP_TIERS } from "../providers/catalog.js";
26
26
  import { runValidatedGeneration } from "../runtime/ai-doctrine.js";
27
27
  import { validateAiDoctrineTree } from "../runtime/ai-doctrine-validators.js";
28
+ import { aiSiteKindDesignLines } from "../model/ai-site-kinds.js";
28
29
  import { aiGenerationWorstCaseOnTierMs, aiJobStepBudget } from "./ai-job-budget.js";
29
30
  import { aiJobBriefLine, aiPlanReferenceLines } from "./ai-job-generation.js";
30
31
  import { aiLayoutSitePages } from "./ai-job-layout-site-pages.js";
@@ -50,15 +51,40 @@ import { aiLayoutInventedContactViolations } from "./ai-layout-site-facts.js";
50
51
  return ((_job_inputs = job.inputs) == null ? void 0 : _job_inputs[AI_LAYOUT_LANGUAGE_INPUT]) === true;
51
52
  }
52
53
  /**
53
- * The most one page's answer asks for. A section in the language is about
54
- * 120 to 250 tokens with its copy, so the largest page a plan admits — eight
55
- * sections — fits with room; the compiler, not the ceiling, keeps a page small.
56
- */ export const AI_JOB_PAGE_LANGUAGE_TOKENS = 3000;
54
+ * How the language doors think (AGL-3660): adaptively, at an effort named
55
+ * rather than left to the model's default. Zach, 2026-10-07: quality first —
56
+ * a page and a site's frame are designed, not transcribed, and the caching
57
+ * work pays for the thinking. Medium is where Claude Sonnet 5.5, the balanced
58
+ * default, is tuned to start for multistep design work.
59
+ */ export const AI_LAYOUT_LANGUAGE_THINKING = {
60
+ thinking: 'adaptive',
61
+ effort: 'medium'
62
+ };
63
+ /**
64
+ * Room for the thinking a page's answer does before it writes, inside the
65
+ * same `max_tokens` as the answer: thinking is drawn from the ceiling first,
66
+ * so a ceiling sized for the answer alone would cut a thoughtful answer off.
67
+ *
68
+ * Bounded by TIME, not by money: the page step's answer, its re-ask and the
69
+ * follow-up must fit the least time a beat gives a step
70
+ * (`AI_JOB_STEP_MAX_MINIMUM_MS`), and `aiJobStepBudget` quietly lowers every
71
+ * ceiling to what fits. 2,000 here, with the follow-up's 500, is the most
72
+ * that fits whole on the balanced tier; at 5,000 the budget cut a page's
73
+ * ceiling to 879 tokens, under the answer itself.
74
+ */ export const AI_LAYOUT_PAGE_THINKING_TOKENS = 2000;
75
+ /** The same room for a site's header and footer, whose step has the time for more. */ export const AI_LAYOUT_FRAME_THINKING_TOKENS = 4000;
76
+ /**
77
+ * The most one page's answer asks for: the answer and the thinking before it.
78
+ * A section in the language is about 120 to 250 tokens with its copy, so the
79
+ * largest page a plan admits — eight sections — fits in 3,000 with room (the
80
+ * live runs of 2026-10-07 wrote 683 to 1,745); the compiler, not the ceiling,
81
+ * keeps a page small.
82
+ */ export const AI_JOB_PAGE_LANGUAGE_TOKENS = 3000 + AI_LAYOUT_PAGE_THINKING_TOKENS;
57
83
  /**
58
84
  * The time a language page pass needs: its lookup rounds, its answer and its
59
85
  * re-ask at `AI_JOB_PAGE_LANGUAGE_TOKENS` on the tier the page step is served
60
86
  * from, with the step's reads and writes.
61
- */ /** The most the follow-up asking only for the sections an answer left out may run to. */ export const AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS = 1500;
87
+ */ /** The most the follow-up asking only for the sections an answer left out may run to, its thinking included. */ export const AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS = 1500 + 500;
62
88
  export const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({
63
89
  tier: AI_STEP_TIERS['job.page'],
64
90
  maxTokens: AI_JOB_PAGE_LANGUAGE_TOKENS,
@@ -137,6 +163,8 @@ export const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({
137
163
  return [
138
164
  `Page: "${screen.title}" at ${screen.slug}`,
139
165
  aiJobBriefLine(job),
166
+ // The kind of site the person picked sets how its pages are arranged (AGL-3660).
167
+ ...aiSiteKindDesignLines(job.inputs),
140
168
  ...aiPlanReferenceLines(plan),
141
169
  `Sections to design, in order:`,
142
170
  ...sections,
@@ -168,6 +196,37 @@ export const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({
168
196
  ]);
169
197
  }
170
198
  /** The violation a planned section the answer gave nothing usable for is named by. */ export const AI_LAYOUT_SECTION_MISSING_CODE = 'layout-section-missing';
199
+ /** The code a section planned with items that shows none is refused under (AGL-3660). */ export const AI_LAYOUT_SECTION_EMPTY_CODE = 'layout-section-no-items';
200
+ /** How many of a section's planned items it must show: one for a short list, two for anything longer. */ const leastItems = (planned)=>planned >= 3 ? 2 : planned >= 1 ? 1 : 0;
201
+ /**
202
+ * How many repeated items each section shows once stored (AGL-3660): the
203
+ * elements the compiler drew for them, under the ids they are stored by,
204
+ * that are still in the page after anything later took words out.
205
+ */ export function aiLayoutShownItems(itemIds, storedIds, nodes) {
206
+ return itemIds.map((ids)=>ids.filter((id)=>storedIds[id] !== undefined && storedIds[id] in nodes).length);
207
+ }
208
+ /**
209
+ * The sections a page planned with repeated items — cards, steps, a list —
210
+ * that show none, or too few, once the page is built (AGL-3660). A heading
211
+ * and an intro over nothing is a section a visitor reads as broken, so it is
212
+ * asked for again.
213
+ */ export function aiLayoutEmptyItemSections(screen, items) {
214
+ return screen.sections.flatMap((section, index)=>{
215
+ var _items_index;
216
+ const least = leastItems(section.items);
217
+ if (!least) return [];
218
+ const shown = (_items_index = items[index]) != null ? _items_index : 0;
219
+ if (shown >= least) return [];
220
+ return [
221
+ {
222
+ rule: null,
223
+ code: AI_LAYOUT_SECTION_EMPTY_CODE,
224
+ message: `Section ${index + 1} ("${section.name}") is planned with ${section.items} items and shows ${shown || 'none'}.`,
225
+ detail: `Give section ${index + 1} one cards, steps, stats, list or faq block holding its ${section.items} items, each with a title and its words.`
226
+ }
227
+ ];
228
+ });
229
+ }
171
230
  /**
172
231
  * The check a language page answer is held to: each planned section read,
173
232
  * the page compiled, stored and checked whole. Sections that read are kept
@@ -182,7 +241,7 @@ export const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({
182
241
  // The last answer a generation takes has its gaps taken out rather than asked about again.
183
242
  answers += 1;
184
243
  const last = answers >= AI_GENERATION_MAX_ATTEMPTS;
185
- const reading = aiReadLayoutPage(answer, fills.length);
244
+ const reading = aiReadLayoutPage(answer, fills.length, fills.map((index)=>input.screen.sections[index]));
186
245
  reading.sections.forEach((section, position)=>{
187
246
  // A section the answer gives is taken as given; one it leaves out keeps
188
247
  // what an earlier answer gave, so a re-ask may send only what was missing.
@@ -251,12 +310,15 @@ export const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({
251
310
  }, 'page', _extends({}, input.context, {
252
311
  scrollTargetIds: input.sectionIds
253
312
  }));
313
+ // Each section's items as they are stored, after anything the gaps took out.
314
+ const items = aiLayoutShownItems(compiled.itemIds, stored.storedIds, nodes);
254
315
  // A street address or opening hours the job was never given is invented
255
316
  // (rule 14, AGL-3596), the same check a layout is held to; a phone number
256
317
  // or an email the compiler already writes as its gap.
257
318
  const violations = [
258
319
  ...report.violations,
259
320
  ...copy.violations,
321
+ ...aiLayoutEmptyItemSections(input.screen, items),
260
322
  ...aiLayoutInventedContactViolations({
261
323
  rootId: CANVAS_ROOT_ELEMENT_ID,
262
324
  nodes: nodes
@@ -270,7 +332,8 @@ export const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({
270
332
  ...reading.settled,
271
333
  ...compiled.settled
272
334
  ],
273
- dropped
335
+ dropped,
336
+ items
274
337
  },
275
338
  violations
276
339
  };
@@ -324,8 +387,8 @@ export const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({
324
387
  cutOff: {
325
388
  noun: 'page',
326
389
  smaller: 'Write shorter copy, and fewer items in each group.'
327
- },
328
- thinking: 'off',
390
+ }
391
+ }, AI_LAYOUT_LANGUAGE_THINKING, {
329
392
  check: observe(aiLayoutPageCheck(_extends({
330
393
  screen: input.screen,
331
394
  sectionIds: input.sectionIds,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-page-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 { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { isAiPlanNewRef, type AiBuildPlanScreen } from '../model/ai-build-plan'\nimport type { AiJob, AiJobPlan } from '../model/ai-jobs.types'\nimport {\n aiHomeScreenIds,\n type AiSiteInventory,\n} from '../model/ai-site-inventory'\nimport { aiCompileLayoutPage } from '../layout-language/ai-layout-compiler'\nimport {\n AI_LAYOUT_LANGUAGE_TEXT,\n AI_LAYOUT_PAGE_TOOL,\n aiReadLayoutPage,\n type AiLayoutSection,\n type AiLayoutSettlement,\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 { AI_STEP_TIERS } from '../providers/catalog'\nimport {\n runValidatedGeneration,\n type AiGenerationCheck,\n type AiGenerationSpend,\n type AiValidatedGeneration,\n} from '../runtime/ai-doctrine'\nimport type { AiUsage } from '../providers/contract'\nimport {\n validateAiDoctrineTree,\n type AiDoctrineNode,\n type AiDoctrineTreeContext,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { aiGenerationWorstCaseOnTierMs, aiJobStepBudget } from './ai-job-budget'\nimport { aiJobBriefLine, aiPlanReferenceLines } from './ai-job-generation'\nimport { aiLayoutSitePages } from './ai-job-layout-site-pages'\nimport { aiLayoutInventedContactViolations } from './ai-layout-site-facts'\n\n/**\n * A page built in the compact layout language (AGL-3660): the page step's\n * pass for a site's page units — a guided start's and a build's — in place of\n * the raw-tree section passes.\n *\n * ONE answer a page: every planned section, in the language, through\n * `submit_page`. The compiler writes the tree, the palette validator stores\n * it as it stores any generated tree, and the doctrine checks the whole page.\n * A section the answer left out or left empty is the one thing a re-ask asks\n * for: the sections that read are kept, and the second answer fills only\n * what is still missing. Then the page step's last pass runs as it always\n * has — the whole page held to the doctrine again, its listing written, the\n * draft reported — so the item ledger, the credits and Try again work exactly\n * as they did.\n */\n\n/** The unit input that says a page or a layout is built in the layout language. */\nexport const AI_LAYOUT_LANGUAGE_INPUT = 'layoutLanguage'\n\n/** The unit input naming the page of the site that places its form, where it is another page. */\nexport const AI_LAYOUT_FORM_PAGE_INPUT = 'layoutFormPage'\n\n/** Whether this job builds its page or its layout in the layout language. */\nexport function aiJobUsesLayoutLanguage(job: Pick<AiJob, 'inputs'>): boolean {\n return job.inputs?.[AI_LAYOUT_LANGUAGE_INPUT] === true\n}\n\n/**\n * The most one page's answer asks for. A section in the language is about\n * 120 to 250 tokens with its copy, so the largest page a plan admits — eight\n * sections — fits with room; the compiler, not the ceiling, keeps a page small.\n */\nexport const AI_JOB_PAGE_LANGUAGE_TOKENS = 3_000\n\n/**\n * The time a language page pass needs: its lookup rounds, its answer and its\n * re-ask at `AI_JOB_PAGE_LANGUAGE_TOKENS` on the tier the page step is served\n * from, with the step's reads and writes.\n */\n/** The most the follow-up asking only for the sections an answer left out may run to. */\nexport const AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS = 1_500\n\nexport const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.page'],\n maxTokens: AI_JOB_PAGE_LANGUAGE_TOKENS,\n // The follow-up for sections an answer left out is one more generation.\n ownReadsMs: aiGenerationWorstCaseOnTierMs({\n tier: AI_STEP_TIERS['job.page'],\n maxTokens: AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS,\n }),\n})\n\n/** The doctrine scope the language doors are told: values the compiler checks itself. */\nexport const AI_LAYOUT_PAGE_KIND = 'layout-page'\n\n/** The language, as the page door's cached instructions. */\nexport const AI_JOB_PAGE_LANGUAGE_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n AI_LAYOUT_LANGUAGE_TEXT,\n '',\n 'Answer with submit_page: the whole page, one entry in sections for each planned section, in order.',\n ].join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\n/** The page of a site plan that places its form: the form the plan creates, or one the site has. */\nexport function aiLayoutFormPageOfPlan(\n plan: Pick<AiJobPlan, 'create' | 'screens'>,\n formIds: readonly string[] = [],\n): string | null {\n const created = new Set(\n plan.create\n .filter((entry) => entry.kind === 'form')\n .map((entry) => `new:${entry.name}`.toLowerCase()),\n )\n const forms = new Set(formIds)\n const screen = plan.screens.find((entry) =>\n entry.sections.some((section) =>\n section.uses.some((ref) =>\n isAiPlanNewRef(ref) ? created.has(ref.toLowerCase()) : forms.has(ref),\n ),\n ),\n )\n return typeof screen?.id === 'string' ? screen.id : null\n}\n\n/** Every word the job was given, which a page's contact details are held to. */\nexport function aiLayoutFacts(job: Pick<AiJob, 'brief' | 'inputs'>): string {\n const values = Object.values(job.inputs ?? {}).filter(\n (value): value is string => typeof value === 'string',\n )\n return [job.brief, ...values].join('\\n')\n}\n\n/** What a page's links resolve against: the site's pages, built and planned, its forms and components. */\nexport function aiLayoutPageTargets(input: {\n job: Pick<AiJob, 'brief' | 'inputs'>\n inventory: AiSiteInventory | null\n own: ReadonlyArray<string | null | undefined>\n}): AiLayoutTargets {\n const { job, inventory } = input\n const own = input.own.filter((id): id is string => !!id)\n const planned = aiLayoutSitePages(job.inputs)\n const pages: AiLayoutPage[] = [\n ...planned,\n ...(inventory?.screens ?? [])\n .filter(\n (row) => !row.template && !planned.some((page) => page.id === row.id),\n )\n .map((row) => ({ id: row.id, label: row.name, slug: row.slug })),\n ]\n const formPage = job.inputs?.[AI_LAYOUT_FORM_PAGE_INPUT]\n return {\n pageId: own[0] ?? null,\n pages: pages.filter((page) => !own.includes(page.id)),\n homeIds: aiHomeScreenIds(inventory),\n forms: (inventory?.forms ?? []).map((form) => ({\n id: form.id,\n name: form.name,\n })),\n formPageId:\n typeof formPage === 'string' && !own.includes(formPage) ? formPage : null,\n components: (inventory?.components ?? []).map((component) => ({\n id: component.id,\n name: component.name,\n props: component.props,\n })),\n facts: aiLayoutFacts(job),\n }\n}\n\n/** The page's user turn: the page, the brief, the plan, its sections and where its links may go. */\nexport function aiLayoutPagePrompt(input: {\n job: Pick<AiJob, 'brief' | 'inputs' | '$id'>\n plan: AiJobPlan\n screen: AiBuildPlanScreen\n targets: AiLayoutTargets\n reusableComponents: boolean\n /** The sections a re-ask still needs, by plan index; absent on the first ask. */\n missing?: readonly number[]\n}): string {\n const { job, plan, screen, targets } = input\n const sections = screen.sections.map((section, index) => {\n const places = section.uses.length\n ? `; places ${section.uses.join(', ')}`\n : ''\n const items = section.items ? `; shows ${section.items} items` : ''\n return `${index + 1}. \"${section.name}\"${places}${items}`\n })\n const pages = targets.pages.map((page) => `${page.label} (page:${page.id})`)\n const components = targets.components.map(\n (component) =>\n `${component.name} (${component.id}; props ${Object.keys(component.props).join(', ') || 'none'})`,\n )\n return [\n `Page: \"${screen.title}\" at ${screen.slug}`,\n aiJobBriefLine(job),\n ...aiPlanReferenceLines(plan),\n `Sections to design, in order:`,\n ...sections,\n pages.length\n ? `Other pages of this site: ${pages.join(', ')}.`\n : 'This site has no other page to link.',\n targets.forms.length\n ? `The site's form: ${targets.forms.map((form) => `${form.name} (${form.id})`).join(', ')}.`\n : 'The site has no saved form: place none.',\n ...(components.length\n ? [`Components this page may place: ${components.join('; ')}.`]\n : []),\n input.reusableComponents\n ? 'Where a section repeats an item, place the component its plan line names with cards whose to is that component.'\n : 'This site keeps no reusable components: draw repeated items as cards, steps, stats or a list.',\n // A per-job word, so two identical briefs are designed apart.\n `Design seed: ${job.$id.slice(-6)}.`,\n ].join('\\n')\n}\n\n/** A validated language page, stored as the page keeps it. */\nexport interface AiLayoutPageBuilt {\n nodes: NodesMap\n load: AiLoadEstimate | null\n settled: AiLayoutSettlement[]\n /** What the last answer's gaps took out of the page (`ai-layout-gaps.ts`). */\n dropped: string[]\n}\n\nexport interface AiLayoutPageCheckInput {\n screen: AiBuildPlanScreen\n sectionIds: readonly string[]\n targets: AiLayoutTargets\n context: AiDoctrineTreeContext\n reusableComponents: boolean\n /**\n * What earlier answers gave, by plan index, shared between the page's\n * generations: a follow-up asking only for missing sections fills the rest.\n */\n kept?: Array<AiLayoutSection | null>\n /** The plan indices this answer's sections fill, in order; absent, every section. */\n only?: readonly number[]\n}\n\n/**\n * The follow-up turn for the sections a page's answer left out (AGL-3660):\n * the page as before, told which sections it already has, and asked for only\n * the missing ones, in order — the one part of a page asked for again.\n */\nexport function aiLayoutMissingSectionsPrompt(input: {\n base: string\n screen: AiBuildPlanScreen\n missing: readonly number[]\n}): string {\n const names = input.missing.map((index) => `${index + 1}. \"${input.screen.sections[index].name}\"`)\n return [\n input.base,\n '',\n `The other sections of this page are already designed. Design ONLY these, in this order, one entry in sections each: ${names.join('; ')}. Give each at least one block.`,\n ].join('\\n')\n}\n\n/** The plan indices a page still lacks, after the answers so far. */\nexport function aiLayoutMissingSections(kept: ReadonlyArray<AiLayoutSection | null>): number[] {\n return kept.flatMap((section, index) => (section ? [] : [index]))\n}\n\n/** The violation a planned section the answer gave nothing usable for is named by. */\nexport const AI_LAYOUT_SECTION_MISSING_CODE = 'layout-section-missing'\n\n/**\n * The check a language page answer is held to: each planned section read,\n * the page compiled, stored and checked whole. Sections that read are kept\n * across the re-ask, so the second answer is asked only for what was missing,\n * and a section it sends again that already read is not taken twice.\n */\nexport function aiLayoutPageCheck(\n input: AiLayoutPageCheckInput,\n): AiGenerationCheck<AiLayoutPageBuilt> {\n const kept: Array<AiLayoutSection | null> = input.kept ?? input.screen.sections.map(() => null)\n const fills = input.only ?? input.screen.sections.map((_, index) => index)\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 reading = aiReadLayoutPage(answer, fills.length)\n reading.sections.forEach((section, position) => {\n // A section the answer gives is taken as given; one it leaves out keeps\n // what an earlier answer gave, so a re-ask may send only what was missing.\n if (section) kept[fills[position]] = section\n })\n const missing = kept.flatMap((section, index) => (section ? [] : [index]))\n if (missing.length) {\n const names = missing.map(\n (index) => `${index + 1} (\"${input.screen.sections[index].name}\")`,\n )\n return {\n value: null,\n violations: [\n {\n rule: null,\n code: AI_LAYOUT_SECTION_MISSING_CODE,\n message: `The page left out section ${names.join(', ')}.`,\n detail: `Answer again with every section in order; the sections you already designed are kept, so only ${names.join(', ')} ${missing.length === 1 ? 'needs' : 'need'} its design. Give each at least one block.`,\n },\n ],\n }\n }\n const compiled = aiCompileLayoutPage(\n kept as AiLayoutSection[],\n { title: input.screen.title, sections: input.screen.sections },\n input.targets,\n {\n reusableComponents: input.reusableComponents,\n sectionIds: input.sectionIds,\n },\n )\n const stored = aiLayoutStoredTree(\n compiled.tree,\n 'screen',\n input.context,\n input.sectionIds,\n )\n if (stored.ok === false) {\n // The compiler writes only palette trees, which the property spec holds;\n // a refusal here is the platform's, and is reported as one.\n console.error('ai layout page: a compiled page 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 page 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 page that is published as built.\n const copy = aiLayoutCopyCheck(\n stored.nodes,\n CANVAS_ROOT_ELEMENT_ID,\n {\n pages: input.targets.pages,\n ids: [\n ...input.targets.pages.map((page) => page.id),\n ...input.targets.forms.map((form) => form.id),\n ...input.targets.components.map((component) => component.id),\n ...input.sectionIds,\n ...(input.targets.pageId ? [input.targets.pageId] : []),\n ],\n },\n last,\n )\n const { nodes, dropped } = copy\n const report = validateAiDoctrineTree(\n { rootId: CANVAS_ROOT_ELEMENT_ID, nodes },\n 'page',\n {\n ...input.context,\n scrollTargetIds: input.sectionIds,\n },\n )\n // A street address or opening hours the job was never given is invented\n // (rule 14, AGL-3596), the same check a layout is held to; a phone number\n // or an email the compiler already writes as its gap.\n const violations: AiDoctrineViolation[] = [\n ...report.violations,\n ...copy.violations,\n ...aiLayoutInventedContactViolations(\n { rootId: CANVAS_ROOT_ELEMENT_ID, nodes: nodes as unknown as Record<string, AiDoctrineNode> },\n input.targets.facts,\n ),\n ]\n return {\n value: violations.length\n ? null\n : {\n nodes,\n load: report.load,\n settled: [...reading.settled, ...compiled.settled],\n dropped,\n },\n violations,\n }\n }\n}\n\nexport interface AiLayoutPageRunInput {\n job: Pick<AiJob, 'brief' | 'inputs' | '$id'>\n plan: AiJobPlan\n screen: AiBuildPlanScreen\n sectionIds: readonly string[]\n targets: AiLayoutTargets\n context: AiDoctrineTreeContext\n reusableComponents: boolean\n inventory: AiSiteInventory | null\n model: string\n signal?: AbortSignal\n /** Wraps the check, for an eval that records each answer it reads. */\n observe?: (check: AiGenerationCheck<AiLayoutPageBuilt>) => AiGenerationCheck<AiLayoutPageBuilt>\n}\n\n/** Two generations' spend as one: every call is billed. */\nfunction spentTogether(first: AiGenerationSpend, second: AiGenerationSpend): AiGenerationSpend {\n const usage: AiUsage = {\n inputTokens: first.usage.inputTokens + second.usage.inputTokens,\n outputTokens: first.usage.outputTokens + second.usage.outputTokens,\n cacheReadTokens: first.usage.cacheReadTokens + second.usage.cacheReadTokens,\n cacheWriteTokens: first.usage.cacheWriteTokens + second.usage.cacheWriteTokens,\n }\n return {\n attempts: first.attempts + second.attempts,\n usage,\n estCostUsd: Math.round((first.estCostUsd + second.estCostUsd) * 1_000_000) / 1_000_000,\n model: second.model,\n stopReason: second.stopReason,\n effort: second.effort,\n }\n}\n\n/**\n * A whole page in the layout language: one answer for every section, and,\n * where that answer and its re-ask still left a section out, one follow-up\n * asking for only those (AGL-3660). The sections that read are kept between\n * them, so the page is compiled whole once every section is designed.\n */\nexport async function aiRunLayoutPage(input: AiLayoutPageRunInput): Promise<AiValidatedGeneration<AiLayoutPageBuilt>> {\n const kept: Array<AiLayoutSection | null> = input.screen.sections.map(() => null)\n const observe = input.observe ?? ((check) => check)\n const base = aiLayoutPagePrompt({\n job: input.job,\n plan: input.plan,\n screen: input.screen,\n targets: input.targets,\n reusableComponents: input.reusableComponents,\n })\n const ask = (content: string, maxTokens: number, only?: readonly number[]) =>\n runValidatedGeneration<AiLayoutPageBuilt>(AI_LAYOUT_PAGE_KIND, {\n step: 'job.page',\n model: input.model,\n instructions: AI_JOB_PAGE_LANGUAGE_INSTRUCTIONS,\n inventory: input.inventory,\n messages: [{ role: 'user', content }],\n tool: AI_LAYOUT_PAGE_TOOL,\n maxTokens,\n cutOff: { noun: 'page', smaller: 'Write shorter copy, and fewer items in each group.' },\n thinking: 'off',\n check: observe(\n aiLayoutPageCheck({\n screen: input.screen,\n sectionIds: input.sectionIds,\n targets: input.targets,\n context: input.context,\n reusableComponents: input.reusableComponents,\n kept,\n ...(only ? { only } : {}),\n }),\n ),\n ...(input.signal ? { signal: input.signal } : {}),\n })\n const first = await ask(base, AI_JOB_PAGE_LANGUAGE_BUDGET.maxTokens(input.model))\n const missing = aiLayoutMissingSections(kept)\n if (\n first.status !== 'needs_input' ||\n !missing.length ||\n !first.violations.every((violation) => violation.code === AI_LAYOUT_SECTION_MISSING_CODE)\n ) {\n return first\n }\n const second = await ask(\n aiLayoutMissingSectionsPrompt({ base, screen: input.screen, missing }),\n Math.min(AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS, AI_JOB_PAGE_LANGUAGE_BUDGET.maxTokens(input.model)),\n missing,\n )\n return { ...second, ...spentTogether(first, second) }\n}\n"],"names":["CANVAS_ROOT_ELEMENT_ID","isAiPlanNewRef","aiHomeScreenIds","aiCompileLayoutPage","AI_LAYOUT_LANGUAGE_TEXT","AI_LAYOUT_PAGE_TOOL","aiReadLayoutPage","aiLayoutCopyCheck","aiLayoutStoredTree","AI_GENERATION_MAX_ATTEMPTS","AI_STEP_TIERS","runValidatedGeneration","validateAiDoctrineTree","aiGenerationWorstCaseOnTierMs","aiJobStepBudget","aiJobBriefLine","aiPlanReferenceLines","aiLayoutSitePages","aiLayoutInventedContactViolations","AI_LAYOUT_LANGUAGE_INPUT","AI_LAYOUT_FORM_PAGE_INPUT","aiJobUsesLayoutLanguage","job","inputs","AI_JOB_PAGE_LANGUAGE_TOKENS","AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS","AI_JOB_PAGE_LANGUAGE_BUDGET","tier","maxTokens","ownReadsMs","AI_LAYOUT_PAGE_KIND","AI_JOB_PAGE_LANGUAGE_INSTRUCTIONS","text","join","cacheBreakpoint","aiLayoutFormPageOfPlan","plan","formIds","created","Set","create","filter","entry","kind","map","name","toLowerCase","forms","screen","screens","find","sections","some","section","uses","ref","has","id","aiLayoutFacts","values","Object","value","brief","aiLayoutPageTargets","input","own","inventory","planned","pages","row","template","page","label","slug","formPage","pageId","includes","homeIds","form","formPageId","components","component","props","facts","aiLayoutPagePrompt","targets","index","places","length","items","keys","title","reusableComponents","$id","slice","aiLayoutMissingSectionsPrompt","names","missing","base","aiLayoutMissingSections","kept","flatMap","AI_LAYOUT_SECTION_MISSING_CODE","aiLayoutPageCheck","fills","only","_","answers","answer","last","reading","forEach","position","violations","rule","code","message","detail","compiled","sectionIds","stored","tree","context","ok","console","error","copy","nodes","ids","dropped","report","rootId","scrollTargetIds","load","settled","spentTogether","first","second","usage","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","attempts","estCostUsd","Math","round","model","stopReason","effort","aiRunLayoutPage","observe","check","ask","content","step","instructions","messages","role","tool","cutOff","noun","smaller","thinking","signal","status","every","violation","min"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-page-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 { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { isAiPlanNewRef, type AiBuildPlanScreen } from '../model/ai-build-plan'\nimport type { AiJob, AiJobPlan } from '../model/ai-jobs.types'\nimport {\n aiHomeScreenIds,\n type AiSiteInventory,\n} from '../model/ai-site-inventory'\nimport { aiCompileLayoutPage } from '../layout-language/ai-layout-compiler'\nimport {\n AI_LAYOUT_LANGUAGE_TEXT,\n AI_LAYOUT_PAGE_TOOL,\n aiReadLayoutPage,\n type AiLayoutSection,\n type AiLayoutSettlement,\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 { AI_STEP_TIERS } from '../providers/catalog'\nimport {\n runValidatedGeneration,\n type AiGenerationCheck,\n type AiGenerationSpend,\n type AiValidatedGeneration,\n} from '../runtime/ai-doctrine'\nimport type { AiUsage } from '../providers/contract'\nimport {\n validateAiDoctrineTree,\n type AiDoctrineNode,\n type AiDoctrineTreeContext,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport { aiSiteKindDesignLines } from '../model/ai-site-kinds'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { aiGenerationWorstCaseOnTierMs, aiJobStepBudget } from './ai-job-budget'\nimport { aiJobBriefLine, aiPlanReferenceLines } from './ai-job-generation'\nimport { aiLayoutSitePages } from './ai-job-layout-site-pages'\nimport { aiLayoutInventedContactViolations } from './ai-layout-site-facts'\n\n/**\n * A page built in the compact layout language (AGL-3660): the page step's\n * pass for a site's page units — a guided start's and a build's — in place of\n * the raw-tree section passes.\n *\n * ONE answer a page: every planned section, in the language, through\n * `submit_page`. The compiler writes the tree, the palette validator stores\n * it as it stores any generated tree, and the doctrine checks the whole page.\n * A section the answer left out or left empty is the one thing a re-ask asks\n * for: the sections that read are kept, and the second answer fills only\n * what is still missing. Then the page step's last pass runs as it always\n * has — the whole page held to the doctrine again, its listing written, the\n * draft reported — so the item ledger, the credits and Try again work exactly\n * as they did.\n */\n\n/** The unit input that says a page or a layout is built in the layout language. */\nexport const AI_LAYOUT_LANGUAGE_INPUT = 'layoutLanguage'\n\n/** The unit input naming the page of the site that places its form, where it is another page. */\nexport const AI_LAYOUT_FORM_PAGE_INPUT = 'layoutFormPage'\n\n/** Whether this job builds its page or its layout in the layout language. */\nexport function aiJobUsesLayoutLanguage(job: Pick<AiJob, 'inputs'>): boolean {\n return job.inputs?.[AI_LAYOUT_LANGUAGE_INPUT] === true\n}\n\n/**\n * How the language doors think (AGL-3660): adaptively, at an effort named\n * rather than left to the model's default. Zach, 2026-10-07: quality first —\n * a page and a site's frame are designed, not transcribed, and the caching\n * work pays for the thinking. Medium is where Claude Sonnet 5.5, the balanced\n * default, is tuned to start for multistep design work.\n */\nexport const AI_LAYOUT_LANGUAGE_THINKING = { thinking: 'adaptive', effort: 'medium' } as const\n\n/**\n * Room for the thinking a page's answer does before it writes, inside the\n * same `max_tokens` as the answer: thinking is drawn from the ceiling first,\n * so a ceiling sized for the answer alone would cut a thoughtful answer off.\n *\n * Bounded by TIME, not by money: the page step's answer, its re-ask and the\n * follow-up must fit the least time a beat gives a step\n * (`AI_JOB_STEP_MAX_MINIMUM_MS`), and `aiJobStepBudget` quietly lowers every\n * ceiling to what fits. 2,000 here, with the follow-up's 500, is the most\n * that fits whole on the balanced tier; at 5,000 the budget cut a page's\n * ceiling to 879 tokens, under the answer itself.\n */\nexport const AI_LAYOUT_PAGE_THINKING_TOKENS = 2_000\n\n/** The same room for a site's header and footer, whose step has the time for more. */\nexport const AI_LAYOUT_FRAME_THINKING_TOKENS = 4_000\n\n/**\n * The most one page's answer asks for: the answer and the thinking before it.\n * A section in the language is about 120 to 250 tokens with its copy, so the\n * largest page a plan admits — eight sections — fits in 3,000 with room (the\n * live runs of 2026-10-07 wrote 683 to 1,745); the compiler, not the ceiling,\n * keeps a page small.\n */\nexport const AI_JOB_PAGE_LANGUAGE_TOKENS = 3_000 + AI_LAYOUT_PAGE_THINKING_TOKENS\n\n/**\n * The time a language page pass needs: its lookup rounds, its answer and its\n * re-ask at `AI_JOB_PAGE_LANGUAGE_TOKENS` on the tier the page step is served\n * from, with the step's reads and writes.\n */\n/** The most the follow-up asking only for the sections an answer left out may run to, its thinking included. */\nexport const AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS = 1_500 + 500\n\nexport const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.page'],\n maxTokens: AI_JOB_PAGE_LANGUAGE_TOKENS,\n // The follow-up for sections an answer left out is one more generation.\n ownReadsMs: aiGenerationWorstCaseOnTierMs({\n tier: AI_STEP_TIERS['job.page'],\n maxTokens: AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS,\n }),\n})\n\n/** The doctrine scope the language doors are told: values the compiler checks itself. */\nexport const AI_LAYOUT_PAGE_KIND = 'layout-page'\n\n/** The language, as the page door's cached instructions. */\nexport const AI_JOB_PAGE_LANGUAGE_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n AI_LAYOUT_LANGUAGE_TEXT,\n '',\n 'Answer with submit_page: the whole page, one entry in sections for each planned section, in order.',\n ].join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\n/** The page of a site plan that places its form: the form the plan creates, or one the site has. */\nexport function aiLayoutFormPageOfPlan(\n plan: Pick<AiJobPlan, 'create' | 'screens'>,\n formIds: readonly string[] = [],\n): string | null {\n const created = new Set(\n plan.create\n .filter((entry) => entry.kind === 'form')\n .map((entry) => `new:${entry.name}`.toLowerCase()),\n )\n const forms = new Set(formIds)\n const screen = plan.screens.find((entry) =>\n entry.sections.some((section) =>\n section.uses.some((ref) =>\n isAiPlanNewRef(ref) ? created.has(ref.toLowerCase()) : forms.has(ref),\n ),\n ),\n )\n return typeof screen?.id === 'string' ? screen.id : null\n}\n\n/** Every word the job was given, which a page's contact details are held to. */\nexport function aiLayoutFacts(job: Pick<AiJob, 'brief' | 'inputs'>): string {\n const values = Object.values(job.inputs ?? {}).filter(\n (value): value is string => typeof value === 'string',\n )\n return [job.brief, ...values].join('\\n')\n}\n\n/** What a page's links resolve against: the site's pages, built and planned, its forms and components. */\nexport function aiLayoutPageTargets(input: {\n job: Pick<AiJob, 'brief' | 'inputs'>\n inventory: AiSiteInventory | null\n own: ReadonlyArray<string | null | undefined>\n}): AiLayoutTargets {\n const { job, inventory } = input\n const own = input.own.filter((id): id is string => !!id)\n const planned = aiLayoutSitePages(job.inputs)\n const pages: AiLayoutPage[] = [\n ...planned,\n ...(inventory?.screens ?? [])\n .filter(\n (row) => !row.template && !planned.some((page) => page.id === row.id),\n )\n .map((row) => ({ id: row.id, label: row.name, slug: row.slug })),\n ]\n const formPage = job.inputs?.[AI_LAYOUT_FORM_PAGE_INPUT]\n return {\n pageId: own[0] ?? null,\n pages: pages.filter((page) => !own.includes(page.id)),\n homeIds: aiHomeScreenIds(inventory),\n forms: (inventory?.forms ?? []).map((form) => ({\n id: form.id,\n name: form.name,\n })),\n formPageId:\n typeof formPage === 'string' && !own.includes(formPage) ? formPage : null,\n components: (inventory?.components ?? []).map((component) => ({\n id: component.id,\n name: component.name,\n props: component.props,\n })),\n facts: aiLayoutFacts(job),\n }\n}\n\n/** The page's user turn: the page, the brief, the plan, its sections and where its links may go. */\nexport function aiLayoutPagePrompt(input: {\n job: Pick<AiJob, 'brief' | 'inputs' | '$id'>\n plan: AiJobPlan\n screen: AiBuildPlanScreen\n targets: AiLayoutTargets\n reusableComponents: boolean\n /** The sections a re-ask still needs, by plan index; absent on the first ask. */\n missing?: readonly number[]\n}): string {\n const { job, plan, screen, targets } = input\n const sections = screen.sections.map((section, index) => {\n const places = section.uses.length\n ? `; places ${section.uses.join(', ')}`\n : ''\n const items = section.items ? `; shows ${section.items} items` : ''\n return `${index + 1}. \"${section.name}\"${places}${items}`\n })\n const pages = targets.pages.map((page) => `${page.label} (page:${page.id})`)\n const components = targets.components.map(\n (component) =>\n `${component.name} (${component.id}; props ${Object.keys(component.props).join(', ') || 'none'})`,\n )\n return [\n `Page: \"${screen.title}\" at ${screen.slug}`,\n aiJobBriefLine(job),\n // The kind of site the person picked sets how its pages are arranged (AGL-3660).\n ...aiSiteKindDesignLines(job.inputs),\n ...aiPlanReferenceLines(plan),\n `Sections to design, in order:`,\n ...sections,\n pages.length\n ? `Other pages of this site: ${pages.join(', ')}.`\n : 'This site has no other page to link.',\n targets.forms.length\n ? `The site's form: ${targets.forms.map((form) => `${form.name} (${form.id})`).join(', ')}.`\n : 'The site has no saved form: place none.',\n ...(components.length\n ? [`Components this page may place: ${components.join('; ')}.`]\n : []),\n input.reusableComponents\n ? 'Where a section repeats an item, place the component its plan line names with cards whose to is that component.'\n : 'This site keeps no reusable components: draw repeated items as cards, steps, stats or a list.',\n // A per-job word, so two identical briefs are designed apart.\n `Design seed: ${job.$id.slice(-6)}.`,\n ].join('\\n')\n}\n\n/** A validated language page, stored as the page keeps it. */\nexport interface AiLayoutPageBuilt {\n nodes: NodesMap\n load: AiLoadEstimate | null\n settled: AiLayoutSettlement[]\n /** What the last answer's gaps took out of the page (`ai-layout-gaps.ts`). */\n dropped: string[]\n /** How many repeated items each plan section shows, as built (AGL-3660). */\n items: number[]\n}\n\nexport interface AiLayoutPageCheckInput {\n screen: AiBuildPlanScreen\n sectionIds: readonly string[]\n targets: AiLayoutTargets\n context: AiDoctrineTreeContext\n reusableComponents: boolean\n /**\n * What earlier answers gave, by plan index, shared between the page's\n * generations: a follow-up asking only for missing sections fills the rest.\n */\n kept?: Array<AiLayoutSection | null>\n /** The plan indices this answer's sections fill, in order; absent, every section. */\n only?: readonly number[]\n}\n\n/**\n * The follow-up turn for the sections a page's answer left out (AGL-3660):\n * the page as before, told which sections it already has, and asked for only\n * the missing ones, in order — the one part of a page asked for again.\n */\nexport function aiLayoutMissingSectionsPrompt(input: {\n base: string\n screen: AiBuildPlanScreen\n missing: readonly number[]\n}): string {\n const names = input.missing.map((index) => `${index + 1}. \"${input.screen.sections[index].name}\"`)\n return [\n input.base,\n '',\n `The other sections of this page are already designed. Design ONLY these, in this order, one entry in sections each: ${names.join('; ')}. Give each at least one block.`,\n ].join('\\n')\n}\n\n/** The plan indices a page still lacks, after the answers so far. */\nexport function aiLayoutMissingSections(kept: ReadonlyArray<AiLayoutSection | null>): number[] {\n return kept.flatMap((section, index) => (section ? [] : [index]))\n}\n\n/** The violation a planned section the answer gave nothing usable for is named by. */\nexport const AI_LAYOUT_SECTION_MISSING_CODE = 'layout-section-missing'\n\n/** The code a section planned with items that shows none is refused under (AGL-3660). */\nexport const AI_LAYOUT_SECTION_EMPTY_CODE = 'layout-section-no-items'\n\n/** How many of a section's planned items it must show: one for a short list, two for anything longer. */\nconst leastItems = (planned: number) => (planned >= 3 ? 2 : planned >= 1 ? 1 : 0)\n\n/**\n * How many repeated items each section shows once stored (AGL-3660): the\n * elements the compiler drew for them, under the ids they are stored by,\n * that are still in the page after anything later took words out.\n */\nexport function aiLayoutShownItems(\n itemIds: readonly (readonly string[])[],\n storedIds: Readonly<Record<string, string>>,\n nodes: Readonly<Record<string, unknown>>,\n): number[] {\n return itemIds.map((ids) => ids.filter((id) => storedIds[id] !== undefined && storedIds[id] in nodes).length)\n}\n\n/**\n * The sections a page planned with repeated items — cards, steps, a list —\n * that show none, or too few, once the page is built (AGL-3660). A heading\n * and an intro over nothing is a section a visitor reads as broken, so it is\n * asked for again.\n */\nexport function aiLayoutEmptyItemSections(\n screen: Pick<AiBuildPlanScreen, 'sections'>,\n items: readonly number[],\n): AiDoctrineViolation[] {\n return screen.sections.flatMap((section, index): AiDoctrineViolation[] => {\n const least = leastItems(section.items)\n if (!least) return []\n const shown = items[index] ?? 0\n if (shown >= least) return []\n return [\n {\n rule: null,\n code: AI_LAYOUT_SECTION_EMPTY_CODE,\n message: `Section ${index + 1} (\"${section.name}\") is planned with ${section.items} items and shows ${shown || 'none'}.`,\n detail: `Give section ${index + 1} one cards, steps, stats, list or faq block holding its ${section.items} items, each with a title and its words.`,\n },\n ]\n })\n}\n\n/**\n * The check a language page answer is held to: each planned section read,\n * the page compiled, stored and checked whole. Sections that read are kept\n * across the re-ask, so the second answer is asked only for what was missing,\n * and a section it sends again that already read is not taken twice.\n */\nexport function aiLayoutPageCheck(\n input: AiLayoutPageCheckInput,\n): AiGenerationCheck<AiLayoutPageBuilt> {\n const kept: Array<AiLayoutSection | null> = input.kept ?? input.screen.sections.map(() => null)\n const fills = input.only ?? input.screen.sections.map((_, index) => index)\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 reading = aiReadLayoutPage(\n answer,\n fills.length,\n fills.map((index) => input.screen.sections[index]),\n )\n reading.sections.forEach((section, position) => {\n // A section the answer gives is taken as given; one it leaves out keeps\n // what an earlier answer gave, so a re-ask may send only what was missing.\n if (section) kept[fills[position]] = section\n })\n const missing = kept.flatMap((section, index) => (section ? [] : [index]))\n if (missing.length) {\n const names = missing.map(\n (index) => `${index + 1} (\"${input.screen.sections[index].name}\")`,\n )\n return {\n value: null,\n violations: [\n {\n rule: null,\n code: AI_LAYOUT_SECTION_MISSING_CODE,\n message: `The page left out section ${names.join(', ')}.`,\n detail: `Answer again with every section in order; the sections you already designed are kept, so only ${names.join(', ')} ${missing.length === 1 ? 'needs' : 'need'} its design. Give each at least one block.`,\n },\n ],\n }\n }\n const compiled = aiCompileLayoutPage(\n kept as AiLayoutSection[],\n { title: input.screen.title, sections: input.screen.sections },\n input.targets,\n {\n reusableComponents: input.reusableComponents,\n sectionIds: input.sectionIds,\n },\n )\n const stored = aiLayoutStoredTree(\n compiled.tree,\n 'screen',\n input.context,\n input.sectionIds,\n )\n if (stored.ok === false) {\n // The compiler writes only palette trees, which the property spec holds;\n // a refusal here is the platform's, and is reported as one.\n console.error('ai layout page: a compiled page 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 page 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 page that is published as built.\n const copy = aiLayoutCopyCheck(\n stored.nodes,\n CANVAS_ROOT_ELEMENT_ID,\n {\n pages: input.targets.pages,\n ids: [\n ...input.targets.pages.map((page) => page.id),\n ...input.targets.forms.map((form) => form.id),\n ...input.targets.components.map((component) => component.id),\n ...input.sectionIds,\n ...(input.targets.pageId ? [input.targets.pageId] : []),\n ],\n },\n last,\n )\n const { nodes, dropped } = copy\n const report = validateAiDoctrineTree(\n { rootId: CANVAS_ROOT_ELEMENT_ID, nodes },\n 'page',\n {\n ...input.context,\n scrollTargetIds: input.sectionIds,\n },\n )\n // Each section's items as they are stored, after anything the gaps took out.\n const items = aiLayoutShownItems(compiled.itemIds, stored.storedIds, nodes as unknown as Record<string, unknown>)\n // A street address or opening hours the job was never given is invented\n // (rule 14, AGL-3596), the same check a layout is held to; a phone number\n // or an email the compiler already writes as its gap.\n const violations: AiDoctrineViolation[] = [\n ...report.violations,\n ...copy.violations,\n ...aiLayoutEmptyItemSections(input.screen, items),\n ...aiLayoutInventedContactViolations(\n { rootId: CANVAS_ROOT_ELEMENT_ID, nodes: nodes as unknown as Record<string, AiDoctrineNode> },\n input.targets.facts,\n ),\n ]\n return {\n value: violations.length\n ? null\n : {\n nodes,\n load: report.load,\n settled: [...reading.settled, ...compiled.settled],\n dropped,\n items,\n },\n violations,\n }\n }\n}\n\nexport interface AiLayoutPageRunInput {\n job: Pick<AiJob, 'brief' | 'inputs' | '$id'>\n plan: AiJobPlan\n screen: AiBuildPlanScreen\n sectionIds: readonly string[]\n targets: AiLayoutTargets\n context: AiDoctrineTreeContext\n reusableComponents: boolean\n inventory: AiSiteInventory | null\n model: string\n signal?: AbortSignal\n /** Wraps the check, for an eval that records each answer it reads. */\n observe?: (check: AiGenerationCheck<AiLayoutPageBuilt>) => AiGenerationCheck<AiLayoutPageBuilt>\n}\n\n/** Two generations' spend as one: every call is billed. */\nfunction spentTogether(first: AiGenerationSpend, second: AiGenerationSpend): AiGenerationSpend {\n const usage: AiUsage = {\n inputTokens: first.usage.inputTokens + second.usage.inputTokens,\n outputTokens: first.usage.outputTokens + second.usage.outputTokens,\n cacheReadTokens: first.usage.cacheReadTokens + second.usage.cacheReadTokens,\n cacheWriteTokens: first.usage.cacheWriteTokens + second.usage.cacheWriteTokens,\n }\n return {\n attempts: first.attempts + second.attempts,\n usage,\n estCostUsd: Math.round((first.estCostUsd + second.estCostUsd) * 1_000_000) / 1_000_000,\n model: second.model,\n stopReason: second.stopReason,\n effort: second.effort,\n }\n}\n\n/**\n * A whole page in the layout language: one answer for every section, and,\n * where that answer and its re-ask still left a section out, one follow-up\n * asking for only those (AGL-3660). The sections that read are kept between\n * them, so the page is compiled whole once every section is designed.\n */\nexport async function aiRunLayoutPage(input: AiLayoutPageRunInput): Promise<AiValidatedGeneration<AiLayoutPageBuilt>> {\n const kept: Array<AiLayoutSection | null> = input.screen.sections.map(() => null)\n const observe = input.observe ?? ((check) => check)\n const base = aiLayoutPagePrompt({\n job: input.job,\n plan: input.plan,\n screen: input.screen,\n targets: input.targets,\n reusableComponents: input.reusableComponents,\n })\n const ask = (content: string, maxTokens: number, only?: readonly number[]) =>\n runValidatedGeneration<AiLayoutPageBuilt>(AI_LAYOUT_PAGE_KIND, {\n step: 'job.page',\n model: input.model,\n instructions: AI_JOB_PAGE_LANGUAGE_INSTRUCTIONS,\n inventory: input.inventory,\n messages: [{ role: 'user', content }],\n tool: AI_LAYOUT_PAGE_TOOL,\n maxTokens,\n cutOff: { noun: 'page', smaller: 'Write shorter copy, and fewer items in each group.' },\n ...AI_LAYOUT_LANGUAGE_THINKING,\n check: observe(\n aiLayoutPageCheck({\n screen: input.screen,\n sectionIds: input.sectionIds,\n targets: input.targets,\n context: input.context,\n reusableComponents: input.reusableComponents,\n kept,\n ...(only ? { only } : {}),\n }),\n ),\n ...(input.signal ? { signal: input.signal } : {}),\n })\n const first = await ask(base, AI_JOB_PAGE_LANGUAGE_BUDGET.maxTokens(input.model))\n const missing = aiLayoutMissingSections(kept)\n if (\n first.status !== 'needs_input' ||\n !missing.length ||\n !first.violations.every((violation) => violation.code === AI_LAYOUT_SECTION_MISSING_CODE)\n ) {\n return first\n }\n const second = await ask(\n aiLayoutMissingSectionsPrompt({ base, screen: input.screen, missing }),\n Math.min(AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS, AI_JOB_PAGE_LANGUAGE_BUDGET.maxTokens(input.model)),\n missing,\n )\n return { ...second, ...spentTogether(first, second) }\n}\n"],"names":["CANVAS_ROOT_ELEMENT_ID","isAiPlanNewRef","aiHomeScreenIds","aiCompileLayoutPage","AI_LAYOUT_LANGUAGE_TEXT","AI_LAYOUT_PAGE_TOOL","aiReadLayoutPage","aiLayoutCopyCheck","aiLayoutStoredTree","AI_GENERATION_MAX_ATTEMPTS","AI_STEP_TIERS","runValidatedGeneration","validateAiDoctrineTree","aiSiteKindDesignLines","aiGenerationWorstCaseOnTierMs","aiJobStepBudget","aiJobBriefLine","aiPlanReferenceLines","aiLayoutSitePages","aiLayoutInventedContactViolations","AI_LAYOUT_LANGUAGE_INPUT","AI_LAYOUT_FORM_PAGE_INPUT","aiJobUsesLayoutLanguage","job","inputs","AI_LAYOUT_LANGUAGE_THINKING","thinking","effort","AI_LAYOUT_PAGE_THINKING_TOKENS","AI_LAYOUT_FRAME_THINKING_TOKENS","AI_JOB_PAGE_LANGUAGE_TOKENS","AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS","AI_JOB_PAGE_LANGUAGE_BUDGET","tier","maxTokens","ownReadsMs","AI_LAYOUT_PAGE_KIND","AI_JOB_PAGE_LANGUAGE_INSTRUCTIONS","text","join","cacheBreakpoint","aiLayoutFormPageOfPlan","plan","formIds","created","Set","create","filter","entry","kind","map","name","toLowerCase","forms","screen","screens","find","sections","some","section","uses","ref","has","id","aiLayoutFacts","values","Object","value","brief","aiLayoutPageTargets","input","own","inventory","planned","pages","row","template","page","label","slug","formPage","pageId","includes","homeIds","form","formPageId","components","component","props","facts","aiLayoutPagePrompt","targets","index","places","length","items","keys","title","reusableComponents","$id","slice","aiLayoutMissingSectionsPrompt","names","missing","base","aiLayoutMissingSections","kept","flatMap","AI_LAYOUT_SECTION_MISSING_CODE","AI_LAYOUT_SECTION_EMPTY_CODE","leastItems","aiLayoutShownItems","itemIds","storedIds","nodes","ids","undefined","aiLayoutEmptyItemSections","least","shown","rule","code","message","detail","aiLayoutPageCheck","fills","only","_","answers","answer","last","reading","forEach","position","violations","compiled","sectionIds","stored","tree","context","ok","console","error","copy","dropped","report","rootId","scrollTargetIds","load","settled","spentTogether","first","second","usage","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","attempts","estCostUsd","Math","round","model","stopReason","aiRunLayoutPage","observe","check","ask","content","step","instructions","messages","role","tool","cutOff","noun","smaller","signal","status","every","violation","min"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,sBAAsB,QAAQ,2CAA0C;AAEjF,SAASC,cAAc,QAAgC,4BAAwB;AAE/E,SACEC,eAAe,QAEV,gCAA4B;AACnC,SAASC,mBAAmB,QAAQ,2CAAuC;AAC3E,SACEC,uBAAuB,EACvBC,mBAAmB,EACnBC,gBAAgB,QAGX,2CAAuC;AAK9C,SAASC,iBAAiB,QAAQ,uCAAmC;AACrE,SAASC,kBAAkB,QAAQ,wCAAoC;AACvE,SAASC,0BAA0B,QAAQ,qCAAiC;AAC5E,SAASC,aAAa,QAAQ,0BAAsB;AACpD,SACEC,sBAAsB,QAIjB,4BAAwB;AAE/B,SACEC,sBAAsB,QAIjB,uCAAmC;AAE1C,SAASC,qBAAqB,QAAQ,4BAAwB;AAE9D,SAASC,6BAA6B,EAAEC,eAAe,QAAQ,qBAAiB;AAChF,SAASC,cAAc,EAAEC,oBAAoB,QAAQ,yBAAqB;AAC1E,SAASC,iBAAiB,QAAQ,gCAA4B;AAC9D,SAASC,iCAAiC,QAAQ,4BAAwB;AAE1E;;;;;;;;;;;;;;CAcC,GAED,iFAAiF,GACjF,OAAO,MAAMC,2BAA2B,iBAAgB;AAExD,+FAA+F,GAC/F,OAAO,MAAMC,4BAA4B,iBAAgB;AAEzD,2EAA2E,GAC3E,OAAO,SAASC,wBAAwBC,GAA0B;QACzDA;IAAP,OAAOA,EAAAA,cAAAA,IAAIC,MAAM,qBAAVD,WAAY,CAACH,yBAAyB,MAAK;AACpD;AAEA;;;;;;CAMC,GACD,OAAO,MAAMK,8BAA8B;IAAEC,UAAU;IAAYC,QAAQ;AAAS,EAAU;AAE9F;;;;;;;;;;;CAWC,GACD,OAAO,MAAMC,iCAAiC,KAAK;AAEnD,oFAAoF,GACpF,OAAO,MAAMC,kCAAkC,KAAK;AAEpD;;;;;;CAMC,GACD,OAAO,MAAMC,8BAA8B,OAAQF,+BAA8B;AAEjF;;;;CAIC,GACD,8GAA8G,GAC9G,OAAO,MAAMG,wCAAwC,OAAQ,IAAG;AAEhE,OAAO,MAAMC,8BAA8BjB,gBAAgB;IACzDkB,MAAMvB,aAAa,CAAC,WAAW;IAC/BwB,WAAWJ;IACX,wEAAwE;IACxEK,YAAYrB,8BAA8B;QACxCmB,MAAMvB,aAAa,CAAC,WAAW;QAC/BwB,WAAWH;IACb;AACF,GAAE;AAEF,uFAAuF,GACvF,OAAO,MAAMK,sBAAsB,cAAa;AAEhD,0DAA0D,GAC1D,OAAO,MAAMC,oCAA8D;IACzE;QACEC,MAAM;YACJlC;YACA;YACA;SACD,CAACmC,IAAI,CAAC;QACPC,iBAAiB;IACnB;CACD,CAAA;AAED,kGAAkG,GAClG,OAAO,SAASC,uBACdC,IAA2C,EAC3CC,UAA6B,EAAE;IAE/B,MAAMC,UAAU,IAAIC,IAClBH,KAAKI,MAAM,CACRC,MAAM,CAAC,CAACC,QAAUA,MAAMC,IAAI,KAAK,QACjCC,GAAG,CAAC,CAACF,QAAU,CAAC,IAAI,EAAEA,MAAMG,IAAI,EAAE,CAACC,WAAW;IAEnD,MAAMC,QAAQ,IAAIR,IAAIF;IACtB,MAAMW,SAASZ,KAAKa,OAAO,CAACC,IAAI,CAAC,CAACR,QAChCA,MAAMS,QAAQ,CAACC,IAAI,CAAC,CAACC,UACnBA,QAAQC,IAAI,CAACF,IAAI,CAAC,CAACG,MACjB5D,eAAe4D,OAAOjB,QAAQkB,GAAG,CAACD,IAAIT,WAAW,MAAMC,MAAMS,GAAG,CAACD;IAIvE,OAAO,QAAOP,0BAAAA,OAAQS,EAAE,MAAK,WAAWT,OAAOS,EAAE,GAAG;AACtD;AAEA,8EAA8E,GAC9E,OAAO,SAASC,cAAczC,GAAoC;QACnCA;IAA7B,MAAM0C,SAASC,OAAOD,MAAM,EAAC1C,cAAAA,IAAIC,MAAM,YAAVD,cAAc,CAAC,GAAGwB,MAAM,CACnD,CAACoB,QAA2B,OAAOA,UAAU;IAE/C,OAAO;QAAC5C,IAAI6C,KAAK;WAAKH;KAAO,CAAC1B,IAAI,CAAC;AACrC;AAEA,wGAAwG,GACxG,OAAO,SAAS8B,oBAAoBC,KAInC;cAcWC;QAFOhD;IAXjB,MAAM,EAAEA,GAAG,EAAEiD,SAAS,EAAE,GAAGF;IAC3B,MAAMC,MAAMD,MAAMC,GAAG,CAACxB,MAAM,CAAC,CAACgB,KAAqB,CAAC,CAACA;IACrD,MAAMU,UAAUvD,kBAAkBK,IAAIC,MAAM;IAC5C,MAAMkD,QAAwB;WACzBD;WACA,SAACD,6BAAAA,UAAWjB,OAAO,mBAAI,EAAE,EACzBR,MAAM,CACL,CAAC4B,MAAQ,CAACA,IAAIC,QAAQ,IAAI,CAACH,QAAQf,IAAI,CAAC,CAACmB,OAASA,KAAKd,EAAE,KAAKY,IAAIZ,EAAE,GAErEb,GAAG,CAAC,CAACyB,MAAS,CAAA;gBAAEZ,IAAIY,IAAIZ,EAAE;gBAAEe,OAAOH,IAAIxB,IAAI;gBAAE4B,MAAMJ,IAAII,IAAI;YAAC,CAAA;KAChE;IACD,MAAMC,YAAWzD,cAAAA,IAAIC,MAAM,qBAAVD,WAAY,CAACF,0BAA0B;IACxD,OAAO;QACL4D,MAAM,GAAEV,QAAAA,GAAG,CAAC,EAAE,YAANA,QAAU;QAClBG,OAAOA,MAAM3B,MAAM,CAAC,CAAC8B,OAAS,CAACN,IAAIW,QAAQ,CAACL,KAAKd,EAAE;QACnDoB,SAASjF,gBAAgBsE;QACzBnB,OAAO,UAACmB,6BAAAA,UAAWnB,KAAK,oBAAI,EAAE,EAAEH,GAAG,CAAC,CAACkC,OAAU,CAAA;gBAC7CrB,IAAIqB,KAAKrB,EAAE;gBACXZ,MAAMiC,KAAKjC,IAAI;YACjB,CAAA;QACAkC,YACE,OAAOL,aAAa,YAAY,CAACT,IAAIW,QAAQ,CAACF,YAAYA,WAAW;QACvEM,YAAY,UAACd,6BAAAA,UAAWc,UAAU,oBAAI,EAAE,EAAEpC,GAAG,CAAC,CAACqC,YAAe,CAAA;gBAC5DxB,IAAIwB,UAAUxB,EAAE;gBAChBZ,MAAMoC,UAAUpC,IAAI;gBACpBqC,OAAOD,UAAUC,KAAK;YACxB,CAAA;QACAC,OAAOzB,cAAczC;IACvB;AACF;AAEA,kGAAkG,GAClG,OAAO,SAASmE,mBAAmBpB,KAQlC;IACC,MAAM,EAAE/C,GAAG,EAAEmB,IAAI,EAAEY,MAAM,EAAEqC,OAAO,EAAE,GAAGrB;IACvC,MAAMb,WAAWH,OAAOG,QAAQ,CAACP,GAAG,CAAC,CAACS,SAASiC;QAC7C,MAAMC,SAASlC,QAAQC,IAAI,CAACkC,MAAM,GAC9B,CAAC,SAAS,EAAEnC,QAAQC,IAAI,CAACrB,IAAI,CAAC,OAAO,GACrC;QACJ,MAAMwD,QAAQpC,QAAQoC,KAAK,GAAG,CAAC,QAAQ,EAAEpC,QAAQoC,KAAK,CAAC,MAAM,CAAC,GAAG;QACjE,OAAO,GAAGH,QAAQ,EAAE,GAAG,EAAEjC,QAAQR,IAAI,CAAC,CAAC,EAAE0C,SAASE,OAAO;IAC3D;IACA,MAAMrB,QAAQiB,QAAQjB,KAAK,CAACxB,GAAG,CAAC,CAAC2B,OAAS,GAAGA,KAAKC,KAAK,CAAC,OAAO,EAAED,KAAKd,EAAE,CAAC,CAAC,CAAC;IAC3E,MAAMuB,aAAaK,QAAQL,UAAU,CAACpC,GAAG,CACvC,CAACqC,YACC,GAAGA,UAAUpC,IAAI,CAAC,EAAE,EAAEoC,UAAUxB,EAAE,CAAC,QAAQ,EAAEG,OAAO8B,IAAI,CAACT,UAAUC,KAAK,EAAEjD,IAAI,CAAC,SAAS,OAAO,CAAC,CAAC;IAErG,OAAO;QACL,CAAC,OAAO,EAAEe,OAAO2C,KAAK,CAAC,KAAK,EAAE3C,OAAOyB,IAAI,EAAE;QAC3C/D,eAAeO;QACf,iFAAiF;WAC9EV,sBAAsBU,IAAIC,MAAM;WAChCP,qBAAqByB;QACxB,CAAC,6BAA6B,CAAC;WAC5Be;QACHiB,MAAMoB,MAAM,GACR,CAAC,0BAA0B,EAAEpB,MAAMnC,IAAI,CAAC,MAAM,CAAC,CAAC,GAChD;QACJoD,QAAQtC,KAAK,CAACyC,MAAM,GAChB,CAAC,iBAAiB,EAAEH,QAAQtC,KAAK,CAACH,GAAG,CAAC,CAACkC,OAAS,GAAGA,KAAKjC,IAAI,CAAC,EAAE,EAAEiC,KAAKrB,EAAE,CAAC,CAAC,CAAC,EAAExB,IAAI,CAAC,MAAM,CAAC,CAAC,GAC1F;WACA+C,WAAWQ,MAAM,GACjB;YAAC,CAAC,gCAAgC,EAAER,WAAW/C,IAAI,CAAC,MAAM,CAAC,CAAC;SAAC,GAC7D,EAAE;QACN+B,MAAM4B,kBAAkB,GACpB,oHACA;QACJ,8DAA8D;QAC9D,CAAC,aAAa,EAAE3E,IAAI4E,GAAG,CAACC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC;KACrC,CAAC7D,IAAI,CAAC;AACT;AA4BA;;;;CAIC,GACD,OAAO,SAAS8D,8BAA8B/B,KAI7C;IACC,MAAMgC,QAAQhC,MAAMiC,OAAO,CAACrD,GAAG,CAAC,CAAC0C,QAAU,GAAGA,QAAQ,EAAE,GAAG,EAAEtB,MAAMhB,MAAM,CAACG,QAAQ,CAACmC,MAAM,CAACzC,IAAI,CAAC,CAAC,CAAC;IACjG,OAAO;QACLmB,MAAMkC,IAAI;QACV;QACA,CAAC,oHAAoH,EAAEF,MAAM/D,IAAI,CAAC,MAAM,+BAA+B,CAAC;KACzK,CAACA,IAAI,CAAC;AACT;AAEA,mEAAmE,GACnE,OAAO,SAASkE,wBAAwBC,IAA2C;IACjF,OAAOA,KAAKC,OAAO,CAAC,CAAChD,SAASiC,QAAWjC,UAAU,EAAE,GAAG;YAACiC;SAAM;AACjE;AAEA,oFAAoF,GACpF,OAAO,MAAMgB,iCAAiC,yBAAwB;AAEtE,uFAAuF,GACvF,OAAO,MAAMC,+BAA+B,0BAAyB;AAErE,uGAAuG,GACvG,MAAMC,aAAa,CAACrC,UAAqBA,WAAW,IAAI,IAAIA,WAAW,IAAI,IAAI;AAE/E;;;;CAIC,GACD,OAAO,SAASsC,mBACdC,OAAuC,EACvCC,SAA2C,EAC3CC,KAAwC;IAExC,OAAOF,QAAQ9D,GAAG,CAAC,CAACiE,MAAQA,IAAIpE,MAAM,CAAC,CAACgB,KAAOkD,SAAS,CAAClD,GAAG,KAAKqD,aAAaH,SAAS,CAAClD,GAAG,IAAImD,OAAOpB,MAAM;AAC9G;AAEA;;;;;CAKC,GACD,OAAO,SAASuB,0BACd/D,MAA2C,EAC3CyC,KAAwB;IAExB,OAAOzC,OAAOG,QAAQ,CAACkD,OAAO,CAAC,CAAChD,SAASiC;YAGzBG;QAFd,MAAMuB,QAAQR,WAAWnD,QAAQoC,KAAK;QACtC,IAAI,CAACuB,OAAO,OAAO,EAAE;QACrB,MAAMC,SAAQxB,eAAAA,KAAK,CAACH,MAAM,YAAZG,eAAgB;QAC9B,IAAIwB,SAASD,OAAO,OAAO,EAAE;QAC7B,OAAO;YACL;gBACEE,MAAM;gBACNC,MAAMZ;gBACNa,SAAS,CAAC,QAAQ,EAAE9B,QAAQ,EAAE,GAAG,EAAEjC,QAAQR,IAAI,CAAC,mBAAmB,EAAEQ,QAAQoC,KAAK,CAAC,iBAAiB,EAAEwB,SAAS,OAAO,CAAC,CAAC;gBACxHI,QAAQ,CAAC,aAAa,EAAE/B,QAAQ,EAAE,wDAAwD,EAAEjC,QAAQoC,KAAK,CAAC,wCAAwC,CAAC;YACrJ;SACD;IACH;AACF;AAEA;;;;;CAKC,GACD,OAAO,SAAS6B,kBACdtD,KAA6B;QAEeA,aAC9BA;IADd,MAAMoC,QAAsCpC,cAAAA,MAAMoC,IAAI,YAAVpC,cAAcA,MAAMhB,MAAM,CAACG,QAAQ,CAACP,GAAG,CAAC,IAAM;IAC1F,MAAM2E,SAAQvD,cAAAA,MAAMwD,IAAI,YAAVxD,cAAcA,MAAMhB,MAAM,CAACG,QAAQ,CAACP,GAAG,CAAC,CAAC6E,GAAGnC,QAAUA;IACpE,IAAIoC,UAAU;IACd,OAAO,CAACC;QACN,2FAA2F;QAC3FD,WAAW;QACX,MAAME,OAAOF,WAAWvH;QACxB,MAAM0H,UAAU7H,iBACd2H,QACAJ,MAAM/B,MAAM,EACZ+B,MAAM3E,GAAG,CAAC,CAAC0C,QAAUtB,MAAMhB,MAAM,CAACG,QAAQ,CAACmC,MAAM;QAEnDuC,QAAQ1E,QAAQ,CAAC2E,OAAO,CAAC,CAACzE,SAAS0E;YACjC,wEAAwE;YACxE,2EAA2E;YAC3E,IAAI1E,SAAS+C,IAAI,CAACmB,KAAK,CAACQ,SAAS,CAAC,GAAG1E;QACvC;QACA,MAAM4C,UAAUG,KAAKC,OAAO,CAAC,CAAChD,SAASiC,QAAWjC,UAAU,EAAE,GAAG;gBAACiC;aAAM;QACxE,IAAIW,QAAQT,MAAM,EAAE;YAClB,MAAMQ,QAAQC,QAAQrD,GAAG,CACvB,CAAC0C,QAAU,GAAGA,QAAQ,EAAE,GAAG,EAAEtB,MAAMhB,MAAM,CAACG,QAAQ,CAACmC,MAAM,CAACzC,IAAI,CAAC,EAAE,CAAC;YAEpE,OAAO;gBACLgB,OAAO;gBACPmE,YAAY;oBACV;wBACEd,MAAM;wBACNC,MAAMb;wBACNc,SAAS,CAAC,0BAA0B,EAAEpB,MAAM/D,IAAI,CAAC,MAAM,CAAC,CAAC;wBACzDoF,QAAQ,CAAC,8FAA8F,EAAErB,MAAM/D,IAAI,CAAC,MAAM,CAAC,EAAEgE,QAAQT,MAAM,KAAK,IAAI,UAAU,OAAO,0CAA0C,CAAC;oBAClN;iBACD;YACH;QACF;QACA,MAAMyC,WAAWpI,oBACfuG,MACA;YAAET,OAAO3B,MAAMhB,MAAM,CAAC2C,KAAK;YAAExC,UAAUa,MAAMhB,MAAM,CAACG,QAAQ;QAAC,GAC7Da,MAAMqB,OAAO,EACb;YACEO,oBAAoB5B,MAAM4B,kBAAkB;YAC5CsC,YAAYlE,MAAMkE,UAAU;QAC9B;QAEF,MAAMC,SAASjI,mBACb+H,SAASG,IAAI,EACb,UACApE,MAAMqE,OAAO,EACbrE,MAAMkE,UAAU;QAElB,IAAIC,OAAOG,EAAE,KAAK,OAAO;YACvB,yEAAyE;YACzE,4DAA4D;YAC5DC,QAAQC,KAAK,CAAC,iDAAiD;gBAC7DA,OAAOL,OAAOK,KAAK;YACrB;YACA,OAAO;gBACL3E,OAAO;gBACPmE,YAAY;oBACV;wBACEd,MAAM;wBACNC,MAAM;wBACNC,SAAS;wBACTC,QAAQc,OAAOK,KAAK;oBACtB;iBACD;YACH;QACF;QACA,sFAAsF;QACtF,MAAMC,OAAOxI,kBACXkI,OAAOvB,KAAK,EACZlH,wBACA;YACE0E,OAAOJ,MAAMqB,OAAO,CAACjB,KAAK;YAC1ByC,KAAK;mBACA7C,MAAMqB,OAAO,CAACjB,KAAK,CAACxB,GAAG,CAAC,CAAC2B,OAASA,KAAKd,EAAE;mBACzCO,MAAMqB,OAAO,CAACtC,KAAK,CAACH,GAAG,CAAC,CAACkC,OAASA,KAAKrB,EAAE;mBACzCO,MAAMqB,OAAO,CAACL,UAAU,CAACpC,GAAG,CAAC,CAACqC,YAAcA,UAAUxB,EAAE;mBACxDO,MAAMkE,UAAU;mBACflE,MAAMqB,OAAO,CAACV,MAAM,GAAG;oBAACX,MAAMqB,OAAO,CAACV,MAAM;iBAAC,GAAG,EAAE;aACvD;QACH,GACAiD;QAEF,MAAM,EAAEhB,KAAK,EAAE8B,OAAO,EAAE,GAAGD;QAC3B,MAAME,SAASrI,uBACb;YAAEsI,QAAQlJ;YAAwBkH;QAAM,GACxC,QACA,aACK5C,MAAMqE,OAAO;YAChBQ,iBAAiB7E,MAAMkE,UAAU;;QAGrC,6EAA6E;QAC7E,MAAMzC,QAAQgB,mBAAmBwB,SAASvB,OAAO,EAAEyB,OAAOxB,SAAS,EAAEC;QACrE,wEAAwE;QACxE,0EAA0E;QAC1E,sDAAsD;QACtD,MAAMoB,aAAoC;eACrCW,OAAOX,UAAU;eACjBS,KAAKT,UAAU;eACfjB,0BAA0B/C,MAAMhB,MAAM,EAAEyC;eACxC5E,kCACD;gBAAE+H,QAAQlJ;gBAAwBkH,OAAOA;YAAmD,GAC5F5C,MAAMqB,OAAO,CAACF,KAAK;SAEtB;QACD,OAAO;YACLtB,OAAOmE,WAAWxC,MAAM,GACpB,OACA;gBACEoB;gBACAkC,MAAMH,OAAOG,IAAI;gBACjBC,SAAS;uBAAIlB,QAAQkB,OAAO;uBAAKd,SAASc,OAAO;iBAAC;gBAClDL;gBACAjD;YACF;YACJuC;QACF;IACF;AACF;AAiBA,yDAAyD,GACzD,SAASgB,cAAcC,KAAwB,EAAEC,MAAyB;IACxE,MAAMC,QAAiB;QACrBC,aAAaH,MAAME,KAAK,CAACC,WAAW,GAAGF,OAAOC,KAAK,CAACC,WAAW;QAC/DC,cAAcJ,MAAME,KAAK,CAACE,YAAY,GAAGH,OAAOC,KAAK,CAACE,YAAY;QAClEC,iBAAiBL,MAAME,KAAK,CAACG,eAAe,GAAGJ,OAAOC,KAAK,CAACG,eAAe;QAC3EC,kBAAkBN,MAAME,KAAK,CAACI,gBAAgB,GAAGL,OAAOC,KAAK,CAACI,gBAAgB;IAChF;IACA,OAAO;QACLC,UAAUP,MAAMO,QAAQ,GAAGN,OAAOM,QAAQ;QAC1CL;QACAM,YAAYC,KAAKC,KAAK,CAAC,AAACV,CAAAA,MAAMQ,UAAU,GAAGP,OAAOO,UAAU,AAAD,IAAK,WAAa;QAC7EG,OAAOV,OAAOU,KAAK;QACnBC,YAAYX,OAAOW,UAAU;QAC7BxI,QAAQ6H,OAAO7H,MAAM;IACvB;AACF;AAEA;;;;;CAKC,GACD,OAAO,eAAeyI,gBAAgB9F,KAA2B;QAE/CA;IADhB,MAAMoC,OAAsCpC,MAAMhB,MAAM,CAACG,QAAQ,CAACP,GAAG,CAAC,IAAM;IAC5E,MAAMmH,WAAU/F,iBAAAA,MAAM+F,OAAO,YAAb/F,iBAAkB,CAACgG,QAAUA;IAC7C,MAAM9D,OAAOd,mBAAmB;QAC9BnE,KAAK+C,MAAM/C,GAAG;QACdmB,MAAM4B,MAAM5B,IAAI;QAChBY,QAAQgB,MAAMhB,MAAM;QACpBqC,SAASrB,MAAMqB,OAAO;QACtBO,oBAAoB5B,MAAM4B,kBAAkB;IAC9C;IACA,MAAMqE,MAAM,CAACC,SAAiBtI,WAAmB4F,OAC/CnH,uBAA0CyB,qBAAqB;YAC7DqI,MAAM;YACNP,OAAO5F,MAAM4F,KAAK;YAClBQ,cAAcrI;YACdmC,WAAWF,MAAME,SAAS;YAC1BmG,UAAU;gBAAC;oBAAEC,MAAM;oBAAQJ;gBAAQ;aAAE;YACrCK,MAAMxK;YACN6B;YACA4I,QAAQ;gBAAEC,MAAM;gBAAQC,SAAS;YAAqD;WACnFvJ;YACH6I,OAAOD,QACLzC,kBAAkB;gBAChBtE,QAAQgB,MAAMhB,MAAM;gBACpBkF,YAAYlE,MAAMkE,UAAU;gBAC5B7C,SAASrB,MAAMqB,OAAO;gBACtBgD,SAASrE,MAAMqE,OAAO;gBACtBzC,oBAAoB5B,MAAM4B,kBAAkB;gBAC5CQ;eACIoB,OAAO;gBAAEA;YAAK,IAAI,CAAC;WAGvBxD,MAAM2G,MAAM,GAAG;YAAEA,QAAQ3G,MAAM2G,MAAM;QAAC,IAAI,CAAC;IAEnD,MAAM1B,QAAQ,MAAMgB,IAAI/D,MAAMxE,4BAA4BE,SAAS,CAACoC,MAAM4F,KAAK;IAC/E,MAAM3D,UAAUE,wBAAwBC;IACxC,IACE6C,MAAM2B,MAAM,KAAK,iBACjB,CAAC3E,QAAQT,MAAM,IACf,CAACyD,MAAMjB,UAAU,CAAC6C,KAAK,CAAC,CAACC,YAAcA,UAAU3D,IAAI,KAAKb,iCAC1D;QACA,OAAO2C;IACT;IACA,MAAMC,SAAS,MAAMe,IACnBlE,8BAA8B;QAAEG;QAAMlD,QAAQgB,MAAMhB,MAAM;QAAEiD;IAAQ,IACpEyD,KAAKqB,GAAG,CAACtJ,uCAAuCC,4BAA4BE,SAAS,CAACoC,MAAM4F,KAAK,IACjG3D;IAEF,OAAO,aAAKiD,QAAWF,cAAcC,OAAOC;AAC9C"}
@@ -36,6 +36,7 @@ import { aiBracketedFactsNote, aiConfirmedPlan, aiDoctrineReview, aiGenerationSp
36
36
  import { AI_JOB_PAGE_SECTION_MAX_TOKENS, AI_JOB_PAGE_SECTION_TOKENS, AI_JOB_PAGE_STEP_BUDGET, AI_JOB_PAGE_STEP_MINIMUM_MS, aiJobPageSectionMaxTokens } from "./ai-job-page-budget.js";
37
37
  import { AI_JOB_PAGE_INSTRUCTIONS, AI_PAGE_SECTION_TOOL, aiEmptyPage, aiPageCheckContext, aiPageLinkablePages, aiPageRecordNote, aiPageRecordTemplate, aiPageRecordTokens, aiPageSectionCheck, aiPageSectionNodeId, aiPageSectionPrompt, aiPageSectionSmaller, aiPageWithSection } from "./ai-job-page-sections.js";
38
38
  import { aiLayoutSitePages } from "./ai-job-layout-site-pages.js";
39
+ import { aiResolveLayoutPictures } from "../layout-language/ai-layout-pictures.js";
39
40
  import { AI_PLAN_ITEMS_MIN, aiPlanCopiedPageViolations } from "./ai-job-plan-conformance.js";
40
41
  import { AI_JOB_PAGE_LANGUAGE_BUDGET, aiJobUsesLayoutLanguage, aiLayoutPageTargets, aiRunLayoutPage } from "./ai-job-page-language.js";
41
42
  import { aiCreationUnit, aiRunJobUnit, aiSiteBuiltRefs, aiSitePendingUnits, aiSiteUnitJob } from "./ai-job-site-step.js";
@@ -661,6 +662,7 @@ export function createAiJobPageStep(deps = {}) {
661
662
  });
662
663
  }
663
664
  if (language) {
665
+ var _screen_id;
664
666
  // ── The whole page in one answer, in the layout language ──
665
667
  const targets = aiLayoutPageTargets({
666
668
  job,
@@ -690,6 +692,14 @@ export function createAiJobPageStep(deps = {}) {
690
692
  if (result.status === 'needs_input') return _extends({}, spent, {
691
693
  review: aiDoctrineReview(result)
692
694
  });
695
+ // The compiler leaves each picture slot empty; a photo the site serves
696
+ // itself fills it here, after the page is checked (AGL-3660).
697
+ const pictured = await aiResolveLayoutPictures(result.value.nodes, {
698
+ rootId: CANVAS_ROOT_ELEMENT_ID,
699
+ sectionIds,
700
+ sectionNames: screen.sections.map((section)=>section.name),
701
+ seed: `${aiOriginJobId(job)}:${(_screen_id = screen.id) != null ? _screen_id : screen.slug}`
702
+ });
693
703
  if (!written) {
694
704
  const draft = await writeAiDraft(firestore, {
695
705
  kind: 'screen',
@@ -698,7 +708,7 @@ export function createAiJobPageStep(deps = {}) {
698
708
  uid: job.createdBy,
699
709
  org,
700
710
  name,
701
- nodes: result.value.nodes,
711
+ nodes: pictured,
702
712
  slug,
703
713
  layoutId: aiPageDraftLayoutId(screen, inventory),
704
714
  aiJobId: aiOriginJobId(job),
@@ -717,7 +727,7 @@ export function createAiJobPageStep(deps = {}) {
717
727
  hostId,
718
728
  id: draftId,
719
729
  now,
720
- update: ()=>result.value.nodes
730
+ update: ()=>pictured
721
731
  });
722
732
  if (update.ok === false) return _extends({}, spent, {
723
733
  failure: AI_JOB_PAGE_DELETED_COPY