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

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 (115) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-job-links.d.ts +6 -0
  3. package/src/lib/components/ai-job-links.js +9 -0
  4. package/src/lib/components/ai-job-links.js.map +1 -1
  5. package/src/lib/components/ai-site-build-page.component.js +41 -3
  6. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  7. package/src/lib/components/ai-site-start-card.component.js +91 -8
  8. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  9. package/src/lib/declarations.server.js +7 -4
  10. package/src/lib/declarations.server.js.map +1 -1
  11. package/src/lib/jobs/ai-build-capabilities.js +8 -2
  12. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  13. package/src/lib/jobs/ai-job-admission.d.ts +2 -1
  14. package/src/lib/jobs/ai-job-admission.js.map +1 -1
  15. package/src/lib/jobs/ai-job-email-step.d.ts +39 -0
  16. package/src/lib/jobs/ai-job-email-step.js +116 -5
  17. package/src/lib/jobs/ai-job-email-step.js.map +1 -1
  18. package/src/lib/jobs/ai-job-layout-language.d.ts +3 -0
  19. package/src/lib/jobs/ai-job-layout-language.js +3 -1
  20. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  21. package/src/lib/jobs/ai-job-layout-site-pages.d.ts +2 -0
  22. package/src/lib/jobs/ai-job-layout-site-pages.js +23 -11
  23. package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
  24. package/src/lib/jobs/ai-job-layout-step.js +3 -2
  25. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  26. package/src/lib/jobs/ai-job-page-language.d.ts +16 -0
  27. package/src/lib/jobs/ai-job-page-language.js +35 -6
  28. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  29. package/src/lib/jobs/ai-job-page-step.d.ts +3 -0
  30. package/src/lib/jobs/ai-job-page-step.js +18 -4
  31. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  32. package/src/lib/jobs/ai-job-plan-step.d.ts +40 -1
  33. package/src/lib/jobs/ai-job-plan-step.js +121 -16
  34. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  35. package/src/lib/jobs/ai-job-site-content.d.ts +6 -6
  36. package/src/lib/jobs/ai-job-site-content.js +6 -12
  37. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  38. package/src/lib/jobs/ai-job-site-step.d.ts +11 -0
  39. package/src/lib/jobs/ai-job-site-step.js +96 -44
  40. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  41. package/src/lib/jobs/ai-jobs-notify.d.ts +14 -4
  42. package/src/lib/jobs/ai-jobs-notify.js +70 -4
  43. package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
  44. package/src/lib/jobs/ai-jobs.d.ts +22 -4
  45. package/src/lib/jobs/ai-jobs.js +47 -12
  46. package/src/lib/jobs/ai-jobs.js.map +1 -1
  47. package/src/lib/jobs/ai-layout-stock-photos.d.ts +98 -0
  48. package/src/lib/jobs/ai-layout-stock-photos.js +248 -0
  49. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -0
  50. package/src/lib/jobs/ai-site-publish.d.ts +11 -0
  51. package/src/lib/jobs/ai-site-publish.js +31 -1
  52. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  53. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.d.ts +151 -0
  54. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.js +183 -0
  55. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.js.map +1 -0
  56. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.d.ts +27 -0
  57. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.js +192 -0
  58. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.js.map +1 -0
  59. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.d.ts +27 -0
  60. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.js +222 -0
  61. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.js.map +1 -0
  62. package/src/lib/layout-language/ai-layout-compiler.d.ts +29 -0
  63. package/src/lib/layout-language/ai-layout-compiler.js +698 -12
  64. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  65. package/src/lib/layout-language/ai-layout-design.d.ts +107 -0
  66. package/src/lib/layout-language/ai-layout-design.js +197 -0
  67. package/src/lib/layout-language/ai-layout-design.js.map +1 -0
  68. package/src/lib/layout-language/ai-layout-frame.d.ts +6 -0
  69. package/src/lib/layout-language/ai-layout-frame.js +22 -6
  70. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  71. package/src/lib/layout-language/ai-layout-language.d.ts +2 -0
  72. package/src/lib/layout-language/ai-layout-language.js +15 -1
  73. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  74. package/src/lib/layout-language/ai-layout-links.d.ts +11 -0
  75. package/src/lib/layout-language/ai-layout-links.js +4 -0
  76. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  77. package/src/lib/layout-language/ai-layout-pictures.d.ts +11 -5
  78. package/src/lib/layout-language/ai-layout-pictures.js +80 -17
  79. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  80. package/src/lib/model/ai-job-notice.d.ts +9 -1
  81. package/src/lib/model/ai-job-notice.js +23 -0
  82. package/src/lib/model/ai-job-notice.js.map +1 -1
  83. package/src/lib/model/ai-plan-capabilities.d.ts +23 -0
  84. package/src/lib/model/ai-plan-capabilities.js +5 -0
  85. package/src/lib/model/ai-plan-capabilities.js.map +1 -1
  86. package/src/lib/model/ai-site-build-progress.d.ts +12 -2
  87. package/src/lib/model/ai-site-build-progress.js +78 -4
  88. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  89. package/src/lib/model/ai-site-job.d.ts +209 -22
  90. package/src/lib/model/ai-site-job.js +247 -20
  91. package/src/lib/model/ai-site-job.js.map +1 -1
  92. package/src/lib/model/ai-site-kinds.js +6 -1
  93. package/src/lib/model/ai-site-kinds.js.map +1 -1
  94. package/src/lib/model/ai-site-look.d.ts +21 -0
  95. package/src/lib/model/ai-site-look.js +63 -14
  96. package/src/lib/model/ai-site-look.js.map +1 -1
  97. package/src/lib/operator-alerts.d.ts +18 -0
  98. package/src/lib/operator-alerts.js +42 -0
  99. package/src/lib/operator-alerts.js.map +1 -1
  100. package/src/lib/runtime/ai-doctrine-validators.d.ts +39 -1
  101. package/src/lib/runtime/ai-doctrine-validators.js +236 -6
  102. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  103. package/src/lib/runtime/ai-doctrine.js +5 -3
  104. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  105. package/src/lib/runtime/ai-palette.generated.d.ts +2 -0
  106. package/src/lib/runtime/ai-palette.generated.js +18 -2
  107. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  108. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  109. package/src/lib/server/ai-jobs-route.js +15 -3
  110. package/src/lib/server/ai-jobs-route.js.map +1 -1
  111. package/src/lib/server/assist-edit.js +5 -2
  112. package/src/lib/server/assist-edit.js.map +1 -1
  113. package/src/lib/usage/free-ai-credits-left.d.ts +54 -0
  114. package/src/lib/usage/free-ai-credits-left.js +62 -0
  115. package/src/lib/usage/free-ai-credits-left.js.map +1 -0
