@aglyn/plugins-ai 1.0.0-beta.271 → 1.0.0-beta.272
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 +4 -3
- 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-site-build-page.component.js +18 -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 +41 -0
- package/src/lib/components/ai-site-import-job.component.js +707 -0
- package/src/lib/components/ai-site-import-job.component.js.map +1 -0
- package/src/lib/components/ai-site-import-start.component.d.ts +64 -0
- package/src/lib/components/ai-site-import-start.component.js +335 -0
- package/src/lib/components/ai-site-import-start.component.js.map +1 -0
- package/src/lib/components/ai-site-ownership-proof.component.d.ts +42 -0
- package/src/lib/components/ai-site-ownership-proof.component.js +353 -0
- package/src/lib/components/ai-site-ownership-proof.component.js.map +1 -0
- package/src/lib/components/ai-site-start-card.component.js +165 -91
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.d.ts +14 -0
- package/src/lib/jobs/ai-job-drafts.js +24 -0
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-import-step.d.ts +136 -0
- package/src/lib/jobs/ai-job-import-step.js +809 -0
- package/src/lib/jobs/ai-job-import-step.js.map +1 -0
- package/src/lib/jobs/ai-job-page-language.d.ts +39 -1
- package/src/lib/jobs/ai-job-page-language.js +62 -4
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.d.ts +2 -2
- package/src/lib/jobs/ai-job-page-step.js +5 -2
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-text-step.d.ts +7 -0
- package/src/lib/jobs/ai-job-text-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +5 -0
- package/src/lib/jobs/ai-jobs.js +15 -3
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-owner-photos.d.ts +16 -1
- package/src/lib/jobs/ai-layout-owner-photos.js +47 -0
- package/src/lib/jobs/ai-layout-owner-photos.js.map +1 -1
- package/src/lib/jobs/ai-theme-brand-inputs.d.ts +61 -3
- package/src/lib/jobs/ai-theme-brand-inputs.js +117 -27
- package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
- package/src/lib/model/ai-credit-estimate.d.ts +20 -0
- package/src/lib/model/ai-credit-estimate.js +13 -0
- package/src/lib/model/ai-credit-estimate.js.map +1 -1
- package/src/lib/model/ai-job-activity.d.ts +2 -2
- package/src/lib/model/ai-job-activity.js +6 -4
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-job-notice.js +18 -4
- package/src/lib/model/ai-job-notice.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +29 -2
- package/src/lib/model/ai-jobs.types.js +2 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-site-import-view.d.ts +130 -0
- package/src/lib/model/ai-site-import-view.js +182 -0
- package/src/lib/model/ai-site-import-view.js.map +1 -0
- package/src/lib/model/ai-site-import.d.ts +302 -0
- package/src/lib/model/ai-site-import.js +526 -0
- package/src/lib/model/ai-site-import.js.map +1 -0
- package/src/lib/model/ai-site-start.d.ts +22 -9
- package/src/lib/model/ai-site-start.js +64 -23
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/server/ai-jobs-route.js +11 -0
- package/src/lib/server/ai-jobs-route.js.map +1 -1
- package/src/lib/server/ai-site-import-route.d.ts +89 -0
- package/src/lib/server/ai-site-import-route.js +356 -0
- package/src/lib/server/ai-site-import-route.js.map +1 -0
- package/src/lib/server.js +14 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/site-import/fixtures/site-import-crawl-recordings.d.ts +45 -0
- package/src/lib/site-import/fixtures/site-import-crawl-recordings.js +313 -0
- package/src/lib/site-import/fixtures/site-import-crawl-recordings.js.map +1 -0
- package/src/lib/site-import/fixtures/site-import-wave-recordings.d.ts +50 -0
- package/src/lib/site-import/fixtures/site-import-wave-recordings.js +217 -0
- package/src/lib/site-import/fixtures/site-import-wave-recordings.js.map +1 -0
- package/src/lib/site-import/site-import-crawl.d.ts +163 -0
- package/src/lib/site-import/site-import-crawl.js +467 -0
- package/src/lib/site-import/site-import-crawl.js.map +1 -0
- package/src/lib/site-import/site-import-flag.d.ts +23 -0
- package/src/lib/site-import/site-import-flag.js +23 -0
- package/src/lib/site-import/site-import-flag.js.map +1 -0
- package/src/lib/site-import/site-import-html.d.ts +94 -0
- package/src/lib/site-import/site-import-html.js +462 -0
- package/src/lib/site-import/site-import-html.js.map +1 -0
- package/src/lib/site-import/site-import-images.d.ts +54 -0
- package/src/lib/site-import/site-import-images.js +204 -0
- package/src/lib/site-import/site-import-images.js.map +1 -0
- package/src/lib/site-import/site-import-page.d.ts +48 -0
- package/src/lib/site-import/site-import-page.js +150 -0
- package/src/lib/site-import/site-import-page.js.map +1 -0
- package/src/lib/site-import/site-import-review.d.ts +40 -0
- package/src/lib/site-import/site-import-review.js +149 -0
- package/src/lib/site-import/site-import-review.js.map +1 -0
- package/src/lib/site-import/site-import-robots.d.ts +52 -0
- package/src/lib/site-import/site-import-robots.js +126 -0
- package/src/lib/site-import/site-import-robots.js.map +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-page-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { throwIfAiJobCanceled } from './ai-job-cancel'\nimport { buildPageMarkdown } from '@aglyn/aglyn/app-utils/page-markdown'\nimport { SCREEN_SEO_TEXT_GUIDANCE } from '@aglyn/aglyn/app-utils/screen-seo-fields'\nimport { SCREEN_ROOT_PATH, screenRoutePathToUrl } from '@aglyn/aglyn/app-utils/screen-route'\nimport { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { duplicateResource } from '@aglyn/tenant-data-admin/server/duplicate-resource'\nimport { AI_BUILD_PLAN_LIMITS, aiPlanEmbedsFor, type AiBuildPlanScreen } from '../model/ai-build-plan'\nimport {\n AI_PAGE_CREATE_KINDS,\n aiPageCreationRefusal,\n aiPagePlanRefusal,\n parseAiPageJobInputs,\n} from '../model/ai-page-job'\nimport { aiPlanCapabilitiesForJob, aiPlanUncreatable } from '../model/ai-plan-capabilities'\nimport { aiSiteWords } from '../model/ai-site-job'\nimport type { AiJob, AiJobOutput, AiJobPlan, AiJobReview } from '../model/ai-jobs.types'\nimport { aiModelForStep } from '../providers/routing'\nimport { aiDoctrineNeedsInputMessage, runValidatedGeneration } from '../runtime/ai-doctrine'\nimport {\n aiClipAtWord,\n aiSettleMechanicalRules,\n aiSettlePlaceholderItems,\n aiSettleUnrelatedScreenLinks,\n validateAiDoctrineTree,\n type AiDoctrineTree,\n} from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport { aiHasPlaceholderToken } from '../runtime/ai-placeholder-tokens'\nimport { AI_SEO_FIELDS_MAX_TOKENS, generateSeoFields } from '../runtime/seo-fields'\nimport { readSiteInventory } from '../runtime/site-inventory'\nimport type { AiSiteInventory } from '../model/ai-site-inventory'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport { aiGenerationWorstCaseMs } from './ai-job-budget'\nimport { aiJobDraftId, aiOriginJobId } from './ai-job-draft-ids'\nimport {\n aiBuildsWithComponents,\n aiDraftAdmissionRefusal,\n aiDraftAllowanceRefusal,\n aiSiteSubdomain,\n readAiDraft,\n readAiDraftNodes,\n readAiPlanCapabilities,\n updateAiDraftNodes,\n writeAiDraft,\n writeAiDraftScreenSeo,\n type AiDraftRecord,\n} from './ai-job-drafts'\nimport {\n aiBracketedFactsNote,\n aiConfirmedPlan,\n aiDoctrineReview,\n aiGenerationSpent,\n aiLimitReview,\n aiModelNodeIds,\n aiUnspentOutcome,\n} from './ai-job-generation'\nimport {\n AI_JOB_PAGE_SECTION_MAX_TOKENS,\n AI_JOB_PAGE_SECTION_TOKENS,\n AI_JOB_PAGE_STEP_BUDGET,\n AI_JOB_PAGE_STEP_MINIMUM_MS,\n aiJobPageSectionMaxTokens,\n} from './ai-job-page-budget'\nimport {\n AI_JOB_PAGE_INSTRUCTIONS,\n AI_PAGE_SECTION_TOOL,\n aiEmptyPage,\n aiPageCheckContext,\n aiPageLinkablePages,\n aiPageRecordNote,\n aiPageRecordTemplate,\n aiPageRecordTokens,\n aiPageSectionCheck,\n aiPageSectionNodeId,\n aiPageSectionPrompt,\n aiPageSectionSmaller,\n aiPageSectionWithRecordImage,\n aiPageWithSection,\n type AiPageSection,\n} from './ai-job-page-sections'\nimport { aiLayoutSitePages } from './ai-job-layout-site-pages'\nimport { aiResolveLayoutPictures, type AiLayoutPicturePhoto } from '../layout-language/ai-layout-pictures'\nimport { aiLayoutSitePhotoSource } from './ai-layout-owner-photos'\nimport {\n AI_STOCK_PHOTO_AVOID_INPUT,\n AI_STOCK_PHOTO_JOB_PHOTOS_INPUT,\n AI_STOCK_PHOTO_PAGES_INPUT,\n aiLayoutStockPhotoSource,\n aiStockJobPhotosOf,\n aiStockPhotoWordsOf,\n aiStockPlacedPhotos,\n aiStockPlacedSrcs,\n} from './ai-layout-stock-photos'\nimport { AI_PLAN_ITEMS_MIN, aiPlanCopiedPageViolations } from './ai-job-plan-conformance'\nimport {\n AI_JOB_PAGE_LANGUAGE_BUDGET,\n aiJobUsesLayoutLanguage,\n aiLayoutListingContext,\n aiLayoutPageTargets,\n aiLayoutLeftOutNote,\n aiLayoutMotionSiteOf,\n aiRunLayoutPage,\n type AiLayoutStartPage,\n} from './ai-job-page-language'\nimport { aiWithSiteMotion } from '../layout-language/ai-layout-motion'\nimport {\n aiCreationUnit,\n aiRunJobUnit,\n aiSiteBuiltRefs,\n aiSitePendingUnits,\n aiSiteUnitJob,\n type AiSiteUnit,\n} from './ai-job-site-step'\nimport type { AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport {\n aiJobStepRunMinimumMs,\n aiJobStepRunnerFor,\n registerAiJobStep,\n registerAiJobStepPasses,\n} from './ai-jobs'\n\n/**\n * The page step (AGL-2907): the generation step of a `page` job, run once a\n * member confirmed the job's plan. It builds ONE screen, section by section,\n * from what the site already has, and leaves it an unpublished draft.\n *\n * ── Why a page is built in passes ────────────────────────────────────────\n *\n * A page is far larger than one answer the job beat can wait for: the beat\n * gives a step what is left of its budget, and a whole page, answered and re-asked,\n * takes several times that. The confirmed plan already names the page's\n * sections, so the step builds one section per pass and asks the machine to\n * continue. Every pass is one reservation and one generation — the section's\n * lookup rounds, its answer and, when it breaks a rule, its one re-ask — sized\n * so that worst case fits `AI_JOB_PAGE_STEP_MINIMUM_MS`, which the step\n * registers as the least time a section pass needs before it starts.\n *\n * - The first pass builds the top section, which holds the page's h1, and\n * writes the draft screen and its first version.\n * - Each later pass builds the next section and adds it to that version.\n * - The last pass generates no tree: it holds the whole page to the\n * doctrine, writes the search title and description, and reports the\n * draft with its measured weight and a navigation proposal.\n *\n * How a section is asked for and held to the rules as part of the page is\n * `ai-job-page-sections.ts`.\n *\n * ── What the plan creates comes first (AGL-3031) ─────────────────────────\n *\n * A plan that creates a layout, forms or components is built in the same job,\n * before its page, one creation a pass, through the site scaffold's unit\n * machinery: each creation is handed to the step that builds that kind, under\n * a job derived from this one, and lands as the draft that step writes. Where\n * the job stands is read from its outputs, so nothing is kept anywhere else.\n * Once every creation is built, the page's passes run on the plan with each\n * `new:<name>` resolved to the record that was built, which the site's\n * inventory now lists — so the page places the new component and binds the new\n * form by id, and renders inside the new layout, exactly as it would one the\n * site always had. A pass that builds a creation needs the time the step that\n * builds it registers, not a section's (`aiPageJobRunMinimumMs`, AGL-3035), so\n * neither the beat nor a door starts it with only a section's time left.\n *\n * ── Never published ──────────────────────────────────────────────────────\n *\n * The draft is written the way `/api/hosts/resources` and\n * `/api/hosts/versions` write a screen and its first version, and nothing\n * else is touched: not the host document, whose routing map is what the live\n * site serves, and no collection, store, layout or navigation pointer\n * (`ai-job-drafts.ts`). A screen the routing map does not name serves nothing\n * until a member publishes it.\n */\n\n/**\n * A section pass's time, and the ceiling each pass asks on each model, are\n * declared apart from the step (`ai-job-page-budget.ts`): the site scaffold\n * builds its pages through this step and reads them without loading it.\n */\nexport {\n AI_JOB_PAGE_SECTION_MAX_TOKENS,\n AI_JOB_PAGE_SECTION_TOKENS,\n AI_JOB_PAGE_STEP_BUDGET,\n AI_JOB_PAGE_STEP_MINIMUM_MS,\n aiJobPageSectionMaxTokens,\n}\n\n/**\n * Tokens one element takes in a section's answer, ESTIMATED: the tree as JSON\n * text inside the tool call, at four characters a token. Measured on the ten\n * golden pages at a median of 38 and a most of 43 over their 39 sections, and\n * `ai-job-page-evals.spec.ts` holds every golden section under it.\n */\nexport const AI_JOB_PAGE_TOKENS_PER_ELEMENT = 45\n\n/**\n * Real tokens to every token estimated at four characters (AGL-3042). A\n * section's ceiling is counted by the provider in its own tokens, which run\n * above that estimate. MEASURED on the first live document run on a Free\n * workspace (AGL-3024, test-org, 2026-09-16), as the cache read of a request\n * over the estimate of the same cached prefix: 4,059 over 2,825 for the page\n * plan and 6,649 over 4,310 for the layout, the higher of the two, 1.5427 —\n * the ratio `ai-job-free-page.spec.ts` prices a Free page at. It is measured on\n * prompt text; an answer's own is read off a recorded pass, whose output\n * tokens the step's record keeps (`lastRuns`), beside the section it stored.\n */\nexport const AI_JOB_PAGE_REAL_TOKENS_PER_ESTIMATED = Math.max(4_059 / 2_825, 6_649 / 4_310)\n\n/**\n * Real tokens one element takes in a section's answer: the estimate at its\n * golden most, in real tokens, rounded up — 45 × 1.5427 → 70 (AGL-3042).\n */\nexport const AI_JOB_PAGE_REAL_TOKENS_PER_ELEMENT = Math.ceil(\n AI_JOB_PAGE_TOKENS_PER_ELEMENT * AI_JOB_PAGE_REAL_TOKENS_PER_ESTIMATED,\n)\n\n/**\n * The most elements a section's request asks it to keep under at an answer\n * ceiling: 15 at the balanced tier's 1,050 (AGL-3042). Counted in real\n * tokens, because the ceiling is: counted in the estimate, the budget is 23\n * elements, which a section of the goldens' largest elements fills at 1,597\n * real tokens — half as much again as the ceiling that cuts it off.\n */\nexport function aiJobPageSectionMaxElements(maxTokens: number): number {\n return Math.max(1, Math.floor(maxTokens / AI_JOB_PAGE_REAL_TOKENS_PER_ELEMENT))\n}\n\n/** A page's name when the plan names none. */\nexport const AI_JOB_PAGE_DEFAULT_NAME = 'New page'\n\nexport const AI_JOB_PAGE_NO_PLAN_COPY = 'This page job has no confirmed plan to build.'\nexport const AI_JOB_PAGE_DELETED_COPY = 'The draft page was deleted while it was being built.'\n\n/** A creation the plan names that this deployment has no step to build. */\nexport const AI_JOB_PAGE_CREATION_UNAVAILABLE_COPY =\n 'This page needs something that cannot be built here yet. Describe the page again.'\n\n/** A creation that finished without reporting what it built; the page cannot place it. */\nexport const AI_JOB_PAGE_CREATION_EMPTY_COPY =\n 'Part of this page could not be built. Describe the page again.'\n\n/**\n * What a page refused on its last pass tells the member after the plain\n * refusal (AGL-3143, AGL-3596). The draft is reported with the review, and\n * mending its layout in the editor is what lets the next pass finish the job.\n */\nexport const AI_JOB_PAGE_REFUSED_DRAFT_COPY = 'The draft is yours to open: fix its layout in the editor, then try again.'\n\n/**\n * The most passes a page job may take (AGL-3031): every creation the plan\n * limits admit, every section they admit, and the last pass that reports the\n * draft. A creation's step builds it in one pass, and a page one section a\n * pass, so a runner that never finishes is still bounded while a real page is\n * not.\n */\nexport const AI_JOB_PAGE_MAX_PASSES = AI_BUILD_PLAN_LIMITS.create + AI_BUILD_PLAN_LIMITS.sections + 1\n\n/**\n * The units a plan's creations are built as, in the order a scaffold builds\n * them: the layout, then the forms, then the components. Each is addressed by\n * its place in the plan, so a unit run again finds its own draft.\n */\nexport function aiPageJobUnits(plan: Pick<AiJobPlan, 'create'>): AiSiteUnit[] {\n return AI_PAGE_CREATE_KINDS.flatMap((kind) =>\n plan.create.flatMap((creation, index) => {\n if (creation.kind !== kind) return []\n const unit = aiCreationUnit(creation, `c${index}`)\n return unit ? [unit] : []\n }),\n )\n}\n\n/**\n * The least time this page job's next pass needs (AGL-3035). A pass that\n * builds a creation hands it to the step registered for that kind, under the\n * job derived for it, so it needs what that step registers for that job — a\n * layout's time, not a section's. Every other pass — a section, the last one —\n * needs a section pass's.\n */\nexport function aiPageJobRunMinimumMs(job: AiJob): number {\n const confirmed = aiConfirmedPlan(job)\n // A page built in the layout language is one answer for the whole page (AGL-3660).\n const pagePass = aiJobUsesLayoutLanguage(job) ? AI_JOB_PAGE_LANGUAGE_BUDGET.minimumMs : AI_JOB_PAGE_STEP_MINIMUM_MS\n if (!confirmed) return pagePass\n const units = aiPageJobUnits(confirmed)\n const outputs = job.outputs ?? []\n const [unit] = aiSitePendingUnits(units, outputs)\n if (!unit) return pagePass\n return aiJobStepRunMinimumMs(aiSiteUnitJob(job, unit, aiSiteBuiltRefs(units, outputs)))\n}\n\n/**\n * The one-segment address a draft asks for, from the plan's slug; `/` for a\n * planned home page, which asks for the root rather than for an address made\n * from its name (AGL-3596).\n */\nexport function aiPageDraftSlug(screen: Pick<AiBuildPlanScreen, 'slug'>): string {\n const segments = screen.slug.split('/').map((part) => part.trim()).filter(Boolean)\n if (!segments.length && screen.slug.trim() === SCREEN_ROOT_PATH) return SCREEN_ROOT_PATH\n return segments[segments.length - 1] ?? ''\n}\n\n/**\n * The layout a site's pages render inside (AGL-3596): the one its home page\n * uses, else the one the most pages use, else the site's only layout. `null`\n * for a site with no layout, or several and no page to choose between them.\n */\nexport function aiSiteDefaultLayoutId(\n inventory: Pick<AiSiteInventory, 'layouts' | 'screens'>,\n): string | null {\n const layouts = new Set(inventory.layouts.map((row) => row.id))\n if (!layouts.size) return null\n const pages = inventory.screens.filter(\n (row) => !row.template && row.layoutId && layouts.has(row.layoutId),\n )\n const home = pages.find((row) => row.slug.trim() === SCREEN_ROOT_PATH)\n if (home?.layoutId) return home.layoutId\n const counts = new Map<string, number>()\n for (const row of pages) counts.set(row.layoutId as string, (counts.get(row.layoutId as string) ?? 0) + 1)\n const [mostUsed] = [...counts.entries()].sort((a, b) => b[1] - a[1])\n if (mostUsed) return mostUsed[0]\n return layouts.size === 1 ? [...layouts][0] : null\n}\n\n/**\n * The layout a page draft renders inside: the plan's, where the site has it,\n * else the site's own (AGL-3596). A page the plan named no layout for used to\n * be written with none, and so with no header and no footer, beside pages\n * that had them.\n */\nexport function aiPageDraftLayoutId(\n screen: Pick<AiBuildPlanScreen, 'layout'>,\n inventory: Pick<AiSiteInventory, 'layouts' | 'screens'>,\n): string | null {\n if (screen.layout && inventory.layouts.some((row) => row.id === screen.layout)) return screen.layout\n return aiSiteDefaultLayoutId(inventory)\n}\n\n/** The site's name as its visitors see it, read the way the SEO step reads it. */\nfunction siteNameOf(host: FirebaseFirestore.DocumentSnapshot): string {\n const text = (value: unknown) => (typeof value === 'string' ? value.trim() : '')\n return text(host.get('seo.title')) || text(host.get('displayName')) || text(host.get('subdomain'))\n}\n\n/**\n * The counts the confirmed plan's sections promised, against the page a COPY\n * actually produced (AGL-3024); `null` when the copy keeps the plan, or when\n * the plan promised no count to keep.\n *\n * The same hole the layout copy had (`aiCopiedLayoutReview`), on the kind the\n * defect was measured on. This branch generates nothing, so no model ever\n * answers for the plan: a plan reading \"start from the Services page, one\n * card per practice area\" gets the Services page and whatever IT had, and\n * until now reported `Done` — the one report that tells a customer there is\n * no reason to re-read the page.\n *\n * A copy that cannot be read back settles nothing, and a promise this step\n * cannot settle is reviewed by a person rather than reported as kept.\n */\nexport async function aiCopiedPageReview(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; id: string; name: string; screen: Pick<AiBuildPlanScreen, 'sections'> },\n): Promise<AiJobReview | null> {\n if (!input.screen.sections.some((section) => section.items >= AI_PLAN_ITEMS_MIN)) return null\n const stored = await readAiDraftNodes(firestore, { kind: 'screen', hostId: input.hostId, id: input.id })\n const nodes = (stored?.nodes ?? {}) as unknown as AiDoctrineTree['nodes']\n if (!nodes[CANVAS_ROOT_ELEMENT_ID]) {\n return {\n reason: 'doctrine',\n message: `\"${input.name}\" was copied from the page the plan names, and this job could not read the copy back to check it shows what the plan says it shows. Open the page and check it before you put it in front of anyone.`,\n findings: [],\n }\n }\n const violations = aiPlanCopiedPageViolations(input.screen, { rootId: CANVAS_ROOT_ELEMENT_ID, nodes })\n if (!violations.length) return null\n return aiDoctrineReview({\n message: `\"${input.name}\" is a copy of the page the plan names, and a copy is all it is: ${violations[0].message}`,\n violations,\n })\n}\n\n/**\n * A page job is admitted with inputs that read, for a site of the job's own\n * org with a screen to spare; and confirmed only for a plan it can build —\n * one screen, whose creations are ones a page job builds, this deployment has\n * a step for, and this workspace may still make on this site (AGL-3031).\n */\nexport const aiPageJobAdmission: AiJobAdmission = async (context) => {\n const inputs = parseAiPageJobInputs(context.inputs)\n if (typeof inputs === 'string') return { status: 400, error: inputs }\n const plan = context.plan\n if (plan) {\n const refusal = aiPagePlanRefusal(plan)\n if (refusal) return { status: 400, error: refusal }\n if (aiPageJobUnits(plan).some((unit) => !aiJobStepRunnerFor(unit.jobKind))) {\n return { status: 400, error: AI_JOB_PAGE_CREATION_UNAVAILABLE_COPY }\n }\n }\n const org = context.org as Partial<AglynOrgBilling> | null\n return aiDraftAdmissionRefusal(context.firestore, {\n orgId: context.orgId,\n hostId: context.hostId,\n kind: 'screen',\n noun: 'page',\n org,\n // What the workspace may still make here, read once the site is known to\n // be the org's: the site may have changed since the plan was told.\n ...(plan?.create.length\n ? {\n ownCheck: async (hostId: string) => {\n const capabilities = aiPlanCapabilitiesForJob(\n await readAiPlanCapabilities(context.firestore, { hostId, org }),\n { noun: 'a page job', creates: AI_PAGE_CREATE_KINDS },\n )\n const refusal = aiPageCreationRefusal(aiPlanUncreatable(plan, capabilities))\n return refusal ? { status: 403 as const, error: refusal } : null\n },\n }\n : {}),\n })\n}\n\n/**\n * The site's page a page of a build or a site starts from (AGL-3832): the\n * plan's `duplicateOf`, by its name and its words, for the page's prompt.\n * Only a page the site has, and never its starter home page, which is a\n * placeholder rather than words of the owner's (AGL-3596) — nor an entry\n * template, whose words are its records'. `null` when there is no such page\n * or its words cannot be read: the page is then built from its plan alone.\n */\nexport async function aiPageStartPage(\n firestore: Parameters<typeof readAiDraftNodes>[0],\n input: { hostId: string; screen: Pick<AiBuildPlanScreen, 'duplicateOf'>; inventory: Pick<AiSiteInventory, 'screens'> },\n readNodes: typeof readAiDraftNodes = readAiDraftNodes,\n): Promise<AiLayoutStartPage | null> {\n const id = input.screen.duplicateOf\n const row = id ? input.inventory.screens.find((one) => one.id === id && !one.template && !one.replaceable) : undefined\n if (!row) return null\n const stored = await readNodes(firestore, { kind: 'screen', hostId: input.hostId, id: row.id }).catch(() => null)\n const text = stored ? buildPageMarkdown({ nodes: stored.nodes as never, rootId: CANVAS_ROOT_ELEMENT_ID }).trim() : ''\n return text ? { label: row.name, text } : null\n}\n\n/**\n * The library photos the job's other pages already show (AGL-3660): read\n * from the screen drafts a site job names for its page unit\n * (`AI_STOCK_PHOTO_PAGES_INPUT`), never this page's own, so a pass repeated\n * over it keeps its photos, and those its datasets' records show\n * (`AI_STOCK_PHOTO_AVOID_INPUT`, AGL-3616). Empty on any failure: a repeat is better than a\n * page that fails over decoration.\n */\nexport async function aiJobPagesPlacedPhotos(\n firestore: Parameters<typeof readAiDraftNodes>[0],\n input: { hostId: string; job: Pick<AiJob, 'inputs'>; draftId: string },\n readNodes: typeof readAiDraftNodes = readAiDraftNodes,\n): Promise<string[]> {\n return (await aiJobPlacedPhotos(firestore, input, readNodes)).avoid\n}\n\n/**\n * What the job already placed on this site (AGL-3660, AGL-3988), read once:\n * the library photos a page does not place again (`avoid`,\n * {@link aiJobPagesPlacedPhotos}), and the same photos with their size and\n * words (`photos`), which a slot the library failed takes before any\n * starter: the other pages' pictures and the datasets' records' photos the\n * job names (`AI_STOCK_PHOTO_JOB_PHOTOS_INPUT`). Empty on any failure.\n */\nexport async function aiJobPlacedPhotos(\n firestore: Parameters<typeof readAiDraftNodes>[0],\n input: { hostId: string; job: Pick<AiJob, 'inputs'>; draftId: string },\n readNodes: typeof readAiDraftNodes = readAiDraftNodes,\n): Promise<{ avoid: string[]; photos: AiLayoutPicturePhoto[] }> {\n const listed = input.job.inputs?.[AI_STOCK_PHOTO_PAGES_INPUT]\n const ids = Array.isArray(listed)\n ? [...new Set(listed.filter((id): id is string => typeof id === 'string' && !!id && id !== input.draftId))]\n : []\n // What the site's datasets' records show (AGL-3616), named by the job.\n const elsewhere = input.job.inputs?.[AI_STOCK_PHOTO_AVOID_INPUT]\n const shown = Array.isArray(elsewhere)\n ? elsewhere.filter((src): src is string => typeof src === 'string' && src.startsWith('media:'))\n : []\n // The same records' photos with their size and words (AGL-3988).\n const records = aiStockJobPhotosOf(input.job.inputs?.[AI_STOCK_PHOTO_JOB_PHOTOS_INPUT])\n const read = ids.length\n ? await Promise.all(ids.map((id) => readNodes(firestore, { kind: 'screen', hostId: input.hostId, id }).catch(() => null)))\n : []\n const pages = read.flatMap((stored) => aiStockPlacedPhotos(stored?.nodes))\n const seen = new Set<string>()\n const photos = [...records, ...pages].filter((photo) => !seen.has(photo.src) && seen.add(photo.src))\n return {\n avoid: [...new Set([...shown, ...read.flatMap((stored) => aiStockPlacedSrcs(stored?.nodes))])],\n photos,\n }\n}\n\nexport interface AiJobPageStepDeps {\n /** The inventory reader; specs hand in a fake. */\n readInventory?: typeof readSiteInventory\n /** The platform's duplicate module, for a plan that starts from a copy. */\n duplicate?: typeof duplicateResource\n /** The listing generator (AGL-2910). */\n seoFields?: typeof generateSeoFields\n /** The runners a plan's creations are built by (AGL-3031); the registry's otherwise. */\n runnerFor?: typeof aiJobStepRunnerFor\n /** The stock photo source of a language page's pictures (AGL-3660); core's provider otherwise. */\n stockPhotos?: typeof aiLayoutStockPhotoSource\n}\n\nexport function createAiJobPageStep(deps: AiJobPageStepDeps = {}): AiJobStepRunner {\n const readInventory = deps.readInventory ?? readSiteInventory\n const duplicate = deps.duplicate ?? duplicateResource\n const seoFields = deps.seoFields ?? generateSeoFields\n const runnerFor = deps.runnerFor ?? aiJobStepRunnerFor\n return async (stepContext): Promise<AiJobStepOutcome> => {\n const { job, now, signal, firestore, modelFor } = stepContext\n const hostId = job.hostId\n if (!hostId) throw new Error('a page job names no site, and its admission refuses one')\n // The switch's answer for this job, else the routing table's (AGL-2942).\n const model = modelFor?.('job.page') ?? aiModelForStep('job.page')\n const confirmed = aiConfirmedPlan(job)\n if (!confirmed) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_NO_PLAN_COPY }\n // The resume door refuses such a plan at confirmation; one confirmed\n // before a site changed still stops here, before any spend.\n const refusal = aiPagePlanRefusal(confirmed)\n if (refusal) return { ...aiUnspentOutcome(model), failure: refusal }\n\n // ── The creations first (AGL-3031): one a pass, by the steps that own them ──\n const units = aiPageJobUnits(confirmed)\n const outputs = job.outputs ?? []\n const [unit] = aiSitePendingUnits(units, outputs)\n if (unit) {\n const runner = runnerFor(unit.jobKind)\n if (!runner) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_CREATION_UNAVAILABLE_COPY }\n const pass = await aiRunJobUnit(stepContext, {\n unit,\n units,\n runner,\n emptyCopy: AI_JOB_PAGE_CREATION_EMPTY_COPY,\n })\n // A built creation is followed by the next, or by the page's first section.\n return pass.built ? { ...pass.outcome, continue: true } : pass.outcome\n }\n // The page's plan with every creation resolved to the record it built, as\n // a scaffold's page is planned against what the scaffold built.\n const plan: AiJobPlan = units.length\n ? (aiSiteUnitJob(\n job,\n {\n kind: 'page',\n jobKind: 'page',\n resource: 'screen',\n slot: 'page',\n screen: confirmed.screens[0],\n label: confirmed.screens[0].title,\n },\n aiSiteBuiltRefs(units, outputs),\n ).plan as AiJobPlan)\n : confirmed\n const screen = plan.screens[0] as AiBuildPlanScreen\n const name = screen.title || AI_JOB_PAGE_DEFAULT_NAME\n const slug = aiPageDraftSlug(screen)\n const sectionIds = screen.sections.map((_, index) => aiPageSectionNodeId(job.$id, index))\n // The draft screen, by the id the job recorded for it: every pass writes and finds the same one.\n const draftId = aiJobDraftId(job, 'screen')\n\n const output = (\n draft: Pick<AiDraftRecord, 'id' | 'versionId' | 'name' | 'hostSubdomain'>,\n load?: AiLoadEstimate | null,\n note?: string | null,\n ): AiJobOutput => ({\n resource: 'screen',\n id: draft.id,\n versionId: draft.versionId,\n hostId,\n hostSubdomain: draft.hostSubdomain,\n label: draft.name,\n ...(load ? { load } : {}),\n ...(note ? { note } : {}),\n // Rule 10: a navigation entry travels with the page as a proposal, for\n // the member to add once the page is live; no menu is written.\n ...(screen.nav ? { proposal: { navigation: { label: name, slug } } } : {}),\n })\n\n const [inventory, orgSnapshot, written] = await Promise.all([\n readInventory(job.orgId, hostId, { firestore }),\n firestore.collection('orgs').doc(job.orgId).get(),\n readAiDraft(firestore, { kind: 'screen', hostId, id: draftId }),\n ])\n const org = (orgSnapshot.data() ?? null) as Partial<AglynOrgBilling> | null\n if (written?.deleted) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_DELETED_COPY }\n // A record template's copy binds its dataset's fields, and its draft says\n // where the member saves the binding (AGL-3475).\n const record = aiPageRecordTemplate(screen.record, inventory, plan)\n const recordNote = record ? aiPageRecordNote(record) : null\n\n // A page job's plan starts from a copy of a screen the site has (rule\n // 15): the copy is the draft — unrouted, as every copy is — and nothing\n // is generated.\n // Only a page is a page's start: a collection entry template's copy stays\n // a template, so a plan naming one builds the page instead.\n // A copy shows what its SOURCE shows, and this branch generates nothing.\n // So if the source cannot already show what the plan promised, copying it\n // can only produce a page that breaks the promise — the refusal below\n // would be certain before the copy was ever minted. Read the source and\n // build instead, rather than leave the member a page they must throw away\n // and a Try again that can only copy the same thing (AGL-3024).\n // Only a source we could READ and found short diverts: one we cannot read\n // is one we cannot judge, and the review after the copy still answers for\n // that. A short source carries findings; an unreadable one carries none.\n // A copy of the starter home page is never a page the job built\n // (AGL-3596): the site's own placeholder is replaced by a page generated\n // from the plan, so a plan that names it as a start builds instead.\n const copiesStarter = Boolean(\n screen.duplicateOf &&\n inventory.screens.some((row) => row.id === screen.duplicateOf && row.replaceable),\n )\n // A page a build or a site makes (every one designed in the layout\n // language) counts as built only once its plan's sections are in it,\n // under the ids this step writes them by (AGL-3596). A copy holds its\n // source's sections under the source's ids, so it never counts: a build\n // that copied \"Our Story\" for its About page failed the page and left the\n // copy behind (AGL-3832). Such a page is generated from its plan, its\n // prompt shown the words of the page it starts from (`aiPageStartPage`).\n const generatedWhole = aiJobUsesLayoutLanguage(job)\n const sourceReview =\n !written && screen.duplicateOf && !copiesStarter && !generatedWhole\n ? await aiCopiedPageReview(firestore, { hostId, id: screen.duplicateOf, name, screen })\n : null\n const sourceShort = Boolean(sourceReview?.findings.length)\n if (\n !written &&\n !sourceShort &&\n !copiesStarter &&\n !generatedWhole &&\n screen.duplicateOf &&\n inventory.screens.some((row) => row.id === screen.duplicateOf && !row.template)\n ) {\n const copy = await duplicate('screen', {\n orgId: job.orgId,\n hostId,\n sourceId: screen.duplicateOf,\n name,\n uid: job.createdBy,\n org: org as Record<string, unknown> | null,\n // A copy core mints is written under core's id, never `draftId`, so\n // `written` above cannot find it and this branch would fire on every\n // pass — and the pass after a refused copy is the common one, since\n // the review below offers to try again. The job's recorded id is the\n // claim, so a second pass replays the one copy instead of leaving\n // another orphan screen behind it (AGL-3024).\n attemptKey: draftId,\n })\n if (copy.ok) {\n // A copy is not a build (AGL-3024). Nothing is generated here, so\n // whatever the plan promised BEYOND the source is in the draft only\n // if the source already had it, and no model is left to answer for\n // the difference — the copy itself has to.\n const review = await aiCopiedPageReview(firestore, { hostId, id: copy.id, name, screen })\n if (review) return aiUnspentOutcome(model, { review })\n const hostSubdomain = await aiSiteSubdomain(firestore, hostId)\n return aiUnspentOutcome(model, {\n outputs: [output({ id: copy.id, versionId: copy.versionId, name: copy.name, hostSubdomain }, null, recordNote)],\n })\n }\n if (copy.ok === false && copy.status === 403) {\n return aiUnspentOutcome(model, { review: aiLimitReview(copy.error) })\n }\n }\n\n const stored = written ? await readAiDraftNodes(firestore, { kind: 'screen', hostId, id: draftId }) : null\n if (written && !stored) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_DELETED_COPY }\n let page = stored?.nodes ?? aiEmptyPage()\n const index = sectionIds.findIndex((id) => !(id in page))\n // A workspace whose component allowance is finite — Free's one — draws\n // its repeats inline, and every pass is held to the rules that way\n // (AGL-3030, AGL-3615). A form is a saved one placed by id on every plan,\n // or none (AGL-3596).\n const reusableComponents = aiBuildsWithComponents(org)\n // A link may take a visitor to a section of this page, by its name in the plan (AGL-3097).\n const sections = screen.sections.map((section) => section.name)\n // A third-party player the confirmed plan lists for this page is the only one it may embed (AGL-3433).\n const embeds = aiPlanEmbedsFor(plan, { slug: screen.slug })\n // The site's other pages a link may go to, built or minted on a guided\n // start's plan, and the one a link that names none can only mean (AGL-3596).\n const { linkablePages, linkTarget } = aiPageLinkablePages(inventory, aiLayoutSitePages(job.inputs), [screen.id, draftId])\n // A page unit of a guided start or a build is designed in the layout\n // language and compiled (AGL-3660); a record template keeps the raw tree,\n // since its copy binds fields the language does not name.\n const language = aiJobUsesLayoutLanguage(job) && !record\n const checked = {\n ...aiPageCheckContext(inventory, {\n reusableComponents,\n sections,\n embeds,\n recordTokens: aiPageRecordTokens(record),\n linkablePages,\n }),\n // The last pass holds a compiled page to what its own check did: the\n // design's picture cards are the compiler's, not a block rule 1 asks a\n // component of (AGL-3660). Without it, a Pro store's Shop page passed\n // its check and was refused at the last pass (live run, 2026-10-09).\n ...(language ? { codeBuilt: true, repeatsCompiled: true } : {}),\n }\n // And a post's card in a section listing the blog links its post and shows\n // its cover by the tokens the page fills per post (AGL-3676), as its check admitted.\n const context = language\n ? aiLayoutListingContext(checked, aiLayoutPageTargets({ job, inventory, own: [screen.id, draftId] }))\n : checked\n\n // ── The last pass: the whole page, its listing, and the draft reported ──\n // A page in the layout language is written whole by one pass (AGL-3660),\n // so once written it is complete: a planned section missing from it was\n // taken out by its last answer, never one still to build (AGL-3771).\n if ((index === -1 || language) && written) {\n // An attribute a rule has one answer for is settled on the stored page\n // and stored (AGL-3660): this pass asks no model, so a page refused\n // here for one would be refused the same way on every Try again. A\n // Portfolio page whose opening photos all loaded eagerly was (2026-10-10).\n // And a link sent home for nothing is sent to the site's form, or left out (AGL-3729),\n // and an item of a list that shows only placeholders is taken out where the list keeps a real one (AGL-3827).\n // And the site's entrance motion is written on a page built a section at\n // a time, as on one built whole (AGL-3838); a page that already moves —\n // by this pass or by its author — comes back as it is, and is not written.\n const motion = aiLayoutMotionSiteOf(job, screen.slug)\n const settle = <T extends AiDoctrineTree>(tree: T): T => {\n const settled = aiSettleUnrelatedScreenLinks(\n aiSettlePlaceholderItems(aiSettleMechanicalRules(tree, 'page'), 'page', context),\n 'page',\n {\n ...context,\n ownScreenIds: [screen.id, draftId],\n },\n )\n const moved = aiWithSiteMotion(settled.nodes, motion, { sectionIds })\n return moved === settled.nodes ? settled : { ...settled, nodes: moved }\n }\n const settled = settle({ rootId: CANVAS_ROOT_ELEMENT_ID, nodes: page as unknown as AiDoctrineTree['nodes'] })\n if (settled.nodes !== (page as unknown as AiDoctrineTree['nodes'])) {\n // Never written once the job is canceled (AGL-3616).\n throwIfAiJobCanceled(signal)\n const update = await updateAiDraftNodes(firestore, {\n kind: 'screen',\n hostId,\n id: draftId,\n now,\n update: (nodes) => {\n const tree = { rootId: CANVAS_ROOT_ELEMENT_ID, nodes: nodes as unknown as AiDoctrineTree['nodes'] }\n const next = settle(tree)\n return next === tree ? null : (next.nodes as unknown as typeof nodes)\n },\n })\n if (update.ok === false) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_DELETED_COPY }\n page = settled.nodes as unknown as typeof page\n }\n // A link the page carries to one of its sections goes there, now every section is built.\n const report = validateAiDoctrineTree({ rootId: CANVAS_ROOT_ELEMENT_ID, nodes: page }, 'page', {\n ...context,\n scrollTargetIds: sectionIds,\n })\n // The facts the brief did not give, on the page or in the defaults its components show (AGL-3056).\n const note =\n [\n aiBracketedFactsNote({\n tree: { rootId: CANVAS_ROOT_ELEMENT_ID, nodes: page as unknown as AiDoctrineTree['nodes'] },\n inventory,\n }),\n recordNote,\n // A section the last answer could not fill was left out, and the page says which (AGL-3771).\n language ? aiLayoutLeftOutNote(screen, sectionIds, page as unknown as Record<string, unknown>) : null,\n ]\n .filter(Boolean)\n .join(' ') || null\n // The page's own draft, reported once however many times the last pass\n // runs (AGL-3143): a refused pass reports it and a later one that\n // passes does not report it twice.\n const reported = outputs.some((entry) => entry.resource === 'screen' && entry.id === draftId)\n const reports = reported ? [] : [output(written, report.load, note)]\n if (report.violations.length) {\n // The check mints its own ids; the review names the nodes by the ids the draft stores them under.\n const storedIds = report.tree?.sourceIds ?? {}\n const violations = report.violations.map((violation) =>\n violation.nodeIds ? { ...violation, nodeIds: aiModelNodeIds(violation.nodeIds, storedIds) } : violation,\n )\n // THE DRAFT TRAVELS WITH THE REFUSAL (AGL-3143). Every other review\n // parks on a model's answer, so trying again asks the model again and\n // can come back different. This one reads the STORED page and spends\n // nothing, so trying again refuses the same page for the same reasons\n // forever — and a job parked here having reported no output names no\n // draft at all, which leaves the member nowhere to mend the nodes the\n // findings name and no way to finish the job from the console. The\n // draft is reported now instead, and the job still parks: what the\n // review names is mended in the draft, and the next pass then passes.\n return aiUnspentOutcome(model, {\n outputs: reports,\n review: aiDoctrineReview(\n {\n message: aiDoctrineNeedsInputMessage(violations),\n violations,\n answer: { tree: { rootId: CANVAS_ROOT_ELEMENT_ID, nodes: page } },\n },\n { page: true, then: AI_JOB_PAGE_REFUSED_DRAFT_COPY },\n ),\n })\n }\n let spent: AiJobStepOutcome = aiUnspentOutcome(model)\n if (!written.seo?.title?.trim() || !written.seo?.description?.trim()) {\n const seoModel = modelFor?.('job.seo') ?? aiModelForStep('job.seo')\n // The plan's own listing, held to the editor's guidance, when the\n // listing cannot be written inside this pass's time on the model the\n // job runs.\n let values = {\n title: aiClipAtWord(screen.seoTitle, SCREEN_SEO_TEXT_GUIDANCE.title, plan.language),\n description: aiClipAtWord(screen.seoDescription, SCREEN_SEO_TEXT_GUIDANCE.description, plan.language),\n }\n if (\n // A listing is written from the page's own text, with no inventory\n // and so no lookup round.\n aiGenerationWorstCaseMs({ maxTokens: AI_SEO_FIELDS_MAX_TOKENS, model: seoModel, lookups: 0 }) <=\n AI_JOB_PAGE_STEP_MINIMUM_MS\n ) {\n const host = await firestore.collection('hosts').doc(hostId).get()\n const result = await seoFields({\n subject: { kind: 'screen', name, path: screenRoutePathToUrl(slug || SCREEN_ROOT_PATH) },\n brand: siteNameOf(host),\n // What the site is and who it is for (AGL-2918). The page's own\n // Markdown says what the page is about and cannot say who it was\n // written for, so the listing borrows the two answers the job was\n // started from — the scaffold's derived page job carries them,\n // and a page job started from the Screens page carries neither.\n site: aiSiteWords(job.inputs),\n text: buildPageMarkdown({ nodes: page as never, rootId: CANVAS_ROOT_ELEMENT_ID }),\n fields: ['title', 'description'],\n // Other screens by name only: their own listings are their content.\n otherTitles: inventory.screens.map((row) => row.name),\n model: seoModel,\n ...(signal ? { signal } : {}),\n })\n spent = { ...spent, ...aiGenerationSpent(result) }\n if (result.status === 'ok') {\n // A written field that shows a placeholder keeps the plan's, which\n // the plan's own check already held to rule 14 (AGL-3827).\n const usable = (text: string | null | undefined): string =>\n text?.trim() && !aiHasPlaceholderToken(text) ? text.trim() : ''\n values = {\n title: usable(result.value.title) || values.title,\n description: usable(result.value.description) || values.description,\n }\n }\n }\n // Never written once the job is canceled (AGL-3616).\n throwIfAiJobCanceled(signal)\n await writeAiDraftScreenSeo(firestore, { hostId, id: draftId, seo: values, now })\n }\n return { ...spent, outputs: reports }\n }\n\n // ── A section pass ──\n if (!written) {\n const allowance = await aiDraftAllowanceRefusal(firestore, { kind: 'screen', hostId, org })\n if (allowance) return aiUnspentOutcome(model, { review: aiLimitReview(allowance) })\n }\n if (language) {\n // ── The whole page in one answer, in the layout language ──\n const targets = aiLayoutPageTargets({ job, inventory, own: [screen.id, draftId] })\n const startsFrom = await aiPageStartPage(firestore, { hostId, screen, inventory })\n const result = await aiRunLayoutPage({\n job,\n plan,\n screen,\n sectionIds,\n targets,\n context,\n reusableComponents,\n inventory,\n model,\n startsFrom,\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result) }\n // The compiler leaves each picture slot empty; a photo the site serves\n // itself fills it here, after the page is checked (AGL-3660): a stock\n // photo copied into the site's library where the deployment has a\n // library, else a starter photo. No AI credits; never fails the job.\n const sectionNames = screen.sections.map((section) => section.name)\n const seed = `${aiOriginJobId(job)}:${screen.id ?? screen.slug}`\n // A photo another page of the job already shows is not placed here, and\n // is what a picture the library fails takes before any starter (AGL-3988).\n const { avoid, photos: jobPhotos } = await aiJobPlacedPhotos(firestore, { hostId, job, draftId })\n const photoWords = aiStockPhotoWordsOf(job)\n const pictured = await aiResolveLayoutPictures(result.value.nodes, {\n rootId: CANVAS_ROOT_ELEMENT_ID,\n sectionIds,\n sectionNames,\n seed,\n ...(jobPhotos.length ? { jobPhotos } : {}),\n ...(photoWords.language ? { language: photoWords.language } : {}),\n // The owner's own photos first (AGL-3957), the stock library for the rest.\n source: (deps.stockPhotos ?? aiLayoutSitePhotoSource)({\n hostId,\n uid: job.createdBy,\n seed,\n business: aiSiteWords(job.inputs).about,\n ...photoWords,\n sectionNames,\n jobId: aiOriginJobId(job),\n ...(avoid.length ? { avoid } : {}),\n ...(signal ? { signal } : {}),\n }),\n })\n if (!written) {\n // Never written once the job is canceled (AGL-3616).\n throwIfAiJobCanceled(signal)\n const draft = await writeAiDraft(firestore, {\n kind: 'screen',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: pictured,\n slug,\n layoutId: aiPageDraftLayoutId(screen, inventory),\n aiJobId: aiOriginJobId(job),\n now,\n })\n if (draft.ok === false) {\n if (draft.status === 404) throw new Error(`site ${hostId} vanished while its page was generated`)\n return { ...spent, review: aiLimitReview(draft.error) }\n }\n } else {\n // A draft written by an earlier, interrupted pass is replaced whole.\n // Never written once the job is canceled (AGL-3616).\n throwIfAiJobCanceled(signal)\n const update = await updateAiDraftNodes(firestore, {\n kind: 'screen',\n hostId,\n id: draftId,\n now,\n update: () => pictured,\n })\n if (update.ok === false) return { ...spent, failure: AI_JOB_PAGE_DELETED_COPY }\n }\n return { ...spent, continue: true }\n }\n const maxTokens = aiJobPageSectionMaxTokens(model)\n const maxElements = aiJobPageSectionMaxElements(maxTokens)\n const result = await runValidatedGeneration<AiPageSection>('page-section', {\n step: 'job.page',\n model,\n instructions: AI_JOB_PAGE_INSTRUCTIONS,\n inventory,\n messages: [\n {\n role: 'user',\n content: aiPageSectionPrompt({ job, plan, screen, index, maxElements, reusableComponents, record, linkablePages }),\n },\n ],\n tool: AI_PAGE_SECTION_TOOL,\n maxTokens,\n // A section cut off at its ceiling is asked for smaller, and a member\n // whose section still does not fit reads that it was too large (AGL-3042).\n cutOff: { noun: 'section', smaller: aiPageSectionSmaller({ maxElements, reusableComponents }) },\n thinking: 'off',\n check: aiPageSectionCheck({\n page,\n sectionIds,\n index,\n context,\n section: screen.sections[index],\n inventory,\n linkTarget,\n }),\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result, { page: true }) }\n // A record template's page leads with each record's own photo (AGL-3616).\n const section = aiPageSectionWithRecordImage(result.value, page, record)\n\n if (!written) {\n // Never written once the job is canceled (AGL-3616).\n throwIfAiJobCanceled(signal)\n const draft = await writeAiDraft(firestore, {\n kind: 'screen',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: aiPageWithSection(aiEmptyPage(), section, sectionIds),\n slug,\n layoutId: aiPageDraftLayoutId(screen, inventory),\n aiJobId: aiOriginJobId(job),\n now,\n })\n if (draft.ok === false) {\n if (draft.status === 404) throw new Error(`site ${hostId} vanished while its page was generated`)\n return { ...spent, review: aiLimitReview(draft.error) }\n }\n } else {\n // Never written once the job is canceled (AGL-3616).\n throwIfAiJobCanceled(signal)\n const update = await updateAiDraftNodes(firestore, {\n kind: 'screen',\n hostId,\n id: draftId,\n now,\n // A pass run again after its write finds its section and changes nothing.\n update: (nodes) =>\n section.rootId in nodes ? null : aiPageWithSection(nodes, section, sectionIds),\n })\n if (update.ok === false) return { ...spent, failure: AI_JOB_PAGE_DELETED_COPY }\n }\n return { ...spent, continue: true }\n }\n}\n\nexport const runAiJobPageStep = createAiJobPageStep()\n\n/**\n * Registers the page step with the least time a section pass needs and the\n * time its next pass needs, the passes a page with its creations may take, and\n * the check a page job passes when it is created or its plan is confirmed.\n */\nexport function registerAiPageJob(): void {\n registerAiJobStep('page', runAiJobPageStep, {\n minimumMs: AI_JOB_PAGE_STEP_MINIMUM_MS,\n minimumMsFor: aiPageJobRunMinimumMs,\n })\n registerAiJobStepPasses('page', AI_JOB_PAGE_MAX_PASSES)\n registerAiJobAdmission('page', aiPageJobAdmission)\n}\n"],"names":["throwIfAiJobCanceled","buildPageMarkdown","SCREEN_SEO_TEXT_GUIDANCE","SCREEN_ROOT_PATH","screenRoutePathToUrl","CANVAS_ROOT_ELEMENT_ID","duplicateResource","AI_BUILD_PLAN_LIMITS","aiPlanEmbedsFor","AI_PAGE_CREATE_KINDS","aiPageCreationRefusal","aiPagePlanRefusal","parseAiPageJobInputs","aiPlanCapabilitiesForJob","aiPlanUncreatable","aiSiteWords","aiModelForStep","aiDoctrineNeedsInputMessage","runValidatedGeneration","aiClipAtWord","aiSettleMechanicalRules","aiSettlePlaceholderItems","aiSettleUnrelatedScreenLinks","validateAiDoctrineTree","aiHasPlaceholderToken","AI_SEO_FIELDS_MAX_TOKENS","generateSeoFields","readSiteInventory","registerAiJobAdmission","aiGenerationWorstCaseMs","aiJobDraftId","aiOriginJobId","aiBuildsWithComponents","aiDraftAdmissionRefusal","aiDraftAllowanceRefusal","aiSiteSubdomain","readAiDraft","readAiDraftNodes","readAiPlanCapabilities","updateAiDraftNodes","writeAiDraft","writeAiDraftScreenSeo","aiBracketedFactsNote","aiConfirmedPlan","aiDoctrineReview","aiGenerationSpent","aiLimitReview","aiModelNodeIds","aiUnspentOutcome","AI_JOB_PAGE_SECTION_MAX_TOKENS","AI_JOB_PAGE_SECTION_TOKENS","AI_JOB_PAGE_STEP_BUDGET","AI_JOB_PAGE_STEP_MINIMUM_MS","aiJobPageSectionMaxTokens","AI_JOB_PAGE_INSTRUCTIONS","AI_PAGE_SECTION_TOOL","aiEmptyPage","aiPageCheckContext","aiPageLinkablePages","aiPageRecordNote","aiPageRecordTemplate","aiPageRecordTokens","aiPageSectionCheck","aiPageSectionNodeId","aiPageSectionPrompt","aiPageSectionSmaller","aiPageSectionWithRecordImage","aiPageWithSection","aiLayoutSitePages","aiResolveLayoutPictures","aiLayoutSitePhotoSource","AI_STOCK_PHOTO_AVOID_INPUT","AI_STOCK_PHOTO_JOB_PHOTOS_INPUT","AI_STOCK_PHOTO_PAGES_INPUT","aiStockJobPhotosOf","aiStockPhotoWordsOf","aiStockPlacedPhotos","aiStockPlacedSrcs","AI_PLAN_ITEMS_MIN","aiPlanCopiedPageViolations","AI_JOB_PAGE_LANGUAGE_BUDGET","aiJobUsesLayoutLanguage","aiLayoutListingContext","aiLayoutPageTargets","aiLayoutLeftOutNote","aiLayoutMotionSiteOf","aiRunLayoutPage","aiWithSiteMotion","aiCreationUnit","aiRunJobUnit","aiSiteBuiltRefs","aiSitePendingUnits","aiSiteUnitJob","aiJobStepRunMinimumMs","aiJobStepRunnerFor","registerAiJobStep","registerAiJobStepPasses","AI_JOB_PAGE_TOKENS_PER_ELEMENT","AI_JOB_PAGE_REAL_TOKENS_PER_ESTIMATED","Math","max","AI_JOB_PAGE_REAL_TOKENS_PER_ELEMENT","ceil","aiJobPageSectionMaxElements","maxTokens","floor","AI_JOB_PAGE_DEFAULT_NAME","AI_JOB_PAGE_NO_PLAN_COPY","AI_JOB_PAGE_DELETED_COPY","AI_JOB_PAGE_CREATION_UNAVAILABLE_COPY","AI_JOB_PAGE_CREATION_EMPTY_COPY","AI_JOB_PAGE_REFUSED_DRAFT_COPY","AI_JOB_PAGE_MAX_PASSES","create","sections","aiPageJobUnits","plan","flatMap","kind","creation","index","unit","aiPageJobRunMinimumMs","job","confirmed","pagePass","minimumMs","units","outputs","aiPageDraftSlug","screen","segments","slug","split","map","part","trim","filter","Boolean","length","aiSiteDefaultLayoutId","inventory","counts","layouts","Set","row","id","size","pages","screens","template","layoutId","has","home","find","Map","set","get","mostUsed","entries","sort","a","b","aiPageDraftLayoutId","layout","some","siteNameOf","host","text","value","aiCopiedPageReview","firestore","input","section","items","stored","hostId","nodes","reason","message","name","findings","violations","rootId","aiPageJobAdmission","context","inputs","status","error","refusal","jobKind","org","orgId","noun","ownCheck","capabilities","creates","aiPageStartPage","readNodes","duplicateOf","one","replaceable","undefined","catch","label","aiJobPagesPlacedPhotos","aiJobPlacedPhotos","avoid","listed","ids","Array","isArray","draftId","elsewhere","shown","src","startsWith","records","read","Promise","all","seen","photos","photo","add","createAiJobPageStep","deps","readInventory","duplicate","seoFields","runnerFor","stepContext","orgSnapshot","now","signal","modelFor","Error","model","failure","runner","pass","emptyCopy","built","outcome","continue","resource","slot","title","sectionIds","_","$id","output","draft","load","note","versionId","hostSubdomain","nav","proposal","navigation","written","collection","doc","data","deleted","record","recordNote","copiesStarter","generatedWhole","sourceReview","sourceShort","copy","sourceId","uid","createdBy","attemptKey","ok","review","page","findIndex","reusableComponents","embeds","linkablePages","linkTarget","language","checked","recordTokens","codeBuilt","repeatsCompiled","own","motion","settle","tree","settled","ownScreenIds","moved","update","next","report","scrollTargetIds","join","reported","entry","reports","storedIds","sourceIds","violation","nodeIds","answer","then","spent","seo","description","seoModel","values","seoTitle","seoDescription","lookups","result","subject","path","brand","site","fields","otherTitles","usable","allowance","targets","startsFrom","refused","sectionNames","seed","jobPhotos","photoWords","pictured","source","stockPhotos","business","about","jobId","aiJobId","maxElements","step","instructions","messages","role","content","tool","cutOff","smaller","thinking","check","runAiJobPageStep","registerAiPageJob","minimumMsFor"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-page-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { throwIfAiJobCanceled } from './ai-job-cancel'\nimport { buildPageMarkdown } from '@aglyn/aglyn/app-utils/page-markdown'\nimport { SCREEN_SEO_TEXT_GUIDANCE } from '@aglyn/aglyn/app-utils/screen-seo-fields'\nimport { SCREEN_ROOT_PATH, screenRoutePathToUrl } from '@aglyn/aglyn/app-utils/screen-route'\nimport { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { duplicateResource } from '@aglyn/tenant-data-admin/server/duplicate-resource'\nimport { AI_BUILD_PLAN_LIMITS, aiPlanEmbedsFor, type AiBuildPlanScreen } from '../model/ai-build-plan'\nimport {\n AI_PAGE_CREATE_KINDS,\n aiPageCreationRefusal,\n aiPagePlanRefusal,\n parseAiPageJobInputs,\n} from '../model/ai-page-job'\nimport { aiPlanCapabilitiesForJob, aiPlanUncreatable } from '../model/ai-plan-capabilities'\nimport { aiSiteWords } from '../model/ai-site-job'\nimport type { AiJob, AiJobOutput, AiJobPlan, AiJobReview } from '../model/ai-jobs.types'\nimport { aiModelForStep } from '../providers/routing'\nimport { aiDoctrineNeedsInputMessage, runValidatedGeneration } from '../runtime/ai-doctrine'\nimport {\n aiClipAtWord,\n aiSettleMechanicalRules,\n aiSettlePlaceholderItems,\n aiSettleUnrelatedScreenLinks,\n validateAiDoctrineTree,\n type AiDoctrineTree,\n} from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport { aiHasPlaceholderToken } from '../runtime/ai-placeholder-tokens'\nimport { AI_SEO_FIELDS_MAX_TOKENS, generateSeoFields } from '../runtime/seo-fields'\nimport { readSiteInventory } from '../runtime/site-inventory'\nimport type { AiSiteInventory } from '../model/ai-site-inventory'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport { aiGenerationWorstCaseMs } from './ai-job-budget'\nimport { aiJobDraftId, aiOriginJobId } from './ai-job-draft-ids'\nimport {\n aiBuildsWithComponents,\n aiDraftAdmissionRefusal,\n aiDraftAllowanceRefusal,\n aiSiteSubdomain,\n readAiDraft,\n readAiDraftNodes,\n readAiPlanCapabilities,\n updateAiDraftNodes,\n writeAiDraft,\n writeAiDraftScreenSeo,\n type AiDraftRecord,\n} from './ai-job-drafts'\nimport {\n aiBracketedFactsNote,\n aiConfirmedPlan,\n aiDoctrineReview,\n aiGenerationSpent,\n aiLimitReview,\n aiModelNodeIds,\n aiUnspentOutcome,\n} from './ai-job-generation'\nimport {\n AI_JOB_PAGE_SECTION_MAX_TOKENS,\n AI_JOB_PAGE_SECTION_TOKENS,\n AI_JOB_PAGE_STEP_BUDGET,\n AI_JOB_PAGE_STEP_MINIMUM_MS,\n aiJobPageSectionMaxTokens,\n} from './ai-job-page-budget'\nimport {\n AI_JOB_PAGE_INSTRUCTIONS,\n AI_PAGE_SECTION_TOOL,\n aiEmptyPage,\n aiPageCheckContext,\n aiPageLinkablePages,\n aiPageRecordNote,\n aiPageRecordTemplate,\n aiPageRecordTokens,\n aiPageSectionCheck,\n aiPageSectionNodeId,\n aiPageSectionPrompt,\n aiPageSectionSmaller,\n aiPageSectionWithRecordImage,\n aiPageWithSection,\n type AiPageSection,\n} from './ai-job-page-sections'\nimport { aiLayoutSitePages } from './ai-job-layout-site-pages'\nimport { aiResolveLayoutPictures, type AiLayoutPicturePhoto } from '../layout-language/ai-layout-pictures'\nimport { aiLayoutPagePhotosOf, aiLayoutSitePhotoSource } from './ai-layout-owner-photos'\nimport {\n AI_STOCK_PHOTO_AVOID_INPUT,\n AI_STOCK_PHOTO_JOB_PHOTOS_INPUT,\n AI_STOCK_PHOTO_PAGES_INPUT,\n aiLayoutStockPhotoSource,\n aiStockJobPhotosOf,\n aiStockPhotoWordsOf,\n aiStockPlacedPhotos,\n aiStockPlacedSrcs,\n} from './ai-layout-stock-photos'\nimport { AI_PLAN_ITEMS_MIN, aiPlanCopiedPageViolations } from './ai-job-plan-conformance'\nimport {\n AI_JOB_PAGE_LANGUAGE_BUDGET,\n aiJobUsesLayoutLanguage,\n aiLayoutListingContext,\n aiLayoutPageTargets,\n aiLayoutLeftOutNote,\n aiLayoutMotionSiteOf,\n aiRunLayoutPage,\n type AiLayoutStartPage,\n} from './ai-job-page-language'\nimport { aiWithSiteMotion } from '../layout-language/ai-layout-motion'\nimport {\n aiCreationUnit,\n aiRunJobUnit,\n aiSiteBuiltRefs,\n aiSitePendingUnits,\n aiSiteUnitJob,\n type AiSiteUnit,\n} from './ai-job-site-step'\nimport type { AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport {\n aiJobStepRunMinimumMs,\n aiJobStepRunnerFor,\n registerAiJobStep,\n registerAiJobStepPasses,\n} from './ai-jobs'\n\n/**\n * The page step (AGL-2907): the generation step of a `page` job, run once a\n * member confirmed the job's plan. It builds ONE screen, section by section,\n * from what the site already has, and leaves it an unpublished draft.\n *\n * ── Why a page is built in passes ────────────────────────────────────────\n *\n * A page is far larger than one answer the job beat can wait for: the beat\n * gives a step what is left of its budget, and a whole page, answered and re-asked,\n * takes several times that. The confirmed plan already names the page's\n * sections, so the step builds one section per pass and asks the machine to\n * continue. Every pass is one reservation and one generation — the section's\n * lookup rounds, its answer and, when it breaks a rule, its one re-ask — sized\n * so that worst case fits `AI_JOB_PAGE_STEP_MINIMUM_MS`, which the step\n * registers as the least time a section pass needs before it starts.\n *\n * - The first pass builds the top section, which holds the page's h1, and\n * writes the draft screen and its first version.\n * - Each later pass builds the next section and adds it to that version.\n * - The last pass generates no tree: it holds the whole page to the\n * doctrine, writes the search title and description, and reports the\n * draft with its measured weight and a navigation proposal.\n *\n * How a section is asked for and held to the rules as part of the page is\n * `ai-job-page-sections.ts`.\n *\n * ── What the plan creates comes first (AGL-3031) ─────────────────────────\n *\n * A plan that creates a layout, forms or components is built in the same job,\n * before its page, one creation a pass, through the site scaffold's unit\n * machinery: each creation is handed to the step that builds that kind, under\n * a job derived from this one, and lands as the draft that step writes. Where\n * the job stands is read from its outputs, so nothing is kept anywhere else.\n * Once every creation is built, the page's passes run on the plan with each\n * `new:<name>` resolved to the record that was built, which the site's\n * inventory now lists — so the page places the new component and binds the new\n * form by id, and renders inside the new layout, exactly as it would one the\n * site always had. A pass that builds a creation needs the time the step that\n * builds it registers, not a section's (`aiPageJobRunMinimumMs`, AGL-3035), so\n * neither the beat nor a door starts it with only a section's time left.\n *\n * ── Never published ──────────────────────────────────────────────────────\n *\n * The draft is written the way `/api/hosts/resources` and\n * `/api/hosts/versions` write a screen and its first version, and nothing\n * else is touched: not the host document, whose routing map is what the live\n * site serves, and no collection, store, layout or navigation pointer\n * (`ai-job-drafts.ts`). A screen the routing map does not name serves nothing\n * until a member publishes it.\n */\n\n/**\n * A section pass's time, and the ceiling each pass asks on each model, are\n * declared apart from the step (`ai-job-page-budget.ts`): the site scaffold\n * builds its pages through this step and reads them without loading it.\n */\nexport {\n AI_JOB_PAGE_SECTION_MAX_TOKENS,\n AI_JOB_PAGE_SECTION_TOKENS,\n AI_JOB_PAGE_STEP_BUDGET,\n AI_JOB_PAGE_STEP_MINIMUM_MS,\n aiJobPageSectionMaxTokens,\n}\n\n/**\n * Tokens one element takes in a section's answer, ESTIMATED: the tree as JSON\n * text inside the tool call, at four characters a token. Measured on the ten\n * golden pages at a median of 38 and a most of 43 over their 39 sections, and\n * `ai-job-page-evals.spec.ts` holds every golden section under it.\n */\nexport const AI_JOB_PAGE_TOKENS_PER_ELEMENT = 45\n\n/**\n * Real tokens to every token estimated at four characters (AGL-3042). A\n * section's ceiling is counted by the provider in its own tokens, which run\n * above that estimate. MEASURED on the first live document run on a Free\n * workspace (AGL-3024, test-org, 2026-09-16), as the cache read of a request\n * over the estimate of the same cached prefix: 4,059 over 2,825 for the page\n * plan and 6,649 over 4,310 for the layout, the higher of the two, 1.5427 —\n * the ratio `ai-job-free-page.spec.ts` prices a Free page at. It is measured on\n * prompt text; an answer's own is read off a recorded pass, whose output\n * tokens the step's record keeps (`lastRuns`), beside the section it stored.\n */\nexport const AI_JOB_PAGE_REAL_TOKENS_PER_ESTIMATED = Math.max(4_059 / 2_825, 6_649 / 4_310)\n\n/**\n * Real tokens one element takes in a section's answer: the estimate at its\n * golden most, in real tokens, rounded up — 45 × 1.5427 → 70 (AGL-3042).\n */\nexport const AI_JOB_PAGE_REAL_TOKENS_PER_ELEMENT = Math.ceil(\n AI_JOB_PAGE_TOKENS_PER_ELEMENT * AI_JOB_PAGE_REAL_TOKENS_PER_ESTIMATED,\n)\n\n/**\n * The most elements a section's request asks it to keep under at an answer\n * ceiling: 15 at the balanced tier's 1,050 (AGL-3042). Counted in real\n * tokens, because the ceiling is: counted in the estimate, the budget is 23\n * elements, which a section of the goldens' largest elements fills at 1,597\n * real tokens — half as much again as the ceiling that cuts it off.\n */\nexport function aiJobPageSectionMaxElements(maxTokens: number): number {\n return Math.max(1, Math.floor(maxTokens / AI_JOB_PAGE_REAL_TOKENS_PER_ELEMENT))\n}\n\n/** A page's name when the plan names none. */\nexport const AI_JOB_PAGE_DEFAULT_NAME = 'New page'\n\nexport const AI_JOB_PAGE_NO_PLAN_COPY = 'This page job has no confirmed plan to build.'\nexport const AI_JOB_PAGE_DELETED_COPY = 'The draft page was deleted while it was being built.'\n\n/** A creation the plan names that this deployment has no step to build. */\nexport const AI_JOB_PAGE_CREATION_UNAVAILABLE_COPY =\n 'This page needs something that cannot be built here yet. Describe the page again.'\n\n/** A creation that finished without reporting what it built; the page cannot place it. */\nexport const AI_JOB_PAGE_CREATION_EMPTY_COPY =\n 'Part of this page could not be built. Describe the page again.'\n\n/**\n * What a page refused on its last pass tells the member after the plain\n * refusal (AGL-3143, AGL-3596). The draft is reported with the review, and\n * mending its layout in the editor is what lets the next pass finish the job.\n */\nexport const AI_JOB_PAGE_REFUSED_DRAFT_COPY = 'The draft is yours to open: fix its layout in the editor, then try again.'\n\n/**\n * The most passes a page job may take (AGL-3031): every creation the plan\n * limits admit, every section they admit, and the last pass that reports the\n * draft. A creation's step builds it in one pass, and a page one section a\n * pass, so a runner that never finishes is still bounded while a real page is\n * not.\n */\nexport const AI_JOB_PAGE_MAX_PASSES = AI_BUILD_PLAN_LIMITS.create + AI_BUILD_PLAN_LIMITS.sections + 1\n\n/**\n * The units a plan's creations are built as, in the order a scaffold builds\n * them: the layout, then the forms, then the components. Each is addressed by\n * its place in the plan, so a unit run again finds its own draft.\n */\nexport function aiPageJobUnits(plan: Pick<AiJobPlan, 'create'>): AiSiteUnit[] {\n return AI_PAGE_CREATE_KINDS.flatMap((kind) =>\n plan.create.flatMap((creation, index) => {\n if (creation.kind !== kind) return []\n const unit = aiCreationUnit(creation, `c${index}`)\n return unit ? [unit] : []\n }),\n )\n}\n\n/**\n * The least time this page job's next pass needs (AGL-3035). A pass that\n * builds a creation hands it to the step registered for that kind, under the\n * job derived for it, so it needs what that step registers for that job — a\n * layout's time, not a section's. Every other pass — a section, the last one —\n * needs a section pass's.\n */\nexport function aiPageJobRunMinimumMs(job: AiJob): number {\n const confirmed = aiConfirmedPlan(job)\n // A page built in the layout language is one answer for the whole page (AGL-3660).\n const pagePass = aiJobUsesLayoutLanguage(job) ? AI_JOB_PAGE_LANGUAGE_BUDGET.minimumMs : AI_JOB_PAGE_STEP_MINIMUM_MS\n if (!confirmed) return pagePass\n const units = aiPageJobUnits(confirmed)\n const outputs = job.outputs ?? []\n const [unit] = aiSitePendingUnits(units, outputs)\n if (!unit) return pagePass\n return aiJobStepRunMinimumMs(aiSiteUnitJob(job, unit, aiSiteBuiltRefs(units, outputs)))\n}\n\n/**\n * The one-segment address a draft asks for, from the plan's slug; `/` for a\n * planned home page, which asks for the root rather than for an address made\n * from its name (AGL-3596).\n */\nexport function aiPageDraftSlug(screen: Pick<AiBuildPlanScreen, 'slug'>): string {\n const segments = screen.slug.split('/').map((part) => part.trim()).filter(Boolean)\n if (!segments.length && screen.slug.trim() === SCREEN_ROOT_PATH) return SCREEN_ROOT_PATH\n return segments[segments.length - 1] ?? ''\n}\n\n/**\n * The layout a site's pages render inside (AGL-3596): the one its home page\n * uses, else the one the most pages use, else the site's only layout. `null`\n * for a site with no layout, or several and no page to choose between them.\n */\nexport function aiSiteDefaultLayoutId(\n inventory: Pick<AiSiteInventory, 'layouts' | 'screens'>,\n): string | null {\n const layouts = new Set(inventory.layouts.map((row) => row.id))\n if (!layouts.size) return null\n const pages = inventory.screens.filter(\n (row) => !row.template && row.layoutId && layouts.has(row.layoutId),\n )\n const home = pages.find((row) => row.slug.trim() === SCREEN_ROOT_PATH)\n if (home?.layoutId) return home.layoutId\n const counts = new Map<string, number>()\n for (const row of pages) counts.set(row.layoutId as string, (counts.get(row.layoutId as string) ?? 0) + 1)\n const [mostUsed] = [...counts.entries()].sort((a, b) => b[1] - a[1])\n if (mostUsed) return mostUsed[0]\n return layouts.size === 1 ? [...layouts][0] : null\n}\n\n/**\n * The layout a page draft renders inside: the plan's, where the site has it,\n * else the site's own (AGL-3596). A page the plan named no layout for used to\n * be written with none, and so with no header and no footer, beside pages\n * that had them.\n */\nexport function aiPageDraftLayoutId(\n screen: Pick<AiBuildPlanScreen, 'layout'>,\n inventory: Pick<AiSiteInventory, 'layouts' | 'screens'>,\n): string | null {\n if (screen.layout && inventory.layouts.some((row) => row.id === screen.layout)) return screen.layout\n return aiSiteDefaultLayoutId(inventory)\n}\n\n/** The site's name as its visitors see it, read the way the SEO step reads it. */\nfunction siteNameOf(host: FirebaseFirestore.DocumentSnapshot): string {\n const text = (value: unknown) => (typeof value === 'string' ? value.trim() : '')\n return text(host.get('seo.title')) || text(host.get('displayName')) || text(host.get('subdomain'))\n}\n\n/**\n * The counts the confirmed plan's sections promised, against the page a COPY\n * actually produced (AGL-3024); `null` when the copy keeps the plan, or when\n * the plan promised no count to keep.\n *\n * The same hole the layout copy had (`aiCopiedLayoutReview`), on the kind the\n * defect was measured on. This branch generates nothing, so no model ever\n * answers for the plan: a plan reading \"start from the Services page, one\n * card per practice area\" gets the Services page and whatever IT had, and\n * until now reported `Done` — the one report that tells a customer there is\n * no reason to re-read the page.\n *\n * A copy that cannot be read back settles nothing, and a promise this step\n * cannot settle is reviewed by a person rather than reported as kept.\n */\nexport async function aiCopiedPageReview(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; id: string; name: string; screen: Pick<AiBuildPlanScreen, 'sections'> },\n): Promise<AiJobReview | null> {\n if (!input.screen.sections.some((section) => section.items >= AI_PLAN_ITEMS_MIN)) return null\n const stored = await readAiDraftNodes(firestore, { kind: 'screen', hostId: input.hostId, id: input.id })\n const nodes = (stored?.nodes ?? {}) as unknown as AiDoctrineTree['nodes']\n if (!nodes[CANVAS_ROOT_ELEMENT_ID]) {\n return {\n reason: 'doctrine',\n message: `\"${input.name}\" was copied from the page the plan names, and this job could not read the copy back to check it shows what the plan says it shows. Open the page and check it before you put it in front of anyone.`,\n findings: [],\n }\n }\n const violations = aiPlanCopiedPageViolations(input.screen, { rootId: CANVAS_ROOT_ELEMENT_ID, nodes })\n if (!violations.length) return null\n return aiDoctrineReview({\n message: `\"${input.name}\" is a copy of the page the plan names, and a copy is all it is: ${violations[0].message}`,\n violations,\n })\n}\n\n/**\n * A page job is admitted with inputs that read, for a site of the job's own\n * org with a screen to spare; and confirmed only for a plan it can build —\n * one screen, whose creations are ones a page job builds, this deployment has\n * a step for, and this workspace may still make on this site (AGL-3031).\n */\nexport const aiPageJobAdmission: AiJobAdmission = async (context) => {\n const inputs = parseAiPageJobInputs(context.inputs)\n if (typeof inputs === 'string') return { status: 400, error: inputs }\n const plan = context.plan\n if (plan) {\n const refusal = aiPagePlanRefusal(plan)\n if (refusal) return { status: 400, error: refusal }\n if (aiPageJobUnits(plan).some((unit) => !aiJobStepRunnerFor(unit.jobKind))) {\n return { status: 400, error: AI_JOB_PAGE_CREATION_UNAVAILABLE_COPY }\n }\n }\n const org = context.org as Partial<AglynOrgBilling> | null\n return aiDraftAdmissionRefusal(context.firestore, {\n orgId: context.orgId,\n hostId: context.hostId,\n kind: 'screen',\n noun: 'page',\n org,\n // What the workspace may still make here, read once the site is known to\n // be the org's: the site may have changed since the plan was told.\n ...(plan?.create.length\n ? {\n ownCheck: async (hostId: string) => {\n const capabilities = aiPlanCapabilitiesForJob(\n await readAiPlanCapabilities(context.firestore, { hostId, org }),\n { noun: 'a page job', creates: AI_PAGE_CREATE_KINDS },\n )\n const refusal = aiPageCreationRefusal(aiPlanUncreatable(plan, capabilities))\n return refusal ? { status: 403 as const, error: refusal } : null\n },\n }\n : {}),\n })\n}\n\n/**\n * The site's page a page of a build or a site starts from (AGL-3832): the\n * plan's `duplicateOf`, by its name and its words, for the page's prompt.\n * Only a page the site has, and never its starter home page, which is a\n * placeholder rather than words of the owner's (AGL-3596) — nor an entry\n * template, whose words are its records'. `null` when there is no such page\n * or its words cannot be read: the page is then built from its plan alone.\n */\nexport async function aiPageStartPage(\n firestore: Parameters<typeof readAiDraftNodes>[0],\n input: { hostId: string; screen: Pick<AiBuildPlanScreen, 'duplicateOf'>; inventory: Pick<AiSiteInventory, 'screens'> },\n readNodes: typeof readAiDraftNodes = readAiDraftNodes,\n): Promise<AiLayoutStartPage | null> {\n const id = input.screen.duplicateOf\n const row = id ? input.inventory.screens.find((one) => one.id === id && !one.template && !one.replaceable) : undefined\n if (!row) return null\n const stored = await readNodes(firestore, { kind: 'screen', hostId: input.hostId, id: row.id }).catch(() => null)\n const text = stored ? buildPageMarkdown({ nodes: stored.nodes as never, rootId: CANVAS_ROOT_ELEMENT_ID }).trim() : ''\n return text ? { label: row.name, text } : null\n}\n\n/**\n * The library photos the job's other pages already show (AGL-3660): read\n * from the screen drafts a site job names for its page unit\n * (`AI_STOCK_PHOTO_PAGES_INPUT`), never this page's own, so a pass repeated\n * over it keeps its photos, and those its datasets' records show\n * (`AI_STOCK_PHOTO_AVOID_INPUT`, AGL-3616). Empty on any failure: a repeat is better than a\n * page that fails over decoration.\n */\nexport async function aiJobPagesPlacedPhotos(\n firestore: Parameters<typeof readAiDraftNodes>[0],\n input: { hostId: string; job: Pick<AiJob, 'inputs'>; draftId: string },\n readNodes: typeof readAiDraftNodes = readAiDraftNodes,\n): Promise<string[]> {\n return (await aiJobPlacedPhotos(firestore, input, readNodes)).avoid\n}\n\n/**\n * What the job already placed on this site (AGL-3660, AGL-3988), read once:\n * the library photos a page does not place again (`avoid`,\n * {@link aiJobPagesPlacedPhotos}), and the same photos with their size and\n * words (`photos`), which a slot the library failed takes before any\n * starter: the other pages' pictures and the datasets' records' photos the\n * job names (`AI_STOCK_PHOTO_JOB_PHOTOS_INPUT`). Empty on any failure.\n */\nexport async function aiJobPlacedPhotos(\n firestore: Parameters<typeof readAiDraftNodes>[0],\n input: { hostId: string; job: Pick<AiJob, 'inputs'>; draftId: string },\n readNodes: typeof readAiDraftNodes = readAiDraftNodes,\n): Promise<{ avoid: string[]; photos: AiLayoutPicturePhoto[] }> {\n const listed = input.job.inputs?.[AI_STOCK_PHOTO_PAGES_INPUT]\n const ids = Array.isArray(listed)\n ? [...new Set(listed.filter((id): id is string => typeof id === 'string' && !!id && id !== input.draftId))]\n : []\n // What the site's datasets' records show (AGL-3616), named by the job.\n const elsewhere = input.job.inputs?.[AI_STOCK_PHOTO_AVOID_INPUT]\n const shown = Array.isArray(elsewhere)\n ? elsewhere.filter((src): src is string => typeof src === 'string' && src.startsWith('media:'))\n : []\n // The same records' photos with their size and words (AGL-3988).\n const records = aiStockJobPhotosOf(input.job.inputs?.[AI_STOCK_PHOTO_JOB_PHOTOS_INPUT])\n const read = ids.length\n ? await Promise.all(ids.map((id) => readNodes(firestore, { kind: 'screen', hostId: input.hostId, id }).catch(() => null)))\n : []\n const pages = read.flatMap((stored) => aiStockPlacedPhotos(stored?.nodes))\n const seen = new Set<string>()\n const photos = [...records, ...pages].filter((photo) => !seen.has(photo.src) && seen.add(photo.src))\n return {\n avoid: [...new Set([...shown, ...read.flatMap((stored) => aiStockPlacedSrcs(stored?.nodes))])],\n photos,\n }\n}\n\nexport interface AiJobPageStepDeps {\n /** The inventory reader; specs hand in a fake. */\n readInventory?: typeof readSiteInventory\n /** The platform's duplicate module, for a plan that starts from a copy. */\n duplicate?: typeof duplicateResource\n /** The listing generator (AGL-2910). */\n seoFields?: typeof generateSeoFields\n /** The runners a plan's creations are built by (AGL-3031); the registry's otherwise. */\n runnerFor?: typeof aiJobStepRunnerFor\n /** The stock photo source of a language page's pictures (AGL-3660); core's provider otherwise. */\n stockPhotos?: typeof aiLayoutSitePhotoSource\n}\n\nexport function createAiJobPageStep(deps: AiJobPageStepDeps = {}): AiJobStepRunner {\n const readInventory = deps.readInventory ?? readSiteInventory\n const duplicate = deps.duplicate ?? duplicateResource\n const seoFields = deps.seoFields ?? generateSeoFields\n const runnerFor = deps.runnerFor ?? aiJobStepRunnerFor\n return async (stepContext): Promise<AiJobStepOutcome> => {\n const { job, now, signal, firestore, modelFor } = stepContext\n const hostId = job.hostId\n if (!hostId) throw new Error('a page job names no site, and its admission refuses one')\n // The switch's answer for this job, else the routing table's (AGL-2942).\n const model = modelFor?.('job.page') ?? aiModelForStep('job.page')\n const confirmed = aiConfirmedPlan(job)\n if (!confirmed) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_NO_PLAN_COPY }\n // The resume door refuses such a plan at confirmation; one confirmed\n // before a site changed still stops here, before any spend.\n const refusal = aiPagePlanRefusal(confirmed)\n if (refusal) return { ...aiUnspentOutcome(model), failure: refusal }\n\n // ── The creations first (AGL-3031): one a pass, by the steps that own them ──\n const units = aiPageJobUnits(confirmed)\n const outputs = job.outputs ?? []\n const [unit] = aiSitePendingUnits(units, outputs)\n if (unit) {\n const runner = runnerFor(unit.jobKind)\n if (!runner) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_CREATION_UNAVAILABLE_COPY }\n const pass = await aiRunJobUnit(stepContext, {\n unit,\n units,\n runner,\n emptyCopy: AI_JOB_PAGE_CREATION_EMPTY_COPY,\n })\n // A built creation is followed by the next, or by the page's first section.\n return pass.built ? { ...pass.outcome, continue: true } : pass.outcome\n }\n // The page's plan with every creation resolved to the record it built, as\n // a scaffold's page is planned against what the scaffold built.\n const plan: AiJobPlan = units.length\n ? (aiSiteUnitJob(\n job,\n {\n kind: 'page',\n jobKind: 'page',\n resource: 'screen',\n slot: 'page',\n screen: confirmed.screens[0],\n label: confirmed.screens[0].title,\n },\n aiSiteBuiltRefs(units, outputs),\n ).plan as AiJobPlan)\n : confirmed\n const screen = plan.screens[0] as AiBuildPlanScreen\n const name = screen.title || AI_JOB_PAGE_DEFAULT_NAME\n const slug = aiPageDraftSlug(screen)\n const sectionIds = screen.sections.map((_, index) => aiPageSectionNodeId(job.$id, index))\n // The draft screen, by the id the job recorded for it: every pass writes and finds the same one.\n const draftId = aiJobDraftId(job, 'screen')\n\n const output = (\n draft: Pick<AiDraftRecord, 'id' | 'versionId' | 'name' | 'hostSubdomain'>,\n load?: AiLoadEstimate | null,\n note?: string | null,\n ): AiJobOutput => ({\n resource: 'screen',\n id: draft.id,\n versionId: draft.versionId,\n hostId,\n hostSubdomain: draft.hostSubdomain,\n label: draft.name,\n ...(load ? { load } : {}),\n ...(note ? { note } : {}),\n // Rule 10: a navigation entry travels with the page as a proposal, for\n // the member to add once the page is live; no menu is written.\n ...(screen.nav ? { proposal: { navigation: { label: name, slug } } } : {}),\n })\n\n const [inventory, orgSnapshot, written] = await Promise.all([\n readInventory(job.orgId, hostId, { firestore }),\n firestore.collection('orgs').doc(job.orgId).get(),\n readAiDraft(firestore, { kind: 'screen', hostId, id: draftId }),\n ])\n const org = (orgSnapshot.data() ?? null) as Partial<AglynOrgBilling> | null\n if (written?.deleted) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_DELETED_COPY }\n // A record template's copy binds its dataset's fields, and its draft says\n // where the member saves the binding (AGL-3475).\n const record = aiPageRecordTemplate(screen.record, inventory, plan)\n const recordNote = record ? aiPageRecordNote(record) : null\n\n // A page job's plan starts from a copy of a screen the site has (rule\n // 15): the copy is the draft — unrouted, as every copy is — and nothing\n // is generated.\n // Only a page is a page's start: a collection entry template's copy stays\n // a template, so a plan naming one builds the page instead.\n // A copy shows what its SOURCE shows, and this branch generates nothing.\n // So if the source cannot already show what the plan promised, copying it\n // can only produce a page that breaks the promise — the refusal below\n // would be certain before the copy was ever minted. Read the source and\n // build instead, rather than leave the member a page they must throw away\n // and a Try again that can only copy the same thing (AGL-3024).\n // Only a source we could READ and found short diverts: one we cannot read\n // is one we cannot judge, and the review after the copy still answers for\n // that. A short source carries findings; an unreadable one carries none.\n // A copy of the starter home page is never a page the job built\n // (AGL-3596): the site's own placeholder is replaced by a page generated\n // from the plan, so a plan that names it as a start builds instead.\n const copiesStarter = Boolean(\n screen.duplicateOf &&\n inventory.screens.some((row) => row.id === screen.duplicateOf && row.replaceable),\n )\n // A page a build or a site makes (every one designed in the layout\n // language) counts as built only once its plan's sections are in it,\n // under the ids this step writes them by (AGL-3596). A copy holds its\n // source's sections under the source's ids, so it never counts: a build\n // that copied \"Our Story\" for its About page failed the page and left the\n // copy behind (AGL-3832). Such a page is generated from its plan, its\n // prompt shown the words of the page it starts from (`aiPageStartPage`).\n const generatedWhole = aiJobUsesLayoutLanguage(job)\n const sourceReview =\n !written && screen.duplicateOf && !copiesStarter && !generatedWhole\n ? await aiCopiedPageReview(firestore, { hostId, id: screen.duplicateOf, name, screen })\n : null\n const sourceShort = Boolean(sourceReview?.findings.length)\n if (\n !written &&\n !sourceShort &&\n !copiesStarter &&\n !generatedWhole &&\n screen.duplicateOf &&\n inventory.screens.some((row) => row.id === screen.duplicateOf && !row.template)\n ) {\n const copy = await duplicate('screen', {\n orgId: job.orgId,\n hostId,\n sourceId: screen.duplicateOf,\n name,\n uid: job.createdBy,\n org: org as Record<string, unknown> | null,\n // A copy core mints is written under core's id, never `draftId`, so\n // `written` above cannot find it and this branch would fire on every\n // pass — and the pass after a refused copy is the common one, since\n // the review below offers to try again. The job's recorded id is the\n // claim, so a second pass replays the one copy instead of leaving\n // another orphan screen behind it (AGL-3024).\n attemptKey: draftId,\n })\n if (copy.ok) {\n // A copy is not a build (AGL-3024). Nothing is generated here, so\n // whatever the plan promised BEYOND the source is in the draft only\n // if the source already had it, and no model is left to answer for\n // the difference — the copy itself has to.\n const review = await aiCopiedPageReview(firestore, { hostId, id: copy.id, name, screen })\n if (review) return aiUnspentOutcome(model, { review })\n const hostSubdomain = await aiSiteSubdomain(firestore, hostId)\n return aiUnspentOutcome(model, {\n outputs: [output({ id: copy.id, versionId: copy.versionId, name: copy.name, hostSubdomain }, null, recordNote)],\n })\n }\n if (copy.ok === false && copy.status === 403) {\n return aiUnspentOutcome(model, { review: aiLimitReview(copy.error) })\n }\n }\n\n const stored = written ? await readAiDraftNodes(firestore, { kind: 'screen', hostId, id: draftId }) : null\n if (written && !stored) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_DELETED_COPY }\n let page = stored?.nodes ?? aiEmptyPage()\n const index = sectionIds.findIndex((id) => !(id in page))\n // A workspace whose component allowance is finite — Free's one — draws\n // its repeats inline, and every pass is held to the rules that way\n // (AGL-3030, AGL-3615). A form is a saved one placed by id on every plan,\n // or none (AGL-3596).\n const reusableComponents = aiBuildsWithComponents(org)\n // A link may take a visitor to a section of this page, by its name in the plan (AGL-3097).\n const sections = screen.sections.map((section) => section.name)\n // A third-party player the confirmed plan lists for this page is the only one it may embed (AGL-3433).\n const embeds = aiPlanEmbedsFor(plan, { slug: screen.slug })\n // The site's other pages a link may go to, built or minted on a guided\n // start's plan, and the one a link that names none can only mean (AGL-3596).\n const { linkablePages, linkTarget } = aiPageLinkablePages(inventory, aiLayoutSitePages(job.inputs), [screen.id, draftId])\n // A page unit of a guided start or a build is designed in the layout\n // language and compiled (AGL-3660); a record template keeps the raw tree,\n // since its copy binds fields the language does not name.\n const language = aiJobUsesLayoutLanguage(job) && !record\n const checked = {\n ...aiPageCheckContext(inventory, {\n reusableComponents,\n sections,\n embeds,\n recordTokens: aiPageRecordTokens(record),\n linkablePages,\n }),\n // The last pass holds a compiled page to what its own check did: the\n // design's picture cards are the compiler's, not a block rule 1 asks a\n // component of (AGL-3660). Without it, a Pro store's Shop page passed\n // its check and was refused at the last pass (live run, 2026-10-09).\n ...(language ? { codeBuilt: true, repeatsCompiled: true } : {}),\n }\n // And a post's card in a section listing the blog links its post and shows\n // its cover by the tokens the page fills per post (AGL-3676), as its check admitted.\n const context = language\n ? aiLayoutListingContext(checked, aiLayoutPageTargets({ job, inventory, own: [screen.id, draftId] }))\n : checked\n\n // ── The last pass: the whole page, its listing, and the draft reported ──\n // A page in the layout language is written whole by one pass (AGL-3660),\n // so once written it is complete: a planned section missing from it was\n // taken out by its last answer, never one still to build (AGL-3771).\n if ((index === -1 || language) && written) {\n // An attribute a rule has one answer for is settled on the stored page\n // and stored (AGL-3660): this pass asks no model, so a page refused\n // here for one would be refused the same way on every Try again. A\n // Portfolio page whose opening photos all loaded eagerly was (2026-10-10).\n // And a link sent home for nothing is sent to the site's form, or left out (AGL-3729),\n // and an item of a list that shows only placeholders is taken out where the list keeps a real one (AGL-3827).\n // And the site's entrance motion is written on a page built a section at\n // a time, as on one built whole (AGL-3838); a page that already moves —\n // by this pass or by its author — comes back as it is, and is not written.\n const motion = aiLayoutMotionSiteOf(job, screen.slug)\n const settle = <T extends AiDoctrineTree>(tree: T): T => {\n const settled = aiSettleUnrelatedScreenLinks(\n aiSettlePlaceholderItems(aiSettleMechanicalRules(tree, 'page'), 'page', context),\n 'page',\n {\n ...context,\n ownScreenIds: [screen.id, draftId],\n },\n )\n const moved = aiWithSiteMotion(settled.nodes, motion, { sectionIds })\n return moved === settled.nodes ? settled : { ...settled, nodes: moved }\n }\n const settled = settle({ rootId: CANVAS_ROOT_ELEMENT_ID, nodes: page as unknown as AiDoctrineTree['nodes'] })\n if (settled.nodes !== (page as unknown as AiDoctrineTree['nodes'])) {\n // Never written once the job is canceled (AGL-3616).\n throwIfAiJobCanceled(signal)\n const update = await updateAiDraftNodes(firestore, {\n kind: 'screen',\n hostId,\n id: draftId,\n now,\n update: (nodes) => {\n const tree = { rootId: CANVAS_ROOT_ELEMENT_ID, nodes: nodes as unknown as AiDoctrineTree['nodes'] }\n const next = settle(tree)\n return next === tree ? null : (next.nodes as unknown as typeof nodes)\n },\n })\n if (update.ok === false) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_DELETED_COPY }\n page = settled.nodes as unknown as typeof page\n }\n // A link the page carries to one of its sections goes there, now every section is built.\n const report = validateAiDoctrineTree({ rootId: CANVAS_ROOT_ELEMENT_ID, nodes: page }, 'page', {\n ...context,\n scrollTargetIds: sectionIds,\n })\n // The facts the brief did not give, on the page or in the defaults its components show (AGL-3056).\n const note =\n [\n aiBracketedFactsNote({\n tree: { rootId: CANVAS_ROOT_ELEMENT_ID, nodes: page as unknown as AiDoctrineTree['nodes'] },\n inventory,\n }),\n recordNote,\n // A section the last answer could not fill was left out, and the page says which (AGL-3771).\n language ? aiLayoutLeftOutNote(screen, sectionIds, page as unknown as Record<string, unknown>) : null,\n ]\n .filter(Boolean)\n .join(' ') || null\n // The page's own draft, reported once however many times the last pass\n // runs (AGL-3143): a refused pass reports it and a later one that\n // passes does not report it twice.\n const reported = outputs.some((entry) => entry.resource === 'screen' && entry.id === draftId)\n const reports = reported ? [] : [output(written, report.load, note)]\n if (report.violations.length) {\n // The check mints its own ids; the review names the nodes by the ids the draft stores them under.\n const storedIds = report.tree?.sourceIds ?? {}\n const violations = report.violations.map((violation) =>\n violation.nodeIds ? { ...violation, nodeIds: aiModelNodeIds(violation.nodeIds, storedIds) } : violation,\n )\n // THE DRAFT TRAVELS WITH THE REFUSAL (AGL-3143). Every other review\n // parks on a model's answer, so trying again asks the model again and\n // can come back different. This one reads the STORED page and spends\n // nothing, so trying again refuses the same page for the same reasons\n // forever — and a job parked here having reported no output names no\n // draft at all, which leaves the member nowhere to mend the nodes the\n // findings name and no way to finish the job from the console. The\n // draft is reported now instead, and the job still parks: what the\n // review names is mended in the draft, and the next pass then passes.\n return aiUnspentOutcome(model, {\n outputs: reports,\n review: aiDoctrineReview(\n {\n message: aiDoctrineNeedsInputMessage(violations),\n violations,\n answer: { tree: { rootId: CANVAS_ROOT_ELEMENT_ID, nodes: page } },\n },\n { page: true, then: AI_JOB_PAGE_REFUSED_DRAFT_COPY },\n ),\n })\n }\n let spent: AiJobStepOutcome = aiUnspentOutcome(model)\n if (!written.seo?.title?.trim() || !written.seo?.description?.trim()) {\n const seoModel = modelFor?.('job.seo') ?? aiModelForStep('job.seo')\n // The plan's own listing, held to the editor's guidance, when the\n // listing cannot be written inside this pass's time on the model the\n // job runs.\n let values = {\n title: aiClipAtWord(screen.seoTitle, SCREEN_SEO_TEXT_GUIDANCE.title, plan.language),\n description: aiClipAtWord(screen.seoDescription, SCREEN_SEO_TEXT_GUIDANCE.description, plan.language),\n }\n if (\n // A listing is written from the page's own text, with no inventory\n // and so no lookup round.\n aiGenerationWorstCaseMs({ maxTokens: AI_SEO_FIELDS_MAX_TOKENS, model: seoModel, lookups: 0 }) <=\n AI_JOB_PAGE_STEP_MINIMUM_MS\n ) {\n const host = await firestore.collection('hosts').doc(hostId).get()\n const result = await seoFields({\n subject: { kind: 'screen', name, path: screenRoutePathToUrl(slug || SCREEN_ROOT_PATH) },\n brand: siteNameOf(host),\n // What the site is and who it is for (AGL-2918). The page's own\n // Markdown says what the page is about and cannot say who it was\n // written for, so the listing borrows the two answers the job was\n // started from — the scaffold's derived page job carries them,\n // and a page job started from the Screens page carries neither.\n site: aiSiteWords(job.inputs),\n text: buildPageMarkdown({ nodes: page as never, rootId: CANVAS_ROOT_ELEMENT_ID }),\n fields: ['title', 'description'],\n // Other screens by name only: their own listings are their content.\n otherTitles: inventory.screens.map((row) => row.name),\n model: seoModel,\n ...(signal ? { signal } : {}),\n })\n spent = { ...spent, ...aiGenerationSpent(result) }\n if (result.status === 'ok') {\n // A written field that shows a placeholder keeps the plan's, which\n // the plan's own check already held to rule 14 (AGL-3827).\n const usable = (text: string | null | undefined): string =>\n text?.trim() && !aiHasPlaceholderToken(text) ? text.trim() : ''\n values = {\n title: usable(result.value.title) || values.title,\n description: usable(result.value.description) || values.description,\n }\n }\n }\n // Never written once the job is canceled (AGL-3616).\n throwIfAiJobCanceled(signal)\n await writeAiDraftScreenSeo(firestore, { hostId, id: draftId, seo: values, now })\n }\n return { ...spent, outputs: reports }\n }\n\n // ── A section pass ──\n if (!written) {\n const allowance = await aiDraftAllowanceRefusal(firestore, { kind: 'screen', hostId, org })\n if (allowance) return aiUnspentOutcome(model, { review: aiLimitReview(allowance) })\n }\n if (language) {\n // ── The whole page in one answer, in the layout language ──\n const targets = aiLayoutPageTargets({ job, inventory, own: [screen.id, draftId] })\n const startsFrom = await aiPageStartPage(firestore, { hostId, screen, inventory })\n const result = await aiRunLayoutPage({\n job,\n plan,\n screen,\n sectionIds,\n targets,\n context,\n reusableComponents,\n inventory,\n model,\n startsFrom,\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result) }\n // The compiler leaves each picture slot empty; a photo the site serves\n // itself fills it here, after the page is checked (AGL-3660): a stock\n // photo copied into the site's library where the deployment has a\n // library, else a starter photo. No AI credits; never fails the job.\n const sectionNames = screen.sections.map((section) => section.name)\n const seed = `${aiOriginJobId(job)}:${screen.id ?? screen.slug}`\n // A photo another page of the job already shows is not placed here, and\n // is what a picture the library fails takes before any starter (AGL-3988).\n const { avoid, photos: jobPhotos } = await aiJobPlacedPhotos(firestore, { hostId, job, draftId })\n const photoWords = aiStockPhotoWordsOf(job)\n const pictured = await aiResolveLayoutPictures(result.value.nodes, {\n rootId: CANVAS_ROOT_ELEMENT_ID,\n sectionIds,\n sectionNames,\n seed,\n ...(jobPhotos.length ? { jobPhotos } : {}),\n ...(photoWords.language ? { language: photoWords.language } : {}),\n // The owner's own photos first (AGL-3957), the stock library for the rest.\n source: (deps.stockPhotos ?? aiLayoutSitePhotoSource)({\n hostId,\n uid: job.createdBy,\n seed,\n business: aiSiteWords(job.inputs).about,\n ...photoWords,\n sectionNames,\n jobId: aiOriginJobId(job),\n ...(avoid.length ? { avoid } : {}),\n // A page brought in from the owner's site places its own pictures first (AGL-3976).\n pagePhotos: aiLayoutPagePhotosOf(job.inputs),\n ...(signal ? { signal } : {}),\n }),\n })\n if (!written) {\n // Never written once the job is canceled (AGL-3616).\n throwIfAiJobCanceled(signal)\n const draft = await writeAiDraft(firestore, {\n kind: 'screen',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: pictured,\n slug,\n layoutId: aiPageDraftLayoutId(screen, inventory),\n aiJobId: aiOriginJobId(job),\n now,\n })\n if (draft.ok === false) {\n if (draft.status === 404) throw new Error(`site ${hostId} vanished while its page was generated`)\n return { ...spent, review: aiLimitReview(draft.error) }\n }\n } else {\n // A draft written by an earlier, interrupted pass is replaced whole.\n // Never written once the job is canceled (AGL-3616).\n throwIfAiJobCanceled(signal)\n const update = await updateAiDraftNodes(firestore, {\n kind: 'screen',\n hostId,\n id: draftId,\n now,\n update: () => pictured,\n })\n if (update.ok === false) return { ...spent, failure: AI_JOB_PAGE_DELETED_COPY }\n }\n return { ...spent, continue: true }\n }\n const maxTokens = aiJobPageSectionMaxTokens(model)\n const maxElements = aiJobPageSectionMaxElements(maxTokens)\n const result = await runValidatedGeneration<AiPageSection>('page-section', {\n step: 'job.page',\n model,\n instructions: AI_JOB_PAGE_INSTRUCTIONS,\n inventory,\n messages: [\n {\n role: 'user',\n content: aiPageSectionPrompt({ job, plan, screen, index, maxElements, reusableComponents, record, linkablePages }),\n },\n ],\n tool: AI_PAGE_SECTION_TOOL,\n maxTokens,\n // A section cut off at its ceiling is asked for smaller, and a member\n // whose section still does not fit reads that it was too large (AGL-3042).\n cutOff: { noun: 'section', smaller: aiPageSectionSmaller({ maxElements, reusableComponents }) },\n thinking: 'off',\n check: aiPageSectionCheck({\n page,\n sectionIds,\n index,\n context,\n section: screen.sections[index],\n inventory,\n linkTarget,\n }),\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result, { page: true }) }\n // A record template's page leads with each record's own photo (AGL-3616).\n const section = aiPageSectionWithRecordImage(result.value, page, record)\n\n if (!written) {\n // Never written once the job is canceled (AGL-3616).\n throwIfAiJobCanceled(signal)\n const draft = await writeAiDraft(firestore, {\n kind: 'screen',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: aiPageWithSection(aiEmptyPage(), section, sectionIds),\n slug,\n layoutId: aiPageDraftLayoutId(screen, inventory),\n aiJobId: aiOriginJobId(job),\n now,\n })\n if (draft.ok === false) {\n if (draft.status === 404) throw new Error(`site ${hostId} vanished while its page was generated`)\n return { ...spent, review: aiLimitReview(draft.error) }\n }\n } else {\n // Never written once the job is canceled (AGL-3616).\n throwIfAiJobCanceled(signal)\n const update = await updateAiDraftNodes(firestore, {\n kind: 'screen',\n hostId,\n id: draftId,\n now,\n // A pass run again after its write finds its section and changes nothing.\n update: (nodes) =>\n section.rootId in nodes ? null : aiPageWithSection(nodes, section, sectionIds),\n })\n if (update.ok === false) return { ...spent, failure: AI_JOB_PAGE_DELETED_COPY }\n }\n return { ...spent, continue: true }\n }\n}\n\nexport const runAiJobPageStep = createAiJobPageStep()\n\n/**\n * Registers the page step with the least time a section pass needs and the\n * time its next pass needs, the passes a page with its creations may take, and\n * the check a page job passes when it is created or its plan is confirmed.\n */\nexport function registerAiPageJob(): void {\n registerAiJobStep('page', runAiJobPageStep, {\n minimumMs: AI_JOB_PAGE_STEP_MINIMUM_MS,\n minimumMsFor: aiPageJobRunMinimumMs,\n })\n registerAiJobStepPasses('page', AI_JOB_PAGE_MAX_PASSES)\n registerAiJobAdmission('page', aiPageJobAdmission)\n}\n"],"names":["throwIfAiJobCanceled","buildPageMarkdown","SCREEN_SEO_TEXT_GUIDANCE","SCREEN_ROOT_PATH","screenRoutePathToUrl","CANVAS_ROOT_ELEMENT_ID","duplicateResource","AI_BUILD_PLAN_LIMITS","aiPlanEmbedsFor","AI_PAGE_CREATE_KINDS","aiPageCreationRefusal","aiPagePlanRefusal","parseAiPageJobInputs","aiPlanCapabilitiesForJob","aiPlanUncreatable","aiSiteWords","aiModelForStep","aiDoctrineNeedsInputMessage","runValidatedGeneration","aiClipAtWord","aiSettleMechanicalRules","aiSettlePlaceholderItems","aiSettleUnrelatedScreenLinks","validateAiDoctrineTree","aiHasPlaceholderToken","AI_SEO_FIELDS_MAX_TOKENS","generateSeoFields","readSiteInventory","registerAiJobAdmission","aiGenerationWorstCaseMs","aiJobDraftId","aiOriginJobId","aiBuildsWithComponents","aiDraftAdmissionRefusal","aiDraftAllowanceRefusal","aiSiteSubdomain","readAiDraft","readAiDraftNodes","readAiPlanCapabilities","updateAiDraftNodes","writeAiDraft","writeAiDraftScreenSeo","aiBracketedFactsNote","aiConfirmedPlan","aiDoctrineReview","aiGenerationSpent","aiLimitReview","aiModelNodeIds","aiUnspentOutcome","AI_JOB_PAGE_SECTION_MAX_TOKENS","AI_JOB_PAGE_SECTION_TOKENS","AI_JOB_PAGE_STEP_BUDGET","AI_JOB_PAGE_STEP_MINIMUM_MS","aiJobPageSectionMaxTokens","AI_JOB_PAGE_INSTRUCTIONS","AI_PAGE_SECTION_TOOL","aiEmptyPage","aiPageCheckContext","aiPageLinkablePages","aiPageRecordNote","aiPageRecordTemplate","aiPageRecordTokens","aiPageSectionCheck","aiPageSectionNodeId","aiPageSectionPrompt","aiPageSectionSmaller","aiPageSectionWithRecordImage","aiPageWithSection","aiLayoutSitePages","aiResolveLayoutPictures","aiLayoutPagePhotosOf","aiLayoutSitePhotoSource","AI_STOCK_PHOTO_AVOID_INPUT","AI_STOCK_PHOTO_JOB_PHOTOS_INPUT","AI_STOCK_PHOTO_PAGES_INPUT","aiStockJobPhotosOf","aiStockPhotoWordsOf","aiStockPlacedPhotos","aiStockPlacedSrcs","AI_PLAN_ITEMS_MIN","aiPlanCopiedPageViolations","AI_JOB_PAGE_LANGUAGE_BUDGET","aiJobUsesLayoutLanguage","aiLayoutListingContext","aiLayoutPageTargets","aiLayoutLeftOutNote","aiLayoutMotionSiteOf","aiRunLayoutPage","aiWithSiteMotion","aiCreationUnit","aiRunJobUnit","aiSiteBuiltRefs","aiSitePendingUnits","aiSiteUnitJob","aiJobStepRunMinimumMs","aiJobStepRunnerFor","registerAiJobStep","registerAiJobStepPasses","AI_JOB_PAGE_TOKENS_PER_ELEMENT","AI_JOB_PAGE_REAL_TOKENS_PER_ESTIMATED","Math","max","AI_JOB_PAGE_REAL_TOKENS_PER_ELEMENT","ceil","aiJobPageSectionMaxElements","maxTokens","floor","AI_JOB_PAGE_DEFAULT_NAME","AI_JOB_PAGE_NO_PLAN_COPY","AI_JOB_PAGE_DELETED_COPY","AI_JOB_PAGE_CREATION_UNAVAILABLE_COPY","AI_JOB_PAGE_CREATION_EMPTY_COPY","AI_JOB_PAGE_REFUSED_DRAFT_COPY","AI_JOB_PAGE_MAX_PASSES","create","sections","aiPageJobUnits","plan","flatMap","kind","creation","index","unit","aiPageJobRunMinimumMs","job","confirmed","pagePass","minimumMs","units","outputs","aiPageDraftSlug","screen","segments","slug","split","map","part","trim","filter","Boolean","length","aiSiteDefaultLayoutId","inventory","counts","layouts","Set","row","id","size","pages","screens","template","layoutId","has","home","find","Map","set","get","mostUsed","entries","sort","a","b","aiPageDraftLayoutId","layout","some","siteNameOf","host","text","value","aiCopiedPageReview","firestore","input","section","items","stored","hostId","nodes","reason","message","name","findings","violations","rootId","aiPageJobAdmission","context","inputs","status","error","refusal","jobKind","org","orgId","noun","ownCheck","capabilities","creates","aiPageStartPage","readNodes","duplicateOf","one","replaceable","undefined","catch","label","aiJobPagesPlacedPhotos","aiJobPlacedPhotos","avoid","listed","ids","Array","isArray","draftId","elsewhere","shown","src","startsWith","records","read","Promise","all","seen","photos","photo","add","createAiJobPageStep","deps","readInventory","duplicate","seoFields","runnerFor","stepContext","orgSnapshot","now","signal","modelFor","Error","model","failure","runner","pass","emptyCopy","built","outcome","continue","resource","slot","title","sectionIds","_","$id","output","draft","load","note","versionId","hostSubdomain","nav","proposal","navigation","written","collection","doc","data","deleted","record","recordNote","copiesStarter","generatedWhole","sourceReview","sourceShort","copy","sourceId","uid","createdBy","attemptKey","ok","review","page","findIndex","reusableComponents","embeds","linkablePages","linkTarget","language","checked","recordTokens","codeBuilt","repeatsCompiled","own","motion","settle","tree","settled","ownScreenIds","moved","update","next","report","scrollTargetIds","join","reported","entry","reports","storedIds","sourceIds","violation","nodeIds","answer","then","spent","seo","description","seoModel","values","seoTitle","seoDescription","lookups","result","subject","path","brand","site","fields","otherTitles","usable","allowance","targets","startsFrom","refused","sectionNames","seed","jobPhotos","photoWords","pictured","source","stockPhotos","business","about","jobId","pagePhotos","aiJobId","maxElements","step","instructions","messages","role","content","tool","cutOff","smaller","thinking","check","runAiJobPageStep","registerAiPageJob","minimumMsFor"],"mappings":"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"}
|
|
@@ -19,6 +19,7 @@ import type { AiJob, AiJobOutput, AiJobPlan, AiJobReview, AiJobInputRequest, AiJ
|
|
|
19
19
|
import { type AiStepKind } from '../providers/catalog';
|
|
20
20
|
import { type AiEffort, type AiSystemBlock } from '../runtime/ai-runtime';
|
|
21
21
|
import type { AssistTokenUsage } from '../usage/assist-usage';
|
|
22
|
+
import type { AiSiteImportState } from '../model/ai-site-import';
|
|
22
23
|
/**
|
|
23
24
|
* The `text` step (AGL-2904): a brief in, a short piece of copy out.
|
|
24
25
|
*
|
|
@@ -129,6 +130,12 @@ export interface AiJobStepOutcome {
|
|
|
129
130
|
item?: AiJobItemOutcome;
|
|
130
131
|
/** A `build`'s ledger to start from, written by the machine in the same transaction (AGL-3616). */
|
|
131
132
|
items?: AiJobItemLedger[];
|
|
133
|
+
/**
|
|
134
|
+
* An `import` job's waves after this pass (AGL-3976), kept on the job by
|
|
135
|
+
* the machine in the write that records the pass: a step never writes the
|
|
136
|
+
* job document itself.
|
|
137
|
+
*/
|
|
138
|
+
siteImport?: AiSiteImportState;
|
|
132
139
|
}
|
|
133
140
|
/** What one `build` pass came to for the unit it ran (AGL-3616). */
|
|
134
141
|
export interface AiJobItemOutcome {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-text-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 type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport type {\n AiJob,\n AiJobOutput,\n AiJobPlan,\n AiJobReview,\n AiJobInputRequest,\n AiJobItemFailure,\n AiJobItemLedger,\n AiJobItemStatus,\n AiJobSitePublish,\n} from '../model/ai-jobs.types'\nimport { AI_STEP_TIERS, type AiStepKind } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport { aiDevEffortFor } from '../runtime/ai-dev-env'\nimport { runAiRequest, type AiEffort, type AiSystemBlock } from '../runtime/ai-runtime'\nimport type { AssistTokenUsage } from '../usage/assist-usage'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiOverlayCopyRequested } from '../model/ai-overlay-copy'\nimport { runAiOverlayCopy } from './ai-job-overlay-copy'\n\n/**\n * The `text` step (AGL-2904): a brief in, a short piece of copy out.\n *\n * The one step kind this issue ships for real, so the state machine — the\n * lease, the reservation, the record, the beat — is exercised end to end by\n * something that spends tokens, rather than by a stub that could pass with\n * the meter unplugged. Every other kind refuses until its own issue lands.\n *\n * The balanced tier with adaptive thinking: a brief is open-ended in a way the copy\n * assistant's element mode is not, and the cost is one call per job. The\n * static system block is cached; the brief rides in the user turn, so no\n * per-org byte sits inside the cached prefix (AGL-2352, enforced by the\n * runtime before the request leaves).\n */\n\n/** The model the text step runs on: the routing table's answer for `job.text`. */\nexport function aiJobTextModel(): string {\n return aiModelForStep('job.text')\n}\n\n/** The text step's answer ceiling, from the routing table. */\nexport const AI_JOB_TEXT_MAX_TOKENS = AI_ROUTING_TABLE['job.text'].maxTokens\n\n/**\n * The text step's time (AGL-3035): ONE request — no re-ask, and no site\n * inventory to look anything up in — at the routing table's ceiling on the\n * tier `job.text` is served from, with the step's reads and writes. That fits\n * an inline door's budget, so a door runs a text job where it is asked for,\n * and the least time is the helper's worst case rather than a figure anyone\n * chose; a slower tier asks less so its worst case still fits.\n */\nexport const AI_JOB_TEXT_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.text'],\n maxTokens: AI_JOB_TEXT_MAX_TOKENS,\n attempts: 1,\n lookups: 0,\n})\n\n/** The least time one text step needs before it starts. */\nexport const AI_JOB_TEXT_STEP_MINIMUM_MS = AI_JOB_TEXT_STEP_BUDGET.minimumMs\n\n/** How much of a brief is sent. Past this a brief is a document, not a brief. */\nexport const AI_JOB_BRIEF_MAX_CHARS = 4_000\n\n/**\n * What a step hands back to the machine. The machine meters `usage` at\n * `model`'s rates, records the step and appends the outputs; a step never\n * touches the job document or the meter itself.\n */\nexport interface AiJobStepOutcome {\n outputs: AiJobOutput[]\n /**\n * The step made progress and has more of the same work to do (AGL-2910):\n * a site audit works through its pages a batch at a time. The machine\n * records this pass — its spend and its outputs — and hands the SAME step\n * back to run again, so every pass is one reservation and one provider\n * exchange, and the job document says how far it got.\n */\n continue?: boolean\n usage: AssistTokenUsage\n estCostUsd: number\n model: string\n stopReason: string | null\n /**\n * The thinking effort the request asked for, recorded on the step\n * (AGL-2937); absent or `null` when the request named none.\n */\n effort?: AiEffort | null\n /** The model declined the brief (`stop_reason: 'refusal'`). Tokens were spent. */\n refused?: boolean\n /**\n * The model answered and nothing usable came of it — no structured answer\n * even after the step's own re-ask, say (AGL-2938). Tokens were spent, so\n * the machine meters the step before it fails the job with this sentence,\n * which is customer-safe and the only thing a customer reads.\n */\n failure?: string\n /** The plan the plan step proposed, which the machine keeps on the job (AGL-2935). */\n plan?: AiJobPlan\n /**\n * The step stopped for a person (AGL-2935). The machine records what it\n * spent and parks the job `needs_review`: a `plan` review completes the\n * step, so confirming runs the next one; a `doctrine` review hands the\n * step back, so trying again runs the same one.\n */\n review?: AiJobReview\n /**\n * With a `plan` review: the plan needs an answer only the person can give\n * first (AGL-3978) — a tracking change with no pixel ID. The machine keeps\n * the plan and its review and parks the job `needs_input` with these\n * fields instead of `needs_review`; the answer returns it to the review.\n */\n input?: AiJobInputRequest\n /**\n * The answer was refused by the platform's own checks, not the model's\n * (AGL-3594): the machine meters the step as any other and then gives the\n * job's credits back to the meters it was charged to (`refundJobCredits`),\n * a bounded number of times a day.\n */\n uncredited?: boolean\n /**\n * The `failure` is the workspace's own limit, not a fault of ours: the\n * month's Free AI credits are spent (a 429 from the kind's admission, asked\n * when the plan is kept). The machine fails the job and gives the step's\n * credits back exactly as for our failure, but announces it as NOT ours, so\n * no staff alert is raised for a customer reaching their allowance.\n */\n customerLimit?: boolean\n /**\n * What a guided site start put live on its last pass (AGL-3596), kept on\n * the job by the machine in the write that records the pass: a step never\n * writes the job document itself.\n */\n sitePublish?: AiJobSitePublish\n /**\n * A `build` pass's unit and what it came to (AGL-3616), which the machine\n * writes to the job's item ledger in the transaction that records the\n * pass's spend. An item's failure is never the step's: it is a row, and\n * the build goes on.\n */\n item?: AiJobItemOutcome\n /** A `build`'s ledger to start from, written by the machine in the same transaction (AGL-3616). */\n items?: AiJobItemLedger[]\n}\n\n/** What one `build` pass came to for the unit it ran (AGL-3616). */\nexport interface AiJobItemOutcome {\n slot: string\n /** `running` while the unit asks for another pass of its own. */\n status: AiJobItemStatus\n failure?: AiJobItemFailure | null\n /** Customer-safe: what the person should know about the item. */\n note?: string | null\n degradedBy?: string[]\n /** The ids of the outputs this pass produced for the item. */\n outputs?: string[]\n}\n\nexport interface AiJobStepContext {\n job: AiJob\n stepIndex: number\n now: Date\n /** Aborts the provider call when the runner's budget ends. */\n signal?: AbortSignal\n /**\n * The Admin SDK handle the machine runs on, for a step that reads what it\n * builds from — a site's theme, say — or writes a draft. A step never\n * writes the job document, the meter or the lease through it.\n */\n firestore: FirebaseFirestore.Firestore\n /** The org document the machine read for the step's reservation. */\n org?: Partial<AglynOrgBilling> | null\n /**\n * The model a step of this kind runs on for THIS job (AGL-2942): the\n * creator's pick where the plan and the allotment allowlists allow it,\n * the routing table otherwise. Absent, a runner asks the table itself.\n */\n modelFor?: (kind: AiStepKind) => string | undefined\n}\n\nexport type AiJobStepRunner = (\n context: AiJobStepContext,\n) => Promise<AiJobStepOutcome>\n\n/**\n * The static block, byte-identical on every request so it caches. The\n * customer's brief and the org's inputs never enter it.\n */\nexport const AI_JOB_TEXT_SYSTEM: AiSystemBlock[] = [\n {\n text:\n 'You write short, publishable copy for a website builder. You are ' +\n 'given a brief from the person who owns the site. Answer with the ' +\n 'copy only: no preamble, no headings unless the brief asks for them, ' +\n 'no closing remarks, no markdown fences. Keep to the length the brief ' +\n 'implies; when it implies none, write under 150 words. Write in the ' +\n 'language of the brief. Never invent facts, prices, names or claims ' +\n 'the brief did not give you; where a detail is missing, leave a ' +\n 'clearly marked placeholder in square brackets.',\n cacheBreakpoint: true,\n },\n]\n\n/** The brief as the user turn, with the kind-specific inputs stated plainly. */\nexport function aiJobTextPrompt(job: Pick<AiJob, 'brief' | 'inputs'>): string {\n const brief = job.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)\n const tone = typeof job.inputs?.['tone'] === 'string' ? job.inputs['tone'] : ''\n const audience =\n typeof job.inputs?.['audience'] === 'string' ? job.inputs['audience'] : ''\n const lines = [`Brief: ${brief}`]\n if (tone) lines.push(`Tone: ${tone}`)\n if (audience) lines.push(`Audience: ${audience}`)\n return lines.join('\\n')\n}\n\nexport const runAiJobTextStep: AiJobStepRunner = async ({ job, signal, modelFor }) => {\n const model = modelFor?.('job.text') ?? aiJobTextModel()\n // Overlay copy (AGL-3603): the same one request, answered as fields.\n if (aiOverlayCopyRequested(job.inputs)) {\n return runAiOverlayCopy({\n job,\n model,\n maxTokens: AI_JOB_TEXT_STEP_BUDGET.maxTokens(model),\n ...(signal ? { signal } : {}),\n })\n }\n // A development run's effort A/B arm (AGL-3781), else the row's.\n const effort = aiDevEffortFor('job.text') ?? AI_ROUTING_TABLE['job.text'].effort\n const result = await runAiRequest({\n model,\n system: AI_JOB_TEXT_SYSTEM,\n messages: [{ role: 'user', content: aiJobTextPrompt(job) }],\n // The routing ceiling, lowered on a tier too slow to answer it inside the\n // least time the step registered.\n maxTokens: AI_JOB_TEXT_STEP_BUDGET.maxTokens(model),\n ...(AI_ROUTING_TABLE['job.text'].thinking ? { thinking: AI_ROUTING_TABLE['job.text'].thinking } : {}),\n ...(effort ? { effort } : {}),\n stream: false,\n ...(signal ? { signal } : {}),\n })\n if (result.kind === 'refusal') {\n return {\n outputs: [],\n usage: result.usage,\n estCostUsd: result.estCostUsd,\n model,\n stopReason: result.stopReason,\n refused: true,\n }\n }\n const text = result.text.trim()\n return {\n outputs: text\n ? [\n {\n resource: 'text',\n // A text output has no document of its own; the id names the\n // step within the job so a later step can address it.\n id: 'draft',\n hostId: job.hostId ?? null,\n label: 'Draft copy',\n text,\n },\n ]\n : [],\n usage: result.usage,\n estCostUsd: result.estCostUsd,\n model,\n stopReason: result.stopReason,\n }\n}\n"],"names":["AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","aiDevEffortFor","runAiRequest","aiJobStepBudget","aiOverlayCopyRequested","runAiOverlayCopy","aiJobTextModel","AI_JOB_TEXT_MAX_TOKENS","maxTokens","AI_JOB_TEXT_STEP_BUDGET","tier","attempts","lookups","AI_JOB_TEXT_STEP_MINIMUM_MS","minimumMs","AI_JOB_BRIEF_MAX_CHARS","AI_JOB_TEXT_SYSTEM","text","cacheBreakpoint","aiJobTextPrompt","job","brief","slice","tone","inputs","audience","lines","push","join","runAiJobTextStep","signal","modelFor","model","effort","result","system","messages","role","content","thinking","stream","kind","outputs","usage","estCostUsd","stopReason","refused","trim","resource","id","hostId","label"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-text-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 type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport type {\n AiJob,\n AiJobOutput,\n AiJobPlan,\n AiJobReview,\n AiJobInputRequest,\n AiJobItemFailure,\n AiJobItemLedger,\n AiJobItemStatus,\n AiJobSitePublish,\n} from '../model/ai-jobs.types'\nimport { AI_STEP_TIERS, type AiStepKind } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport { aiDevEffortFor } from '../runtime/ai-dev-env'\nimport { runAiRequest, type AiEffort, type AiSystemBlock } from '../runtime/ai-runtime'\nimport type { AssistTokenUsage } from '../usage/assist-usage'\nimport type { AiSiteImportState } from '../model/ai-site-import'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiOverlayCopyRequested } from '../model/ai-overlay-copy'\nimport { runAiOverlayCopy } from './ai-job-overlay-copy'\n\n/**\n * The `text` step (AGL-2904): a brief in, a short piece of copy out.\n *\n * The one step kind this issue ships for real, so the state machine — the\n * lease, the reservation, the record, the beat — is exercised end to end by\n * something that spends tokens, rather than by a stub that could pass with\n * the meter unplugged. Every other kind refuses until its own issue lands.\n *\n * The balanced tier with adaptive thinking: a brief is open-ended in a way the copy\n * assistant's element mode is not, and the cost is one call per job. The\n * static system block is cached; the brief rides in the user turn, so no\n * per-org byte sits inside the cached prefix (AGL-2352, enforced by the\n * runtime before the request leaves).\n */\n\n/** The model the text step runs on: the routing table's answer for `job.text`. */\nexport function aiJobTextModel(): string {\n return aiModelForStep('job.text')\n}\n\n/** The text step's answer ceiling, from the routing table. */\nexport const AI_JOB_TEXT_MAX_TOKENS = AI_ROUTING_TABLE['job.text'].maxTokens\n\n/**\n * The text step's time (AGL-3035): ONE request — no re-ask, and no site\n * inventory to look anything up in — at the routing table's ceiling on the\n * tier `job.text` is served from, with the step's reads and writes. That fits\n * an inline door's budget, so a door runs a text job where it is asked for,\n * and the least time is the helper's worst case rather than a figure anyone\n * chose; a slower tier asks less so its worst case still fits.\n */\nexport const AI_JOB_TEXT_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.text'],\n maxTokens: AI_JOB_TEXT_MAX_TOKENS,\n attempts: 1,\n lookups: 0,\n})\n\n/** The least time one text step needs before it starts. */\nexport const AI_JOB_TEXT_STEP_MINIMUM_MS = AI_JOB_TEXT_STEP_BUDGET.minimumMs\n\n/** How much of a brief is sent. Past this a brief is a document, not a brief. */\nexport const AI_JOB_BRIEF_MAX_CHARS = 4_000\n\n/**\n * What a step hands back to the machine. The machine meters `usage` at\n * `model`'s rates, records the step and appends the outputs; a step never\n * touches the job document or the meter itself.\n */\nexport interface AiJobStepOutcome {\n outputs: AiJobOutput[]\n /**\n * The step made progress and has more of the same work to do (AGL-2910):\n * a site audit works through its pages a batch at a time. The machine\n * records this pass — its spend and its outputs — and hands the SAME step\n * back to run again, so every pass is one reservation and one provider\n * exchange, and the job document says how far it got.\n */\n continue?: boolean\n usage: AssistTokenUsage\n estCostUsd: number\n model: string\n stopReason: string | null\n /**\n * The thinking effort the request asked for, recorded on the step\n * (AGL-2937); absent or `null` when the request named none.\n */\n effort?: AiEffort | null\n /** The model declined the brief (`stop_reason: 'refusal'`). Tokens were spent. */\n refused?: boolean\n /**\n * The model answered and nothing usable came of it — no structured answer\n * even after the step's own re-ask, say (AGL-2938). Tokens were spent, so\n * the machine meters the step before it fails the job with this sentence,\n * which is customer-safe and the only thing a customer reads.\n */\n failure?: string\n /** The plan the plan step proposed, which the machine keeps on the job (AGL-2935). */\n plan?: AiJobPlan\n /**\n * The step stopped for a person (AGL-2935). The machine records what it\n * spent and parks the job `needs_review`: a `plan` review completes the\n * step, so confirming runs the next one; a `doctrine` review hands the\n * step back, so trying again runs the same one.\n */\n review?: AiJobReview\n /**\n * With a `plan` review: the plan needs an answer only the person can give\n * first (AGL-3978) — a tracking change with no pixel ID. The machine keeps\n * the plan and its review and parks the job `needs_input` with these\n * fields instead of `needs_review`; the answer returns it to the review.\n */\n input?: AiJobInputRequest\n /**\n * The answer was refused by the platform's own checks, not the model's\n * (AGL-3594): the machine meters the step as any other and then gives the\n * job's credits back to the meters it was charged to (`refundJobCredits`),\n * a bounded number of times a day.\n */\n uncredited?: boolean\n /**\n * The `failure` is the workspace's own limit, not a fault of ours: the\n * month's Free AI credits are spent (a 429 from the kind's admission, asked\n * when the plan is kept). The machine fails the job and gives the step's\n * credits back exactly as for our failure, but announces it as NOT ours, so\n * no staff alert is raised for a customer reaching their allowance.\n */\n customerLimit?: boolean\n /**\n * What a guided site start put live on its last pass (AGL-3596), kept on\n * the job by the machine in the write that records the pass: a step never\n * writes the job document itself.\n */\n sitePublish?: AiJobSitePublish\n /**\n * A `build` pass's unit and what it came to (AGL-3616), which the machine\n * writes to the job's item ledger in the transaction that records the\n * pass's spend. An item's failure is never the step's: it is a row, and\n * the build goes on.\n */\n item?: AiJobItemOutcome\n /** A `build`'s ledger to start from, written by the machine in the same transaction (AGL-3616). */\n items?: AiJobItemLedger[]\n /**\n * An `import` job's waves after this pass (AGL-3976), kept on the job by\n * the machine in the write that records the pass: a step never writes the\n * job document itself.\n */\n siteImport?: AiSiteImportState\n}\n\n/** What one `build` pass came to for the unit it ran (AGL-3616). */\nexport interface AiJobItemOutcome {\n slot: string\n /** `running` while the unit asks for another pass of its own. */\n status: AiJobItemStatus\n failure?: AiJobItemFailure | null\n /** Customer-safe: what the person should know about the item. */\n note?: string | null\n degradedBy?: string[]\n /** The ids of the outputs this pass produced for the item. */\n outputs?: string[]\n}\n\nexport interface AiJobStepContext {\n job: AiJob\n stepIndex: number\n now: Date\n /** Aborts the provider call when the runner's budget ends. */\n signal?: AbortSignal\n /**\n * The Admin SDK handle the machine runs on, for a step that reads what it\n * builds from — a site's theme, say — or writes a draft. A step never\n * writes the job document, the meter or the lease through it.\n */\n firestore: FirebaseFirestore.Firestore\n /** The org document the machine read for the step's reservation. */\n org?: Partial<AglynOrgBilling> | null\n /**\n * The model a step of this kind runs on for THIS job (AGL-2942): the\n * creator's pick where the plan and the allotment allowlists allow it,\n * the routing table otherwise. Absent, a runner asks the table itself.\n */\n modelFor?: (kind: AiStepKind) => string | undefined\n}\n\nexport type AiJobStepRunner = (\n context: AiJobStepContext,\n) => Promise<AiJobStepOutcome>\n\n/**\n * The static block, byte-identical on every request so it caches. The\n * customer's brief and the org's inputs never enter it.\n */\nexport const AI_JOB_TEXT_SYSTEM: AiSystemBlock[] = [\n {\n text:\n 'You write short, publishable copy for a website builder. You are ' +\n 'given a brief from the person who owns the site. Answer with the ' +\n 'copy only: no preamble, no headings unless the brief asks for them, ' +\n 'no closing remarks, no markdown fences. Keep to the length the brief ' +\n 'implies; when it implies none, write under 150 words. Write in the ' +\n 'language of the brief. Never invent facts, prices, names or claims ' +\n 'the brief did not give you; where a detail is missing, leave a ' +\n 'clearly marked placeholder in square brackets.',\n cacheBreakpoint: true,\n },\n]\n\n/** The brief as the user turn, with the kind-specific inputs stated plainly. */\nexport function aiJobTextPrompt(job: Pick<AiJob, 'brief' | 'inputs'>): string {\n const brief = job.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)\n const tone = typeof job.inputs?.['tone'] === 'string' ? job.inputs['tone'] : ''\n const audience =\n typeof job.inputs?.['audience'] === 'string' ? job.inputs['audience'] : ''\n const lines = [`Brief: ${brief}`]\n if (tone) lines.push(`Tone: ${tone}`)\n if (audience) lines.push(`Audience: ${audience}`)\n return lines.join('\\n')\n}\n\nexport const runAiJobTextStep: AiJobStepRunner = async ({ job, signal, modelFor }) => {\n const model = modelFor?.('job.text') ?? aiJobTextModel()\n // Overlay copy (AGL-3603): the same one request, answered as fields.\n if (aiOverlayCopyRequested(job.inputs)) {\n return runAiOverlayCopy({\n job,\n model,\n maxTokens: AI_JOB_TEXT_STEP_BUDGET.maxTokens(model),\n ...(signal ? { signal } : {}),\n })\n }\n // A development run's effort A/B arm (AGL-3781), else the row's.\n const effort = aiDevEffortFor('job.text') ?? AI_ROUTING_TABLE['job.text'].effort\n const result = await runAiRequest({\n model,\n system: AI_JOB_TEXT_SYSTEM,\n messages: [{ role: 'user', content: aiJobTextPrompt(job) }],\n // The routing ceiling, lowered on a tier too slow to answer it inside the\n // least time the step registered.\n maxTokens: AI_JOB_TEXT_STEP_BUDGET.maxTokens(model),\n ...(AI_ROUTING_TABLE['job.text'].thinking ? { thinking: AI_ROUTING_TABLE['job.text'].thinking } : {}),\n ...(effort ? { effort } : {}),\n stream: false,\n ...(signal ? { signal } : {}),\n })\n if (result.kind === 'refusal') {\n return {\n outputs: [],\n usage: result.usage,\n estCostUsd: result.estCostUsd,\n model,\n stopReason: result.stopReason,\n refused: true,\n }\n }\n const text = result.text.trim()\n return {\n outputs: text\n ? [\n {\n resource: 'text',\n // A text output has no document of its own; the id names the\n // step within the job so a later step can address it.\n id: 'draft',\n hostId: job.hostId ?? null,\n label: 'Draft copy',\n text,\n },\n ]\n : [],\n usage: result.usage,\n estCostUsd: result.estCostUsd,\n model,\n stopReason: result.stopReason,\n }\n}\n"],"names":["AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","aiDevEffortFor","runAiRequest","aiJobStepBudget","aiOverlayCopyRequested","runAiOverlayCopy","aiJobTextModel","AI_JOB_TEXT_MAX_TOKENS","maxTokens","AI_JOB_TEXT_STEP_BUDGET","tier","attempts","lookups","AI_JOB_TEXT_STEP_MINIMUM_MS","minimumMs","AI_JOB_BRIEF_MAX_CHARS","AI_JOB_TEXT_SYSTEM","text","cacheBreakpoint","aiJobTextPrompt","job","brief","slice","tone","inputs","audience","lines","push","join","runAiJobTextStep","signal","modelFor","model","effort","result","system","messages","role","content","thinking","stream","kind","outputs","usage","estCostUsd","stopReason","refused","trim","resource","id","hostId","label"],"mappings":"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"}
|
|
@@ -25,6 +25,7 @@ import { type AssistReservation } from '../usage/assist-usage';
|
|
|
25
25
|
import { type AiJobRefundReason } from '../usage/assist-job-refund';
|
|
26
26
|
import type { AiJobItemOutcome } from './ai-job-text-step';
|
|
27
27
|
import { type FreeAssistAccount } from '../usage/assist-free-taste';
|
|
28
|
+
import { type AiSiteImportState } from '../model/ai-site-import';
|
|
28
29
|
/**
|
|
29
30
|
* AI generation jobs — the Firestore state machine (AGL-2904).
|
|
30
31
|
*
|
|
@@ -369,6 +370,8 @@ export interface RecordStepInput {
|
|
|
369
370
|
* transaction, with what was given back for it.
|
|
370
371
|
*/
|
|
371
372
|
item?: AiJobItemRecord;
|
|
373
|
+
/** An `import` job's waves after this pass (AGL-3976), kept in this write. */
|
|
374
|
+
siteImport?: AiSiteImportState;
|
|
372
375
|
}
|
|
373
376
|
/** One `build` pass as its item's row records it (AGL-3616). */
|
|
374
377
|
export interface AiJobItemRecord extends AiJobItemOutcome {
|
|
@@ -494,6 +497,8 @@ export declare function aiJobSettlesByItem(kind: AiJobKind): boolean;
|
|
|
494
497
|
export declare function aiBuildRetryable(job: Pick<AiJob, 'kind' | 'status'>): boolean;
|
|
495
498
|
/** What a build that delivered nothing says; its planning was given back too. */
|
|
496
499
|
export declare const AI_BUILD_NOTHING_BUILT_COPY = "Nothing in this request could be built. Try again, or ask for less at once.";
|
|
500
|
+
/** What a site import that brought no page in says (AGL-3976); every page's spend was given back. */
|
|
501
|
+
export declare const AI_SITE_IMPORT_NOTHING_BUILT_COPY = "No page of your site could be brought in. Nothing was charged for the pages that failed; try again later.";
|
|
497
502
|
/** What an item the build never reached says when the build stopped early. */
|
|
498
503
|
export declare const AI_BUILD_NOT_REACHED_COPY = "Not built: the build stopped before it reached this.";
|
|
499
504
|
/**
|
package/src/lib/jobs/ai-jobs.js
CHANGED
|
@@ -46,6 +46,7 @@ import { freeAssistAccount } from "../usage/assist-free-taste.js";
|
|
|
46
46
|
import { aiJobAutoConfirms } from "./ai-job-auto-confirm.js";
|
|
47
47
|
import { AI_SITE_GUIDED_BUILD_FAILED_COPY, aiCustomerSafeCopy, aiCustomerSafeItem, aiCustomerSafeReview } from "../model/ai-job-failure-copy.js";
|
|
48
48
|
import { aiSiteStartInputsOf } from "../model/ai-site-start.js";
|
|
49
|
+
import { aiSiteImportWire } from "../model/ai-site-import.js";
|
|
49
50
|
import { addAdminAudit } from "@aglyn/tenant-data-admin/server/admin-audit-write";
|
|
50
51
|
import { AI_JOB_CANCELED_ITEM_NOTE, aiJobCancelPending, isAiJobCanceledError, watchAiJobCancel } from "./ai-job-cancel.js";
|
|
51
52
|
import { clearAiEmptySiteSweep, scheduleAiEmptySiteSweep } from "./ai-empty-site-sweep.js";
|
|
@@ -546,6 +547,8 @@ export async function getAiJob(firestore, orgId, jobId) {
|
|
|
546
547
|
cancelRequested: true
|
|
547
548
|
} : {}, job.siteRemoved ? {
|
|
548
549
|
siteRemoved: true
|
|
550
|
+
} : {}, job.siteImport ? {
|
|
551
|
+
siteImport: aiSiteImportWire(job.siteImport)
|
|
549
552
|
} : {});
|
|
550
553
|
}
|
|
551
554
|
function leaseIsLive(job, owner, nowMs) {
|
|
@@ -883,13 +886,17 @@ function nextStepIndex(job) {
|
|
|
883
886
|
plan: input.plan
|
|
884
887
|
} : {}, input.sitePublish ? {
|
|
885
888
|
sitePublish: input.sitePublish
|
|
889
|
+
} : {}, input.siteImport ? {
|
|
890
|
+
siteImport: input.siteImport
|
|
886
891
|
} : {}, items ? {
|
|
887
892
|
items
|
|
888
893
|
} : {}, ((_input_item = input.item) == null ? void 0 : _input_item.creditsRefunded) ? {
|
|
889
894
|
refundedCredits: ((_job_refundedCredits = job.refundedCredits) != null ? _job_refundedCredits : 0) + input.item.creditsRefunded
|
|
890
895
|
} : {}, parksForReview && input.review ? {
|
|
891
896
|
review: input.review,
|
|
892
|
-
|
|
897
|
+
// A plan to confirm and a site import's wave to review (AGL-3976)
|
|
898
|
+
// are decisions waiting, not errors.
|
|
899
|
+
error: input.input ? input.input.message : input.review.reason === 'plan' || input.review.reason === 'wave' ? null : input.review.message,
|
|
893
900
|
input: (_input_input = input.input) != null ? _input_input : null
|
|
894
901
|
} : {}, canceling ? _extends({
|
|
895
902
|
creditsReserved: 0,
|
|
@@ -1163,7 +1170,9 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1163
1170
|
* which is the build's preset. Their step writes an item ledger; a failed
|
|
1164
1171
|
* item gives back its own spend and the rest goes on.
|
|
1165
1172
|
*/ export function aiJobSettlesByItem(kind) {
|
|
1166
|
-
|
|
1173
|
+
// A site import's pages are its items too (AGL-3976): one that fails on
|
|
1174
|
+
// our side gives back its own spend, and its wave goes on.
|
|
1175
|
+
return kind === 'build' || kind === 'site' || kind === 'import';
|
|
1167
1176
|
}
|
|
1168
1177
|
/**
|
|
1169
1178
|
* Whether Try again may run a job's failed items: a finished build, done or
|
|
@@ -1174,6 +1183,7 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1174
1183
|
return job.kind === 'site' && job.status === 'done';
|
|
1175
1184
|
}
|
|
1176
1185
|
/** What a build that delivered nothing says; its planning was given back too. */ export const AI_BUILD_NOTHING_BUILT_COPY = 'Nothing in this request could be built. Try again, or ask for less at once.';
|
|
1186
|
+
/** What a site import that brought no page in says (AGL-3976); every page's spend was given back. */ export const AI_SITE_IMPORT_NOTHING_BUILT_COPY = 'No page of your site could be brought in. Nothing was charged for the pages that failed; try again later.';
|
|
1177
1187
|
/** What an item the build never reached says when the build stopped early. */ export const AI_BUILD_NOT_REACHED_COPY = 'Not built: the build stopped before it reached this.';
|
|
1178
1188
|
/**
|
|
1179
1189
|
* Gives back one build item's spend, or its planning's (`slot: 'plan'`),
|
|
@@ -1364,7 +1374,7 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1364
1374
|
updatedAt: now
|
|
1365
1375
|
});
|
|
1366
1376
|
});
|
|
1367
|
-
const nothing = job.kind === 'site' ? AI_SITE_GUIDED_BUILD_FAILED_COPY : AI_BUILD_NOTHING_BUILT_COPY;
|
|
1377
|
+
const nothing = job.kind === 'site' ? AI_SITE_GUIDED_BUILD_FAILED_COPY : job.kind === 'import' ? AI_SITE_IMPORT_NOTHING_BUILT_COPY : AI_BUILD_NOTHING_BUILT_COPY;
|
|
1368
1378
|
return failAiJob(firestore, orgId, jobId, (_ref1 = stopped == null ? void 0 : stopped.message) != null ? _ref1 : nothing, {
|
|
1369
1379
|
error: 'build delivered nothing',
|
|
1370
1380
|
// Ours unless every item that failed was the model declining (AGL-3616).
|
|
@@ -2031,6 +2041,8 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
2031
2041
|
input: outcome.input
|
|
2032
2042
|
} : {}, outcome.sitePublish ? {
|
|
2033
2043
|
sitePublish: outcome.sitePublish
|
|
2044
|
+
} : {}, job.kind === 'import' && outcome.siteImport ? {
|
|
2045
|
+
siteImport: outcome.siteImport
|
|
2034
2046
|
} : {}, aiJobSettlesByItem(job.kind) && outcome.items ? {
|
|
2035
2047
|
items: outcome.items
|
|
2036
2048
|
} : {}, itemRecord ? {
|