@aglyn/plugins-ai 1.0.0-beta.288 → 1.0.0-beta.290
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.
- package/package.json +17 -17
- package/src/lib/activity/ai-activity-actions.d.ts +3 -2
- package/src/lib/activity/ai-activity-actions.js +3 -0
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-job-requests.d.ts +2 -0
- package/src/lib/components/ai-job-requests.js +2 -0
- package/src/lib/components/ai-job-requests.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +4 -0
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-site-import-job.component.d.ts +6 -1
- package/src/lib/components/ai-site-import-job.component.js +124 -17
- package/src/lib/components/ai-site-import-job.component.js.map +1 -1
- package/src/lib/components/ai-site-import-start.component.d.ts +6 -0
- package/src/lib/components/ai-site-import-start.component.js +18 -4
- package/src/lib/components/ai-site-import-start.component.js.map +1 -1
- package/src/lib/components/ai-site-start-card.component.js +2 -1
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/jobs/ai-image-reads.d.ts +20 -2
- package/src/lib/jobs/ai-image-reads.js +47 -0
- package/src/lib/jobs/ai-image-reads.js.map +1 -1
- package/src/lib/jobs/ai-job-import-step.d.ts +53 -3
- package/src/lib/jobs/ai-job-import-step.js +386 -64
- package/src/lib/jobs/ai-job-import-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +33 -0
- package/src/lib/jobs/ai-job-layout-language.js +75 -1
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +8 -3
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +29 -0
- package/src/lib/jobs/ai-job-page-language.js +178 -2
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +3 -0
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-site-import-document.d.ts +117 -0
- package/src/lib/jobs/ai-site-import-document.js +710 -0
- package/src/lib/jobs/ai-site-import-document.js.map +1 -0
- package/src/lib/jobs/ai-site-import-look.d.ts +112 -0
- package/src/lib/jobs/ai-site-import-look.js +402 -0
- package/src/lib/jobs/ai-site-import-look.js.map +1 -0
- package/src/lib/jobs/ai-site-import-publish.d.ts +41 -0
- package/src/lib/jobs/ai-site-import-publish.js +92 -0
- package/src/lib/jobs/ai-site-import-publish.js.map +1 -0
- package/src/lib/jobs/ai-theme-brand-inputs.d.ts +27 -2
- package/src/lib/jobs/ai-theme-brand-inputs.js +37 -8
- package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame.d.ts +17 -0
- package/src/lib/layout-language/ai-layout-frame.js +73 -2
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-language.d.ts +43 -0
- package/src/lib/layout-language/ai-layout-language.js +199 -0
- package/src/lib/layout-language/ai-layout-language.js.map +1 -1
- package/src/lib/model/ai-credit-estimate.d.ts +15 -0
- package/src/lib/model/ai-credit-estimate.js +14 -1
- package/src/lib/model/ai-credit-estimate.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +13 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-site-import-view.d.ts +47 -3
- package/src/lib/model/ai-site-import-view.js +122 -20
- package/src/lib/model/ai-site-import-view.js.map +1 -1
- package/src/lib/model/ai-site-import.d.ts +232 -7
- package/src/lib/model/ai-site-import.js +365 -35
- package/src/lib/model/ai-site-import.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +18 -0
- package/src/lib/runtime/ai-doctrine-validators.js +16 -5
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +31 -0
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/server/ai-image-reads-route.js +24 -1
- package/src/lib/server/ai-image-reads-route.js.map +1 -1
- package/src/lib/server/ai-jobs-resume.js +7 -1
- package/src/lib/server/ai-jobs-resume.js.map +1 -1
- package/src/lib/site-import/fixtures/aglyn-com-recordings.d.ts +57 -0
- package/src/lib/site-import/fixtures/aglyn-com-recordings.js +172 -0
- package/src/lib/site-import/fixtures/aglyn-com-recordings.js.map +1 -0
- package/src/lib/site-import/site-import-crawl.d.ts +32 -7
- package/src/lib/site-import/site-import-crawl.js +133 -44
- package/src/lib/site-import/site-import-crawl.js.map +1 -1
- package/src/lib/site-import/site-import-edge-server.d.ts +31 -0
- package/src/lib/site-import/site-import-edge-server.js +31 -0
- package/src/lib/site-import/site-import-edge-server.js.map +1 -0
- package/src/lib/site-import/site-import-edge.d.ts +44 -0
- package/src/lib/site-import/site-import-edge.js +108 -0
- package/src/lib/site-import/site-import-edge.js.map +1 -0
- package/src/lib/site-import/site-import-html.d.ts +73 -3
- package/src/lib/site-import/site-import-html.js +332 -45
- package/src/lib/site-import/site-import-html.js.map +1 -1
- package/src/lib/site-import/site-import-images.d.ts +22 -0
- package/src/lib/site-import/site-import-images.js +78 -2
- package/src/lib/site-import/site-import-images.js.map +1 -1
- package/src/lib/site-import/site-import-page.d.ts +44 -2
- package/src/lib/site-import/site-import-page.js +116 -16
- package/src/lib/site-import/site-import-page.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-layout-language.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { aiLayoutDesignChoices, type AiLayoutDesign } from '../layout-language/ai-layout-design'\nimport { aiCompileLayoutFrame } from '../layout-language/ai-layout-frame'\nimport {\n aiLayoutFrameHollows,\n aiLayoutFrameHollowsSaid,\n aiLayoutFrameIsHollow,\n aiLayoutRepairFrameHollows,\n} from '../layout-language/ai-layout-frame-hollows'\nimport {\n AI_LAYOUT_FRAME_TOOL,\n AI_LAYOUT_LANGUAGE_TEXT,\n aiReadLayoutFrame,\n} from '../layout-language/ai-layout-language'\nimport type {\n AiLayoutPage,\n AiLayoutTargets,\n} from '../layout-language/ai-layout-links'\nimport { aiLayoutCopyCheck } from '../layout-language/ai-layout-gaps'\nimport { aiLayoutStoredTree } from '../layout-language/ai-layout-store'\nimport { AI_GENERATION_MAX_ATTEMPTS } from '../runtime/ai-generation-bounds'\nimport type { AiJob, AiJobPlan } from '../model/ai-jobs.types'\nimport { aiLayoutSpeaksOtherThanEnglish } from '../layout-language/ai-layout-labels'\nimport {\n aiHomeScreenIds,\n type AiSiteInventory,\n} from '../model/ai-site-inventory'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport type { AiGenerationCheck, AiValidatedTree } from '../runtime/ai-doctrine'\nimport {\n aiSettleMechanicalRules,\n aiSettleUnrelatedScreenLinks,\n validateAiDoctrineTree,\n type AiDoctrineNode,\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 { AiStoreFrameLinks } from '../model/ai-site-store-pages'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiJobBriefLine } from './ai-job-generation'\nimport {\n AI_LAYOUT_SITE_PAGES_MAX,\n aiLayoutIsHomeSlug,\n aiLayoutSitePages,\n} from './ai-job-layout-site-pages'\nimport {\n AI_LAYOUT_FRAME_THINKING_TOKENS,\n aiLayoutPageTargets,\n} from './ai-job-page-language'\nimport { AI_SITE_NAME_TOKEN } from './ai-layout-site-facts'\n\n/**\n * A site's layout built in the compact layout language (AGL-3660): the layout\n * step's pass for a guided start's and a build's layout unit. The model\n * designs the header and the footer — their bands, the call to action, the\n * footer's columns and every word — in one short answer through\n * `submit_frame`, and the compiler writes the layout around what the site\n * owes it: its name as the brand, its pages as the navigation and the phone\n * menu, the Layout Slot, and the footer at the bottom of a short page\n * (`ai-layout-frame.ts`).\n */\n\n/**\n * The most a frame's answer asks for: two sections of a few blocks each (the\n * live runs of 2026-10-07 wrote 442 to 583), and the thinking before them.\n */\nexport const AI_JOB_LAYOUT_LANGUAGE_TOKENS = 1_200 + AI_LAYOUT_FRAME_THINKING_TOKENS\n\n/** A language layout pass's time, on the tier the layout step is served from. */\nexport const AI_JOB_LAYOUT_LANGUAGE_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.layout'],\n maxTokens: AI_JOB_LAYOUT_LANGUAGE_TOKENS,\n})\n\n/** The generation kind a frame is asked under, which the doctrine scopes. */\nexport const AI_LAYOUT_FRAME_KIND = 'layout-frame'\n\n/** The language, as the frame door's cached instructions. */\nexport const AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n AI_LAYOUT_LANGUAGE_TEXT,\n '',\n \"Now you design a site's header and footer, each one section of the language, answered with submit_frame.\",\n \"- header: the platform writes the site's name and its navigation itself. Choose its band and align (start, or center for a centered navigation), and give it at most one button: the call to action every page shows.\",\n '- footer: a section like any other, usually a row of two or three columns: a few words about the business, the details the brief gives, and a list of links (items with to). Put no contact detail, opening hours or address in it that the brief does not give, and no gap in square brackets for one.',\n ].join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\n/** The site's name as the brand: the name the job was given, else the token the site's own name fills. */\nexport function aiLayoutSiteName(job: Pick<AiJob, 'inputs'>): string {\n const given = job.inputs?.['businessName']\n return typeof given === 'string' && given.trim()\n ? given.trim()\n : AI_SITE_NAME_TOKEN\n}\n\n/** The pages a frame's navigation links: the planned pages, else the site's own, in order. */\nexport function aiLayoutNavPages(\n job: Pick<AiJob, 'inputs'> & { plan?: Pick<AiJobPlan, 'language'> | null },\n inventory: AiSiteInventory | null,\n): AiLayoutPage[] {\n // The header and the footer link the pages the job was told (AGL-3660),\n // else the site's own, the home page always and first, labelled Home. The\n // model draws neither list, so it cannot drop a page. A site in another\n // language keeps its home page's own title: a Spanish start's \"Inicio\" was\n // linked as \"Home\" (AGL-3760).\n const planned = aiLayoutSitePages(job.inputs)\n const own = (inventory?.screens ?? [])\n .filter((row) => !row.template)\n .map((row) => ({ id: row.id, label: row.name, slug: row.slug }))\n const siteHome = planned.some((page) => aiLayoutIsHomeSlug(page.slug))\n ? []\n : own.filter((page) => aiLayoutIsHomeSlug(page.slug)).slice(0, 1)\n const pages = [...siteHome, ...(planned.length ? planned : own)].filter((page) => page.label.trim())\n const home = pages.find((page) => aiLayoutIsHomeSlug(page.slug))\n return [\n ...(home ? [aiLayoutSpeaksOtherThanEnglish(job.plan?.language) ? home : { ...home, label: 'Home' }] : []),\n ...pages.filter((page) => page !== home && !aiLayoutIsHomeSlug(page.slug)),\n ].slice(0, AI_LAYOUT_SITE_PAGES_MAX)\n}\n\n/** The home page the brand links: the planned home, else the site's. */\nexport function aiLayoutHomeId(\n pages: readonly AiLayoutPage[],\n inventory: AiSiteInventory | null,\n): string | null {\n const planned = pages.find((page) => aiLayoutIsHomeSlug(page.slug))\n return planned?.id ?? aiHomeScreenIds(inventory)[0] ?? null\n}\n\n/** The frame's user turn: the site, its brief, its pages and its form. */\nexport function aiLayoutFramePrompt(input: {\n job: Pick<AiJob, 'brief' | '$id'> & Partial<Pick<AiJob, 'inputs'>>\n siteName: string\n pages: readonly AiLayoutPage[]\n targets: AiLayoutTargets\n}): string {\n const { job, pages, targets } = input\n return [\n `Site: ${input.siteName === AI_SITE_NAME_TOKEN ? 'the business the brief describes' : `\"${input.siteName}\"`}`,\n aiJobBriefLine(job),\n ...aiSiteKindDesignLines(job.inputs),\n pages.length\n ? `Its pages, which the navigation links: ${pages.map((page) => `${page.label} (page:${page.id})`).join(', ')}.`\n : 'It has no pages yet.',\n targets.forms.length || targets.formPageId\n ? 'A call to action that asks a visitor to get in touch goes to form.'\n : 'The site has no form yet.',\n // A storefront's header (AGL-3676): its cart, and an announcement line.\n ...(aiLayoutShopPageId(targets)\n ? [\n \"This is an online store: give the header no button, since the platform puts the cart beside the navigation. Give the header one note instead: a short announcement for the bar above it, under 60 characters, about the shop from the brief (a promise it makes, such as its shipping, or what is new); never invent an offer, a discount, a price or a shipping threshold the brief does not give.\",\n ]\n : []),\n `Design seed: ${job.$id.slice(-6)}.`,\n ].join('\\n')\n}\n\n/** A store's Shop page, by its id: the page its catalog's whole list is placed on (AGL-3676). */\nexport function aiLayoutShopPageId(targets: Pick<AiLayoutTargets, 'listings'>): string | null {\n const products = (targets.listings ?? []).find((listing) => listing.kind === 'products')\n return products?.placements.find((placement) => placement.role === 'index')?.screenId ?? null\n}\n\n/** A validated language layout. */\nexport interface AiLayoutFrameBuilt {\n rootId: string\n nodes: NodesMap\n load: AiLoadEstimate | null\n /** What the last answer's gaps took out of the frame (`ai-layout-gaps.ts`). */\n dropped: string[]\n}\n\n/** The check a frame answer is held to: read, compiled, stored and checked as a layout, with the layout door's own checks. */\nexport function aiLayoutFrameCheck(input: {\n /** The header arrangement the site's look chose (AGL-3660); the answer's otherwise. */\n headerAlign?: 'start' | 'center'\n siteName: string\n homeId: string | null\n pages: readonly AiLayoutPage[]\n targets: AiLayoutTargets\n extend: (tree: AiValidatedTree) => AiDoctrineViolation[]\n /** The site's design (`aiLayoutDesignOf`), whose pages' closing band the footer sits under. */\n design?: AiLayoutDesign | null\n /** A store's own pages, which the header and footer link by path (AGL-3676). */\n storeLinks?: AiStoreFrameLinks | null\n /** The site's language, which the frame's own words are written in (AGL-3760). */\n language?: string\n}): AiGenerationCheck<AiLayoutFrameBuilt> {\n let answers = 0\n return (answer) => {\n // The last answer a generation takes has its gaps taken out rather than asked about again.\n answers += 1\n const last = answers >= AI_GENERATION_MAX_ATTEMPTS\n const frame = aiReadLayoutFrame(answer)\n if (input.headerAlign && frame.header) frame.header.align = input.headerAlign\n const compiled = aiCompileLayoutFrame(\n frame,\n {\n siteName: input.siteName,\n homeId: input.homeId,\n navPages: input.pages,\n closesDark: input.design ? aiLayoutDesignChoices(input.design).coverClose : false,\n // A site that sells carries its cart in the header (AGL-3676).\n cart: (input.targets.listings ?? []).some((listing) => listing.kind === 'products' && listing.cart !== false),\n // And a store's header is a storefront's: no button repeating its navigation, an announcement bar (AGL-3676).\n shopId: aiLayoutShopPageId(input.targets),\n // And its account and policies, written after it (AGL-3676).\n storeLinks: input.storeLinks ?? null,\n ...(input.language ? { language: input.language } : {}),\n },\n input.targets,\n )\n const context = {\n screenIds: [\n ...new Set([\n ...input.targets.pages.map((page) => page.id),\n ...input.pages.map((page) => page.id),\n ...(input.homeId ? [input.homeId] : []),\n ]),\n ],\n formIds: input.targets.forms.map((form) => form.id),\n homeScreenIds: input.targets.homeIds,\n // A link home under the home page's own title says home, in any language (AGL-3760).\n homeLabels: [\n ...input.pages.filter((page) => aiLayoutIsHomeSlug(page.slug)),\n ...input.targets.pages.filter((page) => input.targets.homeIds.includes(page.id)),\n ].map((page) => page.label),\n // A footer's \"Get in touch\" goes to the form, wherever it is placed (AGL-3729).\n ...(input.targets.formPageId ? { formScreenIds: [input.targets.formPageId] } : {}),\n codeBuilt: true,\n }\n const stored = aiLayoutStoredTree(compiled.tree, 'layout', context)\n if (stored.ok === false) {\n console.error('ai layout frame: a compiled layout did not store', {\n error: stored.error,\n })\n return {\n value: null,\n violations: [\n {\n rule: null,\n code: 'layout-compile',\n message: 'This layout could not be built. Try again.',\n detail: stored.error,\n },\n ],\n }\n }\n // No gap and no internal reference in the words of a frame every published page shows.\n const navPages = [...input.targets.pages, ...input.pages]\n const copy = aiLayoutCopyCheck(\n stored.nodes,\n stored.rootId,\n {\n pages: navPages,\n ids: [...navPages.map((page) => page.id), ...input.targets.forms.map((form) => form.id)],\n },\n last,\n )\n const { dropped } = copy\n // An attribute a rule has one answer for is settled, not asked of the model again (AGL-3660).\n const nodes = aiSettleUnrelatedScreenLinks(\n aiSettleMechanicalRules(\n { rootId: stored.rootId, nodes: copy.nodes as unknown as Record<string, AiDoctrineNode> },\n 'layout',\n // And on the last answer, a link that goes nowhere, as the check below reads it (AGL-4076).\n last ? context : undefined,\n ),\n 'layout',\n context,\n ).nodes as unknown as typeof copy.nodes\n // The copy check can take words out of the footer: no heading or column\n // is left with nothing under it (AGL-3735), and none may remain.\n aiLayoutRepairFrameHollows(nodes as unknown as Record<string, unknown>)\n const hollow = aiLayoutFrameHollows(nodes as unknown as Record<string, unknown>)\n const report = validateAiDoctrineTree(\n { rootId: stored.rootId, nodes },\n 'layout',\n context,\n )\n const violations = [\n ...report.violations,\n ...(aiLayoutFrameIsHollow(hollow)\n ? [\n {\n rule: null,\n code: 'layout-frame-hollow',\n message: 'Every footer heading needs something under it, and every footer column more than a heading.',\n detail: aiLayoutFrameHollowsSaid(hollow).join('; '),\n },\n ]\n : []),\n ...copy.violations,\n ...input.extend({\n rootId: stored.rootId,\n nodes,\n repairs: [],\n sourceIds: {},\n score: report.score as AiValidatedTree['score'],\n load: report.load,\n }),\n ]\n return {\n value: violations.length\n ? null\n : { rootId: stored.rootId, nodes, load: report.load, dropped },\n violations,\n }\n }\n}\n\n/** What a frame's links resolve against: the site's pages and its form, as a page's do. */\nexport function aiLayoutFrameTargets(\n job: Pick<AiJob, 'brief' | 'inputs'>,\n inventory: AiSiteInventory | null,\n): AiLayoutTargets {\n return aiLayoutPageTargets({ job, inventory, own: [] })\n}\n"],"names":["aiLayoutDesignChoices","aiCompileLayoutFrame","aiLayoutFrameHollows","aiLayoutFrameHollowsSaid","aiLayoutFrameIsHollow","aiLayoutRepairFrameHollows","AI_LAYOUT_LANGUAGE_TEXT","aiReadLayoutFrame","aiLayoutCopyCheck","aiLayoutStoredTree","AI_GENERATION_MAX_ATTEMPTS","aiLayoutSpeaksOtherThanEnglish","aiHomeScreenIds","AI_STEP_TIERS","aiSettleMechanicalRules","aiSettleUnrelatedScreenLinks","validateAiDoctrineTree","aiSiteKindDesignLines","aiJobStepBudget","aiJobBriefLine","AI_LAYOUT_SITE_PAGES_MAX","aiLayoutIsHomeSlug","aiLayoutSitePages","AI_LAYOUT_FRAME_THINKING_TOKENS","aiLayoutPageTargets","AI_SITE_NAME_TOKEN","AI_JOB_LAYOUT_LANGUAGE_TOKENS","AI_JOB_LAYOUT_LANGUAGE_BUDGET","tier","maxTokens","AI_LAYOUT_FRAME_KIND","AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS","text","join","cacheBreakpoint","aiLayoutSiteName","job","given","inputs","trim","aiLayoutNavPages","inventory","planned","own","screens","filter","row","template","map","id","label","name","slug","siteHome","some","page","slice","pages","length","home","find","plan","language","aiLayoutHomeId","aiLayoutFramePrompt","input","targets","siteName","forms","formPageId","aiLayoutShopPageId","$id","products","listings","listing","kind","placements","placement","role","screenId","aiLayoutFrameCheck","answers","answer","last","frame","headerAlign","header","align","compiled","homeId","navPages","closesDark","design","coverClose","cart","shopId","storeLinks","context","screenIds","Set","formIds","form","homeScreenIds","homeIds","homeLabels","includes","formScreenIds","codeBuilt","stored","tree","ok","console","error","value","violations","rule","code","message","detail","copy","nodes","rootId","ids","dropped","undefined","hollow","report","extend","repairs","sourceIds","score","load","aiLayoutFrameTargets"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-layout-language.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { aiLayoutDesignChoices, type AiLayoutDesign } from '../layout-language/ai-layout-design'\nimport { aiCompileLayoutFrame, type AiLayoutFrameBrand } from '../layout-language/ai-layout-frame'\nimport {\n aiLayoutFrameHollows,\n aiLayoutFrameHollowsSaid,\n aiLayoutFrameIsHollow,\n aiLayoutRepairFrameHollows,\n} from '../layout-language/ai-layout-frame-hollows'\nimport {\n AI_LAYOUT_FRAME_TOOL,\n AI_LAYOUT_LANGUAGE_TEXT,\n aiReadLayoutFrame,\n} from '../layout-language/ai-layout-language'\nimport type {\n AiLayoutPage,\n AiLayoutTargets,\n} from '../layout-language/ai-layout-links'\nimport { aiLayoutCopyCheck } from '../layout-language/ai-layout-gaps'\nimport { aiLayoutStoredTree } from '../layout-language/ai-layout-store'\nimport { AI_GENERATION_MAX_ATTEMPTS } from '../runtime/ai-generation-bounds'\nimport type { AiJob, AiJobPlan } from '../model/ai-jobs.types'\nimport { aiLayoutSpeaksOtherThanEnglish } from '../layout-language/ai-layout-labels'\nimport {\n aiHomeScreenIds,\n type AiSiteInventory,\n} from '../model/ai-site-inventory'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport type { AiGenerationCheck, AiValidatedTree } from '../runtime/ai-doctrine'\nimport {\n aiSettleMechanicalRules,\n aiSettleUnrelatedScreenLinks,\n validateAiDoctrineTree,\n type AiDoctrineNode,\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 { AiStoreFrameLinks } from '../model/ai-site-store-pages'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { fenceUntrustedText, UNTRUSTED_TEXT_RULE } from '@aglyn/shared-util-tools/untrusted-text'\nimport { aiJobBriefLine } from './ai-job-generation'\nimport {\n AI_LAYOUT_SITE_PAGES_MAX,\n aiLayoutIsHomeSlug,\n aiLayoutSitePages,\n} from './ai-job-layout-site-pages'\nimport {\n AI_LAYOUT_FRAME_THINKING_TOKENS,\n aiLayoutPageTargets,\n} from './ai-job-page-language'\nimport { AI_SITE_NAME_TOKEN } from './ai-layout-site-facts'\n\n/**\n * A site's layout built in the compact layout language (AGL-3660): the layout\n * step's pass for a guided start's and a build's layout unit. The model\n * designs the header and the footer — their bands, the call to action, the\n * footer's columns and every word — in one short answer through\n * `submit_frame`, and the compiler writes the layout around what the site\n * owes it: its name as the brand, its pages as the navigation and the phone\n * menu, the Layout Slot, and the footer at the bottom of a short page\n * (`ai-layout-frame.ts`).\n */\n\n/**\n * The most a frame's answer asks for: two sections of a few blocks each (the\n * live runs of 2026-10-07 wrote 442 to 583), and the thinking before them.\n */\nexport const AI_JOB_LAYOUT_LANGUAGE_TOKENS = 1_200 + AI_LAYOUT_FRAME_THINKING_TOKENS\n\n/** A language layout pass's time, on the tier the layout step is served from. */\nexport const AI_JOB_LAYOUT_LANGUAGE_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.layout'],\n maxTokens: AI_JOB_LAYOUT_LANGUAGE_TOKENS,\n})\n\n/** The generation kind a frame is asked under, which the doctrine scopes. */\nexport const AI_LAYOUT_FRAME_KIND = 'layout-frame'\n\n/** The language, as the frame door's cached instructions. */\nexport const AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n AI_LAYOUT_LANGUAGE_TEXT,\n '',\n \"Now you design a site's header and footer, each one section of the language, answered with submit_frame.\",\n \"- header: the platform writes the site's name and its navigation itself. Choose its band and align (start, or center for a centered navigation), and give it at most one button: the call to action every page shows.\",\n '- footer: a section like any other, usually a row of two or three columns: a few words about the business, the details the brief gives, and a list of links (items with to). Put no contact detail, opening hours or address in it that the brief does not give, and no gap in square brackets for one.',\n ].join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\n/**\n * The unit input a site brought in from elsewhere gives its header and\n * footer (AGL-4084): its header's brand as the source draws it, and the words\n * the source's header and footer carry — its address, phone, email, hours\n * and profiles — word for word. Read defensively; `null` on every other job.\n */\nexport const AI_LAYOUT_SITE_BRAND_INPUT = 'siteBrand'\nexport const AI_LAYOUT_SITE_FACTS_INPUT = 'siteFacts'\n\n/** The most characters of a site's own header and footer words a frame is shown. */\nexport const AI_LAYOUT_SITE_FACTS_MAX_CHARS = 4_000\n\nexport interface AiLayoutSiteFacts {\n /** Where they were read: the owner's own home page. */\n url: string\n /** The site's own description of itself. */\n description: string\n /** Its header's and footer's lines, as written. */\n lines: string[]\n}\n\nexport function aiLayoutSiteBrandOf(inputs: Readonly<Record<string, unknown>> | null | undefined): AiLayoutFrameBrand | null {\n const raw = inputs?.[AI_LAYOUT_SITE_BRAND_INPUT]\n if (!raw || typeof raw !== 'object') return null\n const value = raw as Record<string, unknown>\n const logo = value['logo'] as Record<string, unknown> | null | undefined\n const src = typeof logo?.['src'] === 'string' && logo['src'].trim() ? logo['src'].trim() : null\n return { ...(src ? { logo: { src } } : {}), name: src ? value['name'] === true : true }\n}\n\nexport function aiLayoutSiteFactsOf(inputs: Readonly<Record<string, unknown>> | null | undefined): AiLayoutSiteFacts | null {\n const raw = inputs?.[AI_LAYOUT_SITE_FACTS_INPUT]\n if (!raw || typeof raw !== 'object') return null\n const value = raw as Record<string, unknown>\n const lines = Array.isArray(value['lines']) ? value['lines'].filter((line): line is string => typeof line === 'string' && !!line.trim()) : []\n const description = typeof value['description'] === 'string' ? value['description'] : ''\n if (!lines.length && !description.trim()) return null\n return { url: typeof value['url'] === 'string' ? value['url'].slice(0, 2048) : '', description, lines }\n}\n\n/** A site's own header and footer words as one text, which its frame's contact details are held to (AGL-4084). */\nexport function aiLayoutSiteFactsText(inputs: Readonly<Record<string, unknown>> | null | undefined): string {\n const facts = aiLayoutSiteFactsOf(inputs)\n return facts ? [facts.description, ...facts.lines].filter(Boolean).join('\\n') : ''\n}\n\n/**\n * What a frame brought in from the owner's own site is told (AGL-4084): its\n * footer carries the details the source's header and footer give, as they\n * give them, and nothing else; the words themselves, fenced as data.\n */\nexport function aiLayoutSiteFactsLines(facts: AiLayoutSiteFacts | null): string[] {\n if (!facts) return []\n const text = [facts.description ? `About the site, in its own words: ${facts.description}` : '', ...facts.lines].filter(Boolean).join('\\n')\n return [\n \"This site's header and footer on its owner's current website carry the words below. Put in the footer the details they give (the address, phone number, email, hours and profiles), written exactly as they are written there, and its own words about itself; invent nothing they do not give, and print no word of these instructions.\",\n fenceUntrustedText(`The header and footer of ${facts.url || \"the owner's site\"}`, text, { maxChars: AI_LAYOUT_SITE_FACTS_MAX_CHARS }),\n ]\n}\n\n/** The frame door's system blocks: the language's, and for a site brought in the rule that fenced text is data (AGL-4084). */\nexport function aiJobLayoutLanguageInstructions(job: Pick<AiJob, 'inputs'>): readonly AiSystemBlock[] {\n return aiLayoutSiteFactsOf(job.inputs)\n ? [...AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS, { text: UNTRUSTED_TEXT_RULE }]\n : AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS\n}\n\n/** The site's name as the brand: the name the job was given, else the token the site's own name fills. */\nexport function aiLayoutSiteName(job: Pick<AiJob, 'inputs'>): string {\n const given = job.inputs?.['businessName']\n return typeof given === 'string' && given.trim()\n ? given.trim()\n : AI_SITE_NAME_TOKEN\n}\n\n/** The pages a frame's navigation links: the planned pages, else the site's own, in order. */\nexport function aiLayoutNavPages(\n job: Pick<AiJob, 'inputs'> & { plan?: Pick<AiJobPlan, 'language'> | null },\n inventory: AiSiteInventory | null,\n): AiLayoutPage[] {\n // The header and the footer link the pages the job was told (AGL-3660),\n // else the site's own, the home page always and first, labelled Home. The\n // model draws neither list, so it cannot drop a page. A site in another\n // language keeps its home page's own title: a Spanish start's \"Inicio\" was\n // linked as \"Home\" (AGL-3760).\n const planned = aiLayoutSitePages(job.inputs)\n const own = (inventory?.screens ?? [])\n .filter((row) => !row.template)\n .map((row) => ({ id: row.id, label: row.name, slug: row.slug }))\n const siteHome = planned.some((page) => aiLayoutIsHomeSlug(page.slug))\n ? []\n : own.filter((page) => aiLayoutIsHomeSlug(page.slug)).slice(0, 1)\n const pages = [...siteHome, ...(planned.length ? planned : own)].filter((page) => page.label.trim())\n const home = pages.find((page) => aiLayoutIsHomeSlug(page.slug))\n return [\n ...(home ? [aiLayoutSpeaksOtherThanEnglish(job.plan?.language) ? home : { ...home, label: 'Home' }] : []),\n ...pages.filter((page) => page !== home && !aiLayoutIsHomeSlug(page.slug)),\n ].slice(0, AI_LAYOUT_SITE_PAGES_MAX)\n}\n\n/** The home page the brand links: the planned home, else the site's. */\nexport function aiLayoutHomeId(\n pages: readonly AiLayoutPage[],\n inventory: AiSiteInventory | null,\n): string | null {\n const planned = pages.find((page) => aiLayoutIsHomeSlug(page.slug))\n return planned?.id ?? aiHomeScreenIds(inventory)[0] ?? null\n}\n\n/** The frame's user turn: the site, its brief, its pages and its form. */\nexport function aiLayoutFramePrompt(input: {\n job: Pick<AiJob, 'brief' | '$id'> & Partial<Pick<AiJob, 'inputs'>>\n siteName: string\n pages: readonly AiLayoutPage[]\n targets: AiLayoutTargets\n}): string {\n const { job, pages, targets } = input\n // A site brought in (AGL-4084) is told its own words, fenced, and no brief a footer could print.\n const facts = aiLayoutSiteFactsOf(job.inputs)\n return [\n `Site: ${input.siteName === AI_SITE_NAME_TOKEN ? 'the business the brief describes' : `\"${input.siteName}\"`}`,\n ...(facts ? aiLayoutSiteFactsLines(facts) : [aiJobBriefLine(job)]),\n ...aiSiteKindDesignLines(job.inputs),\n pages.length\n ? `Its pages, which the navigation links: ${pages.map((page) => `${page.label} (page:${page.id})`).join(', ')}.`\n : 'It has no pages yet.',\n targets.forms.length || targets.formPageId\n ? 'A call to action that asks a visitor to get in touch goes to form.'\n : 'The site has no form yet.',\n // A storefront's header (AGL-3676): its cart, and an announcement line.\n ...(aiLayoutShopPageId(targets)\n ? [\n \"This is an online store: give the header no button, since the platform puts the cart beside the navigation. Give the header one note instead: a short announcement for the bar above it, under 60 characters, about the shop from the brief (a promise it makes, such as its shipping, or what is new); never invent an offer, a discount, a price or a shipping threshold the brief does not give.\",\n ]\n : []),\n `Design seed: ${job.$id.slice(-6)}.`,\n ].join('\\n')\n}\n\n/** A store's Shop page, by its id: the page its catalog's whole list is placed on (AGL-3676). */\nexport function aiLayoutShopPageId(targets: Pick<AiLayoutTargets, 'listings'>): string | null {\n const products = (targets.listings ?? []).find((listing) => listing.kind === 'products')\n return products?.placements.find((placement) => placement.role === 'index')?.screenId ?? null\n}\n\n/** A validated language layout. */\nexport interface AiLayoutFrameBuilt {\n rootId: string\n nodes: NodesMap\n load: AiLoadEstimate | null\n /** What the last answer's gaps took out of the frame (`ai-layout-gaps.ts`). */\n dropped: string[]\n}\n\n/** The check a frame answer is held to: read, compiled, stored and checked as a layout, with the layout door's own checks. */\nexport function aiLayoutFrameCheck(input: {\n /** The header arrangement the site's look chose (AGL-3660); the answer's otherwise. */\n headerAlign?: 'start' | 'center'\n siteName: string\n homeId: string | null\n pages: readonly AiLayoutPage[]\n targets: AiLayoutTargets\n extend: (tree: AiValidatedTree) => AiDoctrineViolation[]\n /** The site's design (`aiLayoutDesignOf`), whose pages' closing band the footer sits under. */\n design?: AiLayoutDesign | null\n /** A store's own pages, which the header and footer link by path (AGL-3676). */\n storeLinks?: AiStoreFrameLinks | null\n /** The site's language, which the frame's own words are written in (AGL-3760). */\n language?: string\n /** The header's brand as a site brought in draws it (AGL-4084). */\n brand?: AiLayoutFrameBrand | null\n}): AiGenerationCheck<AiLayoutFrameBuilt> {\n let answers = 0\n return (answer) => {\n // The last answer a generation takes has its gaps taken out rather than asked about again.\n answers += 1\n const last = answers >= AI_GENERATION_MAX_ATTEMPTS\n const frame = aiReadLayoutFrame(answer)\n if (input.headerAlign && frame.header) frame.header.align = input.headerAlign\n const compiled = aiCompileLayoutFrame(\n frame,\n {\n siteName: input.siteName,\n homeId: input.homeId,\n navPages: input.pages,\n closesDark: input.design ? aiLayoutDesignChoices(input.design).coverClose : false,\n // A site that sells carries its cart in the header (AGL-3676).\n cart: (input.targets.listings ?? []).some((listing) => listing.kind === 'products' && listing.cart !== false),\n // And a store's header is a storefront's: no button repeating its navigation, an announcement bar (AGL-3676).\n shopId: aiLayoutShopPageId(input.targets),\n // And its account and policies, written after it (AGL-3676).\n storeLinks: input.storeLinks ?? null,\n ...(input.language ? { language: input.language } : {}),\n ...(input.brand ? { brand: input.brand } : {}),\n },\n input.targets,\n )\n const context = {\n screenIds: [\n ...new Set([\n ...input.targets.pages.map((page) => page.id),\n ...input.pages.map((page) => page.id),\n ...(input.homeId ? [input.homeId] : []),\n ]),\n ],\n formIds: input.targets.forms.map((form) => form.id),\n homeScreenIds: input.targets.homeIds,\n // A link home under the home page's own title says home, in any language (AGL-3760).\n homeLabels: [\n ...input.pages.filter((page) => aiLayoutIsHomeSlug(page.slug)),\n ...input.targets.pages.filter((page) => input.targets.homeIds.includes(page.id)),\n ].map((page) => page.label),\n // A footer's \"Get in touch\" goes to the form, wherever it is placed (AGL-3729).\n ...(input.targets.formPageId ? { formScreenIds: [input.targets.formPageId] } : {}),\n codeBuilt: true,\n }\n const stored = aiLayoutStoredTree(compiled.tree, 'layout', context)\n if (stored.ok === false) {\n console.error('ai layout frame: a compiled layout did not store', {\n error: stored.error,\n })\n return {\n value: null,\n violations: [\n {\n rule: null,\n code: 'layout-compile',\n message: 'This layout could not be built. Try again.',\n detail: stored.error,\n },\n ],\n }\n }\n // No gap and no internal reference in the words of a frame every published page shows.\n const navPages = [...input.targets.pages, ...input.pages]\n const copy = aiLayoutCopyCheck(\n stored.nodes,\n stored.rootId,\n {\n pages: navPages,\n ids: [...navPages.map((page) => page.id), ...input.targets.forms.map((form) => form.id)],\n },\n last,\n )\n const { dropped } = copy\n // An attribute a rule has one answer for is settled, not asked of the model again (AGL-3660).\n const nodes = aiSettleUnrelatedScreenLinks(\n aiSettleMechanicalRules(\n { rootId: stored.rootId, nodes: copy.nodes as unknown as Record<string, AiDoctrineNode> },\n 'layout',\n // And on the last answer, a link that goes nowhere, as the check below reads it (AGL-4076).\n last ? context : undefined,\n ),\n 'layout',\n context,\n ).nodes as unknown as typeof copy.nodes\n // The copy check can take words out of the footer: no heading or column\n // is left with nothing under it (AGL-3735), and none may remain.\n aiLayoutRepairFrameHollows(nodes as unknown as Record<string, unknown>)\n const hollow = aiLayoutFrameHollows(nodes as unknown as Record<string, unknown>)\n const report = validateAiDoctrineTree(\n { rootId: stored.rootId, nodes },\n 'layout',\n context,\n )\n const violations = [\n ...report.violations,\n ...(aiLayoutFrameIsHollow(hollow)\n ? [\n {\n rule: null,\n code: 'layout-frame-hollow',\n message: 'Every footer heading needs something under it, and every footer column more than a heading.',\n detail: aiLayoutFrameHollowsSaid(hollow).join('; '),\n },\n ]\n : []),\n ...copy.violations,\n ...input.extend({\n rootId: stored.rootId,\n nodes,\n repairs: [],\n sourceIds: {},\n score: report.score as AiValidatedTree['score'],\n load: report.load,\n }),\n ]\n return {\n value: violations.length\n ? null\n : { rootId: stored.rootId, nodes, load: report.load, dropped },\n violations,\n }\n }\n}\n\n/** What a frame's links resolve against: the site's pages and its form, as a page's do. */\nexport function aiLayoutFrameTargets(\n job: Pick<AiJob, 'brief' | 'inputs'>,\n inventory: AiSiteInventory | null,\n): AiLayoutTargets {\n return aiLayoutPageTargets({ job, inventory, own: [] })\n}\n"],"names":["aiLayoutDesignChoices","aiCompileLayoutFrame","aiLayoutFrameHollows","aiLayoutFrameHollowsSaid","aiLayoutFrameIsHollow","aiLayoutRepairFrameHollows","AI_LAYOUT_LANGUAGE_TEXT","aiReadLayoutFrame","aiLayoutCopyCheck","aiLayoutStoredTree","AI_GENERATION_MAX_ATTEMPTS","aiLayoutSpeaksOtherThanEnglish","aiHomeScreenIds","AI_STEP_TIERS","aiSettleMechanicalRules","aiSettleUnrelatedScreenLinks","validateAiDoctrineTree","aiSiteKindDesignLines","aiJobStepBudget","fenceUntrustedText","UNTRUSTED_TEXT_RULE","aiJobBriefLine","AI_LAYOUT_SITE_PAGES_MAX","aiLayoutIsHomeSlug","aiLayoutSitePages","AI_LAYOUT_FRAME_THINKING_TOKENS","aiLayoutPageTargets","AI_SITE_NAME_TOKEN","AI_JOB_LAYOUT_LANGUAGE_TOKENS","AI_JOB_LAYOUT_LANGUAGE_BUDGET","tier","maxTokens","AI_LAYOUT_FRAME_KIND","AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS","text","join","cacheBreakpoint","AI_LAYOUT_SITE_BRAND_INPUT","AI_LAYOUT_SITE_FACTS_INPUT","AI_LAYOUT_SITE_FACTS_MAX_CHARS","aiLayoutSiteBrandOf","inputs","raw","value","logo","src","trim","name","aiLayoutSiteFactsOf","lines","Array","isArray","filter","line","description","length","url","slice","aiLayoutSiteFactsText","facts","Boolean","aiLayoutSiteFactsLines","maxChars","aiJobLayoutLanguageInstructions","job","aiLayoutSiteName","given","aiLayoutNavPages","inventory","planned","own","screens","row","template","map","id","label","slug","siteHome","some","page","pages","home","find","plan","language","aiLayoutHomeId","aiLayoutFramePrompt","input","targets","siteName","forms","formPageId","aiLayoutShopPageId","$id","products","listings","listing","kind","placements","placement","role","screenId","aiLayoutFrameCheck","answers","answer","last","frame","headerAlign","header","align","compiled","homeId","navPages","closesDark","design","coverClose","cart","shopId","storeLinks","brand","context","screenIds","Set","formIds","form","homeScreenIds","homeIds","homeLabels","includes","formScreenIds","codeBuilt","stored","tree","ok","console","error","violations","rule","code","message","detail","copy","nodes","rootId","ids","dropped","undefined","hollow","report","extend","repairs","sourceIds","score","load","aiLayoutFrameTargets"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAGD,SAASA,qBAAqB,QAA6B,yCAAqC;AAChG,SAASC,oBAAoB,QAAiC,wCAAoC;AAClG,SACEC,oBAAoB,EACpBC,wBAAwB,EACxBC,qBAAqB,EACrBC,0BAA0B,QACrB,gDAA4C;AACnD,SAEEC,uBAAuB,EACvBC,iBAAiB,QACZ,2CAAuC;AAK9C,SAASC,iBAAiB,QAAQ,uCAAmC;AACrE,SAASC,kBAAkB,QAAQ,wCAAoC;AACvE,SAASC,0BAA0B,QAAQ,qCAAiC;AAE5E,SAASC,8BAA8B,QAAQ,yCAAqC;AACpF,SACEC,eAAe,QAEV,gCAA4B;AACnC,SAASC,aAAa,QAAQ,0BAAsB;AAEpD,SACEC,uBAAuB,EACvBC,4BAA4B,EAC5BC,sBAAsB,QAGjB,uCAAmC;AAE1C,SAASC,qBAAqB,QAAQ,4BAAwB;AAG9D,SAASC,eAAe,QAAQ,qBAAiB;AACjD,SAASC,kBAAkB,EAAEC,mBAAmB,QAAQ,0CAAyC;AACjG,SAASC,cAAc,QAAQ,yBAAqB;AACpD,SACEC,wBAAwB,EACxBC,kBAAkB,EAClBC,iBAAiB,QACZ,gCAA4B;AACnC,SACEC,+BAA+B,EAC/BC,mBAAmB,QACd,4BAAwB;AAC/B,SAASC,kBAAkB,QAAQ,4BAAwB;AAE3D;;;;;;;;;CASC,GAED;;;CAGC,GACD,OAAO,MAAMC,gCAAgC,OAAQH,gCAA+B;AAEpF,+EAA+E,GAC/E,OAAO,MAAMI,gCAAgCX,gBAAgB;IAC3DY,MAAMjB,aAAa,CAAC,aAAa;IACjCkB,WAAWH;AACb,GAAE;AAEF,2EAA2E,GAC3E,OAAO,MAAMI,uBAAuB,eAAc;AAElD,2DAA2D,GAC3D,OAAO,MAAMC,sCAAgE;IAC3E;QACEC,MAAM;YACJ5B;YACA;YACA;YACA;YACA;SACD,CAAC6B,IAAI,CAAC;QACPC,iBAAiB;IACnB;CACD,CAAA;AAED;;;;;CAKC,GACD,OAAO,MAAMC,6BAA6B,YAAW;AACrD,OAAO,MAAMC,6BAA6B,YAAW;AAErD,kFAAkF,GAClF,OAAO,MAAMC,iCAAiC,KAAK;AAWnD,OAAO,SAASC,oBAAoBC,MAA4D;IAC9F,MAAMC,MAAMD,0BAAAA,MAAQ,CAACJ,2BAA2B;IAChD,IAAI,CAACK,OAAO,OAAOA,QAAQ,UAAU,OAAO;IAC5C,MAAMC,QAAQD;IACd,MAAME,OAAOD,KAAK,CAAC,OAAO;IAC1B,MAAME,MAAM,QAAOD,wBAAAA,IAAM,CAAC,MAAM,MAAK,YAAYA,IAAI,CAAC,MAAM,CAACE,IAAI,KAAKF,IAAI,CAAC,MAAM,CAACE,IAAI,KAAK;IAC3F,OAAO,aAAMD,MAAM;QAAED,MAAM;YAAEC;QAAI;IAAE,IAAI,CAAC;QAAIE,MAAMF,MAAMF,KAAK,CAAC,OAAO,KAAK,OAAO;;AACnF;AAEA,OAAO,SAASK,oBAAoBP,MAA4D;IAC9F,MAAMC,MAAMD,0BAAAA,MAAQ,CAACH,2BAA2B;IAChD,IAAI,CAACI,OAAO,OAAOA,QAAQ,UAAU,OAAO;IAC5C,MAAMC,QAAQD;IACd,MAAMO,QAAQC,MAAMC,OAAO,CAACR,KAAK,CAAC,QAAQ,IAAIA,KAAK,CAAC,QAAQ,CAACS,MAAM,CAAC,CAACC,OAAyB,OAAOA,SAAS,YAAY,CAAC,CAACA,KAAKP,IAAI,MAAM,EAAE;IAC7I,MAAMQ,cAAc,OAAOX,KAAK,CAAC,cAAc,KAAK,WAAWA,KAAK,CAAC,cAAc,GAAG;IACtF,IAAI,CAACM,MAAMM,MAAM,IAAI,CAACD,YAAYR,IAAI,IAAI,OAAO;IACjD,OAAO;QAAEU,KAAK,OAAOb,KAAK,CAAC,MAAM,KAAK,WAAWA,KAAK,CAAC,MAAM,CAACc,KAAK,CAAC,GAAG,QAAQ;QAAIH;QAAaL;IAAM;AACxG;AAEA,gHAAgH,GAChH,OAAO,SAASS,sBAAsBjB,MAA4D;IAChG,MAAMkB,QAAQX,oBAAoBP;IAClC,OAAOkB,QAAQ;QAACA,MAAML,WAAW;WAAKK,MAAMV,KAAK;KAAC,CAACG,MAAM,CAACQ,SAASzB,IAAI,CAAC,QAAQ;AAClF;AAEA;;;;CAIC,GACD,OAAO,SAAS0B,uBAAuBF,KAA+B;IACpE,IAAI,CAACA,OAAO,OAAO,EAAE;IACrB,MAAMzB,OAAO;QAACyB,MAAML,WAAW,GAAG,CAAC,kCAAkC,EAAEK,MAAML,WAAW,EAAE,GAAG;WAAOK,MAAMV,KAAK;KAAC,CAACG,MAAM,CAACQ,SAASzB,IAAI,CAAC;IACtI,OAAO;QACL;QACAhB,mBAAmB,CAAC,yBAAyB,EAAEwC,MAAMH,GAAG,IAAI,oBAAoB,EAAEtB,MAAM;YAAE4B,UAAUvB;QAA+B;KACpI;AACH;AAEA,4HAA4H,GAC5H,OAAO,SAASwB,gCAAgCC,GAA0B;IACxE,OAAOhB,oBAAoBgB,IAAIvB,MAAM,IACjC;WAAIR;QAAqC;YAAEC,MAAMd;QAAoB;KAAE,GACvEa;AACN;AAEA,wGAAwG,GACxG,OAAO,SAASgC,iBAAiBD,GAA0B;QAC3CA;IAAd,MAAME,SAAQF,cAAAA,IAAIvB,MAAM,qBAAVuB,WAAY,CAAC,eAAe;IAC1C,OAAO,OAAOE,UAAU,YAAYA,MAAMpB,IAAI,KAC1CoB,MAAMpB,IAAI,KACVnB;AACN;AAEA,4FAA4F,GAC5F,OAAO,SAASwC,iBACdH,GAA0E,EAC1EI,SAAiC;;QAiBYJ;IAf7C,wEAAwE;IACxE,0EAA0E;IAC1E,wEAAwE;IACxE,2EAA2E;IAC3E,+BAA+B;IAC/B,MAAMK,UAAU7C,kBAAkBwC,IAAIvB,MAAM;IAC5C,MAAM6B,MAAM,SAACF,6BAAAA,UAAWG,OAAO,mBAAI,EAAE,EAClCnB,MAAM,CAAC,CAACoB,MAAQ,CAACA,IAAIC,QAAQ,EAC7BC,GAAG,CAAC,CAACF,MAAS,CAAA;YAAEG,IAAIH,IAAIG,EAAE;YAAEC,OAAOJ,IAAIzB,IAAI;YAAE8B,MAAML,IAAIK,IAAI;QAAC,CAAA;IAC/D,MAAMC,WAAWT,QAAQU,IAAI,CAAC,CAACC,OAASzD,mBAAmByD,KAAKH,IAAI,KAChE,EAAE,GACFP,IAAIlB,MAAM,CAAC,CAAC4B,OAASzD,mBAAmByD,KAAKH,IAAI,GAAGpB,KAAK,CAAC,GAAG;IACjE,MAAMwB,QAAQ;WAAIH;WAAcT,QAAQd,MAAM,GAAGc,UAAUC;KAAK,CAAClB,MAAM,CAAC,CAAC4B,OAASA,KAAKJ,KAAK,CAAC9B,IAAI;IACjG,MAAMoC,OAAOD,MAAME,IAAI,CAAC,CAACH,OAASzD,mBAAmByD,KAAKH,IAAI;IAC9D,OAAO;WACDK,OAAO;YAACvE,gCAA+BqD,YAAAA,IAAIoB,IAAI,qBAARpB,UAAUqB,QAAQ,IAAIH,OAAO,aAAKA;gBAAMN,OAAO;;SAAS,GAAG,EAAE;WACrGK,MAAM7B,MAAM,CAAC,CAAC4B,OAASA,SAASE,QAAQ,CAAC3D,mBAAmByD,KAAKH,IAAI;KACzE,CAACpB,KAAK,CAAC,GAAGnC;AACb;AAEA,sEAAsE,GACtE,OAAO,SAASgE,eACdL,KAA8B,EAC9Bb,SAAiC;QAG1BC;IADP,MAAMA,UAAUY,MAAME,IAAI,CAAC,CAACH,OAASzD,mBAAmByD,KAAKH,IAAI;IACjE,QAAOR,gBAAAA,2BAAAA,QAASM,EAAE,oBAAI/D,gBAAgBwD,UAAU,CAAC,EAAE,YAA5CC,OAAgD;AACzD;AAEA,wEAAwE,GACxE,OAAO,SAASkB,oBAAoBC,KAKnC;IACC,MAAM,EAAExB,GAAG,EAAEiB,KAAK,EAAEQ,OAAO,EAAE,GAAGD;IAChC,iGAAiG;IACjG,MAAM7B,QAAQX,oBAAoBgB,IAAIvB,MAAM;IAC5C,OAAO;QACL,CAAC,MAAM,EAAE+C,MAAME,QAAQ,KAAK/D,qBAAqB,qCAAqC,CAAC,CAAC,EAAE6D,MAAME,QAAQ,CAAC,CAAC,CAAC,EAAE;WACzG/B,QAAQE,uBAAuBF,SAAS;YAACtC,eAAe2C;SAAK;WAC9D/C,sBAAsB+C,IAAIvB,MAAM;QACnCwC,MAAM1B,MAAM,GACR,CAAC,uCAAuC,EAAE0B,MAAMP,GAAG,CAAC,CAACM,OAAS,GAAGA,KAAKJ,KAAK,CAAC,OAAO,EAAEI,KAAKL,EAAE,CAAC,CAAC,CAAC,EAAExC,IAAI,CAAC,MAAM,CAAC,CAAC,GAC9G;QACJsD,QAAQE,KAAK,CAACpC,MAAM,IAAIkC,QAAQG,UAAU,GACtC,uEACA;QACJ,wEAAwE;WACpEC,mBAAmBJ,WACnB;YACE;SACD,GACD,EAAE;QACN,CAAC,aAAa,EAAEzB,IAAI8B,GAAG,CAACrC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC;KACrC,CAACtB,IAAI,CAAC;AACT;AAEA,+FAA+F,GAC/F,OAAO,SAAS0D,mBAAmBJ,OAA0C;QACzDA;QACXM;IADP,MAAMA,WAAW,EAACN,oBAAAA,QAAQO,QAAQ,YAAhBP,oBAAoB,EAAE,EAAEN,IAAI,CAAC,CAACc,UAAYA,QAAQC,IAAI,KAAK;IAC7E,eAAOH,6BAAAA,4BAAAA,SAAUI,UAAU,CAAChB,IAAI,CAAC,CAACiB,YAAcA,UAAUC,IAAI,KAAK,6BAA5DN,0BAAsEO,QAAQ,mBAAI;AAC3F;AAWA,4HAA4H,GAC5H,OAAO,SAASC,mBAAmBf,KAgBlC;IACC,IAAIgB,UAAU;IACd,OAAO,CAACC;YAcKjB,yBAIKA;QAjBhB,2FAA2F;QAC3FgB,WAAW;QACX,MAAME,OAAOF,WAAW9F;QACxB,MAAMiG,QAAQpG,kBAAkBkG;QAChC,IAAIjB,MAAMoB,WAAW,IAAID,MAAME,MAAM,EAAEF,MAAME,MAAM,CAACC,KAAK,GAAGtB,MAAMoB,WAAW;QAC7E,MAAMG,WAAW9G,qBACf0G,OACA;YACEjB,UAAUF,MAAME,QAAQ;YACxBsB,QAAQxB,MAAMwB,MAAM;YACpBC,UAAUzB,MAAMP,KAAK;YACrBiC,YAAY1B,MAAM2B,MAAM,GAAGnH,sBAAsBwF,MAAM2B,MAAM,EAAEC,UAAU,GAAG;YAC5E,+DAA+D;YAC/DC,MAAM,EAAC7B,0BAAAA,MAAMC,OAAO,CAACO,QAAQ,YAAtBR,0BAA0B,EAAE,EAAET,IAAI,CAAC,CAACkB,UAAYA,QAAQC,IAAI,KAAK,cAAcD,QAAQoB,IAAI,KAAK;YACvG,8GAA8G;YAC9GC,QAAQzB,mBAAmBL,MAAMC,OAAO;YACxC,6DAA6D;YAC7D8B,UAAU,GAAE/B,oBAAAA,MAAM+B,UAAU,YAAhB/B,oBAAoB;WAC5BA,MAAMH,QAAQ,GAAG;YAAEA,UAAUG,MAAMH,QAAQ;QAAC,IAAI,CAAC,GACjDG,MAAMgC,KAAK,GAAG;YAAEA,OAAOhC,MAAMgC,KAAK;QAAC,IAAI,CAAC,IAE9ChC,MAAMC,OAAO;QAEf,MAAMgC,UAAU;YACdC,WAAW;mBACN,IAAIC,IAAI;uBACNnC,MAAMC,OAAO,CAACR,KAAK,CAACP,GAAG,CAAC,CAACM,OAASA,KAAKL,EAAE;uBACzCa,MAAMP,KAAK,CAACP,GAAG,CAAC,CAACM,OAASA,KAAKL,EAAE;uBAChCa,MAAMwB,MAAM,GAAG;wBAACxB,MAAMwB,MAAM;qBAAC,GAAG,EAAE;iBACvC;aACF;YACDY,SAASpC,MAAMC,OAAO,CAACE,KAAK,CAACjB,GAAG,CAAC,CAACmD,OAASA,KAAKlD,EAAE;YAClDmD,eAAetC,MAAMC,OAAO,CAACsC,OAAO;YACpC,qFAAqF;YACrFC,YAAY;mBACPxC,MAAMP,KAAK,CAAC7B,MAAM,CAAC,CAAC4B,OAASzD,mBAAmByD,KAAKH,IAAI;mBACzDW,MAAMC,OAAO,CAACR,KAAK,CAAC7B,MAAM,CAAC,CAAC4B,OAASQ,MAAMC,OAAO,CAACsC,OAAO,CAACE,QAAQ,CAACjD,KAAKL,EAAE;aAC/E,CAACD,GAAG,CAAC,CAACM,OAASA,KAAKJ,KAAK;WAEtBY,MAAMC,OAAO,CAACG,UAAU,GAAG;YAAEsC,eAAe;gBAAC1C,MAAMC,OAAO,CAACG,UAAU;aAAC;QAAC,IAAI,CAAC;YAChFuC,WAAW;;QAEb,MAAMC,SAAS3H,mBAAmBsG,SAASsB,IAAI,EAAE,UAAUZ;QAC3D,IAAIW,OAAOE,EAAE,KAAK,OAAO;YACvBC,QAAQC,KAAK,CAAC,oDAAoD;gBAChEA,OAAOJ,OAAOI,KAAK;YACrB;YACA,OAAO;gBACL7F,OAAO;gBACP8F,YAAY;oBACV;wBACEC,MAAM;wBACNC,MAAM;wBACNC,SAAS;wBACTC,QAAQT,OAAOI,KAAK;oBACtB;iBACD;YACH;QACF;QACA,uFAAuF;QACvF,MAAMvB,WAAW;eAAIzB,MAAMC,OAAO,CAACR,KAAK;eAAKO,MAAMP,KAAK;SAAC;QACzD,MAAM6D,OAAOtI,kBACX4H,OAAOW,KAAK,EACZX,OAAOY,MAAM,EACb;YACE/D,OAAOgC;YACPgC,KAAK;mBAAIhC,SAASvC,GAAG,CAAC,CAACM,OAASA,KAAKL,EAAE;mBAAMa,MAAMC,OAAO,CAACE,KAAK,CAACjB,GAAG,CAAC,CAACmD,OAASA,KAAKlD,EAAE;aAAE;QAC1F,GACA+B;QAEF,MAAM,EAAEwC,OAAO,EAAE,GAAGJ;QACpB,8FAA8F;QAC9F,MAAMC,QAAQhI,6BACZD,wBACE;YAAEkI,QAAQZ,OAAOY,MAAM;YAAED,OAAOD,KAAKC,KAAK;QAA8C,GACxF,UACA,4FAA4F;QAC5FrC,OAAOe,UAAU0B,YAEnB,UACA1B,SACAsB,KAAK;QACP,wEAAwE;QACxE,iEAAiE;QACjE1I,2BAA2B0I;QAC3B,MAAMK,SAASlJ,qBAAqB6I;QACpC,MAAMM,SAASrI,uBACb;YAAEgI,QAAQZ,OAAOY,MAAM;YAAED;QAAM,GAC/B,UACAtB;QAEF,MAAMgB,aAAa;eACdY,OAAOZ,UAAU;eAChBrI,sBAAsBgJ,UACtB;gBACE;oBACEV,MAAM;oBACNC,MAAM;oBACNC,SAAS;oBACTC,QAAQ1I,yBAAyBiJ,QAAQjH,IAAI,CAAC;gBAChD;aACD,GACD,EAAE;eACH2G,KAAKL,UAAU;eACfjD,MAAM8D,MAAM,CAAC;gBACdN,QAAQZ,OAAOY,MAAM;gBACrBD;gBACAQ,SAAS,EAAE;gBACXC,WAAW,CAAC;gBACZC,OAAOJ,OAAOI,KAAK;gBACnBC,MAAML,OAAOK,IAAI;YACnB;SACD;QACD,OAAO;YACL/G,OAAO8F,WAAWlF,MAAM,GACpB,OACA;gBAAEyF,QAAQZ,OAAOY,MAAM;gBAAED;gBAAOW,MAAML,OAAOK,IAAI;gBAAER;YAAQ;YAC/DT;QACF;IACF;AACF;AAEA,yFAAyF,GACzF,OAAO,SAASkB,qBACd3F,GAAoC,EACpCI,SAAiC;IAEjC,OAAO1C,oBAAoB;QAAEsC;QAAKI;QAAWE,KAAK,EAAE;IAAC;AACvD"}
|
|
@@ -34,7 +34,7 @@ import { aiLayoutSitePages, aiLayoutSitePagesLines, aiLayoutWithSitePages } from
|
|
|
34
34
|
import { aiLayoutInventedContactViolations, aiLayoutWithSiteName } from "./ai-layout-site-facts.js";
|
|
35
35
|
import { aiLayoutWithFullHeight } from "./ai-layout-full-height.js";
|
|
36
36
|
import { AI_LAYOUT_FRAME_TOOL } from "../layout-language/ai-layout-language.js";
|
|
37
|
-
import { AI_JOB_LAYOUT_LANGUAGE_BUDGET,
|
|
37
|
+
import { AI_JOB_LAYOUT_LANGUAGE_BUDGET, AI_LAYOUT_FRAME_KIND, aiJobLayoutLanguageInstructions, aiLayoutFrameCheck, aiLayoutFramePrompt, aiLayoutFrameTargets, aiLayoutHomeId, aiLayoutNavPages, aiLayoutSiteBrandOf, aiLayoutSiteFactsText, aiLayoutSiteName } from "./ai-job-layout-language.js";
|
|
38
38
|
import { AI_LAYOUT_LANGUAGE_THINKING, aiJobUsesLayoutLanguage, aiLayoutDesignOf } from "./ai-job-page-language.js";
|
|
39
39
|
import { aiJobStepBudget } from "./ai-job-budget.js";
|
|
40
40
|
import { registerAiJobStep } from "./ai-jobs.js";
|
|
@@ -345,7 +345,7 @@ export function createAiJobLayoutStep(deps = {}) {
|
|
|
345
345
|
const result = await runValidatedGeneration(AI_LAYOUT_FRAME_KIND, _extends({
|
|
346
346
|
step: 'job.layout',
|
|
347
347
|
model,
|
|
348
|
-
instructions:
|
|
348
|
+
instructions: aiJobLayoutLanguageInstructions(job),
|
|
349
349
|
inventory,
|
|
350
350
|
messages: [
|
|
351
351
|
{
|
|
@@ -366,8 +366,13 @@ export function createAiJobLayoutStep(deps = {}) {
|
|
|
366
366
|
homeId: aiLayoutHomeId(pages, inventory),
|
|
367
367
|
pages,
|
|
368
368
|
targets,
|
|
369
|
-
|
|
369
|
+
// A site brought in may state what its own header and footer state (AGL-4084).
|
|
370
|
+
extend: aiLayoutChecks(inventory, plan, [
|
|
371
|
+
job.brief,
|
|
372
|
+
aiLayoutSiteFactsText(job.inputs)
|
|
373
|
+
].filter(Boolean).join('\n')),
|
|
370
374
|
design: aiLayoutDesignOf(job, '/'),
|
|
375
|
+
brand: aiLayoutSiteBrandOf(job.inputs),
|
|
371
376
|
storeLinks: aiStoreFrameLinksOf(job.inputs)
|
|
372
377
|
}, ((_job_plan = job.plan) == null ? void 0 : _job_plan.language) ? {
|
|
373
378
|
language: job.plan.language
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-layout-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 { throwIfAiJobCanceled } from './ai-job-cancel'\nimport { aiSiteStyleTokens } from '../model/ai-site-kinds'\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 type { AiJob, AiJobOutput, AiJobPlan, AiJobReview } from '../model/ai-jobs.types'\nimport { aiHomeScreenIds, type AiSiteInventory } from '../model/ai-site-inventory'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n aiDoctrineTreeTool,\n runValidatedGeneration,\n type AiValidatedTree,\n} from '../runtime/ai-doctrine'\nimport {\n walkTree,\n type AiDoctrineNode,\n type AiDoctrineTree,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { readSiteInventory } from '../runtime/site-inventory'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport {\n aiBuildingPlanCreations,\n aiJobPlanCreationsRefusal,\n aiPlanCreationsRunMinimumMs,\n} from './ai-job-plan-creations'\nimport { aiJobDraftId, aiOriginJobId } from './ai-job-draft-ids'\nimport {\n aiDraftAdmissionRefusal,\n aiDraftAllowanceRefusal,\n aiSiteSubdomain,\n readAiDraft,\n readAiDraftNodes,\n writeAiDraft,\n type AiDraftRecord,\n} from './ai-job-drafts'\nimport {\n aiBracketedFactsNote,\n aiCopySourceLines,\n aiConfirmedPlan,\n aiDoctrineReview,\n aiGenerationSpent,\n aiJobBriefLine,\n aiLimitReview,\n aiModelNodeIds,\n aiPlacedComponentIds,\n aiPlanCreation,\n aiPlanReferenceLines,\n aiPlanReuseViolations,\n aiUnspentOutcome,\n} from './ai-job-generation'\nimport { aiPlanRegionViolations, aiPlannedLayoutRegions } from './ai-job-plan-conformance'\nimport { aiLayoutSitePages, aiLayoutSitePagesLines, aiLayoutWithSitePages } from './ai-job-layout-site-pages'\nimport { aiLayoutInventedContactViolations, aiLayoutWithSiteName } from './ai-layout-site-facts'\nimport { aiLayoutWithFullHeight } from './ai-layout-full-height'\nimport type { AiJobStepRunner } from './ai-job-text-step'\nimport { AI_LAYOUT_FRAME_TOOL } from '../layout-language/ai-layout-language'\nimport {\n AI_JOB_LAYOUT_LANGUAGE_BUDGET,\n AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS,\n AI_LAYOUT_FRAME_KIND,\n aiLayoutFrameCheck,\n aiLayoutFramePrompt,\n aiLayoutFrameTargets,\n aiLayoutHomeId,\n aiLayoutNavPages,\n aiLayoutSiteName,\n type AiLayoutFrameBuilt,\n} from './ai-job-layout-language'\nimport { AI_LAYOUT_LANGUAGE_THINKING, aiJobUsesLayoutLanguage, aiLayoutDesignOf } from './ai-job-page-language'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { registerAiJobStep } from './ai-jobs'\nimport { aiStoreFrameLinksOf } from '../model/ai-site-store-pages'\n\n/**\n * The layout step (AGL-2909): the generation step of a `layout` job, run once\n * a member confirmed the job's plan.\n *\n * It builds ONE shared layout — a header with the site's navigation, the\n * Layout Slot each page renders in, a footer — through\n * `runValidatedGeneration('layout', …)`, so every building rule holds on the\n * tree before it is kept, and adds three checks of its own through `extend`: a\n * component the confirmed plan reuses is placed (rule 7), every region the\n * plan gives the layout is built (AGL-3024), and a navigation or menu\n * component the site already has is placed rather than drawn again (rule 1).\n *\n * The layout lands as a new draft (`ai-job-drafts.ts`): no screen uses it and\n * no layout nests it, so nothing on the live site changes until a member\n * assigns it. A plan that starts from a copy of a layout the site has gets\n * that copy, through the platform's duplicate module, and nothing generated —\n * so the copy is read back and held to the plan's regions the same way\n * (`aiCopiedLayoutReview`), because a branch that generates nothing is the one\n * branch where a promise can go unmet with no model to answer for it. A\n * source that already lacks a region the plan gives the layout is not copied\n * at all: the layout is built from it (AGL-3143 §14).\n *\n * A generation step runs on the job beat, never at an inline door: it\n * registers the least time its lookup rounds, its answer and its re-ask need\n * (AGL-3035), runs without extended thinking — the plan was reasoned out and\n * confirmed before it — and asks for no more of a tighter ceiling than the\n * doctrine's default for a layout than fits that time on the model it runs.\n */\n\n/** The longest answer a layout may run to; the tree is held to the layout budget either way. */\nexport const AI_JOB_LAYOUT_MAX_TOKENS = AI_ROUTING_TABLE['job.layout'].maxTokens\n\n/**\n * The layout step's time (AGL-3035, AGL-3036): its two inventory-lookup\n * rounds, its answer and its re-ask at the routing table's ceiling on the tier\n * `job.layout` is served from, fitted to what a beat can give a step.\n */\nexport const AI_JOB_LAYOUT_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.layout'],\n maxTokens: AI_JOB_LAYOUT_MAX_TOKENS,\n})\n\n/** The least time one layout step needs before it starts. */\nexport const AI_JOB_LAYOUT_STEP_MINIMUM_MS = AI_JOB_LAYOUT_STEP_BUDGET.minimumMs\n\n/** A layout's name when the plan names none. */\nexport const AI_JOB_LAYOUT_DEFAULT_NAME = 'Site layout'\n\n/** The layout step's own instructions, cached after the doctrine. */\nexport const AI_JOB_LAYOUT_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n 'You build one shared layout for a website: the frame every page of the site renders inside. Answer with submit_layout: the whole layout as one flat node map.',\n 'A layout is a header, one Layout Slot and a footer, top to bottom. The header is an App Bar (muiAppBar) holding a Toolbar Content (muiToolbar) with the site’s name as text and its navigation. The footer is a Section (section) whose element is footer.',\n 'Navigation links the site’s own pages by id: a Page Link (muiScreenLink) whose screenId is a page id from the site inventory, in the order the brief and the confirmed plan give, seven at most in the header. Never invent a page id, and never link a page by its address.',\n 'When the site inventory lists a component whose name says it is navigation, a menu or a mega menu, place it in the header as an instance instead of building the navigation again. Place every component the confirmed plan reuses.',\n 'The Layout Slot (layoutSlot) is where each page renders and is the page’s main landmark: leave its component unset or main, and put nothing inside it. A layout carries no h1, because every page brings its own.',\n ].join('\\n'),\n },\n]\n\n/**\n * The layout job as the generation's user turn: the name, the brief, the\n * confirmed plan, and the layout a short copy would have started from\n * (`aiCopySourceLines`, AGL-3143 §14).\n */\nexport function aiJobLayoutPrompt(\n job: Pick<AiJob, 'brief'> & Partial<Pick<AiJob, 'inputs'>>,\n plan: AiJobPlan | null,\n name: string,\n source: readonly string[] = [],\n): string {\n return [\n `Layout name: ${name}`,\n aiJobBriefLine(job),\n ...aiPlanReferenceLines(plan),\n // A scaffold's layout is built before its pages, whose navigation the platform writes (AGL-3596).\n ...aiLayoutSitePagesLines(aiLayoutSitePages(job.inputs)),\n ...source,\n ].join('\\n')\n}\n\n/** A component name that says it is the site's navigation; a footer's links are not. */\nconst NAVIGATION_NAME = /\\b(?:nav|navigation|navbar|menu|mega ?menu)\\b/i\nconst FOOTER_NAME = /\\bfooter\\b/i\n\n/** A node that links somewhere: a Screen Link, or a Button pointed at a screen or an address. */\nfunction isLink(node: AiDoctrineNode): boolean {\n if (node.componentId === 'muiScreenLink') return true\n return (\n node.componentId === 'muiButton' &&\n (typeof node.props?.['screenId'] === 'string' || typeof node.props?.['href'] === 'string')\n )\n}\n\n/**\n * The layout door's checks on a tree the doctrine admitted: every component\n * the confirmed plan reuses is placed, every region the confirmed plan gives\n * the layout is built (AGL-3024), and navigation the site already keeps as a\n * component is placed rather than drawn again in the header.\n */\nexport function aiLayoutReuseCheck(\n inventory: AiSiteInventory | null,\n plan: AiJobPlan | null,\n): (tree: AiValidatedTree) => AiDoctrineViolation[] {\n const components = inventory?.components ?? []\n const navigation = components.filter(\n (component) => NAVIGATION_NAME.test(component.name) && !FOOTER_NAME.test(component.name),\n )\n return (tree) => {\n const placed = aiPlacedComponentIds(tree)\n const violations: AiDoctrineViolation[] = [\n ...aiPlanReuseViolations(inventory, plan, placed, 'layout'),\n ...aiPlanRegionViolations(plan, {\n rootId: tree.rootId,\n nodes: tree.nodes as unknown as Record<string, AiDoctrineNode>,\n }),\n ]\n if (navigation.length && !navigation.some((component) => placed.has(component.id))) {\n const visits = walkTree({\n rootId: tree.rootId,\n nodes: tree.nodes as unknown as Record<string, AiDoctrineNode>,\n })\n const slot = visits.findIndex(({ node }) => node.componentId === 'layoutSlot')\n const header = slot === -1 ? visits : visits.slice(0, slot)\n const links = header.filter(({ node }) => isLink(node)).map(({ id }) => id)\n if (links.length >= 2) {\n violations.push({\n rule: 1,\n code: 'navigation-rebuilt',\n message: `The site already has the \"${navigation[0].name}\" component. Place it in the header instead of building the navigation again.`,\n nodeIds: aiModelNodeIds(links, tree.sourceIds),\n })\n }\n }\n return violations\n }\n}\n\n/**\n * Every check the layout door adds to the doctrine's: the reuse and region\n * checks above, and no contact detail the brief does not give (AGL-3596).\n */\nexport function aiLayoutChecks(\n inventory: AiSiteInventory | null,\n plan: AiJobPlan | null,\n brief: string,\n): (tree: AiValidatedTree) => AiDoctrineViolation[] {\n const reuse = aiLayoutReuseCheck(inventory, plan)\n return (tree) => [\n ...reuse(tree),\n ...aiLayoutInventedContactViolations(\n { rootId: tree.rootId, nodes: tree.nodes as unknown as Record<string, AiDoctrineNode> },\n brief,\n ).map((violation) => ({ ...violation, nodeIds: aiModelNodeIds(violation.nodeIds ?? [], tree.sourceIds) })),\n ]\n}\n\n/**\n * The regions the confirmed plan gives the layout, against the layout a COPY\n * actually produced (AGL-3024); `null` when the copy keeps the plan, or when\n * the plan promised no region to keep.\n *\n * The duplicate branch generates nothing, so it is the one place a plan's\n * promise can go unmet without a model ever answering for it. A copy that\n * cannot be read — deleted between the write and this read, or stored under a\n * root this check does not know — settles nothing, and a promise this step\n * cannot settle is reviewed by a person rather than reported as kept.\n */\nexport async function aiCopiedLayoutReview(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; id: string; name: string; plan: AiJobPlan | null },\n): Promise<AiJobReview | null> {\n const planned = aiPlannedLayoutRegions(input.plan)\n if (!planned.regions.length && !planned.unreadable.length) return null\n const stored = await readAiDraftNodes(firestore, {\n kind: 'layout',\n hostId: input.hostId,\n id: input.id,\n })\n const nodes = (stored?.nodes ?? {}) as unknown as Record<string, AiDoctrineNode>\n if (!nodes[CANVAS_ROOT_ELEMENT_ID]) {\n return {\n reason: 'doctrine',\n message: `\"${input.name}\" was copied from the layout the plan names, and this job could not read the copy back to check it has the regions the plan gives it. Open the layout and check it before you put a page in it.`,\n findings: [],\n }\n }\n const violations = aiPlanRegionViolations(input.plan, { rootId: CANVAS_ROOT_ELEMENT_ID, nodes })\n if (!violations.length) return null\n return aiDoctrineReview({\n message: `\"${input.name}\" is a copy of the layout the plan names, and a copy is all it is: ${violations[0].message}`,\n violations,\n })\n}\n\n/**\n * A layout job is admitted for a site of the job's own org with a shared\n * layout to spare, and confirmed only for a plan whose other creations it\n * builds (AGL-3143 §15).\n */\nexport const aiLayoutJobAdmission: AiJobAdmission = (context) =>\n aiDraftAdmissionRefusal(context.firestore, {\n orgId: context.orgId,\n hostId: context.hostId,\n kind: 'layout',\n org: context.org,\n ownCheck: (hostId) => aiJobPlanCreationsRefusal('layout', context, hostId),\n })\n\nexport interface AiJobLayoutStepDeps {\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}\n\nexport function createAiJobLayoutStep(deps: AiJobLayoutStepDeps = {}): AiJobStepRunner {\n const readInventory = deps.readInventory ?? readSiteInventory\n const duplicate = deps.duplicate ?? duplicateResource\n return async ({ job, now, signal, firestore, modelFor }) => {\n const hostId = job.hostId\n if (!hostId) throw new Error('a layout job names no site, and its admission refuses one')\n const output = (draft: AiDraftRecord, load?: AiLoadEstimate | null, note?: string | null): AiJobOutput => ({\n resource: 'layout',\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 })\n // The switch's answer for this job, else the routing table's (AGL-2942).\n const model = modelFor?.('job.layout') ?? aiModelForStep('job.layout')\n\n // A run cut off after its draft was written reports that draft, found by the id the job recorded.\n const draftId = aiJobDraftId(job, 'layout')\n const written = await readAiDraft(firestore, { kind: 'layout', hostId, id: draftId })\n if (written) return aiUnspentOutcome(model, { outputs: [output(written)] })\n\n const [inventory, orgSnapshot] = await Promise.all([\n readInventory(job.orgId, hostId, { firestore }),\n firestore.collection('orgs').doc(job.orgId).get(),\n ])\n const org = (orgSnapshot.data() ?? null) as Partial<AglynOrgBilling> | null\n const plan = aiConfirmedPlan(job)\n const creation = aiPlanCreation(plan, 'layout')\n const name = creation?.name || AI_JOB_LAYOUT_DEFAULT_NAME\n\n // The plan starts from a copy of a layout the site has (rule 15): the copy\n // is the draft, and nothing is generated. A source gone since the plan\n // was made is built from the brief instead. A source that cannot keep the\n // plan's regions is built FROM instead (AGL-3143 §14), as the page step\n // builds from a short source: copying it could only end in the review\n // below, and Try again would copy the same thing. Only a source read and\n // found short diverts; one this step cannot read still copies, and the\n // review after the copy answers for it.\n const source = creation?.duplicateOf\n ? inventory.layouts.find((layout) => layout.id === creation.duplicateOf)\n : undefined\n const sourceReview = source\n ? await aiCopiedLayoutReview(firestore, { hostId, id: source.id, plan, name: source.name })\n : null\n const sourceShort = Boolean(sourceReview?.findings.length)\n const sourceTree =\n source && sourceShort ? await readAiDraftNodes(firestore, { kind: 'layout', hostId, id: source.id }) : null\n const sourceLines =\n source && sourceShort ? aiCopySourceLines('layout', source.name, sourceTree?.nodes ?? null) : []\n if (creation?.duplicateOf && source && !sourceShort) {\n const copy = await duplicate('layout', {\n orgId: job.orgId,\n hostId,\n sourceId: creation.duplicateOf,\n name,\n uid: job.createdBy,\n org: org as Record<string, unknown> | null,\n // The same claim the page step makes (AGL-3024): a copy core mints is\n // written under core's own id, never `draftId`, so a step that runs\n // again — a pass resumed, a run cut off after the copy — would mint a\n // second. The job's recorded id makes core replay the one it made.\n attemptKey: draftId,\n })\n if (copy.ok) {\n // A copy is not a build (AGL-3024). This branch generates nothing, so\n // whatever the plan promised BEYOND the source is not in the draft\n // unless the source already had it: a plan reading \"from a copy of the\n // base layout, adding a sidebar\" gets the base layout, the sidebar\n // nowhere in it, and until now reported Done. The copy's own regions\n // answer the plan's, and a copy that cannot be read answers nothing —\n // which is a stop for a person, never a Done.\n const review = await aiCopiedLayoutReview(firestore, { hostId, id: copy.id, plan, name })\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 })],\n })\n }\n if (copy.ok === false && copy.status === 403) {\n return aiUnspentOutcome(model, { review: aiLimitReview(copy.error) })\n }\n }\n\n const allowance = await aiDraftAllowanceRefusal(firestore, { kind: 'layout', hostId, org })\n if (allowance) return aiUnspentOutcome(model, { review: aiLimitReview(allowance) })\n\n if (aiJobUsesLayoutLanguage(job)) {\n // ── The header and footer designed in the layout language (AGL-3660) ──\n const siteName = aiLayoutSiteName(job)\n const pages = aiLayoutNavPages(job, inventory)\n const targets = aiLayoutFrameTargets(job, inventory)\n const result = await runValidatedGeneration<AiLayoutFrameBuilt>(AI_LAYOUT_FRAME_KIND, {\n step: 'job.layout',\n model,\n instructions: AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS,\n inventory,\n messages: [{ role: 'user', content: aiLayoutFramePrompt({ job, siteName, pages, targets }) }],\n tool: AI_LAYOUT_FRAME_TOOL,\n maxTokens: AI_JOB_LAYOUT_LANGUAGE_BUDGET.maxTokens(model),\n ...AI_LAYOUT_LANGUAGE_THINKING,\n check: aiLayoutFrameCheck({\n ...aiSiteStyleTokens(job.inputs),\n siteName,\n homeId: aiLayoutHomeId(pages, inventory),\n pages,\n targets,\n extend: aiLayoutChecks(inventory, plan, job.brief),\n design: aiLayoutDesignOf(job, '/'),\n storeLinks: aiStoreFrameLinksOf(job.inputs),\n // The frame's own words in the site's language (AGL-3760).\n ...(job.plan?.language ? { language: job.plan.language } : {}),\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) }\n // Never written once the job is canceled (AGL-3616).\n throwIfAiJobCanceled(signal)\n const draft = await writeAiDraft(firestore, {\n kind: 'layout',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: result.value.nodes,\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 layout was generated`)\n return { ...spent, review: aiLimitReview(draft.error) }\n }\n const note = aiBracketedFactsNote({\n tree: { rootId: result.value.rootId, nodes: result.value.nodes as unknown as AiDoctrineTree['nodes'] },\n inventory,\n })\n return { ...spent, outputs: [output(draft, result.value.load, note)] }\n }\n\n // The pages a site scaffold builds after this layout (AGL-3596): their\n // ids are the plan's, so the header links them by id, written by the\n // platform before the doctrine checks the tree.\n const sitePages = aiLayoutSitePages(job.inputs)\n const result = await runValidatedGeneration('layout', {\n step: 'job.layout',\n model,\n instructions: AI_JOB_LAYOUT_INSTRUCTIONS,\n inventory,\n messages: [{ role: 'user', content: aiJobLayoutPrompt(job, plan, name, sourceLines) }],\n tool: aiDoctrineTreeTool('layout'),\n maxTokens: AI_JOB_LAYOUT_STEP_BUDGET.maxTokens(model),\n ...(AI_ROUTING_TABLE['job.layout'].thinking ? { thinking: AI_ROUTING_TABLE['job.layout'].thinking } : {}),\n ...(AI_ROUTING_TABLE['job.layout'].effort ? { effort: AI_ROUTING_TABLE['job.layout'].effort } : {}),\n extend: aiLayoutChecks(inventory, plan, job.brief),\n // The site's name is written as the token that reads it, and the\n // layout fills the window so a short page keeps its footer at the\n // bottom (AGL-3596).\n complete: (tree: unknown) =>\n aiLayoutWithFullHeight(\n aiLayoutWithSiteName(\n sitePages.length ? aiLayoutWithSitePages(tree, sitePages, { homeScreenIds: aiHomeScreenIds(inventory) }) : tree,\n typeof job.inputs?.['businessName'] === 'string' ? job.inputs['businessName'] : null,\n ),\n ),\n ...(sitePages.length\n ? { context: { screenIds: [...inventory.screens.map((screen) => screen.id), ...sitePages.map((page) => page.id)] } }\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) }\n\n // Never written once the job is canceled (AGL-3616).\n throwIfAiJobCanceled(signal)\n const draft = await writeAiDraft(firestore, {\n kind: 'layout',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: result.value.nodes,\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 layout was generated`)\n return { ...spent, review: aiLimitReview(draft.error) }\n }\n // The facts the brief did not give, which the member fills before publishing (AGL-3056).\n const note = aiBracketedFactsNote({\n tree: { rootId: result.value.rootId, nodes: result.value.nodes as unknown as AiDoctrineTree['nodes'] },\n inventory,\n })\n return { ...spent, outputs: [output(draft, result.value.load, note)] }\n }\n}\n\n/** The layout step, building the components and forms its plan creates before the layout (AGL-3143 §15). */\nexport const runAiJobLayoutStep = aiBuildingPlanCreations('layout', 'job.layout', createAiJobLayoutStep())\n\n/** Registers the layout step and the check a layout job passes before it is created or resumed. */\nexport function registerAiLayoutJob(): void {\n registerAiJobStep('layout', runAiJobLayoutStep, {\n minimumMs: AI_JOB_LAYOUT_STEP_MINIMUM_MS,\n minimumMsFor: aiPlanCreationsRunMinimumMs('layout'),\n })\n registerAiJobAdmission('layout', aiLayoutJobAdmission)\n}\n"],"names":["throwIfAiJobCanceled","aiSiteStyleTokens","CANVAS_ROOT_ELEMENT_ID","duplicateResource","aiHomeScreenIds","AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","aiDoctrineTreeTool","runValidatedGeneration","walkTree","readSiteInventory","registerAiJobAdmission","aiBuildingPlanCreations","aiJobPlanCreationsRefusal","aiPlanCreationsRunMinimumMs","aiJobDraftId","aiOriginJobId","aiDraftAdmissionRefusal","aiDraftAllowanceRefusal","aiSiteSubdomain","readAiDraft","readAiDraftNodes","writeAiDraft","aiBracketedFactsNote","aiCopySourceLines","aiConfirmedPlan","aiDoctrineReview","aiGenerationSpent","aiJobBriefLine","aiLimitReview","aiModelNodeIds","aiPlacedComponentIds","aiPlanCreation","aiPlanReferenceLines","aiPlanReuseViolations","aiUnspentOutcome","aiPlanRegionViolations","aiPlannedLayoutRegions","aiLayoutSitePages","aiLayoutSitePagesLines","aiLayoutWithSitePages","aiLayoutInventedContactViolations","aiLayoutWithSiteName","aiLayoutWithFullHeight","AI_LAYOUT_FRAME_TOOL","AI_JOB_LAYOUT_LANGUAGE_BUDGET","AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS","AI_LAYOUT_FRAME_KIND","aiLayoutFrameCheck","aiLayoutFramePrompt","aiLayoutFrameTargets","aiLayoutHomeId","aiLayoutNavPages","aiLayoutSiteName","AI_LAYOUT_LANGUAGE_THINKING","aiJobUsesLayoutLanguage","aiLayoutDesignOf","aiJobStepBudget","registerAiJobStep","aiStoreFrameLinksOf","AI_JOB_LAYOUT_MAX_TOKENS","maxTokens","AI_JOB_LAYOUT_STEP_BUDGET","tier","AI_JOB_LAYOUT_STEP_MINIMUM_MS","minimumMs","AI_JOB_LAYOUT_DEFAULT_NAME","AI_JOB_LAYOUT_INSTRUCTIONS","text","join","aiJobLayoutPrompt","job","plan","name","source","inputs","NAVIGATION_NAME","FOOTER_NAME","isLink","node","componentId","props","aiLayoutReuseCheck","inventory","components","navigation","filter","component","test","tree","placed","violations","rootId","nodes","length","some","has","id","visits","slot","findIndex","header","slice","links","map","push","rule","code","message","nodeIds","sourceIds","aiLayoutChecks","brief","reuse","violation","aiCopiedLayoutReview","firestore","input","planned","regions","unreadable","stored","kind","hostId","reason","findings","aiLayoutJobAdmission","context","orgId","org","ownCheck","createAiJobLayoutStep","deps","readInventory","duplicate","now","signal","modelFor","orgSnapshot","Error","output","draft","load","note","resource","versionId","hostSubdomain","label","model","draftId","written","outputs","Promise","all","collection","doc","get","data","creation","duplicateOf","layouts","find","layout","undefined","sourceReview","sourceShort","Boolean","sourceTree","sourceLines","copy","sourceId","uid","createdBy","attemptKey","ok","review","status","error","allowance","siteName","pages","targets","result","step","instructions","messages","role","content","tool","check","homeId","extend","design","storeLinks","language","spent","refused","value","aiJobId","sitePages","thinking","effort","complete","homeScreenIds","screenIds","screens","screen","page","runAiJobLayoutStep","registerAiLayoutJob","minimumMsFor"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-layout-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 { throwIfAiJobCanceled } from './ai-job-cancel'\nimport { aiSiteStyleTokens } from '../model/ai-site-kinds'\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 type { AiJob, AiJobOutput, AiJobPlan, AiJobReview } from '../model/ai-jobs.types'\nimport { aiHomeScreenIds, type AiSiteInventory } from '../model/ai-site-inventory'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n aiDoctrineTreeTool,\n runValidatedGeneration,\n type AiValidatedTree,\n} from '../runtime/ai-doctrine'\nimport {\n walkTree,\n type AiDoctrineNode,\n type AiDoctrineTree,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { readSiteInventory } from '../runtime/site-inventory'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport {\n aiBuildingPlanCreations,\n aiJobPlanCreationsRefusal,\n aiPlanCreationsRunMinimumMs,\n} from './ai-job-plan-creations'\nimport { aiJobDraftId, aiOriginJobId } from './ai-job-draft-ids'\nimport {\n aiDraftAdmissionRefusal,\n aiDraftAllowanceRefusal,\n aiSiteSubdomain,\n readAiDraft,\n readAiDraftNodes,\n writeAiDraft,\n type AiDraftRecord,\n} from './ai-job-drafts'\nimport {\n aiBracketedFactsNote,\n aiCopySourceLines,\n aiConfirmedPlan,\n aiDoctrineReview,\n aiGenerationSpent,\n aiJobBriefLine,\n aiLimitReview,\n aiModelNodeIds,\n aiPlacedComponentIds,\n aiPlanCreation,\n aiPlanReferenceLines,\n aiPlanReuseViolations,\n aiUnspentOutcome,\n} from './ai-job-generation'\nimport { aiPlanRegionViolations, aiPlannedLayoutRegions } from './ai-job-plan-conformance'\nimport { aiLayoutSitePages, aiLayoutSitePagesLines, aiLayoutWithSitePages } from './ai-job-layout-site-pages'\nimport { aiLayoutInventedContactViolations, aiLayoutWithSiteName } from './ai-layout-site-facts'\nimport { aiLayoutWithFullHeight } from './ai-layout-full-height'\nimport type { AiJobStepRunner } from './ai-job-text-step'\nimport { AI_LAYOUT_FRAME_TOOL } from '../layout-language/ai-layout-language'\nimport {\n AI_JOB_LAYOUT_LANGUAGE_BUDGET,\n AI_LAYOUT_FRAME_KIND,\n aiJobLayoutLanguageInstructions,\n aiLayoutFrameCheck,\n aiLayoutFramePrompt,\n aiLayoutFrameTargets,\n aiLayoutHomeId,\n aiLayoutNavPages,\n aiLayoutSiteBrandOf,\n aiLayoutSiteFactsText,\n aiLayoutSiteName,\n type AiLayoutFrameBuilt,\n} from './ai-job-layout-language'\nimport { AI_LAYOUT_LANGUAGE_THINKING, aiJobUsesLayoutLanguage, aiLayoutDesignOf } from './ai-job-page-language'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { registerAiJobStep } from './ai-jobs'\nimport { aiStoreFrameLinksOf } from '../model/ai-site-store-pages'\n\n/**\n * The layout step (AGL-2909): the generation step of a `layout` job, run once\n * a member confirmed the job's plan.\n *\n * It builds ONE shared layout — a header with the site's navigation, the\n * Layout Slot each page renders in, a footer — through\n * `runValidatedGeneration('layout', …)`, so every building rule holds on the\n * tree before it is kept, and adds three checks of its own through `extend`: a\n * component the confirmed plan reuses is placed (rule 7), every region the\n * plan gives the layout is built (AGL-3024), and a navigation or menu\n * component the site already has is placed rather than drawn again (rule 1).\n *\n * The layout lands as a new draft (`ai-job-drafts.ts`): no screen uses it and\n * no layout nests it, so nothing on the live site changes until a member\n * assigns it. A plan that starts from a copy of a layout the site has gets\n * that copy, through the platform's duplicate module, and nothing generated —\n * so the copy is read back and held to the plan's regions the same way\n * (`aiCopiedLayoutReview`), because a branch that generates nothing is the one\n * branch where a promise can go unmet with no model to answer for it. A\n * source that already lacks a region the plan gives the layout is not copied\n * at all: the layout is built from it (AGL-3143 §14).\n *\n * A generation step runs on the job beat, never at an inline door: it\n * registers the least time its lookup rounds, its answer and its re-ask need\n * (AGL-3035), runs without extended thinking — the plan was reasoned out and\n * confirmed before it — and asks for no more of a tighter ceiling than the\n * doctrine's default for a layout than fits that time on the model it runs.\n */\n\n/** The longest answer a layout may run to; the tree is held to the layout budget either way. */\nexport const AI_JOB_LAYOUT_MAX_TOKENS = AI_ROUTING_TABLE['job.layout'].maxTokens\n\n/**\n * The layout step's time (AGL-3035, AGL-3036): its two inventory-lookup\n * rounds, its answer and its re-ask at the routing table's ceiling on the tier\n * `job.layout` is served from, fitted to what a beat can give a step.\n */\nexport const AI_JOB_LAYOUT_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.layout'],\n maxTokens: AI_JOB_LAYOUT_MAX_TOKENS,\n})\n\n/** The least time one layout step needs before it starts. */\nexport const AI_JOB_LAYOUT_STEP_MINIMUM_MS = AI_JOB_LAYOUT_STEP_BUDGET.minimumMs\n\n/** A layout's name when the plan names none. */\nexport const AI_JOB_LAYOUT_DEFAULT_NAME = 'Site layout'\n\n/** The layout step's own instructions, cached after the doctrine. */\nexport const AI_JOB_LAYOUT_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n 'You build one shared layout for a website: the frame every page of the site renders inside. Answer with submit_layout: the whole layout as one flat node map.',\n 'A layout is a header, one Layout Slot and a footer, top to bottom. The header is an App Bar (muiAppBar) holding a Toolbar Content (muiToolbar) with the site’s name as text and its navigation. The footer is a Section (section) whose element is footer.',\n 'Navigation links the site’s own pages by id: a Page Link (muiScreenLink) whose screenId is a page id from the site inventory, in the order the brief and the confirmed plan give, seven at most in the header. Never invent a page id, and never link a page by its address.',\n 'When the site inventory lists a component whose name says it is navigation, a menu or a mega menu, place it in the header as an instance instead of building the navigation again. Place every component the confirmed plan reuses.',\n 'The Layout Slot (layoutSlot) is where each page renders and is the page’s main landmark: leave its component unset or main, and put nothing inside it. A layout carries no h1, because every page brings its own.',\n ].join('\\n'),\n },\n]\n\n/**\n * The layout job as the generation's user turn: the name, the brief, the\n * confirmed plan, and the layout a short copy would have started from\n * (`aiCopySourceLines`, AGL-3143 §14).\n */\nexport function aiJobLayoutPrompt(\n job: Pick<AiJob, 'brief'> & Partial<Pick<AiJob, 'inputs'>>,\n plan: AiJobPlan | null,\n name: string,\n source: readonly string[] = [],\n): string {\n return [\n `Layout name: ${name}`,\n aiJobBriefLine(job),\n ...aiPlanReferenceLines(plan),\n // A scaffold's layout is built before its pages, whose navigation the platform writes (AGL-3596).\n ...aiLayoutSitePagesLines(aiLayoutSitePages(job.inputs)),\n ...source,\n ].join('\\n')\n}\n\n/** A component name that says it is the site's navigation; a footer's links are not. */\nconst NAVIGATION_NAME = /\\b(?:nav|navigation|navbar|menu|mega ?menu)\\b/i\nconst FOOTER_NAME = /\\bfooter\\b/i\n\n/** A node that links somewhere: a Screen Link, or a Button pointed at a screen or an address. */\nfunction isLink(node: AiDoctrineNode): boolean {\n if (node.componentId === 'muiScreenLink') return true\n return (\n node.componentId === 'muiButton' &&\n (typeof node.props?.['screenId'] === 'string' || typeof node.props?.['href'] === 'string')\n )\n}\n\n/**\n * The layout door's checks on a tree the doctrine admitted: every component\n * the confirmed plan reuses is placed, every region the confirmed plan gives\n * the layout is built (AGL-3024), and navigation the site already keeps as a\n * component is placed rather than drawn again in the header.\n */\nexport function aiLayoutReuseCheck(\n inventory: AiSiteInventory | null,\n plan: AiJobPlan | null,\n): (tree: AiValidatedTree) => AiDoctrineViolation[] {\n const components = inventory?.components ?? []\n const navigation = components.filter(\n (component) => NAVIGATION_NAME.test(component.name) && !FOOTER_NAME.test(component.name),\n )\n return (tree) => {\n const placed = aiPlacedComponentIds(tree)\n const violations: AiDoctrineViolation[] = [\n ...aiPlanReuseViolations(inventory, plan, placed, 'layout'),\n ...aiPlanRegionViolations(plan, {\n rootId: tree.rootId,\n nodes: tree.nodes as unknown as Record<string, AiDoctrineNode>,\n }),\n ]\n if (navigation.length && !navigation.some((component) => placed.has(component.id))) {\n const visits = walkTree({\n rootId: tree.rootId,\n nodes: tree.nodes as unknown as Record<string, AiDoctrineNode>,\n })\n const slot = visits.findIndex(({ node }) => node.componentId === 'layoutSlot')\n const header = slot === -1 ? visits : visits.slice(0, slot)\n const links = header.filter(({ node }) => isLink(node)).map(({ id }) => id)\n if (links.length >= 2) {\n violations.push({\n rule: 1,\n code: 'navigation-rebuilt',\n message: `The site already has the \"${navigation[0].name}\" component. Place it in the header instead of building the navigation again.`,\n nodeIds: aiModelNodeIds(links, tree.sourceIds),\n })\n }\n }\n return violations\n }\n}\n\n/**\n * Every check the layout door adds to the doctrine's: the reuse and region\n * checks above, and no contact detail the brief does not give (AGL-3596).\n */\nexport function aiLayoutChecks(\n inventory: AiSiteInventory | null,\n plan: AiJobPlan | null,\n brief: string,\n): (tree: AiValidatedTree) => AiDoctrineViolation[] {\n const reuse = aiLayoutReuseCheck(inventory, plan)\n return (tree) => [\n ...reuse(tree),\n ...aiLayoutInventedContactViolations(\n { rootId: tree.rootId, nodes: tree.nodes as unknown as Record<string, AiDoctrineNode> },\n brief,\n ).map((violation) => ({ ...violation, nodeIds: aiModelNodeIds(violation.nodeIds ?? [], tree.sourceIds) })),\n ]\n}\n\n/**\n * The regions the confirmed plan gives the layout, against the layout a COPY\n * actually produced (AGL-3024); `null` when the copy keeps the plan, or when\n * the plan promised no region to keep.\n *\n * The duplicate branch generates nothing, so it is the one place a plan's\n * promise can go unmet without a model ever answering for it. A copy that\n * cannot be read — deleted between the write and this read, or stored under a\n * root this check does not know — settles nothing, and a promise this step\n * cannot settle is reviewed by a person rather than reported as kept.\n */\nexport async function aiCopiedLayoutReview(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; id: string; name: string; plan: AiJobPlan | null },\n): Promise<AiJobReview | null> {\n const planned = aiPlannedLayoutRegions(input.plan)\n if (!planned.regions.length && !planned.unreadable.length) return null\n const stored = await readAiDraftNodes(firestore, {\n kind: 'layout',\n hostId: input.hostId,\n id: input.id,\n })\n const nodes = (stored?.nodes ?? {}) as unknown as Record<string, AiDoctrineNode>\n if (!nodes[CANVAS_ROOT_ELEMENT_ID]) {\n return {\n reason: 'doctrine',\n message: `\"${input.name}\" was copied from the layout the plan names, and this job could not read the copy back to check it has the regions the plan gives it. Open the layout and check it before you put a page in it.`,\n findings: [],\n }\n }\n const violations = aiPlanRegionViolations(input.plan, { rootId: CANVAS_ROOT_ELEMENT_ID, nodes })\n if (!violations.length) return null\n return aiDoctrineReview({\n message: `\"${input.name}\" is a copy of the layout the plan names, and a copy is all it is: ${violations[0].message}`,\n violations,\n })\n}\n\n/**\n * A layout job is admitted for a site of the job's own org with a shared\n * layout to spare, and confirmed only for a plan whose other creations it\n * builds (AGL-3143 §15).\n */\nexport const aiLayoutJobAdmission: AiJobAdmission = (context) =>\n aiDraftAdmissionRefusal(context.firestore, {\n orgId: context.orgId,\n hostId: context.hostId,\n kind: 'layout',\n org: context.org,\n ownCheck: (hostId) => aiJobPlanCreationsRefusal('layout', context, hostId),\n })\n\nexport interface AiJobLayoutStepDeps {\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}\n\nexport function createAiJobLayoutStep(deps: AiJobLayoutStepDeps = {}): AiJobStepRunner {\n const readInventory = deps.readInventory ?? readSiteInventory\n const duplicate = deps.duplicate ?? duplicateResource\n return async ({ job, now, signal, firestore, modelFor }) => {\n const hostId = job.hostId\n if (!hostId) throw new Error('a layout job names no site, and its admission refuses one')\n const output = (draft: AiDraftRecord, load?: AiLoadEstimate | null, note?: string | null): AiJobOutput => ({\n resource: 'layout',\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 })\n // The switch's answer for this job, else the routing table's (AGL-2942).\n const model = modelFor?.('job.layout') ?? aiModelForStep('job.layout')\n\n // A run cut off after its draft was written reports that draft, found by the id the job recorded.\n const draftId = aiJobDraftId(job, 'layout')\n const written = await readAiDraft(firestore, { kind: 'layout', hostId, id: draftId })\n if (written) return aiUnspentOutcome(model, { outputs: [output(written)] })\n\n const [inventory, orgSnapshot] = await Promise.all([\n readInventory(job.orgId, hostId, { firestore }),\n firestore.collection('orgs').doc(job.orgId).get(),\n ])\n const org = (orgSnapshot.data() ?? null) as Partial<AglynOrgBilling> | null\n const plan = aiConfirmedPlan(job)\n const creation = aiPlanCreation(plan, 'layout')\n const name = creation?.name || AI_JOB_LAYOUT_DEFAULT_NAME\n\n // The plan starts from a copy of a layout the site has (rule 15): the copy\n // is the draft, and nothing is generated. A source gone since the plan\n // was made is built from the brief instead. A source that cannot keep the\n // plan's regions is built FROM instead (AGL-3143 §14), as the page step\n // builds from a short source: copying it could only end in the review\n // below, and Try again would copy the same thing. Only a source read and\n // found short diverts; one this step cannot read still copies, and the\n // review after the copy answers for it.\n const source = creation?.duplicateOf\n ? inventory.layouts.find((layout) => layout.id === creation.duplicateOf)\n : undefined\n const sourceReview = source\n ? await aiCopiedLayoutReview(firestore, { hostId, id: source.id, plan, name: source.name })\n : null\n const sourceShort = Boolean(sourceReview?.findings.length)\n const sourceTree =\n source && sourceShort ? await readAiDraftNodes(firestore, { kind: 'layout', hostId, id: source.id }) : null\n const sourceLines =\n source && sourceShort ? aiCopySourceLines('layout', source.name, sourceTree?.nodes ?? null) : []\n if (creation?.duplicateOf && source && !sourceShort) {\n const copy = await duplicate('layout', {\n orgId: job.orgId,\n hostId,\n sourceId: creation.duplicateOf,\n name,\n uid: job.createdBy,\n org: org as Record<string, unknown> | null,\n // The same claim the page step makes (AGL-3024): a copy core mints is\n // written under core's own id, never `draftId`, so a step that runs\n // again — a pass resumed, a run cut off after the copy — would mint a\n // second. The job's recorded id makes core replay the one it made.\n attemptKey: draftId,\n })\n if (copy.ok) {\n // A copy is not a build (AGL-3024). This branch generates nothing, so\n // whatever the plan promised BEYOND the source is not in the draft\n // unless the source already had it: a plan reading \"from a copy of the\n // base layout, adding a sidebar\" gets the base layout, the sidebar\n // nowhere in it, and until now reported Done. The copy's own regions\n // answer the plan's, and a copy that cannot be read answers nothing —\n // which is a stop for a person, never a Done.\n const review = await aiCopiedLayoutReview(firestore, { hostId, id: copy.id, plan, name })\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 })],\n })\n }\n if (copy.ok === false && copy.status === 403) {\n return aiUnspentOutcome(model, { review: aiLimitReview(copy.error) })\n }\n }\n\n const allowance = await aiDraftAllowanceRefusal(firestore, { kind: 'layout', hostId, org })\n if (allowance) return aiUnspentOutcome(model, { review: aiLimitReview(allowance) })\n\n if (aiJobUsesLayoutLanguage(job)) {\n // ── The header and footer designed in the layout language (AGL-3660) ──\n const siteName = aiLayoutSiteName(job)\n const pages = aiLayoutNavPages(job, inventory)\n const targets = aiLayoutFrameTargets(job, inventory)\n const result = await runValidatedGeneration<AiLayoutFrameBuilt>(AI_LAYOUT_FRAME_KIND, {\n step: 'job.layout',\n model,\n instructions: aiJobLayoutLanguageInstructions(job),\n inventory,\n messages: [{ role: 'user', content: aiLayoutFramePrompt({ job, siteName, pages, targets }) }],\n tool: AI_LAYOUT_FRAME_TOOL,\n maxTokens: AI_JOB_LAYOUT_LANGUAGE_BUDGET.maxTokens(model),\n ...AI_LAYOUT_LANGUAGE_THINKING,\n check: aiLayoutFrameCheck({\n ...aiSiteStyleTokens(job.inputs),\n siteName,\n homeId: aiLayoutHomeId(pages, inventory),\n pages,\n targets,\n // A site brought in may state what its own header and footer state (AGL-4084).\n extend: aiLayoutChecks(inventory, plan, [job.brief, aiLayoutSiteFactsText(job.inputs)].filter(Boolean).join('\\n')),\n design: aiLayoutDesignOf(job, '/'),\n brand: aiLayoutSiteBrandOf(job.inputs),\n storeLinks: aiStoreFrameLinksOf(job.inputs),\n // The frame's own words in the site's language (AGL-3760).\n ...(job.plan?.language ? { language: job.plan.language } : {}),\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) }\n // Never written once the job is canceled (AGL-3616).\n throwIfAiJobCanceled(signal)\n const draft = await writeAiDraft(firestore, {\n kind: 'layout',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: result.value.nodes,\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 layout was generated`)\n return { ...spent, review: aiLimitReview(draft.error) }\n }\n const note = aiBracketedFactsNote({\n tree: { rootId: result.value.rootId, nodes: result.value.nodes as unknown as AiDoctrineTree['nodes'] },\n inventory,\n })\n return { ...spent, outputs: [output(draft, result.value.load, note)] }\n }\n\n // The pages a site scaffold builds after this layout (AGL-3596): their\n // ids are the plan's, so the header links them by id, written by the\n // platform before the doctrine checks the tree.\n const sitePages = aiLayoutSitePages(job.inputs)\n const result = await runValidatedGeneration('layout', {\n step: 'job.layout',\n model,\n instructions: AI_JOB_LAYOUT_INSTRUCTIONS,\n inventory,\n messages: [{ role: 'user', content: aiJobLayoutPrompt(job, plan, name, sourceLines) }],\n tool: aiDoctrineTreeTool('layout'),\n maxTokens: AI_JOB_LAYOUT_STEP_BUDGET.maxTokens(model),\n ...(AI_ROUTING_TABLE['job.layout'].thinking ? { thinking: AI_ROUTING_TABLE['job.layout'].thinking } : {}),\n ...(AI_ROUTING_TABLE['job.layout'].effort ? { effort: AI_ROUTING_TABLE['job.layout'].effort } : {}),\n extend: aiLayoutChecks(inventory, plan, job.brief),\n // The site's name is written as the token that reads it, and the\n // layout fills the window so a short page keeps its footer at the\n // bottom (AGL-3596).\n complete: (tree: unknown) =>\n aiLayoutWithFullHeight(\n aiLayoutWithSiteName(\n sitePages.length ? aiLayoutWithSitePages(tree, sitePages, { homeScreenIds: aiHomeScreenIds(inventory) }) : tree,\n typeof job.inputs?.['businessName'] === 'string' ? job.inputs['businessName'] : null,\n ),\n ),\n ...(sitePages.length\n ? { context: { screenIds: [...inventory.screens.map((screen) => screen.id), ...sitePages.map((page) => page.id)] } }\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) }\n\n // Never written once the job is canceled (AGL-3616).\n throwIfAiJobCanceled(signal)\n const draft = await writeAiDraft(firestore, {\n kind: 'layout',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: result.value.nodes,\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 layout was generated`)\n return { ...spent, review: aiLimitReview(draft.error) }\n }\n // The facts the brief did not give, which the member fills before publishing (AGL-3056).\n const note = aiBracketedFactsNote({\n tree: { rootId: result.value.rootId, nodes: result.value.nodes as unknown as AiDoctrineTree['nodes'] },\n inventory,\n })\n return { ...spent, outputs: [output(draft, result.value.load, note)] }\n }\n}\n\n/** The layout step, building the components and forms its plan creates before the layout (AGL-3143 §15). */\nexport const runAiJobLayoutStep = aiBuildingPlanCreations('layout', 'job.layout', createAiJobLayoutStep())\n\n/** Registers the layout step and the check a layout job passes before it is created or resumed. */\nexport function registerAiLayoutJob(): void {\n registerAiJobStep('layout', runAiJobLayoutStep, {\n minimumMs: AI_JOB_LAYOUT_STEP_MINIMUM_MS,\n minimumMsFor: aiPlanCreationsRunMinimumMs('layout'),\n })\n registerAiJobAdmission('layout', aiLayoutJobAdmission)\n}\n"],"names":["throwIfAiJobCanceled","aiSiteStyleTokens","CANVAS_ROOT_ELEMENT_ID","duplicateResource","aiHomeScreenIds","AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","aiDoctrineTreeTool","runValidatedGeneration","walkTree","readSiteInventory","registerAiJobAdmission","aiBuildingPlanCreations","aiJobPlanCreationsRefusal","aiPlanCreationsRunMinimumMs","aiJobDraftId","aiOriginJobId","aiDraftAdmissionRefusal","aiDraftAllowanceRefusal","aiSiteSubdomain","readAiDraft","readAiDraftNodes","writeAiDraft","aiBracketedFactsNote","aiCopySourceLines","aiConfirmedPlan","aiDoctrineReview","aiGenerationSpent","aiJobBriefLine","aiLimitReview","aiModelNodeIds","aiPlacedComponentIds","aiPlanCreation","aiPlanReferenceLines","aiPlanReuseViolations","aiUnspentOutcome","aiPlanRegionViolations","aiPlannedLayoutRegions","aiLayoutSitePages","aiLayoutSitePagesLines","aiLayoutWithSitePages","aiLayoutInventedContactViolations","aiLayoutWithSiteName","aiLayoutWithFullHeight","AI_LAYOUT_FRAME_TOOL","AI_JOB_LAYOUT_LANGUAGE_BUDGET","AI_LAYOUT_FRAME_KIND","aiJobLayoutLanguageInstructions","aiLayoutFrameCheck","aiLayoutFramePrompt","aiLayoutFrameTargets","aiLayoutHomeId","aiLayoutNavPages","aiLayoutSiteBrandOf","aiLayoutSiteFactsText","aiLayoutSiteName","AI_LAYOUT_LANGUAGE_THINKING","aiJobUsesLayoutLanguage","aiLayoutDesignOf","aiJobStepBudget","registerAiJobStep","aiStoreFrameLinksOf","AI_JOB_LAYOUT_MAX_TOKENS","maxTokens","AI_JOB_LAYOUT_STEP_BUDGET","tier","AI_JOB_LAYOUT_STEP_MINIMUM_MS","minimumMs","AI_JOB_LAYOUT_DEFAULT_NAME","AI_JOB_LAYOUT_INSTRUCTIONS","text","join","aiJobLayoutPrompt","job","plan","name","source","inputs","NAVIGATION_NAME","FOOTER_NAME","isLink","node","componentId","props","aiLayoutReuseCheck","inventory","components","navigation","filter","component","test","tree","placed","violations","rootId","nodes","length","some","has","id","visits","slot","findIndex","header","slice","links","map","push","rule","code","message","nodeIds","sourceIds","aiLayoutChecks","brief","reuse","violation","aiCopiedLayoutReview","firestore","input","planned","regions","unreadable","stored","kind","hostId","reason","findings","aiLayoutJobAdmission","context","orgId","org","ownCheck","createAiJobLayoutStep","deps","readInventory","duplicate","now","signal","modelFor","orgSnapshot","Error","output","draft","load","note","resource","versionId","hostSubdomain","label","model","draftId","written","outputs","Promise","all","collection","doc","get","data","creation","duplicateOf","layouts","find","layout","undefined","sourceReview","sourceShort","Boolean","sourceTree","sourceLines","copy","sourceId","uid","createdBy","attemptKey","ok","review","status","error","allowance","siteName","pages","targets","result","step","instructions","messages","role","content","tool","check","homeId","extend","design","brand","storeLinks","language","spent","refused","value","aiJobId","sitePages","thinking","effort","complete","homeScreenIds","screenIds","screens","screen","page","runAiJobLayoutStep","registerAiLayoutJob","minimumMsFor"],"mappings":"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"}
|
|
@@ -27,6 +27,7 @@ import { type AiDoctrineTreeContext, type AiDoctrineViolation } from '../runtime
|
|
|
27
27
|
import type { AiLoadEstimate } from '../runtime/ai-palette';
|
|
28
28
|
import type { AiLayoutDesign } from '../layout-language/ai-layout-design';
|
|
29
29
|
import type { AiSystemBlock } from '../runtime/ai-runtime';
|
|
30
|
+
import { type AiSiteImportDocument } from './ai-site-import-document';
|
|
30
31
|
/**
|
|
31
32
|
* A page built in the compact layout language (AGL-3660): the page step's
|
|
32
33
|
* pass for a site's page units — a guided start's and a build's — in place of
|
|
@@ -373,3 +374,31 @@ export interface AiLayoutPageRunInput {
|
|
|
373
374
|
* them, so the page is compiled whole once every section is designed.
|
|
374
375
|
*/
|
|
375
376
|
export declare function aiRunLayoutPage(input: AiLayoutPageRunInput): Promise<AiValidatedGeneration<AiLayoutPageBuilt>>;
|
|
377
|
+
/**
|
|
378
|
+
* What an arranged page's answer is told (AGL-4084), cached: the page's words
|
|
379
|
+
* are the source's and fixed, and the answer only lays them out. The tool has
|
|
380
|
+
* no field for a word of copy, so a rewording has nowhere to go.
|
|
381
|
+
*/
|
|
382
|
+
export declare const AI_LAYOUT_ARRANGE_TEXT: string;
|
|
383
|
+
/** The arranged page's cached instructions, and the rule that fenced text is data. */
|
|
384
|
+
export declare const AI_JOB_PAGE_ARRANGE_INSTRUCTIONS: readonly AiSystemBlock[];
|
|
385
|
+
/** An arranged page's user turn: the page, the kind of site it is, and its numbered lines, fenced. */
|
|
386
|
+
export declare function aiLayoutArrangePrompt(input: {
|
|
387
|
+
job: Pick<AiJob, 'inputs' | '$id'>;
|
|
388
|
+
screen: Pick<AiBuildPlanScreen, 'title' | 'slug' | 'sections'>;
|
|
389
|
+
doc: Pick<AiSiteImportDocument, 'blocks' | 'title' | 'url'>;
|
|
390
|
+
}): string;
|
|
391
|
+
/**
|
|
392
|
+
* The check a page brought in is held to (AGL-4084): the arrangement read
|
|
393
|
+
* (or none, for a page copied as it is), the source's lines substituted
|
|
394
|
+
* into it, the page stored and checked whole as a page of the owner's own
|
|
395
|
+
* words. Nothing in an answer can change a word.
|
|
396
|
+
*/
|
|
397
|
+
export declare function aiImportedPageCheck(input: Pick<AiLayoutPageCheckInput, 'screen' | 'sectionIds' | 'targets' | 'context' | 'motion' | 'design'> & {
|
|
398
|
+
doc: AiSiteImportDocument;
|
|
399
|
+
}): (answer: Record<string, unknown> | null) => {
|
|
400
|
+
value: AiLayoutPageBuilt | null;
|
|
401
|
+
violations: AiDoctrineViolation[];
|
|
402
|
+
};
|
|
403
|
+
/** The most an arrangement answer runs to: a few hundred tokens of line numbers, inside a page's ceiling. */
|
|
404
|
+
export declare const AI_LAYOUT_ARRANGE_KIND = "layout-page";
|