@@ -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 { 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"}
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, aiSiteKindOfInputs } from '../model/ai-site-kinds'\nimport { aiSiteSeed } from '../model/ai-site-look'\nimport type { AiLayoutDesign } from '../layout-language/ai-layout-design'\nimport { aiOriginJobId } from './ai-job-draft-ids'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { aiGenerationWorstCaseOnTierMs, aiJobStepBudget } from './ai-job-budget'\nimport { aiJobBriefLine, aiPlanReferenceLines } from './ai-job-generation'\nimport { aiLayoutIsHomeSlug, 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 most one section of a page is written in, its copy included: the top of\n * the 120 to 250 tokens a section runs to. The Free site wall prices each\n * planned section at it (AGL-3660).\n */\nexport const AI_LAYOUT_SECTION_MOST_TOKENS = 250\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/**\n * The site a page is designed for (AGL-3660): the kind its job names, the\n * seed its look was drawn with — the same for every page of the site, so the\n * site reads as one design — and whether the page is its home. `null` for a\n * job that names no kind of site, which compiles the compiler's first way.\n */\nexport function aiLayoutDesignOf(job: Pick<AiJob, '$id' | 'inputs'>, slug: string): AiLayoutDesign | null {\n const kind = aiSiteKindOfInputs(job.inputs)\n if (!kind) return null\n const style = job.inputs?.['siteStyle'] as Record<string, unknown> | undefined\n const seed = typeof style?.['seed'] === 'number' && Number.isFinite(style['seed']) ? (style['seed'] as number) >>> 0 : aiSiteSeed(aiOriginJobId(job))\n return { kind: kind.id, seed, home: aiLayoutIsHomeSlug(slug) }\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 /** The site the page is drawn for (`aiLayoutDesignOf`); absent, the compiler's first design. */\n design?: AiLayoutDesign | null\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 ...(input.design ? { design: input.design } : {}),\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 // The layout language draws its own picture cards (AGL-3660).\n repeatsCompiled: true,\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 design: aiLayoutDesignOf(input.job, input.screen.slug),\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","aiSiteKindOfInputs","aiSiteSeed","aiOriginJobId","aiGenerationWorstCaseOnTierMs","aiJobStepBudget","aiJobBriefLine","aiPlanReferenceLines","aiLayoutIsHomeSlug","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_LAYOUT_SECTION_MOST_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","aiLayoutDesignOf","style","seed","Number","isFinite","home","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","design","stored","tree","context","ok","console","error","copy","dropped","report","rootId","scrollTargetIds","repeatsCompiled","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":"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"}
@@ -22,6 +22,7 @@ import { readSiteInventory } from '../runtime/site-inventory';
22
22
  import type { AiSiteInventory } from '../model/ai-site-inventory';
23
23
  import { type AiJobAdmission } from './ai-job-admission';
24
24
  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';
25
+ import { aiLayoutStockPhotoSource } from './ai-layout-stock-photos';
25
26
  import { type AiSiteUnit } from './ai-job-site-step';
26
27
  import type { AiJobStepRunner } from './ai-job-text-step';
27
28
  import { aiJobStepRunnerFor } from './ai-jobs';
@@ -205,6 +206,8 @@ export interface AiJobPageStepDeps {
205
206
  seoFields?: typeof generateSeoFields;
206
207
  /** The runners a plan's creations are built by (AGL-3031); the registry's otherwise. */
207
208
  runnerFor?: typeof aiJobStepRunnerFor;
209
+ /** The stock photo source of a language page's pictures (AGL-3660); core's provider otherwise. */
210
+ stockPhotos?: typeof aiLayoutStockPhotoSource;
208
211
  }
209
212
  export declare function createAiJobPageStep(deps?: AiJobPageStepDeps): AiJobStepRunner;
210
213
  export declare const runAiJobPageStep: AiJobStepRunner;
@@ -37,6 +37,7 @@ import { AI_JOB_PAGE_SECTION_MAX_TOKENS, AI_JOB_PAGE_SECTION_TOKENS, AI_JOB_PAGE
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
39
  import { aiResolveLayoutPictures } from "../layout-language/ai-layout-pictures.js";
40
+ import { aiLayoutStockPhotoSource } from "./ai-layout-stock-photos.js";
40
41
  import { AI_PLAN_ITEMS_MIN, aiPlanCopiedPageViolations } from "./ai-job-plan-conformance.js";
41
42
  import { AI_JOB_PAGE_LANGUAGE_BUDGET, aiJobUsesLayoutLanguage, aiLayoutPageTargets, aiRunLayoutPage } from "./ai-job-page-language.js";
42
43
  import { aiCreationUnit, aiRunJobUnit, aiSiteBuiltRefs, aiSitePendingUnits, aiSiteUnitJob } from "./ai-job-site-step.js";
@@ -662,7 +663,7 @@ export function createAiJobPageStep(deps = {}) {
662
663
  });
663
664
  }
664
665
  if (language) {
665
- var _screen_id;
666
+ var _screen_id, _deps_stockPhotos;
666
667
  // ── The whole page in one answer, in the layout language ──
667
668
  const targets = aiLayoutPageTargets({
668
669
  job,
@@ -693,12 +694,25 @@ export function createAiJobPageStep(deps = {}) {
693
694
  review: aiDoctrineReview(result)
694
695
  });
695
696
  // 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
+ // itself fills it here, after the page is checked (AGL-3660): a stock
698
+ // photo copied into the site's library where the deployment has a
699
+ // library, else a starter photo. No AI credits; never fails the job.
700
+ const sectionNames = screen.sections.map((section)=>section.name);
701
+ const seed = `${aiOriginJobId(job)}:${(_screen_id = screen.id) != null ? _screen_id : screen.slug}`;
697
702
  const pictured = await aiResolveLayoutPictures(result.value.nodes, {
698
703
  rootId: CANVAS_ROOT_ELEMENT_ID,
699
704
  sectionIds,
700
- sectionNames: screen.sections.map((section)=>section.name),
701
- seed: `${aiOriginJobId(job)}:${(_screen_id = screen.id) != null ? _screen_id : screen.slug}`
705
+ sectionNames,
706
+ seed,
707
+ source: ((_deps_stockPhotos = deps.stockPhotos) != null ? _deps_stockPhotos : aiLayoutStockPhotoSource)(_extends({
708
+ hostId,
709
+ uid: job.createdBy,
710
+ seed,
711
+ business: aiSiteWords(job.inputs).about,
712
+ sectionNames
713
+ }, signal ? {
714
+ signal
715
+ } : {}))
702
716
  });
703
717
  if (!written) {
704
718
  const draft = await writeAiDraft(firestore, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-page-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { buildPageMarkdown } from '@aglyn/aglyn/app-utils/page-markdown'\nimport { SCREEN_SEO_TEXT_GUIDANCE } from '@aglyn/aglyn/app-utils/screen-seo-fields'\nimport { SCREEN_ROOT_PATH, screenRoutePathToUrl } from '@aglyn/aglyn/app-utils/screen-route'\nimport { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { duplicateResource } from '@aglyn/tenant-data-admin/server/duplicate-resource'\nimport { AI_BUILD_PLAN_LIMITS, aiPlanEmbedsFor, type AiBuildPlanScreen } from '../model/ai-build-plan'\nimport {\n AI_PAGE_CREATE_KINDS,\n aiPageCreationRefusal,\n aiPagePlanRefusal,\n parseAiPageJobInputs,\n} from '../model/ai-page-job'\nimport { aiPlanCapabilitiesForJob, aiPlanUncreatable } from '../model/ai-plan-capabilities'\nimport { aiSiteWords } from '../model/ai-site-job'\nimport type { AiJob, AiJobOutput, AiJobPlan, AiJobReview } from '../model/ai-jobs.types'\nimport { aiModelForStep } from '../providers/routing'\nimport { aiDoctrineNeedsInputMessage, runValidatedGeneration } from '../runtime/ai-doctrine'\nimport { validateAiDoctrineTree, type AiDoctrineTree } from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport { AI_SEO_FIELDS_MAX_TOKENS, generateSeoFields } from '../runtime/seo-fields'\nimport { readSiteInventory } from '../runtime/site-inventory'\nimport type { AiSiteInventory } from '../model/ai-site-inventory'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport { aiGenerationWorstCaseMs } from './ai-job-budget'\nimport { aiJobDraftId, aiOriginJobId } from './ai-job-draft-ids'\nimport {\n aiBuildsWithComponents,\n aiDraftAdmissionRefusal,\n aiDraftAllowanceRefusal,\n aiSiteSubdomain,\n readAiDraft,\n readAiDraftNodes,\n readAiPlanCapabilities,\n updateAiDraftNodes,\n writeAiDraft,\n writeAiDraftScreenSeo,\n type AiDraftRecord,\n} from './ai-job-drafts'\nimport {\n aiBracketedFactsNote,\n aiConfirmedPlan,\n aiDoctrineReview,\n aiGenerationSpent,\n aiLimitReview,\n aiModelNodeIds,\n aiUnspentOutcome,\n} from './ai-job-generation'\nimport {\n AI_JOB_PAGE_SECTION_MAX_TOKENS,\n AI_JOB_PAGE_SECTION_TOKENS,\n AI_JOB_PAGE_STEP_BUDGET,\n AI_JOB_PAGE_STEP_MINIMUM_MS,\n aiJobPageSectionMaxTokens,\n} from './ai-job-page-budget'\nimport {\n AI_JOB_PAGE_INSTRUCTIONS,\n AI_PAGE_SECTION_TOOL,\n aiEmptyPage,\n aiPageCheckContext,\n aiPageLinkablePages,\n aiPageRecordNote,\n aiPageRecordTemplate,\n aiPageRecordTokens,\n aiPageSectionCheck,\n aiPageSectionNodeId,\n aiPageSectionPrompt,\n aiPageSectionSmaller,\n aiPageWithSection,\n type AiPageSection,\n} from './ai-job-page-sections'\nimport { aiLayoutSitePages } from './ai-job-layout-site-pages'\nimport { aiResolveLayoutPictures } from '../layout-language/ai-layout-pictures'\nimport { AI_PLAN_ITEMS_MIN, aiPlanCopiedPageViolations } from './ai-job-plan-conformance'\nimport {\n AI_JOB_PAGE_LANGUAGE_BUDGET,\n aiJobUsesLayoutLanguage,\n aiLayoutPageTargets,\n aiRunLayoutPage,\n} from './ai-job-page-language'\nimport {\n aiCreationUnit,\n aiRunJobUnit,\n aiSiteBuiltRefs,\n aiSitePendingUnits,\n aiSiteUnitJob,\n type AiSiteUnit,\n} from './ai-job-site-step'\nimport type { AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport {\n aiJobStepRunMinimumMs,\n aiJobStepRunnerFor,\n registerAiJobStep,\n registerAiJobStepPasses,\n} from './ai-jobs'\n\n/**\n * The page step (AGL-2907): the generation step of a `page` job, run once a\n * member confirmed the job's plan. It builds ONE screen, section by section,\n * from what the site already has, and leaves it an unpublished draft.\n *\n * ── Why a page is built in passes ────────────────────────────────────────\n *\n * A page is far larger than one answer the job beat can wait for: the beat\n * gives a step what is left of its budget, and a whole page, answered and re-asked,\n * takes several times that. The confirmed plan already names the page's\n * sections, so the step builds one section per pass and asks the machine to\n * continue. Every pass is one reservation and one generation — the section's\n * lookup rounds, its answer and, when it breaks a rule, its one re-ask — sized\n * so that worst case fits `AI_JOB_PAGE_STEP_MINIMUM_MS`, which the step\n * registers as the least time a section pass needs before it starts.\n *\n * - The first pass builds the top section, which holds the page's h1, and\n * writes the draft screen and its first version.\n * - Each later pass builds the next section and adds it to that version.\n * - The last pass generates no tree: it holds the whole page to the\n * doctrine, writes the search title and description, and reports the\n * draft with its measured weight and a navigation proposal.\n *\n * How a section is asked for and held to the rules as part of the page is\n * `ai-job-page-sections.ts`.\n *\n * ── What the plan creates comes first (AGL-3031) ─────────────────────────\n *\n * A plan that creates a layout, forms or components is built in the same job,\n * before its page, one creation a pass, through the site scaffold's unit\n * machinery: each creation is handed to the step that builds that kind, under\n * a job derived from this one, and lands as the draft that step writes. Where\n * the job stands is read from its outputs, so nothing is kept anywhere else.\n * Once every creation is built, the page's passes run on the plan with each\n * `new:<name>` resolved to the record that was built, which the site's\n * inventory now lists — so the page places the new component and binds the new\n * form by id, and renders inside the new layout, exactly as it would one the\n * site always had. A pass that builds a creation needs the time the step that\n * builds it registers, not a section's (`aiPageJobRunMinimumMs`, AGL-3035), so\n * neither the beat nor a door starts it with only a section's time left.\n *\n * ── Never published ──────────────────────────────────────────────────────\n *\n * The draft is written the way `/api/hosts/resources` and\n * `/api/hosts/versions` write a screen and its first version, and nothing\n * else is touched: not the host document, whose routing map is what the live\n * site serves, and no collection, store, layout or navigation pointer\n * (`ai-job-drafts.ts`). A screen the routing map does not name serves nothing\n * until a member publishes it.\n */\n\n/**\n * A section pass's time, and the ceiling each pass asks on each model, are\n * declared apart from the step (`ai-job-page-budget.ts`): the site scaffold\n * builds its pages through this step and reads them without loading it.\n */\nexport {\n AI_JOB_PAGE_SECTION_MAX_TOKENS,\n AI_JOB_PAGE_SECTION_TOKENS,\n AI_JOB_PAGE_STEP_BUDGET,\n AI_JOB_PAGE_STEP_MINIMUM_MS,\n aiJobPageSectionMaxTokens,\n}\n\n/**\n * Tokens one element takes in a section's answer, ESTIMATED: the tree as JSON\n * text inside the tool call, at four characters a token. Measured on the ten\n * golden pages at a median of 38 and a most of 43 over their 39 sections, and\n * `ai-job-page-evals.spec.ts` holds every golden section under it.\n */\nexport const AI_JOB_PAGE_TOKENS_PER_ELEMENT = 45\n\n/**\n * Real tokens to every token estimated at four characters (AGL-3042). A\n * section's ceiling is counted by the provider in its own tokens, which run\n * above that estimate. MEASURED on the first live document run on a Free\n * workspace (AGL-3024, test-org, 2026-09-16), as the cache read of a request\n * over the estimate of the same cached prefix: 4,059 over 2,825 for the page\n * plan and 6,649 over 4,310 for the layout, the higher of the two, 1.5427 —\n * the ratio `ai-job-free-page.spec.ts` prices a Free page at. It is measured on\n * prompt text; an answer's own is read off a recorded pass, whose output\n * tokens the step's record keeps (`lastRuns`), beside the section it stored.\n */\nexport const AI_JOB_PAGE_REAL_TOKENS_PER_ESTIMATED = Math.max(4_059 / 2_825, 6_649 / 4_310)\n\n/**\n * Real tokens one element takes in a section's answer: the estimate at its\n * golden most, in real tokens, rounded up — 45 × 1.5427 → 70 (AGL-3042).\n */\nexport const AI_JOB_PAGE_REAL_TOKENS_PER_ELEMENT = Math.ceil(\n AI_JOB_PAGE_TOKENS_PER_ELEMENT * AI_JOB_PAGE_REAL_TOKENS_PER_ESTIMATED,\n)\n\n/**\n * The most elements a section's request asks it to keep under at an answer\n * ceiling: 15 at the balanced tier's 1,050 (AGL-3042). Counted in real\n * tokens, because the ceiling is: counted in the estimate, the budget is 23\n * elements, which a section of the goldens' largest elements fills at 1,597\n * real tokens — half as much again as the ceiling that cuts it off.\n */\nexport function aiJobPageSectionMaxElements(maxTokens: number): number {\n return Math.max(1, Math.floor(maxTokens / AI_JOB_PAGE_REAL_TOKENS_PER_ELEMENT))\n}\n\n/** A page's name when the plan names none. */\nexport const AI_JOB_PAGE_DEFAULT_NAME = 'New page'\n\nexport const AI_JOB_PAGE_NO_PLAN_COPY = 'This page job has no confirmed plan to build.'\nexport const AI_JOB_PAGE_DELETED_COPY = 'The draft page was deleted while it was being built.'\n\n/** A creation the plan names that this deployment has no step to build. */\nexport const AI_JOB_PAGE_CREATION_UNAVAILABLE_COPY =\n 'This page needs something that cannot be built here yet. Describe the page again.'\n\n/** A creation that finished without reporting what it built; the page cannot place it. */\nexport const AI_JOB_PAGE_CREATION_EMPTY_COPY =\n 'Part of this page could not be built. Describe the page again.'\n\n/**\n * What a page refused on its last pass tells the member after the plain\n * refusal (AGL-3143, AGL-3596). The draft is reported with the review, and\n * mending its layout in the editor is what lets the next pass finish the job.\n */\nexport const AI_JOB_PAGE_REFUSED_DRAFT_COPY = 'The draft is yours to open: fix its layout in the editor, then try again.'\n\n/**\n * The most passes a page job may take (AGL-3031): every creation the plan\n * limits admit, every section they admit, and the last pass that reports the\n * draft. A creation's step builds it in one pass, and a page one section a\n * pass, so a runner that never finishes is still bounded while a real page is\n * not.\n */\nexport const AI_JOB_PAGE_MAX_PASSES = AI_BUILD_PLAN_LIMITS.create + AI_BUILD_PLAN_LIMITS.sections + 1\n\n/**\n * The units a plan's creations are built as, in the order a scaffold builds\n * them: the layout, then the forms, then the components. Each is addressed by\n * its place in the plan, so a unit run again finds its own draft.\n */\nexport function aiPageJobUnits(plan: Pick<AiJobPlan, 'create'>): AiSiteUnit[] {\n return AI_PAGE_CREATE_KINDS.flatMap((kind) =>\n plan.create.flatMap((creation, index) => {\n if (creation.kind !== kind) return []\n const unit = aiCreationUnit(creation, `c${index}`)\n return unit ? [unit] : []\n }),\n )\n}\n\n/**\n * The least time this page job's next pass needs (AGL-3035). A pass that\n * builds a creation hands it to the step registered for that kind, under the\n * job derived for it, so it needs what that step registers for that job — a\n * layout's time, not a section's. Every other pass — a section, the last one —\n * needs a section pass's.\n */\nexport function aiPageJobRunMinimumMs(job: AiJob): number {\n const confirmed = aiConfirmedPlan(job)\n // A page built in the layout language is one answer for the whole page (AGL-3660).\n const pagePass = aiJobUsesLayoutLanguage(job) ? AI_JOB_PAGE_LANGUAGE_BUDGET.minimumMs : AI_JOB_PAGE_STEP_MINIMUM_MS\n if (!confirmed) return pagePass\n const units = aiPageJobUnits(confirmed)\n const outputs = job.outputs ?? []\n const [unit] = aiSitePendingUnits(units, outputs)\n if (!unit) return pagePass\n return aiJobStepRunMinimumMs(aiSiteUnitJob(job, unit, aiSiteBuiltRefs(units, outputs)))\n}\n\n/**\n * The one-segment address a draft asks for, from the plan's slug; `/` for a\n * planned home page, which asks for the root rather than for an address made\n * from its name (AGL-3596).\n */\nexport function aiPageDraftSlug(screen: Pick<AiBuildPlanScreen, 'slug'>): string {\n const segments = screen.slug.split('/').map((part) => part.trim()).filter(Boolean)\n if (!segments.length && screen.slug.trim() === SCREEN_ROOT_PATH) return SCREEN_ROOT_PATH\n return segments[segments.length - 1] ?? ''\n}\n\n/**\n * The layout a site's pages render inside (AGL-3596): the one its home page\n * uses, else the one the most pages use, else the site's only layout. `null`\n * for a site with no layout, or several and no page to choose between them.\n */\nexport function aiSiteDefaultLayoutId(\n inventory: Pick<AiSiteInventory, 'layouts' | 'screens'>,\n): string | null {\n const layouts = new Set(inventory.layouts.map((row) => row.id))\n if (!layouts.size) return null\n const pages = inventory.screens.filter(\n (row) => !row.template && row.layoutId && layouts.has(row.layoutId),\n )\n const home = pages.find((row) => row.slug.trim() === SCREEN_ROOT_PATH)\n if (home?.layoutId) return home.layoutId\n const counts = new Map<string, number>()\n for (const row of pages) counts.set(row.layoutId as string, (counts.get(row.layoutId as string) ?? 0) + 1)\n const [mostUsed] = [...counts.entries()].sort((a, b) => b[1] - a[1])\n if (mostUsed) return mostUsed[0]\n return layouts.size === 1 ? [...layouts][0] : null\n}\n\n/**\n * The layout a page draft renders inside: the plan's, where the site has it,\n * else the site's own (AGL-3596). A page the plan named no layout for used to\n * be written with none, and so with no header and no footer, beside pages\n * that had them.\n */\nexport function aiPageDraftLayoutId(\n screen: Pick<AiBuildPlanScreen, 'layout'>,\n inventory: Pick<AiSiteInventory, 'layouts' | 'screens'>,\n): string | null {\n if (screen.layout && inventory.layouts.some((row) => row.id === screen.layout)) return screen.layout\n return aiSiteDefaultLayoutId(inventory)\n}\n\n/** A plan's listing value held to the length the SEO editor guides it to, cut at a word. */\nfunction clip(value: string, max: number): string {\n const text = value.trim()\n if (text.length <= max) return text\n const cut = text.slice(0, max + 1)\n const space = cut.lastIndexOf(' ')\n return (space > max / 2 ? cut.slice(0, space) : text.slice(0, max)).trim()\n}\n\n/** The site's name as its visitors see it, read the way the SEO step reads it. */\nfunction siteNameOf(host: FirebaseFirestore.DocumentSnapshot): string {\n const text = (value: unknown) => (typeof value === 'string' ? value.trim() : '')\n return text(host.get('seo.title')) || text(host.get('displayName')) || text(host.get('subdomain'))\n}\n\n/**\n * The counts the confirmed plan's sections promised, against the page a COPY\n * actually produced (AGL-3024); `null` when the copy keeps the plan, or when\n * the plan promised no count to keep.\n *\n * The same hole the layout copy had (`aiCopiedLayoutReview`), on the kind the\n * defect was measured on. This branch generates nothing, so no model ever\n * answers for the plan: a plan reading \"start from the Services page, one\n * card per practice area\" gets the Services page and whatever IT had, and\n * until now reported `Done` — the one report that tells a customer there is\n * no reason to re-read the page.\n *\n * A copy that cannot be read back settles nothing, and a promise this step\n * cannot settle is reviewed by a person rather than reported as kept.\n */\nexport async function aiCopiedPageReview(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; id: string; name: string; screen: Pick<AiBuildPlanScreen, 'sections'> },\n): Promise<AiJobReview | null> {\n if (!input.screen.sections.some((section) => section.items >= AI_PLAN_ITEMS_MIN)) return null\n const stored = await readAiDraftNodes(firestore, { kind: 'screen', hostId: input.hostId, id: input.id })\n const nodes = (stored?.nodes ?? {}) as unknown as AiDoctrineTree['nodes']\n if (!nodes[CANVAS_ROOT_ELEMENT_ID]) {\n return {\n reason: 'doctrine',\n message: `\"${input.name}\" was copied from the page the plan names, and this job could not read the copy back to check it shows what the plan says it shows. Open the page and check it before you put it in front of anyone.`,\n findings: [],\n }\n }\n const violations = aiPlanCopiedPageViolations(input.screen, { rootId: CANVAS_ROOT_ELEMENT_ID, nodes })\n if (!violations.length) return null\n return aiDoctrineReview({\n message: `\"${input.name}\" is a copy of the page the plan names, and a copy is all it is: ${violations[0].message}`,\n violations,\n })\n}\n\n/**\n * A page job is admitted with inputs that read, for a site of the job's own\n * org with a screen to spare; and confirmed only for a plan it can build —\n * one screen, whose creations are ones a page job builds, this deployment has\n * a step for, and this workspace may still make on this site (AGL-3031).\n */\nexport const aiPageJobAdmission: AiJobAdmission = async (context) => {\n const inputs = parseAiPageJobInputs(context.inputs)\n if (typeof inputs === 'string') return { status: 400, error: inputs }\n const plan = context.plan\n if (plan) {\n const refusal = aiPagePlanRefusal(plan)\n if (refusal) return { status: 400, error: refusal }\n if (aiPageJobUnits(plan).some((unit) => !aiJobStepRunnerFor(unit.jobKind))) {\n return { status: 400, error: AI_JOB_PAGE_CREATION_UNAVAILABLE_COPY }\n }\n }\n const org = context.org as Partial<AglynOrgBilling> | null\n return aiDraftAdmissionRefusal(context.firestore, {\n orgId: context.orgId,\n hostId: context.hostId,\n kind: 'screen',\n noun: 'page',\n org,\n // What the workspace may still make here, read once the site is known to\n // be the org's: the site may have changed since the plan was told.\n ...(plan?.create.length\n ? {\n ownCheck: async (hostId: string) => {\n const capabilities = aiPlanCapabilitiesForJob(\n await readAiPlanCapabilities(context.firestore, { hostId, org }),\n { noun: 'a page job', creates: AI_PAGE_CREATE_KINDS },\n )\n const refusal = aiPageCreationRefusal(aiPlanUncreatable(plan, capabilities))\n return refusal ? { status: 403 as const, error: refusal } : null\n },\n }\n : {}),\n })\n}\n\nexport interface AiJobPageStepDeps {\n /** The inventory reader; specs hand in a fake. */\n readInventory?: typeof readSiteInventory\n /** The platform's duplicate module, for a plan that starts from a copy. */\n duplicate?: typeof duplicateResource\n /** The listing generator (AGL-2910). */\n seoFields?: typeof generateSeoFields\n /** The runners a plan's creations are built by (AGL-3031); the registry's otherwise. */\n runnerFor?: typeof aiJobStepRunnerFor\n}\n\nexport function createAiJobPageStep(deps: AiJobPageStepDeps = {}): AiJobStepRunner {\n const readInventory = deps.readInventory ?? readSiteInventory\n const duplicate = deps.duplicate ?? duplicateResource\n const seoFields = deps.seoFields ?? generateSeoFields\n const runnerFor = deps.runnerFor ?? aiJobStepRunnerFor\n return async (stepContext): Promise<AiJobStepOutcome> => {\n const { job, now, signal, firestore, modelFor } = stepContext\n const hostId = job.hostId\n if (!hostId) throw new Error('a page job names no site, and its admission refuses one')\n // The switch's answer for this job, else the routing table's (AGL-2942).\n const model = modelFor?.('job.page') ?? aiModelForStep('job.page')\n const confirmed = aiConfirmedPlan(job)\n if (!confirmed) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_NO_PLAN_COPY }\n // The resume door refuses such a plan at confirmation; one confirmed\n // before a site changed still stops here, before any spend.\n const refusal = aiPagePlanRefusal(confirmed)\n if (refusal) return { ...aiUnspentOutcome(model), failure: refusal }\n\n // ── The creations first (AGL-3031): one a pass, by the steps that own them ──\n const units = aiPageJobUnits(confirmed)\n const outputs = job.outputs ?? []\n const [unit] = aiSitePendingUnits(units, outputs)\n if (unit) {\n const runner = runnerFor(unit.jobKind)\n if (!runner) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_CREATION_UNAVAILABLE_COPY }\n const pass = await aiRunJobUnit(stepContext, {\n unit,\n units,\n runner,\n emptyCopy: AI_JOB_PAGE_CREATION_EMPTY_COPY,\n })\n // A built creation is followed by the next, or by the page's first section.\n return pass.built ? { ...pass.outcome, continue: true } : pass.outcome\n }\n // The page's plan with every creation resolved to the record it built, as\n // a scaffold's page is planned against what the scaffold built.\n const plan: AiJobPlan = units.length\n ? (aiSiteUnitJob(\n job,\n {\n kind: 'page',\n jobKind: 'page',\n resource: 'screen',\n slot: 'page',\n screen: confirmed.screens[0],\n label: confirmed.screens[0].title,\n },\n aiSiteBuiltRefs(units, outputs),\n ).plan as AiJobPlan)\n : confirmed\n const screen = plan.screens[0] as AiBuildPlanScreen\n const name = screen.title || AI_JOB_PAGE_DEFAULT_NAME\n const slug = aiPageDraftSlug(screen)\n const sectionIds = screen.sections.map((_, index) => aiPageSectionNodeId(job.$id, index))\n // The draft screen, by the id the job recorded for it: every pass writes and finds the same one.\n const draftId = aiJobDraftId(job, 'screen')\n\n const output = (\n draft: Pick<AiDraftRecord, 'id' | 'versionId' | 'name' | 'hostSubdomain'>,\n load?: AiLoadEstimate | null,\n note?: string | null,\n ): AiJobOutput => ({\n resource: 'screen',\n id: draft.id,\n versionId: draft.versionId,\n hostId,\n hostSubdomain: draft.hostSubdomain,\n label: draft.name,\n ...(load ? { load } : {}),\n ...(note ? { note } : {}),\n // Rule 10: a navigation entry travels with the page as a proposal, for\n // the member to add once the page is live; no menu is written.\n ...(screen.nav ? { proposal: { navigation: { label: name, slug } } } : {}),\n })\n\n const [inventory, orgSnapshot, written] = await Promise.all([\n readInventory(job.orgId, hostId, { firestore }),\n firestore.collection('orgs').doc(job.orgId).get(),\n readAiDraft(firestore, { kind: 'screen', hostId, id: draftId }),\n ])\n const org = (orgSnapshot.data() ?? null) as Partial<AglynOrgBilling> | null\n if (written?.deleted) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_DELETED_COPY }\n // A record template's copy binds its dataset's fields, and its draft says\n // where the member saves the binding (AGL-3475).\n const record = aiPageRecordTemplate(screen.record, inventory, plan)\n const recordNote = record ? aiPageRecordNote(record) : null\n\n // The plan starts from a copy of a screen the site has (rule 15): the copy\n // is the draft — unrouted, as every copy is — and nothing is generated.\n // Only a page is a page's start: a collection entry template's copy stays\n // a template, so a plan naming one builds the page instead.\n // A copy shows what its SOURCE shows, and this branch generates nothing.\n // So if the source cannot already show what the plan promised, copying it\n // can only produce a page that breaks the promise — the refusal below\n // would be certain before the copy was ever minted. Read the source and\n // build instead, rather than leave the member a page they must throw away\n // and a Try again that can only copy the same thing (AGL-3024).\n // Only a source we could READ and found short diverts: one we cannot read\n // is one we cannot judge, and the review after the copy still answers for\n // that. A short source carries findings; an unreadable one carries none.\n // A copy of the starter home page is never a page the job built\n // (AGL-3596): the site's own placeholder is replaced by a page generated\n // from the plan, so a plan that names it as a start builds instead.\n const copiesStarter = Boolean(\n screen.duplicateOf &&\n inventory.screens.some((row) => row.id === screen.duplicateOf && row.replaceable),\n )\n const sourceReview =\n !written && screen.duplicateOf && !copiesStarter\n ? await aiCopiedPageReview(firestore, { hostId, id: screen.duplicateOf, name, screen })\n : null\n const sourceShort = Boolean(sourceReview?.findings.length)\n if (\n !written &&\n !sourceShort &&\n !copiesStarter &&\n screen.duplicateOf &&\n inventory.screens.some((row) => row.id === screen.duplicateOf && !row.template)\n ) {\n const copy = await duplicate('screen', {\n orgId: job.orgId,\n hostId,\n sourceId: screen.duplicateOf,\n name,\n uid: job.createdBy,\n org: org as Record<string, unknown> | null,\n // A copy core mints is written under core's id, never `draftId`, so\n // `written` above cannot find it and this branch would fire on every\n // pass — and the pass after a refused copy is the common one, since\n // the review below offers to try again. The job's recorded id is the\n // claim, so a second pass replays the one copy instead of leaving\n // another orphan screen behind it (AGL-3024).\n attemptKey: draftId,\n })\n if (copy.ok) {\n // A copy is not a build (AGL-3024). Nothing is generated here, so\n // whatever the plan promised BEYOND the source is in the draft only\n // if the source already had it, and no model is left to answer for\n // the difference — the copy itself has to.\n const review = await aiCopiedPageReview(firestore, { hostId, id: copy.id, name, screen })\n if (review) return aiUnspentOutcome(model, { review })\n const hostSubdomain = await aiSiteSubdomain(firestore, hostId)\n return aiUnspentOutcome(model, {\n outputs: [output({ id: copy.id, versionId: copy.versionId, name: copy.name, hostSubdomain }, null, recordNote)],\n })\n }\n if (copy.ok === false && copy.status === 403) {\n return aiUnspentOutcome(model, { review: aiLimitReview(copy.error) })\n }\n }\n\n const stored = written ? await readAiDraftNodes(firestore, { kind: 'screen', hostId, id: draftId }) : null\n if (written && !stored) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_DELETED_COPY }\n const page = stored?.nodes ?? aiEmptyPage()\n const index = sectionIds.findIndex((id) => !(id in page))\n // A workspace whose component allowance is finite — Free's one — draws\n // its repeats inline, and every pass is held to the rules that way\n // (AGL-3030, AGL-3615). A form is a saved one placed by id on every plan,\n // or none (AGL-3596).\n const reusableComponents = aiBuildsWithComponents(org)\n // A link may take a visitor to a section of this page, by its name in the plan (AGL-3097).\n const sections = screen.sections.map((section) => section.name)\n // A third-party player the confirmed plan lists for this page is the only one it may embed (AGL-3433).\n const embeds = aiPlanEmbedsFor(plan, { slug: screen.slug })\n // The site's other pages a link may go to, built or minted on a guided\n // start's plan, and the one a link that names none can only mean (AGL-3596).\n const { linkablePages, linkTarget } = aiPageLinkablePages(inventory, aiLayoutSitePages(job.inputs), [screen.id, draftId])\n // A page unit of a guided start or a build is designed in the layout\n // language and compiled (AGL-3660); a record template keeps the raw tree,\n // since its copy binds fields the language does not name.\n const language = aiJobUsesLayoutLanguage(job) && !record\n const context = {\n ...aiPageCheckContext(inventory, {\n reusableComponents,\n sections,\n embeds,\n recordTokens: aiPageRecordTokens(record),\n linkablePages,\n }),\n ...(language ? { codeBuilt: true } : {}),\n }\n\n // ── The last pass: the whole page, its listing, and the draft reported ──\n if (index === -1 && written) {\n // A link the page carries to one of its sections goes there, now every section is built.\n const report = validateAiDoctrineTree({ rootId: CANVAS_ROOT_ELEMENT_ID, nodes: page }, 'page', {\n ...context,\n scrollTargetIds: sectionIds,\n })\n // The facts the brief did not give, on the page or in the defaults its components show (AGL-3056).\n const note =\n [\n aiBracketedFactsNote({\n tree: { rootId: CANVAS_ROOT_ELEMENT_ID, nodes: page as unknown as AiDoctrineTree['nodes'] },\n inventory,\n }),\n recordNote,\n ]\n .filter(Boolean)\n .join(' ') || null\n // The page's own draft, reported once however many times the last pass\n // runs (AGL-3143): a refused pass reports it and a later one that\n // passes does not report it twice.\n const reported = outputs.some((entry) => entry.resource === 'screen' && entry.id === draftId)\n const reports = reported ? [] : [output(written, report.load, note)]\n if (report.violations.length) {\n // The check mints its own ids; the review names the nodes by the ids the draft stores them under.\n const storedIds = report.tree?.sourceIds ?? {}\n const violations = report.violations.map((violation) =>\n violation.nodeIds ? { ...violation, nodeIds: aiModelNodeIds(violation.nodeIds, storedIds) } : violation,\n )\n // THE DRAFT TRAVELS WITH THE REFUSAL (AGL-3143). Every other review\n // parks on a model's answer, so trying again asks the model again and\n // can come back different. This one reads the STORED page and spends\n // nothing, so trying again refuses the same page for the same reasons\n // forever — and a job parked here having reported no output names no\n // draft at all, which leaves the member nowhere to mend the nodes the\n // findings name and no way to finish the job from the console. The\n // draft is reported now instead, and the job still parks: what the\n // review names is mended in the draft, and the next pass then passes.\n return aiUnspentOutcome(model, {\n outputs: reports,\n review: aiDoctrineReview(\n {\n message: aiDoctrineNeedsInputMessage(violations),\n violations,\n answer: { tree: { rootId: CANVAS_ROOT_ELEMENT_ID, nodes: page } },\n },\n { page: true, then: AI_JOB_PAGE_REFUSED_DRAFT_COPY },\n ),\n })\n }\n let spent: AiJobStepOutcome = aiUnspentOutcome(model)\n if (!written.seo?.title?.trim() || !written.seo?.description?.trim()) {\n const seoModel = modelFor?.('job.seo') ?? aiModelForStep('job.seo')\n // The plan's own listing, held to the editor's guidance, when the\n // listing cannot be written inside this pass's time on the model the\n // job runs.\n let values = {\n title: clip(screen.seoTitle, SCREEN_SEO_TEXT_GUIDANCE.title),\n description: clip(screen.seoDescription, SCREEN_SEO_TEXT_GUIDANCE.description),\n }\n if (\n // A listing is written from the page's own text, with no inventory\n // and so no lookup round.\n aiGenerationWorstCaseMs({ maxTokens: AI_SEO_FIELDS_MAX_TOKENS, model: seoModel, lookups: 0 }) <=\n AI_JOB_PAGE_STEP_MINIMUM_MS\n ) {\n const host = await firestore.collection('hosts').doc(hostId).get()\n const result = await seoFields({\n subject: { kind: 'screen', name, path: screenRoutePathToUrl(slug || SCREEN_ROOT_PATH) },\n brand: siteNameOf(host),\n // What the site is and who it is for (AGL-2918). The page's own\n // Markdown says what the page is about and cannot say who it was\n // written for, so the listing borrows the two answers the job was\n // started from — the scaffold's derived page job carries them,\n // and a page job started from the Screens page carries neither.\n site: aiSiteWords(job.inputs),\n text: buildPageMarkdown({ nodes: page as never, rootId: CANVAS_ROOT_ELEMENT_ID }),\n fields: ['title', 'description'],\n // Other screens by name only: their own listings are their content.\n otherTitles: inventory.screens.map((row) => row.name),\n model: seoModel,\n ...(signal ? { signal } : {}),\n })\n spent = { ...spent, ...aiGenerationSpent(result) }\n if (result.status === 'ok') {\n values = {\n title: result.value.title?.trim() || values.title,\n description: result.value.description?.trim() || values.description,\n }\n }\n }\n await writeAiDraftScreenSeo(firestore, { hostId, id: draftId, seo: values, now })\n }\n return { ...spent, outputs: reports }\n }\n\n // ── A section pass ──\n if (!written) {\n const allowance = await aiDraftAllowanceRefusal(firestore, { kind: 'screen', hostId, org })\n if (allowance) return aiUnspentOutcome(model, { review: aiLimitReview(allowance) })\n }\n if (language) {\n // ── The whole page in one answer, in the layout language ──\n const targets = aiLayoutPageTargets({ job, inventory, own: [screen.id, draftId] })\n const result = await aiRunLayoutPage({\n job,\n plan,\n screen,\n sectionIds,\n targets,\n context,\n reusableComponents,\n inventory,\n model,\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result) }\n // The compiler leaves each picture slot empty; a photo the site serves\n // itself fills it here, after the page is checked (AGL-3660).\n const pictured = await aiResolveLayoutPictures(result.value.nodes, {\n rootId: CANVAS_ROOT_ELEMENT_ID,\n sectionIds,\n sectionNames: screen.sections.map((section) => section.name),\n seed: `${aiOriginJobId(job)}:${screen.id ?? screen.slug}`,\n })\n if (!written) {\n const draft = await writeAiDraft(firestore, {\n kind: 'screen',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: pictured,\n slug,\n layoutId: aiPageDraftLayoutId(screen, inventory),\n aiJobId: aiOriginJobId(job),\n now,\n })\n if (draft.ok === false) {\n if (draft.status === 404) throw new Error(`site ${hostId} vanished while its page was generated`)\n return { ...spent, review: aiLimitReview(draft.error) }\n }\n } else {\n // A draft written by an earlier, interrupted pass is replaced whole.\n const update = await updateAiDraftNodes(firestore, {\n kind: 'screen',\n hostId,\n id: draftId,\n now,\n update: () => pictured,\n })\n if (update.ok === false) return { ...spent, failure: AI_JOB_PAGE_DELETED_COPY }\n }\n return { ...spent, continue: true }\n }\n const maxTokens = aiJobPageSectionMaxTokens(model)\n const maxElements = aiJobPageSectionMaxElements(maxTokens)\n const result = await runValidatedGeneration<AiPageSection>('page-section', {\n step: 'job.page',\n model,\n instructions: AI_JOB_PAGE_INSTRUCTIONS,\n inventory,\n messages: [\n {\n role: 'user',\n content: aiPageSectionPrompt({ job, plan, screen, index, maxElements, reusableComponents, record, linkablePages }),\n },\n ],\n tool: AI_PAGE_SECTION_TOOL,\n maxTokens,\n // A section cut off at its ceiling is asked for smaller, and a member\n // whose section still does not fit reads that it was too large (AGL-3042).\n cutOff: { noun: 'section', smaller: aiPageSectionSmaller({ maxElements, reusableComponents }) },\n thinking: 'off',\n check: aiPageSectionCheck({\n page,\n sectionIds,\n index,\n context,\n section: screen.sections[index],\n inventory,\n linkTarget,\n }),\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result, { page: true }) }\n\n if (!written) {\n const draft = await writeAiDraft(firestore, {\n kind: 'screen',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: aiPageWithSection(aiEmptyPage(), result.value, sectionIds),\n slug,\n layoutId: aiPageDraftLayoutId(screen, inventory),\n aiJobId: aiOriginJobId(job),\n now,\n })\n if (draft.ok === false) {\n if (draft.status === 404) throw new Error(`site ${hostId} vanished while its page was generated`)\n return { ...spent, review: aiLimitReview(draft.error) }\n }\n } else {\n const update = await updateAiDraftNodes(firestore, {\n kind: 'screen',\n hostId,\n id: draftId,\n now,\n // A pass run again after its write finds its section and changes nothing.\n update: (nodes) =>\n result.value.rootId in nodes ? null : aiPageWithSection(nodes, result.value, sectionIds),\n })\n if (update.ok === false) return { ...spent, failure: AI_JOB_PAGE_DELETED_COPY }\n }\n return { ...spent, continue: true }\n }\n}\n\nexport const runAiJobPageStep = createAiJobPageStep()\n\n/**\n * Registers the page step with the least time a section pass needs and the\n * time its next pass needs, the passes a page with its creations may take, and\n * the check a page job passes when it is created or its plan is confirmed.\n */\nexport function registerAiPageJob(): void {\n registerAiJobStep('page', runAiJobPageStep, {\n minimumMs: AI_JOB_PAGE_STEP_MINIMUM_MS,\n minimumMsFor: aiPageJobRunMinimumMs,\n })\n registerAiJobStepPasses('page', AI_JOB_PAGE_MAX_PASSES)\n registerAiJobAdmission('page', aiPageJobAdmission)\n}\n"],"names":["buildPageMarkdown","SCREEN_SEO_TEXT_GUIDANCE","SCREEN_ROOT_PATH","screenRoutePathToUrl","CANVAS_ROOT_ELEMENT_ID","duplicateResource","AI_BUILD_PLAN_LIMITS","aiPlanEmbedsFor","AI_PAGE_CREATE_KINDS","aiPageCreationRefusal","aiPagePlanRefusal","parseAiPageJobInputs","aiPlanCapabilitiesForJob","aiPlanUncreatable","aiSiteWords","aiModelForStep","aiDoctrineNeedsInputMessage","runValidatedGeneration","validateAiDoctrineTree","AI_SEO_FIELDS_MAX_TOKENS","generateSeoFields","readSiteInventory","registerAiJobAdmission","aiGenerationWorstCaseMs","aiJobDraftId","aiOriginJobId","aiBuildsWithComponents","aiDraftAdmissionRefusal","aiDraftAllowanceRefusal","aiSiteSubdomain","readAiDraft","readAiDraftNodes","readAiPlanCapabilities","updateAiDraftNodes","writeAiDraft","writeAiDraftScreenSeo","aiBracketedFactsNote","aiConfirmedPlan","aiDoctrineReview","aiGenerationSpent","aiLimitReview","aiModelNodeIds","aiUnspentOutcome","AI_JOB_PAGE_SECTION_MAX_TOKENS","AI_JOB_PAGE_SECTION_TOKENS","AI_JOB_PAGE_STEP_BUDGET","AI_JOB_PAGE_STEP_MINIMUM_MS","aiJobPageSectionMaxTokens","AI_JOB_PAGE_INSTRUCTIONS","AI_PAGE_SECTION_TOOL","aiEmptyPage","aiPageCheckContext","aiPageLinkablePages","aiPageRecordNote","aiPageRecordTemplate","aiPageRecordTokens","aiPageSectionCheck","aiPageSectionNodeId","aiPageSectionPrompt","aiPageSectionSmaller","aiPageWithSection","aiLayoutSitePages","aiResolveLayoutPictures","AI_PLAN_ITEMS_MIN","aiPlanCopiedPageViolations","AI_JOB_PAGE_LANGUAGE_BUDGET","aiJobUsesLayoutLanguage","aiLayoutPageTargets","aiRunLayoutPage","aiCreationUnit","aiRunJobUnit","aiSiteBuiltRefs","aiSitePendingUnits","aiSiteUnitJob","aiJobStepRunMinimumMs","aiJobStepRunnerFor","registerAiJobStep","registerAiJobStepPasses","AI_JOB_PAGE_TOKENS_PER_ELEMENT","AI_JOB_PAGE_REAL_TOKENS_PER_ESTIMATED","Math","max","AI_JOB_PAGE_REAL_TOKENS_PER_ELEMENT","ceil","aiJobPageSectionMaxElements","maxTokens","floor","AI_JOB_PAGE_DEFAULT_NAME","AI_JOB_PAGE_NO_PLAN_COPY","AI_JOB_PAGE_DELETED_COPY","AI_JOB_PAGE_CREATION_UNAVAILABLE_COPY","AI_JOB_PAGE_CREATION_EMPTY_COPY","AI_JOB_PAGE_REFUSED_DRAFT_COPY","AI_JOB_PAGE_MAX_PASSES","create","sections","aiPageJobUnits","plan","flatMap","kind","creation","index","unit","aiPageJobRunMinimumMs","job","confirmed","pagePass","minimumMs","units","outputs","aiPageDraftSlug","screen","segments","slug","split","map","part","trim","filter","Boolean","length","aiSiteDefaultLayoutId","inventory","counts","layouts","Set","row","id","size","pages","screens","template","layoutId","has","home","find","Map","set","get","mostUsed","entries","sort","a","b","aiPageDraftLayoutId","layout","some","clip","value","text","cut","slice","space","lastIndexOf","siteNameOf","host","aiCopiedPageReview","firestore","input","section","items","stored","hostId","nodes","reason","message","name","findings","violations","rootId","aiPageJobAdmission","context","inputs","status","error","refusal","jobKind","org","orgId","noun","ownCheck","capabilities","creates","createAiJobPageStep","deps","readInventory","duplicate","seoFields","runnerFor","stepContext","orgSnapshot","now","signal","modelFor","Error","model","failure","runner","pass","emptyCopy","built","outcome","continue","resource","slot","label","title","sectionIds","_","$id","draftId","output","draft","load","note","versionId","hostSubdomain","nav","proposal","navigation","written","Promise","all","collection","doc","data","deleted","record","recordNote","copiesStarter","duplicateOf","replaceable","sourceReview","sourceShort","copy","sourceId","uid","createdBy","attemptKey","ok","review","page","findIndex","reusableComponents","embeds","linkablePages","linkTarget","language","recordTokens","codeBuilt","report","scrollTargetIds","tree","join","reported","entry","reports","storedIds","sourceIds","violation","nodeIds","answer","then","spent","seo","description","seoModel","values","seoTitle","seoDescription","lookups","result","subject","path","brand","site","fields","otherTitles","allowance","targets","own","refused","pictured","sectionNames","seed","aiJobId","update","maxElements","step","instructions","messages","role","content","tool","cutOff","smaller","thinking","check","runAiJobPageStep","registerAiPageJob","minimumMsFor"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-page-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { buildPageMarkdown } from '@aglyn/aglyn/app-utils/page-markdown'\nimport { SCREEN_SEO_TEXT_GUIDANCE } from '@aglyn/aglyn/app-utils/screen-seo-fields'\nimport { SCREEN_ROOT_PATH, screenRoutePathToUrl } from '@aglyn/aglyn/app-utils/screen-route'\nimport { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { duplicateResource } from '@aglyn/tenant-data-admin/server/duplicate-resource'\nimport { AI_BUILD_PLAN_LIMITS, aiPlanEmbedsFor, type AiBuildPlanScreen } from '../model/ai-build-plan'\nimport {\n AI_PAGE_CREATE_KINDS,\n aiPageCreationRefusal,\n aiPagePlanRefusal,\n parseAiPageJobInputs,\n} from '../model/ai-page-job'\nimport { aiPlanCapabilitiesForJob, aiPlanUncreatable } from '../model/ai-plan-capabilities'\nimport { aiSiteWords } from '../model/ai-site-job'\nimport type { AiJob, AiJobOutput, AiJobPlan, AiJobReview } from '../model/ai-jobs.types'\nimport { aiModelForStep } from '../providers/routing'\nimport { aiDoctrineNeedsInputMessage, runValidatedGeneration } from '../runtime/ai-doctrine'\nimport { validateAiDoctrineTree, type AiDoctrineTree } from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport { AI_SEO_FIELDS_MAX_TOKENS, generateSeoFields } from '../runtime/seo-fields'\nimport { readSiteInventory } from '../runtime/site-inventory'\nimport type { AiSiteInventory } from '../model/ai-site-inventory'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport { aiGenerationWorstCaseMs } from './ai-job-budget'\nimport { aiJobDraftId, aiOriginJobId } from './ai-job-draft-ids'\nimport {\n aiBuildsWithComponents,\n aiDraftAdmissionRefusal,\n aiDraftAllowanceRefusal,\n aiSiteSubdomain,\n readAiDraft,\n readAiDraftNodes,\n readAiPlanCapabilities,\n updateAiDraftNodes,\n writeAiDraft,\n writeAiDraftScreenSeo,\n type AiDraftRecord,\n} from './ai-job-drafts'\nimport {\n aiBracketedFactsNote,\n aiConfirmedPlan,\n aiDoctrineReview,\n aiGenerationSpent,\n aiLimitReview,\n aiModelNodeIds,\n aiUnspentOutcome,\n} from './ai-job-generation'\nimport {\n AI_JOB_PAGE_SECTION_MAX_TOKENS,\n AI_JOB_PAGE_SECTION_TOKENS,\n AI_JOB_PAGE_STEP_BUDGET,\n AI_JOB_PAGE_STEP_MINIMUM_MS,\n aiJobPageSectionMaxTokens,\n} from './ai-job-page-budget'\nimport {\n AI_JOB_PAGE_INSTRUCTIONS,\n AI_PAGE_SECTION_TOOL,\n aiEmptyPage,\n aiPageCheckContext,\n aiPageLinkablePages,\n aiPageRecordNote,\n aiPageRecordTemplate,\n aiPageRecordTokens,\n aiPageSectionCheck,\n aiPageSectionNodeId,\n aiPageSectionPrompt,\n aiPageSectionSmaller,\n aiPageWithSection,\n type AiPageSection,\n} from './ai-job-page-sections'\nimport { aiLayoutSitePages } from './ai-job-layout-site-pages'\nimport { aiResolveLayoutPictures } from '../layout-language/ai-layout-pictures'\nimport { aiLayoutStockPhotoSource } from './ai-layout-stock-photos'\nimport { AI_PLAN_ITEMS_MIN, aiPlanCopiedPageViolations } from './ai-job-plan-conformance'\nimport {\n AI_JOB_PAGE_LANGUAGE_BUDGET,\n aiJobUsesLayoutLanguage,\n aiLayoutPageTargets,\n aiRunLayoutPage,\n} from './ai-job-page-language'\nimport {\n aiCreationUnit,\n aiRunJobUnit,\n aiSiteBuiltRefs,\n aiSitePendingUnits,\n aiSiteUnitJob,\n type AiSiteUnit,\n} from './ai-job-site-step'\nimport type { AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport {\n aiJobStepRunMinimumMs,\n aiJobStepRunnerFor,\n registerAiJobStep,\n registerAiJobStepPasses,\n} from './ai-jobs'\n\n/**\n * The page step (AGL-2907): the generation step of a `page` job, run once a\n * member confirmed the job's plan. It builds ONE screen, section by section,\n * from what the site already has, and leaves it an unpublished draft.\n *\n * ── Why a page is built in passes ────────────────────────────────────────\n *\n * A page is far larger than one answer the job beat can wait for: the beat\n * gives a step what is left of its budget, and a whole page, answered and re-asked,\n * takes several times that. The confirmed plan already names the page's\n * sections, so the step builds one section per pass and asks the machine to\n * continue. Every pass is one reservation and one generation — the section's\n * lookup rounds, its answer and, when it breaks a rule, its one re-ask — sized\n * so that worst case fits `AI_JOB_PAGE_STEP_MINIMUM_MS`, which the step\n * registers as the least time a section pass needs before it starts.\n *\n * - The first pass builds the top section, which holds the page's h1, and\n * writes the draft screen and its first version.\n * - Each later pass builds the next section and adds it to that version.\n * - The last pass generates no tree: it holds the whole page to the\n * doctrine, writes the search title and description, and reports the\n * draft with its measured weight and a navigation proposal.\n *\n * How a section is asked for and held to the rules as part of the page is\n * `ai-job-page-sections.ts`.\n *\n * ── What the plan creates comes first (AGL-3031) ─────────────────────────\n *\n * A plan that creates a layout, forms or components is built in the same job,\n * before its page, one creation a pass, through the site scaffold's unit\n * machinery: each creation is handed to the step that builds that kind, under\n * a job derived from this one, and lands as the draft that step writes. Where\n * the job stands is read from its outputs, so nothing is kept anywhere else.\n * Once every creation is built, the page's passes run on the plan with each\n * `new:<name>` resolved to the record that was built, which the site's\n * inventory now lists — so the page places the new component and binds the new\n * form by id, and renders inside the new layout, exactly as it would one the\n * site always had. A pass that builds a creation needs the time the step that\n * builds it registers, not a section's (`aiPageJobRunMinimumMs`, AGL-3035), so\n * neither the beat nor a door starts it with only a section's time left.\n *\n * ── Never published ──────────────────────────────────────────────────────\n *\n * The draft is written the way `/api/hosts/resources` and\n * `/api/hosts/versions` write a screen and its first version, and nothing\n * else is touched: not the host document, whose routing map is what the live\n * site serves, and no collection, store, layout or navigation pointer\n * (`ai-job-drafts.ts`). A screen the routing map does not name serves nothing\n * until a member publishes it.\n */\n\n/**\n * A section pass's time, and the ceiling each pass asks on each model, are\n * declared apart from the step (`ai-job-page-budget.ts`): the site scaffold\n * builds its pages through this step and reads them without loading it.\n */\nexport {\n AI_JOB_PAGE_SECTION_MAX_TOKENS,\n AI_JOB_PAGE_SECTION_TOKENS,\n AI_JOB_PAGE_STEP_BUDGET,\n AI_JOB_PAGE_STEP_MINIMUM_MS,\n aiJobPageSectionMaxTokens,\n}\n\n/**\n * Tokens one element takes in a section's answer, ESTIMATED: the tree as JSON\n * text inside the tool call, at four characters a token. Measured on the ten\n * golden pages at a median of 38 and a most of 43 over their 39 sections, and\n * `ai-job-page-evals.spec.ts` holds every golden section under it.\n */\nexport const AI_JOB_PAGE_TOKENS_PER_ELEMENT = 45\n\n/**\n * Real tokens to every token estimated at four characters (AGL-3042). A\n * section's ceiling is counted by the provider in its own tokens, which run\n * above that estimate. MEASURED on the first live document run on a Free\n * workspace (AGL-3024, test-org, 2026-09-16), as the cache read of a request\n * over the estimate of the same cached prefix: 4,059 over 2,825 for the page\n * plan and 6,649 over 4,310 for the layout, the higher of the two, 1.5427 —\n * the ratio `ai-job-free-page.spec.ts` prices a Free page at. It is measured on\n * prompt text; an answer's own is read off a recorded pass, whose output\n * tokens the step's record keeps (`lastRuns`), beside the section it stored.\n */\nexport const AI_JOB_PAGE_REAL_TOKENS_PER_ESTIMATED = Math.max(4_059 / 2_825, 6_649 / 4_310)\n\n/**\n * Real tokens one element takes in a section's answer: the estimate at its\n * golden most, in real tokens, rounded up — 45 × 1.5427 → 70 (AGL-3042).\n */\nexport const AI_JOB_PAGE_REAL_TOKENS_PER_ELEMENT = Math.ceil(\n AI_JOB_PAGE_TOKENS_PER_ELEMENT * AI_JOB_PAGE_REAL_TOKENS_PER_ESTIMATED,\n)\n\n/**\n * The most elements a section's request asks it to keep under at an answer\n * ceiling: 15 at the balanced tier's 1,050 (AGL-3042). Counted in real\n * tokens, because the ceiling is: counted in the estimate, the budget is 23\n * elements, which a section of the goldens' largest elements fills at 1,597\n * real tokens — half as much again as the ceiling that cuts it off.\n */\nexport function aiJobPageSectionMaxElements(maxTokens: number): number {\n return Math.max(1, Math.floor(maxTokens / AI_JOB_PAGE_REAL_TOKENS_PER_ELEMENT))\n}\n\n/** A page's name when the plan names none. */\nexport const AI_JOB_PAGE_DEFAULT_NAME = 'New page'\n\nexport const AI_JOB_PAGE_NO_PLAN_COPY = 'This page job has no confirmed plan to build.'\nexport const AI_JOB_PAGE_DELETED_COPY = 'The draft page was deleted while it was being built.'\n\n/** A creation the plan names that this deployment has no step to build. */\nexport const AI_JOB_PAGE_CREATION_UNAVAILABLE_COPY =\n 'This page needs something that cannot be built here yet. Describe the page again.'\n\n/** A creation that finished without reporting what it built; the page cannot place it. */\nexport const AI_JOB_PAGE_CREATION_EMPTY_COPY =\n 'Part of this page could not be built. Describe the page again.'\n\n/**\n * What a page refused on its last pass tells the member after the plain\n * refusal (AGL-3143, AGL-3596). The draft is reported with the review, and\n * mending its layout in the editor is what lets the next pass finish the job.\n */\nexport const AI_JOB_PAGE_REFUSED_DRAFT_COPY = 'The draft is yours to open: fix its layout in the editor, then try again.'\n\n/**\n * The most passes a page job may take (AGL-3031): every creation the plan\n * limits admit, every section they admit, and the last pass that reports the\n * draft. A creation's step builds it in one pass, and a page one section a\n * pass, so a runner that never finishes is still bounded while a real page is\n * not.\n */\nexport const AI_JOB_PAGE_MAX_PASSES = AI_BUILD_PLAN_LIMITS.create + AI_BUILD_PLAN_LIMITS.sections + 1\n\n/**\n * The units a plan's creations are built as, in the order a scaffold builds\n * them: the layout, then the forms, then the components. Each is addressed by\n * its place in the plan, so a unit run again finds its own draft.\n */\nexport function aiPageJobUnits(plan: Pick<AiJobPlan, 'create'>): AiSiteUnit[] {\n return AI_PAGE_CREATE_KINDS.flatMap((kind) =>\n plan.create.flatMap((creation, index) => {\n if (creation.kind !== kind) return []\n const unit = aiCreationUnit(creation, `c${index}`)\n return unit ? [unit] : []\n }),\n )\n}\n\n/**\n * The least time this page job's next pass needs (AGL-3035). A pass that\n * builds a creation hands it to the step registered for that kind, under the\n * job derived for it, so it needs what that step registers for that job — a\n * layout's time, not a section's. Every other pass — a section, the last one —\n * needs a section pass's.\n */\nexport function aiPageJobRunMinimumMs(job: AiJob): number {\n const confirmed = aiConfirmedPlan(job)\n // A page built in the layout language is one answer for the whole page (AGL-3660).\n const pagePass = aiJobUsesLayoutLanguage(job) ? AI_JOB_PAGE_LANGUAGE_BUDGET.minimumMs : AI_JOB_PAGE_STEP_MINIMUM_MS\n if (!confirmed) return pagePass\n const units = aiPageJobUnits(confirmed)\n const outputs = job.outputs ?? []\n const [unit] = aiSitePendingUnits(units, outputs)\n if (!unit) return pagePass\n return aiJobStepRunMinimumMs(aiSiteUnitJob(job, unit, aiSiteBuiltRefs(units, outputs)))\n}\n\n/**\n * The one-segment address a draft asks for, from the plan's slug; `/` for a\n * planned home page, which asks for the root rather than for an address made\n * from its name (AGL-3596).\n */\nexport function aiPageDraftSlug(screen: Pick<AiBuildPlanScreen, 'slug'>): string {\n const segments = screen.slug.split('/').map((part) => part.trim()).filter(Boolean)\n if (!segments.length && screen.slug.trim() === SCREEN_ROOT_PATH) return SCREEN_ROOT_PATH\n return segments[segments.length - 1] ?? ''\n}\n\n/**\n * The layout a site's pages render inside (AGL-3596): the one its home page\n * uses, else the one the most pages use, else the site's only layout. `null`\n * for a site with no layout, or several and no page to choose between them.\n */\nexport function aiSiteDefaultLayoutId(\n inventory: Pick<AiSiteInventory, 'layouts' | 'screens'>,\n): string | null {\n const layouts = new Set(inventory.layouts.map((row) => row.id))\n if (!layouts.size) return null\n const pages = inventory.screens.filter(\n (row) => !row.template && row.layoutId && layouts.has(row.layoutId),\n )\n const home = pages.find((row) => row.slug.trim() === SCREEN_ROOT_PATH)\n if (home?.layoutId) return home.layoutId\n const counts = new Map<string, number>()\n for (const row of pages) counts.set(row.layoutId as string, (counts.get(row.layoutId as string) ?? 0) + 1)\n const [mostUsed] = [...counts.entries()].sort((a, b) => b[1] - a[1])\n if (mostUsed) return mostUsed[0]\n return layouts.size === 1 ? [...layouts][0] : null\n}\n\n/**\n * The layout a page draft renders inside: the plan's, where the site has it,\n * else the site's own (AGL-3596). A page the plan named no layout for used to\n * be written with none, and so with no header and no footer, beside pages\n * that had them.\n */\nexport function aiPageDraftLayoutId(\n screen: Pick<AiBuildPlanScreen, 'layout'>,\n inventory: Pick<AiSiteInventory, 'layouts' | 'screens'>,\n): string | null {\n if (screen.layout && inventory.layouts.some((row) => row.id === screen.layout)) return screen.layout\n return aiSiteDefaultLayoutId(inventory)\n}\n\n/** A plan's listing value held to the length the SEO editor guides it to, cut at a word. */\nfunction clip(value: string, max: number): string {\n const text = value.trim()\n if (text.length <= max) return text\n const cut = text.slice(0, max + 1)\n const space = cut.lastIndexOf(' ')\n return (space > max / 2 ? cut.slice(0, space) : text.slice(0, max)).trim()\n}\n\n/** The site's name as its visitors see it, read the way the SEO step reads it. */\nfunction siteNameOf(host: FirebaseFirestore.DocumentSnapshot): string {\n const text = (value: unknown) => (typeof value === 'string' ? value.trim() : '')\n return text(host.get('seo.title')) || text(host.get('displayName')) || text(host.get('subdomain'))\n}\n\n/**\n * The counts the confirmed plan's sections promised, against the page a COPY\n * actually produced (AGL-3024); `null` when the copy keeps the plan, or when\n * the plan promised no count to keep.\n *\n * The same hole the layout copy had (`aiCopiedLayoutReview`), on the kind the\n * defect was measured on. This branch generates nothing, so no model ever\n * answers for the plan: a plan reading \"start from the Services page, one\n * card per practice area\" gets the Services page and whatever IT had, and\n * until now reported `Done` — the one report that tells a customer there is\n * no reason to re-read the page.\n *\n * A copy that cannot be read back settles nothing, and a promise this step\n * cannot settle is reviewed by a person rather than reported as kept.\n */\nexport async function aiCopiedPageReview(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; id: string; name: string; screen: Pick<AiBuildPlanScreen, 'sections'> },\n): Promise<AiJobReview | null> {\n if (!input.screen.sections.some((section) => section.items >= AI_PLAN_ITEMS_MIN)) return null\n const stored = await readAiDraftNodes(firestore, { kind: 'screen', hostId: input.hostId, id: input.id })\n const nodes = (stored?.nodes ?? {}) as unknown as AiDoctrineTree['nodes']\n if (!nodes[CANVAS_ROOT_ELEMENT_ID]) {\n return {\n reason: 'doctrine',\n message: `\"${input.name}\" was copied from the page the plan names, and this job could not read the copy back to check it shows what the plan says it shows. Open the page and check it before you put it in front of anyone.`,\n findings: [],\n }\n }\n const violations = aiPlanCopiedPageViolations(input.screen, { rootId: CANVAS_ROOT_ELEMENT_ID, nodes })\n if (!violations.length) return null\n return aiDoctrineReview({\n message: `\"${input.name}\" is a copy of the page the plan names, and a copy is all it is: ${violations[0].message}`,\n violations,\n })\n}\n\n/**\n * A page job is admitted with inputs that read, for a site of the job's own\n * org with a screen to spare; and confirmed only for a plan it can build —\n * one screen, whose creations are ones a page job builds, this deployment has\n * a step for, and this workspace may still make on this site (AGL-3031).\n */\nexport const aiPageJobAdmission: AiJobAdmission = async (context) => {\n const inputs = parseAiPageJobInputs(context.inputs)\n if (typeof inputs === 'string') return { status: 400, error: inputs }\n const plan = context.plan\n if (plan) {\n const refusal = aiPagePlanRefusal(plan)\n if (refusal) return { status: 400, error: refusal }\n if (aiPageJobUnits(plan).some((unit) => !aiJobStepRunnerFor(unit.jobKind))) {\n return { status: 400, error: AI_JOB_PAGE_CREATION_UNAVAILABLE_COPY }\n }\n }\n const org = context.org as Partial<AglynOrgBilling> | null\n return aiDraftAdmissionRefusal(context.firestore, {\n orgId: context.orgId,\n hostId: context.hostId,\n kind: 'screen',\n noun: 'page',\n org,\n // What the workspace may still make here, read once the site is known to\n // be the org's: the site may have changed since the plan was told.\n ...(plan?.create.length\n ? {\n ownCheck: async (hostId: string) => {\n const capabilities = aiPlanCapabilitiesForJob(\n await readAiPlanCapabilities(context.firestore, { hostId, org }),\n { noun: 'a page job', creates: AI_PAGE_CREATE_KINDS },\n )\n const refusal = aiPageCreationRefusal(aiPlanUncreatable(plan, capabilities))\n return refusal ? { status: 403 as const, error: refusal } : null\n },\n }\n : {}),\n })\n}\n\nexport interface AiJobPageStepDeps {\n /** The inventory reader; specs hand in a fake. */\n readInventory?: typeof readSiteInventory\n /** The platform's duplicate module, for a plan that starts from a copy. */\n duplicate?: typeof duplicateResource\n /** The listing generator (AGL-2910). */\n seoFields?: typeof generateSeoFields\n /** The runners a plan's creations are built by (AGL-3031); the registry's otherwise. */\n runnerFor?: typeof aiJobStepRunnerFor\n /** The stock photo source of a language page's pictures (AGL-3660); core's provider otherwise. */\n stockPhotos?: typeof aiLayoutStockPhotoSource\n}\n\nexport function createAiJobPageStep(deps: AiJobPageStepDeps = {}): AiJobStepRunner {\n const readInventory = deps.readInventory ?? readSiteInventory\n const duplicate = deps.duplicate ?? duplicateResource\n const seoFields = deps.seoFields ?? generateSeoFields\n const runnerFor = deps.runnerFor ?? aiJobStepRunnerFor\n return async (stepContext): Promise<AiJobStepOutcome> => {\n const { job, now, signal, firestore, modelFor } = stepContext\n const hostId = job.hostId\n if (!hostId) throw new Error('a page job names no site, and its admission refuses one')\n // The switch's answer for this job, else the routing table's (AGL-2942).\n const model = modelFor?.('job.page') ?? aiModelForStep('job.page')\n const confirmed = aiConfirmedPlan(job)\n if (!confirmed) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_NO_PLAN_COPY }\n // The resume door refuses such a plan at confirmation; one confirmed\n // before a site changed still stops here, before any spend.\n const refusal = aiPagePlanRefusal(confirmed)\n if (refusal) return { ...aiUnspentOutcome(model), failure: refusal }\n\n // ── The creations first (AGL-3031): one a pass, by the steps that own them ──\n const units = aiPageJobUnits(confirmed)\n const outputs = job.outputs ?? []\n const [unit] = aiSitePendingUnits(units, outputs)\n if (unit) {\n const runner = runnerFor(unit.jobKind)\n if (!runner) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_CREATION_UNAVAILABLE_COPY }\n const pass = await aiRunJobUnit(stepContext, {\n unit,\n units,\n runner,\n emptyCopy: AI_JOB_PAGE_CREATION_EMPTY_COPY,\n })\n // A built creation is followed by the next, or by the page's first section.\n return pass.built ? { ...pass.outcome, continue: true } : pass.outcome\n }\n // The page's plan with every creation resolved to the record it built, as\n // a scaffold's page is planned against what the scaffold built.\n const plan: AiJobPlan = units.length\n ? (aiSiteUnitJob(\n job,\n {\n kind: 'page',\n jobKind: 'page',\n resource: 'screen',\n slot: 'page',\n screen: confirmed.screens[0],\n label: confirmed.screens[0].title,\n },\n aiSiteBuiltRefs(units, outputs),\n ).plan as AiJobPlan)\n : confirmed\n const screen = plan.screens[0] as AiBuildPlanScreen\n const name = screen.title || AI_JOB_PAGE_DEFAULT_NAME\n const slug = aiPageDraftSlug(screen)\n const sectionIds = screen.sections.map((_, index) => aiPageSectionNodeId(job.$id, index))\n // The draft screen, by the id the job recorded for it: every pass writes and finds the same one.\n const draftId = aiJobDraftId(job, 'screen')\n\n const output = (\n draft: Pick<AiDraftRecord, 'id' | 'versionId' | 'name' | 'hostSubdomain'>,\n load?: AiLoadEstimate | null,\n note?: string | null,\n ): AiJobOutput => ({\n resource: 'screen',\n id: draft.id,\n versionId: draft.versionId,\n hostId,\n hostSubdomain: draft.hostSubdomain,\n label: draft.name,\n ...(load ? { load } : {}),\n ...(note ? { note } : {}),\n // Rule 10: a navigation entry travels with the page as a proposal, for\n // the member to add once the page is live; no menu is written.\n ...(screen.nav ? { proposal: { navigation: { label: name, slug } } } : {}),\n })\n\n const [inventory, orgSnapshot, written] = await Promise.all([\n readInventory(job.orgId, hostId, { firestore }),\n firestore.collection('orgs').doc(job.orgId).get(),\n readAiDraft(firestore, { kind: 'screen', hostId, id: draftId }),\n ])\n const org = (orgSnapshot.data() ?? null) as Partial<AglynOrgBilling> | null\n if (written?.deleted) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_DELETED_COPY }\n // A record template's copy binds its dataset's fields, and its draft says\n // where the member saves the binding (AGL-3475).\n const record = aiPageRecordTemplate(screen.record, inventory, plan)\n const recordNote = record ? aiPageRecordNote(record) : null\n\n // The plan starts from a copy of a screen the site has (rule 15): the copy\n // is the draft — unrouted, as every copy is — and nothing is generated.\n // Only a page is a page's start: a collection entry template's copy stays\n // a template, so a plan naming one builds the page instead.\n // A copy shows what its SOURCE shows, and this branch generates nothing.\n // So if the source cannot already show what the plan promised, copying it\n // can only produce a page that breaks the promise — the refusal below\n // would be certain before the copy was ever minted. Read the source and\n // build instead, rather than leave the member a page they must throw away\n // and a Try again that can only copy the same thing (AGL-3024).\n // Only a source we could READ and found short diverts: one we cannot read\n // is one we cannot judge, and the review after the copy still answers for\n // that. A short source carries findings; an unreadable one carries none.\n // A copy of the starter home page is never a page the job built\n // (AGL-3596): the site's own placeholder is replaced by a page generated\n // from the plan, so a plan that names it as a start builds instead.\n const copiesStarter = Boolean(\n screen.duplicateOf &&\n inventory.screens.some((row) => row.id === screen.duplicateOf && row.replaceable),\n )\n const sourceReview =\n !written && screen.duplicateOf && !copiesStarter\n ? await aiCopiedPageReview(firestore, { hostId, id: screen.duplicateOf, name, screen })\n : null\n const sourceShort = Boolean(sourceReview?.findings.length)\n if (\n !written &&\n !sourceShort &&\n !copiesStarter &&\n screen.duplicateOf &&\n inventory.screens.some((row) => row.id === screen.duplicateOf && !row.template)\n ) {\n const copy = await duplicate('screen', {\n orgId: job.orgId,\n hostId,\n sourceId: screen.duplicateOf,\n name,\n uid: job.createdBy,\n org: org as Record<string, unknown> | null,\n // A copy core mints is written under core's id, never `draftId`, so\n // `written` above cannot find it and this branch would fire on every\n // pass — and the pass after a refused copy is the common one, since\n // the review below offers to try again. The job's recorded id is the\n // claim, so a second pass replays the one copy instead of leaving\n // another orphan screen behind it (AGL-3024).\n attemptKey: draftId,\n })\n if (copy.ok) {\n // A copy is not a build (AGL-3024). Nothing is generated here, so\n // whatever the plan promised BEYOND the source is in the draft only\n // if the source already had it, and no model is left to answer for\n // the difference — the copy itself has to.\n const review = await aiCopiedPageReview(firestore, { hostId, id: copy.id, name, screen })\n if (review) return aiUnspentOutcome(model, { review })\n const hostSubdomain = await aiSiteSubdomain(firestore, hostId)\n return aiUnspentOutcome(model, {\n outputs: [output({ id: copy.id, versionId: copy.versionId, name: copy.name, hostSubdomain }, null, recordNote)],\n })\n }\n if (copy.ok === false && copy.status === 403) {\n return aiUnspentOutcome(model, { review: aiLimitReview(copy.error) })\n }\n }\n\n const stored = written ? await readAiDraftNodes(firestore, { kind: 'screen', hostId, id: draftId }) : null\n if (written && !stored) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_DELETED_COPY }\n const page = stored?.nodes ?? aiEmptyPage()\n const index = sectionIds.findIndex((id) => !(id in page))\n // A workspace whose component allowance is finite — Free's one — draws\n // its repeats inline, and every pass is held to the rules that way\n // (AGL-3030, AGL-3615). A form is a saved one placed by id on every plan,\n // or none (AGL-3596).\n const reusableComponents = aiBuildsWithComponents(org)\n // A link may take a visitor to a section of this page, by its name in the plan (AGL-3097).\n const sections = screen.sections.map((section) => section.name)\n // A third-party player the confirmed plan lists for this page is the only one it may embed (AGL-3433).\n const embeds = aiPlanEmbedsFor(plan, { slug: screen.slug })\n // The site's other pages a link may go to, built or minted on a guided\n // start's plan, and the one a link that names none can only mean (AGL-3596).\n const { linkablePages, linkTarget } = aiPageLinkablePages(inventory, aiLayoutSitePages(job.inputs), [screen.id, draftId])\n // A page unit of a guided start or a build is designed in the layout\n // language and compiled (AGL-3660); a record template keeps the raw tree,\n // since its copy binds fields the language does not name.\n const language = aiJobUsesLayoutLanguage(job) && !record\n const context = {\n ...aiPageCheckContext(inventory, {\n reusableComponents,\n sections,\n embeds,\n recordTokens: aiPageRecordTokens(record),\n linkablePages,\n }),\n ...(language ? { codeBuilt: true } : {}),\n }\n\n // ── The last pass: the whole page, its listing, and the draft reported ──\n if (index === -1 && written) {\n // A link the page carries to one of its sections goes there, now every section is built.\n const report = validateAiDoctrineTree({ rootId: CANVAS_ROOT_ELEMENT_ID, nodes: page }, 'page', {\n ...context,\n scrollTargetIds: sectionIds,\n })\n // The facts the brief did not give, on the page or in the defaults its components show (AGL-3056).\n const note =\n [\n aiBracketedFactsNote({\n tree: { rootId: CANVAS_ROOT_ELEMENT_ID, nodes: page as unknown as AiDoctrineTree['nodes'] },\n inventory,\n }),\n recordNote,\n ]\n .filter(Boolean)\n .join(' ') || null\n // The page's own draft, reported once however many times the last pass\n // runs (AGL-3143): a refused pass reports it and a later one that\n // passes does not report it twice.\n const reported = outputs.some((entry) => entry.resource === 'screen' && entry.id === draftId)\n const reports = reported ? [] : [output(written, report.load, note)]\n if (report.violations.length) {\n // The check mints its own ids; the review names the nodes by the ids the draft stores them under.\n const storedIds = report.tree?.sourceIds ?? {}\n const violations = report.violations.map((violation) =>\n violation.nodeIds ? { ...violation, nodeIds: aiModelNodeIds(violation.nodeIds, storedIds) } : violation,\n )\n // THE DRAFT TRAVELS WITH THE REFUSAL (AGL-3143). Every other review\n // parks on a model's answer, so trying again asks the model again and\n // can come back different. This one reads the STORED page and spends\n // nothing, so trying again refuses the same page for the same reasons\n // forever — and a job parked here having reported no output names no\n // draft at all, which leaves the member nowhere to mend the nodes the\n // findings name and no way to finish the job from the console. The\n // draft is reported now instead, and the job still parks: what the\n // review names is mended in the draft, and the next pass then passes.\n return aiUnspentOutcome(model, {\n outputs: reports,\n review: aiDoctrineReview(\n {\n message: aiDoctrineNeedsInputMessage(violations),\n violations,\n answer: { tree: { rootId: CANVAS_ROOT_ELEMENT_ID, nodes: page } },\n },\n { page: true, then: AI_JOB_PAGE_REFUSED_DRAFT_COPY },\n ),\n })\n }\n let spent: AiJobStepOutcome = aiUnspentOutcome(model)\n if (!written.seo?.title?.trim() || !written.seo?.description?.trim()) {\n const seoModel = modelFor?.('job.seo') ?? aiModelForStep('job.seo')\n // The plan's own listing, held to the editor's guidance, when the\n // listing cannot be written inside this pass's time on the model the\n // job runs.\n let values = {\n title: clip(screen.seoTitle, SCREEN_SEO_TEXT_GUIDANCE.title),\n description: clip(screen.seoDescription, SCREEN_SEO_TEXT_GUIDANCE.description),\n }\n if (\n // A listing is written from the page's own text, with no inventory\n // and so no lookup round.\n aiGenerationWorstCaseMs({ maxTokens: AI_SEO_FIELDS_MAX_TOKENS, model: seoModel, lookups: 0 }) <=\n AI_JOB_PAGE_STEP_MINIMUM_MS\n ) {\n const host = await firestore.collection('hosts').doc(hostId).get()\n const result = await seoFields({\n subject: { kind: 'screen', name, path: screenRoutePathToUrl(slug || SCREEN_ROOT_PATH) },\n brand: siteNameOf(host),\n // What the site is and who it is for (AGL-2918). The page's own\n // Markdown says what the page is about and cannot say who it was\n // written for, so the listing borrows the two answers the job was\n // started from — the scaffold's derived page job carries them,\n // and a page job started from the Screens page carries neither.\n site: aiSiteWords(job.inputs),\n text: buildPageMarkdown({ nodes: page as never, rootId: CANVAS_ROOT_ELEMENT_ID }),\n fields: ['title', 'description'],\n // Other screens by name only: their own listings are their content.\n otherTitles: inventory.screens.map((row) => row.name),\n model: seoModel,\n ...(signal ? { signal } : {}),\n })\n spent = { ...spent, ...aiGenerationSpent(result) }\n if (result.status === 'ok') {\n values = {\n title: result.value.title?.trim() || values.title,\n description: result.value.description?.trim() || values.description,\n }\n }\n }\n await writeAiDraftScreenSeo(firestore, { hostId, id: draftId, seo: values, now })\n }\n return { ...spent, outputs: reports }\n }\n\n // ── A section pass ──\n if (!written) {\n const allowance = await aiDraftAllowanceRefusal(firestore, { kind: 'screen', hostId, org })\n if (allowance) return aiUnspentOutcome(model, { review: aiLimitReview(allowance) })\n }\n if (language) {\n // ── The whole page in one answer, in the layout language ──\n const targets = aiLayoutPageTargets({ job, inventory, own: [screen.id, draftId] })\n const result = await aiRunLayoutPage({\n job,\n plan,\n screen,\n sectionIds,\n targets,\n context,\n reusableComponents,\n inventory,\n model,\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result) }\n // The compiler leaves each picture slot empty; a photo the site serves\n // itself fills it here, after the page is checked (AGL-3660): a stock\n // photo copied into the site's library where the deployment has a\n // library, else a starter photo. No AI credits; never fails the job.\n const sectionNames = screen.sections.map((section) => section.name)\n const seed = `${aiOriginJobId(job)}:${screen.id ?? screen.slug}`\n const pictured = await aiResolveLayoutPictures(result.value.nodes, {\n rootId: CANVAS_ROOT_ELEMENT_ID,\n sectionIds,\n sectionNames,\n seed,\n source: (deps.stockPhotos ?? aiLayoutStockPhotoSource)({\n hostId,\n uid: job.createdBy,\n seed,\n business: aiSiteWords(job.inputs).about,\n sectionNames,\n ...(signal ? { signal } : {}),\n }),\n })\n if (!written) {\n const draft = await writeAiDraft(firestore, {\n kind: 'screen',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: pictured,\n slug,\n layoutId: aiPageDraftLayoutId(screen, inventory),\n aiJobId: aiOriginJobId(job),\n now,\n })\n if (draft.ok === false) {\n if (draft.status === 404) throw new Error(`site ${hostId} vanished while its page was generated`)\n return { ...spent, review: aiLimitReview(draft.error) }\n }\n } else {\n // A draft written by an earlier, interrupted pass is replaced whole.\n const update = await updateAiDraftNodes(firestore, {\n kind: 'screen',\n hostId,\n id: draftId,\n now,\n update: () => pictured,\n })\n if (update.ok === false) return { ...spent, failure: AI_JOB_PAGE_DELETED_COPY }\n }\n return { ...spent, continue: true }\n }\n const maxTokens = aiJobPageSectionMaxTokens(model)\n const maxElements = aiJobPageSectionMaxElements(maxTokens)\n const result = await runValidatedGeneration<AiPageSection>('page-section', {\n step: 'job.page',\n model,\n instructions: AI_JOB_PAGE_INSTRUCTIONS,\n inventory,\n messages: [\n {\n role: 'user',\n content: aiPageSectionPrompt({ job, plan, screen, index, maxElements, reusableComponents, record, linkablePages }),\n },\n ],\n tool: AI_PAGE_SECTION_TOOL,\n maxTokens,\n // A section cut off at its ceiling is asked for smaller, and a member\n // whose section still does not fit reads that it was too large (AGL-3042).\n cutOff: { noun: 'section', smaller: aiPageSectionSmaller({ maxElements, reusableComponents }) },\n thinking: 'off',\n check: aiPageSectionCheck({\n page,\n sectionIds,\n index,\n context,\n section: screen.sections[index],\n inventory,\n linkTarget,\n }),\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result, { page: true }) }\n\n if (!written) {\n const draft = await writeAiDraft(firestore, {\n kind: 'screen',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: aiPageWithSection(aiEmptyPage(), result.value, sectionIds),\n slug,\n layoutId: aiPageDraftLayoutId(screen, inventory),\n aiJobId: aiOriginJobId(job),\n now,\n })\n if (draft.ok === false) {\n if (draft.status === 404) throw new Error(`site ${hostId} vanished while its page was generated`)\n return { ...spent, review: aiLimitReview(draft.error) }\n }\n } else {\n const update = await updateAiDraftNodes(firestore, {\n kind: 'screen',\n hostId,\n id: draftId,\n now,\n // A pass run again after its write finds its section and changes nothing.\n update: (nodes) =>\n result.value.rootId in nodes ? null : aiPageWithSection(nodes, result.value, sectionIds),\n })\n if (update.ok === false) return { ...spent, failure: AI_JOB_PAGE_DELETED_COPY }\n }\n return { ...spent, continue: true }\n }\n}\n\nexport const runAiJobPageStep = createAiJobPageStep()\n\n/**\n * Registers the page step with the least time a section pass needs and the\n * time its next pass needs, the passes a page with its creations may take, and\n * the check a page job passes when it is created or its plan is confirmed.\n */\nexport function registerAiPageJob(): void {\n registerAiJobStep('page', runAiJobPageStep, {\n minimumMs: AI_JOB_PAGE_STEP_MINIMUM_MS,\n minimumMsFor: aiPageJobRunMinimumMs,\n })\n registerAiJobStepPasses('page', AI_JOB_PAGE_MAX_PASSES)\n registerAiJobAdmission('page', aiPageJobAdmission)\n}\n"],"names":["buildPageMarkdown","SCREEN_SEO_TEXT_GUIDANCE","SCREEN_ROOT_PATH","screenRoutePathToUrl","CANVAS_ROOT_ELEMENT_ID","duplicateResource","AI_BUILD_PLAN_LIMITS","aiPlanEmbedsFor","AI_PAGE_CREATE_KINDS","aiPageCreationRefusal","aiPagePlanRefusal","parseAiPageJobInputs","aiPlanCapabilitiesForJob","aiPlanUncreatable","aiSiteWords","aiModelForStep","aiDoctrineNeedsInputMessage","runValidatedGeneration","validateAiDoctrineTree","AI_SEO_FIELDS_MAX_TOKENS","generateSeoFields","readSiteInventory","registerAiJobAdmission","aiGenerationWorstCaseMs","aiJobDraftId","aiOriginJobId","aiBuildsWithComponents","aiDraftAdmissionRefusal","aiDraftAllowanceRefusal","aiSiteSubdomain","readAiDraft","readAiDraftNodes","readAiPlanCapabilities","updateAiDraftNodes","writeAiDraft","writeAiDraftScreenSeo","aiBracketedFactsNote","aiConfirmedPlan","aiDoctrineReview","aiGenerationSpent","aiLimitReview","aiModelNodeIds","aiUnspentOutcome","AI_JOB_PAGE_SECTION_MAX_TOKENS","AI_JOB_PAGE_SECTION_TOKENS","AI_JOB_PAGE_STEP_BUDGET","AI_JOB_PAGE_STEP_MINIMUM_MS","aiJobPageSectionMaxTokens","AI_JOB_PAGE_INSTRUCTIONS","AI_PAGE_SECTION_TOOL","aiEmptyPage","aiPageCheckContext","aiPageLinkablePages","aiPageRecordNote","aiPageRecordTemplate","aiPageRecordTokens","aiPageSectionCheck","aiPageSectionNodeId","aiPageSectionPrompt","aiPageSectionSmaller","aiPageWithSection","aiLayoutSitePages","aiResolveLayoutPictures","aiLayoutStockPhotoSource","AI_PLAN_ITEMS_MIN","aiPlanCopiedPageViolations","AI_JOB_PAGE_LANGUAGE_BUDGET","aiJobUsesLayoutLanguage","aiLayoutPageTargets","aiRunLayoutPage","aiCreationUnit","aiRunJobUnit","aiSiteBuiltRefs","aiSitePendingUnits","aiSiteUnitJob","aiJobStepRunMinimumMs","aiJobStepRunnerFor","registerAiJobStep","registerAiJobStepPasses","AI_JOB_PAGE_TOKENS_PER_ELEMENT","AI_JOB_PAGE_REAL_TOKENS_PER_ESTIMATED","Math","max","AI_JOB_PAGE_REAL_TOKENS_PER_ELEMENT","ceil","aiJobPageSectionMaxElements","maxTokens","floor","AI_JOB_PAGE_DEFAULT_NAME","AI_JOB_PAGE_NO_PLAN_COPY","AI_JOB_PAGE_DELETED_COPY","AI_JOB_PAGE_CREATION_UNAVAILABLE_COPY","AI_JOB_PAGE_CREATION_EMPTY_COPY","AI_JOB_PAGE_REFUSED_DRAFT_COPY","AI_JOB_PAGE_MAX_PASSES","create","sections","aiPageJobUnits","plan","flatMap","kind","creation","index","unit","aiPageJobRunMinimumMs","job","confirmed","pagePass","minimumMs","units","outputs","aiPageDraftSlug","screen","segments","slug","split","map","part","trim","filter","Boolean","length","aiSiteDefaultLayoutId","inventory","counts","layouts","Set","row","id","size","pages","screens","template","layoutId","has","home","find","Map","set","get","mostUsed","entries","sort","a","b","aiPageDraftLayoutId","layout","some","clip","value","text","cut","slice","space","lastIndexOf","siteNameOf","host","aiCopiedPageReview","firestore","input","section","items","stored","hostId","nodes","reason","message","name","findings","violations","rootId","aiPageJobAdmission","context","inputs","status","error","refusal","jobKind","org","orgId","noun","ownCheck","capabilities","creates","createAiJobPageStep","deps","readInventory","duplicate","seoFields","runnerFor","stepContext","orgSnapshot","now","signal","modelFor","Error","model","failure","runner","pass","emptyCopy","built","outcome","continue","resource","slot","label","title","sectionIds","_","$id","draftId","output","draft","load","note","versionId","hostSubdomain","nav","proposal","navigation","written","Promise","all","collection","doc","data","deleted","record","recordNote","copiesStarter","duplicateOf","replaceable","sourceReview","sourceShort","copy","sourceId","uid","createdBy","attemptKey","ok","review","page","findIndex","reusableComponents","embeds","linkablePages","linkTarget","language","recordTokens","codeBuilt","report","scrollTargetIds","tree","join","reported","entry","reports","storedIds","sourceIds","violation","nodeIds","answer","then","spent","seo","description","seoModel","values","seoTitle","seoDescription","lookups","result","subject","path","brand","site","fields","otherTitles","allowance","targets","own","refused","sectionNames","seed","pictured","source","stockPhotos","business","about","aiJobId","update","maxElements","step","instructions","messages","role","content","tool","cutOff","smaller","thinking","check","runAiJobPageStep","registerAiPageJob","minimumMsFor"],"mappings":"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"}