@aglyn/plugins-ai 1.0.0-beta.249 → 1.0.0-beta.252
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/components/ai-brief-dialog.component.js +15 -1
- package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-job-links.js +1 -1
- package/src/lib/components/ai-job-links.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.js +20 -2
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-jobs-store.d.ts +11 -0
- package/src/lib/components/ai-jobs-store.js +22 -0
- package/src/lib/components/ai-jobs-store.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.js +3 -2
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-crm-step.js +7 -4
- package/src/lib/jobs/ai-job-crm-step.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.d.ts +24 -0
- package/src/lib/jobs/ai-job-drafts.js +46 -0
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-experiment-step.js +6 -1
- package/src/lib/jobs/ai-job-experiment-step.js.map +1 -1
- package/src/lib/jobs/ai-job-form-reuse.d.ts +78 -0
- package/src/lib/jobs/ai-job-form-reuse.js +136 -0
- package/src/lib/jobs/ai-job-form-reuse.js.map +1 -0
- package/src/lib/jobs/ai-job-form-step.d.ts +17 -0
- package/src/lib/jobs/ai-job-form-step.js +63 -11
- package/src/lib/jobs/ai-job-form-step.js.map +1 -1
- package/src/lib/jobs/ai-job-insight-step.js +6 -1
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +12 -1
- package/src/lib/jobs/ai-job-page-language.js +48 -11
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +8 -3
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +19 -0
- package/src/lib/jobs/ai-job-plan-step.js +39 -1
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-seo-step.js +5 -3
- package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +7 -5
- package/src/lib/jobs/ai-job-site-content.js +7 -5
- package/src/lib/jobs/ai-job-site-content.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +17 -0
- package/src/lib/jobs/ai-job-site-datasets.js +24 -0
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +5 -2
- package/src/lib/jobs/ai-job-site-step.js +20 -6
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-text-step.js +6 -3
- package/src/lib/jobs/ai-job-text-step.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +22 -8
- package/src/lib/jobs/ai-layout-stock-photos.js +39 -8
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/ai-stock-business-nouns.d.ts +62 -0
- package/src/lib/jobs/ai-stock-business-nouns.js +264 -0
- package/src/lib/jobs/ai-stock-business-nouns.js.map +1 -0
- package/src/lib/layout-language/ai-layout-gaps.d.ts +15 -0
- package/src/lib/layout-language/ai-layout-gaps.js +57 -7
- package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
- package/src/lib/model/ai-build-plan.d.ts +7 -3
- package/src/lib/model/ai-build-plan.js +11 -1
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-credit-estimate.d.ts +32 -18
- package/src/lib/model/ai-credit-estimate.js +36 -18
- package/src/lib/model/ai-credit-estimate.js.map +1 -1
- package/src/lib/model/ai-job-activity.d.ts +30 -0
- package/src/lib/model/ai-job-activity.js +35 -0
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-media-credits.d.ts +5 -3
- package/src/lib/model/ai-media-credits.js +7 -5
- package/src/lib/model/ai-media-credits.js.map +1 -1
- package/src/lib/model/ai-site-job.js +6 -3
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-store-pages.d.ts +5 -0
- package/src/lib/model/ai-site-store-pages.js +2 -0
- package/src/lib/model/ai-site-store-pages.js.map +1 -1
- package/src/lib/plugin-config.js +2 -2
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/providers/anthropic-crm-wording-superseded.d.ts +29 -0
- package/src/lib/providers/anthropic-crm-wording-superseded.js +31 -0
- package/src/lib/providers/anthropic-crm-wording-superseded.js.map +1 -0
- package/src/lib/providers/catalog.d.ts +24 -17
- package/src/lib/providers/catalog.js +63 -30
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/providers/model-choice.d.ts +1 -1
- package/src/lib/providers/model-choice.js +39 -30
- package/src/lib/providers/model-choice.js.map +1 -1
- package/src/lib/providers/routing.d.ts +20 -1
- package/src/lib/providers/routing.js +47 -8
- package/src/lib/providers/routing.js.map +1 -1
- package/src/lib/runtime/ai-dev-env.d.ts +37 -1
- package/src/lib/runtime/ai-dev-env.js +59 -6
- package/src/lib/runtime/ai-dev-env.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.js +3 -1
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +8 -4
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.d.ts +8 -0
- package/src/lib/runtime/ai-eval-live.js +34 -8
- package/src/lib/runtime/ai-eval-live.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +4 -0
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/ai-runtime.js +17 -2
- package/src/lib/runtime/ai-runtime.js.map +1 -1
- package/src/lib/runtime/seo-fields.js +4 -3
- package/src/lib/runtime/seo-fields.js.map +1 -1
- package/src/lib/server/ai-assist.js +3 -2
- package/src/lib/server/ai-assist.js.map +1 -1
- package/src/lib/server/ai-media-svg.js +4 -0
- package/src/lib/server/ai-media-svg.js.map +1 -1
- package/src/lib/usage/ai-allotment-alerts.js +2 -5
- package/src/lib/usage/ai-allotment-alerts.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-plan-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 { aiSiteKindOfInputs } from '../model/ai-site-kinds'\nimport { createHash } from 'node:crypto'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport {\n AI_BUILD_PLAN_TOOL,\n aiBuildPlanToolFor,\n aiPlanSlugKey,\n isAiPlanNewRef,\n type AiBuildPlan,\n} from '../model/ai-build-plan'\nimport type { AiJob, AiJobKind, AiJobPlan, AiJobReview, AiJobStatus } from '../model/ai-jobs.types'\nimport { AI_BUILD_PRODUCT_OP, aiBuildStorePagesFor } from '../model/ai-build-store-pages'\nimport { readFreeAiCreditsLeft } from '../usage/free-ai-credits-left'\nimport {\n AI_TEMPLATE_SUBJECT_DEFINITIONS,\n aiTemplateShownTokens,\n type AiTemplateSubject,\n} from '../model/ai-template-subjects'\nimport { AI_JOB_CREATE_KINDS, AI_JOB_CREATE_NOUNS } from '../model/ai-job-creations'\nimport { AI_PAGE_CREATE_KINDS, aiPagePlanShapeRefusal } from '../model/ai-page-job'\nimport {\n AI_PLAN_COMPILED_REPEATS_SENTENCE,\n AI_PLAN_NO_FORM_SENTENCE,\n aiPlanCanPlaceForm,\n aiPlanCapabilitiesForJob,\n aiPlanCapabilityLines,\n type AiPlanCapabilities,\n type AiPlanJobScope,\n} from '../model/ai-plan-capabilities'\nimport type { AiSiteInventory } from '../model/ai-site-inventory'\nimport { aiSitePlanDatasetCapability } from './ai-job-site-datasets'\nimport {\n AI_FREE_SITE_WORST_CASE_CREDITS,\n AI_SITE_CREATE_KINDS,\n AI_SITE_FREE_PAGES,\n AI_SITE_PLAN_MAX_TOKENS,\n AI_SITE_HOME_BANDS,\n aiFreeSiteSectionsWithin,\n aiSiteFullPlanSentence,\n aiSiteHomeMinSections,\n aiSiteNameSentence,\n aiSitePlanIsHome,\n aiSitePlanShapeRefusal,\n aiSiteBlogStandInViolations,\n aiSiteThinHomeViolations,\n AI_SITE_GALLERY_SENTENCE,\n aiSiteEmptyGalleryViolations,\n AI_SITE_DATASETS_MAX,\n AI_SITE_GALLERY_MIN_ITEMS,\n} from '../model/ai-site-job'\nimport { aiSiteContentPart } from './ai-job-site-content'\nimport { aiLayoutIsShopPage, aiLayoutShopGridSection } from '../layout-language/ai-layout-listings'\nimport { aiStorefrontPlanScreens } from '../layout-language/ai-layout-storefront'\nimport { AI_GENERATION_MAX_ATTEMPTS } from '../runtime/ai-generation-bounds'\nimport { aiPlanFailureCopy, aiPlanRetryRefusal } from '../model/ai-job-failure-copy'\nimport { FREE_AI_TASTE_CREDITS_PER_MONTH } from '../plan-entitlements'\nimport { aiDoctrineSystemBlocks, runValidatedGeneration } from '../runtime/ai-doctrine'\nimport { AI_STEP_TIERS, aiCatalogEntry, aiDefaultModelFor } from '../providers/catalog'\nimport {\n AI_FREE_PAGE_WORST_CASE_CREDITS,\n aiPlanEmbedBriefViolations,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport { assistCreditsFromUsd } from '../usage/assist-credits'\nimport { ASSIST_RETURNED_USD_FIELD, assistSpendAfterReturnsUsd } from '../usage/assist-credit-returns'\nimport { freeAccountUsageRef, freeAssistAccount, type AssistMeteredOrg } from '../usage/assist-free-taste'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport type { AiTool } from '../providers/contract'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { readSiteInventory } from '../runtime/site-inventory'\nimport { readAiSiteContext } from '../runtime/site-context'\nimport { aiInstructionsWithSiteContext, type AiSiteContextInput } from '../model/ai-site-context'\nimport {\n AI_BUILD_CREATE_KINDS,\n AI_BUILD_STRUCTURAL_OPS,\n aiBuildPageLimit,\n aiBuildPlanShapeRefusal,\n type AiBuildOps,\n} from '../model/ai-build-job'\nimport { aiBuildOpLines, aiBuildOps } from './ai-build-capabilities'\nimport { aiBuildUnlistedEventsCheck, aiSettleBuildEventPage } from '../model/ai-build-event-page'\nimport {\n aiSiteEventItemsCheck,\n aiSiteEventPlanLines,\n readAiSiteEventPlanning,\n type AiSiteEventPlanning,\n} from './ai-job-site-events'\nimport { aiJobAdmissionRefusal } from './ai-job-admission'\nimport { AI_CREDITS_CONFIRM_CODE } from '../model/ai-credit-estimate'\nimport { readAiPlanCapabilities } from './ai-job-drafts'\nimport {\n AI_JOB_BRIEF_MAX_CHARS,\n type AiJobStepOutcome,\n type AiJobStepRunner,\n} from './ai-job-text-step'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiDoctrineReview, aiUnspentOutcome } from './ai-job-generation'\nimport { aiPlanWithDraftIds } from './ai-job-draft-ids'\nimport { AI_JOBS_COLLECTION, registerAiJobPlanStep } from './ai-jobs'\n\n/**\n * The plan step (AGL-2935): the first step of every job that builds site\n * structure. Before a node is generated, the model answers with a typed plan\n * — what the site already has that the job reuses, what it creates and why,\n * and the screens it builds from them — held to the doctrine's plan rules\n * against the site's inventory and re-asked once when it breaks one.\n *\n * A plan that passes is kept on the job, and the job stops for review: the\n * member reads what it will build before a credit is spent building it, and\n * confirms through the resume door. A plan that still breaks a rule on its\n * re-ask stops the job the same way, with the rules named. Either way the\n * step writes nothing itself; the machine records the plan, the spend and\n * the stop, as it records every step.\n *\n * ── Told what it may create, refused what it cannot build (AGL-3030) ─────\n *\n * Before it asks, the step reads what the job may create on its site: the\n * workspace's plan, the site's counts against its limits, and — for a kind\n * that builds only some plans — what the job builds itself. The user turn\n * states it, the plan rules refuse a creation outside it with the one re-ask,\n * and where the workspace keeps no reusable components they accept the page\n * built inline. A plan the confirm door would still refuse — the kind's own\n * admission, handed the plan, as the resume door hands it — is refused HERE,\n * before a member is shown a Confirm: the job fails with the door's sentence,\n * its plan is not kept, and it holds nothing.\n */\n\n/** The plan step's own instructions, cached after the doctrine. */\nexport const AI_JOB_PLAN_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text:\n 'You plan what a website builder job will build, before anything is built. You are given the kind of job and the brief from the person who owns the site. ' +\n 'Answer with submit_build_plan: what the site inventory already has that the job reuses, what it must create and why nothing listed will do, and each page it builds (the screens list) with its layout, template, slug, search title, search description and sections from top to bottom. ' +\n 'Refer to inventory records by id and to what the plan creates as new:<name>. Plan only what the brief asks for: a component, layout, template, form or email job plans no pages unless the brief asks for them, and a page job plans exactly one page.',\n },\n]\n\n/**\n * The plan step's time (AGL-3026, AGL-3036): its two inventory-lookup rounds,\n * its answer and its re-ask at the routing table's ceiling for `job.plan`, on\n * the tier that step kind is served from, with the step's reads and writes,\n * at the rates `ai-job-budget.ts` assumes — fitted to what a beat can give a\n * step, so the served tier asks a little under the routing ceiling.\n */\nexport const AI_JOB_PLAN_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.plan'],\n maxTokens: AI_ROUTING_TABLE['job.plan'].maxTokens,\n})\n\n/**\n * The least time a plan needs before it starts. Registered with the step, so\n * an inline door never starts a plan. A plan thinks before it answers and\n * routinely runs past an inline door's budget, and a provider call that\n * budget cuts off is still generated and billed upstream while the meter\n * records nothing. The beat starts a plan only with this much of its own\n * budget left, and a spec holds it inside that budget.\n */\nexport const AI_JOB_PLAN_STEP_MINIMUM_MS = AI_JOB_PLAN_STEP_BUDGET.minimumMs\n\n/**\n * The plan's answer ceiling on the model a job runs: the most whose worst\n * case fits the least time the step registered, and never more than the\n * routing table's. A model the catalog does not know is planned at the\n * slowest.\n */\nexport function aiJobPlanMaxTokens(model: string): number {\n return AI_JOB_PLAN_STEP_BUDGET.maxTokens(model)\n}\n\n/** What the member reads while a plan waits for them. */\nexport const AI_JOB_PLAN_REVIEW_COPY =\n 'The plan is ready. Review what the job will reuse and create, then confirm it to build.'\n\n/**\n * The job as the plan step's user turn: its kind, its brief, its scalar\n * inputs and, where the job read them, what it may create on its site\n * (AGL-3030). Per workspace and per site, which is why they ride here and\n * never in a system block the platform's cache entry is keyed on.\n */\nexport function aiJobPlanPrompt(\n job: Pick<AiJob, 'kind' | 'brief' | 'inputs'>,\n capabilities: AiPlanCapabilities | null = null,\n inventory: AiSiteInventory | null = null,\n ops: AiBuildOps | null = null,\n now: Date | null = null,\n): string {\n const lines = [`Job kind: ${job.kind}`, `Brief: ${job.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)}`]\n for (const [key, value] of Object.entries(job.inputs ?? {})) {\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n lines.push(`${key}: ${String(value)}`)\n }\n }\n if (capabilities) lines.push(...aiPlanCapabilityLines(capabilities))\n // A form is a saved form or none (AGL-3596): said where the site read has\n // none to place and the job may create none.\n if (capabilities && inventory && !aiPlanCanPlaceForm(inventory, capabilities)) {\n lines.push(AI_PLAN_NO_FORM_SENTENCE)\n }\n lines.push(...aiPlanTemplateTokenLines(job))\n lines.push(...aiPlanSiteLines(job, inventory, capabilities))\n lines.push(...aiPlanBuildLines(job, capabilities, ops, now))\n return lines.join('\\n')\n}\n\n/**\n * What a BUILD job's plan is told (AGL-3616): that one request becomes pages,\n * creations and items, how many pages it may hold, and the item operations\n * this site has with their arguments. On the job's own turn, never a cached\n * system block, because the operations are per site.\n */\nexport function aiPlanBuildLines(\n job: Pick<AiJob, 'kind'>,\n capabilities: AiPlanCapabilities | null,\n ops: AiBuildOps | null,\n now: Date | null = null,\n): string[] {\n if (job.kind !== 'build') return []\n const pages = aiBuildPageLimit(capabilities?.freeTaste === true)\n return [\n `This is a build from one request: plan each page it asks for in screens (at most ${pages}), each new layout, form, component or email design in create, and anything else in items. Plan only what the request asks for; reuse what the site has.`,\n 'A page section that shows a form or an item places it: put its new:<name> in that section\\'s uses. An item built after another lists it in dependsOn.',\n // The layout language draws a section's repeated items (AGL-3660), so the\n // doctrine's \"repeated items place a component\" is not asked of a build.\n ...(capabilities?.repeatsCompiled ? [AI_PLAN_COMPILED_REPEATS_SENTENCE] : []),\n ...(ops ? aiBuildOpLines(ops, AI_BUILD_STRUCTURAL_OPS) : []),\n // An item with a date (an event, AGL-3743) is dated after the build.\n ...(now ? [aiPlanTodaySentence(now)] : []),\n ]\n}\n\n/** The build's date, so a dated item falls after it (AGL-3743). */\nexport function aiPlanTodaySentence(now: Date): string {\n return `Today is ${now.toISOString().slice(0, 10)} (UTC). Anything an item dates, such as an event, falls after today.`\n}\n\n/**\n * What a SITE job's plan is told beside the inventory (AGL-3594): the pages\n * the site already has and that their addresses are taken, which one — the\n * untouched starter home — a planned home page at `/` replaces, the Free page\n * cap and its guidance, and that a plan is an outline.\n *\n * A site job only. A new site is born with a published home page at `/`\n * (AGL-3497), and a plan that did not know it could replace it planned its own\n * home there and was refused for the clash on every new site. Kept off a page\n * job's turn, whose length the Free page's wall is proven at.\n */\nexport function aiPlanSiteLines(\n job: Pick<AiJob, 'kind'> & Partial<Pick<AiJob, 'inputs'>>,\n inventory: AiSiteInventory | null,\n capabilities: AiPlanCapabilities | null,\n): string[] {\n if (job.kind !== 'site') return []\n const lines: string[] = []\n // The business's own name, as the name to use (AGL-3596): the plan's search\n // titles carry it, and an input line alone left the model free to coin one.\n const name = job.inputs?.['businessName']\n if (typeof name === 'string' && name.trim()) lines.push(aiSiteNameSentence(name.trim()))\n const pages = (inventory?.screens ?? []).filter((screen) => !screen.template)\n const starter = pages.find((screen) => screen.replaceable)\n if (pages.length) {\n lines.push('Pages the site already has; their addresses are taken unless a line says otherwise:')\n for (const page of pages) {\n lines.push(\n page.replaceable\n ? `- ${page.name} at ${page.slug}: the starter home page the site was created with. Plan this site's home page at / and it replaces this one.`\n : `- ${page.name} at ${page.slug}`,\n )\n }\n if (!starter && pages.some((page) => page.slug.trim() === '/' || page.slug.trim() === '')) {\n lines.push(\"The home page at / is the owner's own and stays: give every page you plan another address.\")\n }\n }\n const cap = capabilities?.freeSitePages\n const home = aiSitePlanHomeRule(inventory, capabilities)\n if (cap !== undefined) {\n lines.push(\n `This is a Free workspace: plan at most ${cap} ${cap === 1 ? 'page' : 'pages'} — the home page at / and the one page the brief most needs, such as services, booking or contact — and ${home.across} sections across them.${aiPlanCanPlaceForm(inventory, capabilities) ? ' Put the contact form on one of them.' : ''}`,\n )\n }\n // A new site is a full website (AGL-3660): its home is planned as one, not\n // left to a ceiling the outline is free to stay far under.\n const plansHome = !pages.some((page) => !page.replaceable && aiSitePlanIsHome(page))\n if (home.min > 0 && plansHome) {\n // A store's home is a storefront (AGL-3676): its own parts, in its own order.\n const bands = aiSiteKindOfInputs(job.inputs)?.id === 'store' ? AI_SITE_STORE_HOME_BANDS : AI_SITE_HOME_BANDS\n lines.push(`The home page at / reads as a full website: at least ${home.min} sections — ${bands}.`)\n }\n // The sections are a budget to use (AGL-3660): a prod start of 2026-10-08\n // read \"at most 8\" as leave to plan a home of two.\n lines.push(aiSiteFullPlanSentence({ pages: cap ?? 1, across: home.across, min: plansHome ? home.min : 0 }))\n // A paid blog's first posts are written at /blog and linked from the\n // header (AGL-3660, AGL-3676), so the plan plans no page standing in for it.\n if (aiSiteContentPart(job.inputs ?? null, capabilities?.freeTaste === true) === 'posts') {\n lines.push(\n \"This site's blog is written for it with its first posts at /blog, and the header links it. Plan no page that stands in for it (no Blog, Articles, Journal, Posts or Stories page); feature the posts in a section of the home page instead.\",\n )\n }\n // A paid store's account, cart and policy pages are added by the platform,\n // outside the plan's pages (AGL-3676), so the plan spends none of its pages on them.\n if (aiSiteContentPart(job.inputs ?? null, capabilities?.freeTaste === true) === 'products') {\n lines.push(AI_SITE_STORE_PAGES_SENTENCE)\n }\n // A section that shows the work counts its pieces (AGL-3660): a planned\n // gallery of no items let a page about the work show none.\n lines.push(AI_SITE_GALLERY_SENTENCE)\n // A section that asks for something places the form (AGL-3660): a live\n // portfolio start planned a commissions inquiry beside the form it made\n // for its Contact page, without placing it.\n if (aiPlanCanPlaceForm(inventory, capabilities)) lines.push(AI_SITE_FORM_SENTENCE)\n // Structured content is kept as a dataset where one may be made (AGL-3616).\n if (capabilities?.create.dataset.allowed) {\n lines.push(aiSiteDatasetSentence(Math.min(capabilities.datasetsMax ?? AI_SITE_DATASETS_MAX, capabilities.create.dataset.left ?? AI_SITE_DATASETS_MAX)))\n }\n // The kind of site the person picked (AGL-3660): the pages it usually has.\n const kind = aiSiteKindOfInputs(job.inputs)\n if (kind) lines.push(`This is a ${kind.label.toLowerCase()} site. ${kind.pages}`)\n // A store's Shop page IS its storefront (AGL-3676): the platform lists the\n // real products there, so the plan names the section and draws no range.\n if (kind?.id === 'store') lines.push(AI_SITE_STORE_SHOP_SENTENCE)\n lines.push(\n \"Keep the plan an outline: each page's title, address, a short search title and description, and its sections named in a few words. The build writes the copy.\",\n )\n return lines\n}\n\n/**\n * The sentence a site plan's turn states about a section that collects\n * answers (AGL-3660): it places the saved form by reference, and one form\n * may be placed on more than one page.\n */\nexport const AI_SITE_FORM_SENTENCE =\n \"A section that asks visitors for something (contact, inquiry, commissions, booking, sign-up) places the form: put new:<the form's name> or the site's form id in its uses. One form may be placed on several pages.\"\n\n/**\n * The sentence a site plan's turn states where it may create datasets, with\n * how many it may (the live eval of 2026-10-10 planned four past a cap of\n * three it was never told)\n * (AGL-3616): a list of like things is kept as one, named in the sections\n * that list it, and a page of its own per record is that dataset's record\n * template. Only where datasets may be made, so a Free plan's turn, whose\n * length its wall is proven at, is unchanged.\n */\nexport function aiSiteDatasetSentence(max: number): string {\n return `Keep structured content as a dataset, at most ${max} ${max === 1 ? 'dataset' : 'datasets'}, and only for a list of 4 or more similar items: a menu, a team, services, portfolio pieces, events, questions and answers; never reviews or testimonials. Create the dataset, its fields the items' field names, and put new:<the dataset's name> in the uses of each section that lists them; the platform lists its records there. Where each item deserves a page of its own, plan one page as the dataset's record template. Anything else stays written on its page. ${AI_SITE_FORM_DATASET_SENTENCE}`\n}\n\n/**\n * What a paid site plan is told about a form that writes to a dataset\n * (AGL-3616, Zach 2026-10-10): only where the person would want each\n * submission kept as a record, the dataset created with the form's own\n * field names, and never listed, because its records are the people who\n * sent the form.\n */\nexport const AI_SITE_FORM_DATASET_SENTENCE =\n \"Where each submission of the form should also be kept as a record (volunteer sign-ups, RSVPs, inquiries), the form writes to a dataset: create the dataset, its fields the form's field names, and set the form's writesTo to new:<the dataset's name>; that dataset counts toward the same limit and no section lists it. Otherwise writesTo is null.\"\n\n\n/**\n * What a site start's home page is held to (AGL-3660): its fewest sections,\n * and on the Free taste the sections the wall fits across the pages, which\n * the home's count is shared out of. The plan's turn and its re-ask read this\n * one figure, so the sentence the model is told is the check it is held to.\n */\nexport function aiSitePlanHomeRule(\n inventory: AiSiteInventory | null,\n capabilities: AiPlanCapabilities | null,\n): { min: number; across: number | null } {\n const cap = capabilities?.freeSitePages\n if (cap === undefined) return { min: aiSiteHomeMinSections({ pages: 1, across: null }), across: null }\n // Dear: a guided start that places a form makes one (AGL-3596).\n const across = aiFreeSiteSectionsWithin(\n { layouts: (inventory?.layouts.length ?? 0) ? 0 : 1, pages: cap, forms: 1 },\n FREE_AI_TASTE_CREDITS_PER_MONTH,\n )\n return { min: aiSiteHomeMinSections({ pages: cap, across }), across }\n}\n\n/**\n * A site plan's thin home, asked about on the FIRST answer only (AGL-3660).\n * The re-ask names the count; an answer that still plans a thinner home is\n * kept, since a home with four sections is a site and a stopped start is\n * none — the same trade a language page makes with its last answer's gaps.\n */\nexport function aiSiteThinHomeCheck(\n rule: { min: number; across: number | null },\n): (plan: AiBuildPlan) => AiDoctrineViolation[] {\n let answers = 0\n return (plan) => {\n answers += 1\n if (answers >= AI_GENERATION_MAX_ATTEMPTS) return []\n return aiSiteThinHomeViolations(plan, rule)\n }\n}\n\n/** What a store's plan is told about its Shop page (AGL-3676). */\nexport const AI_SITE_STORE_SHOP_SENTENCE =\n 'Plan a Shop page at /shop: a short title section, then a section named \"Product grid\" second, where the platform lists the store\\'s real products with sort, filters, photos, prices and cart, then at most one or two short sections (care, shipping, gifting help). Never open it with a hero, and never plan the products themselves as cards of your own.'\n\n/** What a paid store's plan is told about the pages the platform adds beside it (AGL-3676). */\nexport const AI_SITE_STORE_PAGES_SENTENCE =\n \"The platform adds this store's account page (sign in, orders, saved items), its cart page, and its Shipping & returns, Privacy policy and Terms of sale pages, and links them in the header and footer; they do not count toward this plan's pages. Plan none of them: spend every page on what the brief is about.\"\n\n/** What a store's home is composed of (AGL-3676): a storefront's parts, in a storefront's order. */\nexport const AI_SITE_STORE_HOME_BANDS =\n 'a hero that sells first, then \"Bestsellers\" (the platform lists the real products), \"Shop by collection\" with 3 or 4 items, the brand story, why buy here with 3 or 4 items, \"Customer reviews\" (the platform shows the store\\'s real ones) and a \"Newsletter sign-up\" band last'\n\n/**\n * A store plan held to a storefront (AGL-3676, `ai-layout-storefront.ts`):\n * on a paid start its home gains the parts a shopper expects that the plan\n * left out — bestsellers, collections, reviews, a newsletter sign-up —\n * while the page has room; on any start its Shop page opens on its grid.\n * Settled, never re-asked: each part has one answer, and the Free wall's\n * home keeps the sections it planned.\n */\nexport function aiSiteStorefrontPlan<P extends Pick<AiBuildPlan, 'screens'>>(plan: P, options: { paid: boolean }): P {\n const screens = aiStorefrontPlanScreens(plan.screens, {\n paid: options.paid,\n isShopPage: aiLayoutIsShopPage,\n gridSection: aiLayoutShopGridSection,\n })\n return screens.every((screen, index) => screen === plan.screens[index]) ? plan : { ...plan, screens }\n}\n\n/** The code a store plan with no Shop page is re-asked under. */\nexport const AI_SITE_STORE_SHOP_CODE = 'plan-store-shop-page'\n\n/**\n * A store plan of two or more pages with no Shop page (AGL-3676): the page\n * the platform lists the catalog on is the one a shopper looks for. A\n * one-page Free taste lists its products on the home instead.\n */\nexport function aiSiteStoreShopPageViolations(\n plan: Pick<AiBuildPlan, 'screens'>,\n): Array<{ rule: null; code: string; message: string; paths: string[] }> {\n if (plan.screens.length < 2 || plan.screens.some((screen) => aiLayoutIsShopPage(screen))) return []\n return [\n {\n rule: null,\n code: AI_SITE_STORE_SHOP_CODE,\n message:\n 'This store has no Shop page. Plan one at /shop, its second section named \"Product grid\": the platform lists the real products there.',\n paths: ['screens'],\n },\n ]\n}\n\n/** A store plan's missing Shop page, asked about on the FIRST answer only, like a thin home. */\nexport function aiSiteStoreShopCheck(): (plan: AiBuildPlan) => AiDoctrineViolation[] {\n let answers = 0\n return (plan) => {\n answers += 1\n if (answers >= AI_GENERATION_MAX_ATTEMPTS) return []\n return aiSiteStoreShopPageViolations(plan)\n }\n}\n\n/**\n * A site plan's section that shows the work without its pieces (AGL-3660),\n * asked about on the FIRST answer only, like a thin home: the re-ask names\n * the section, and an answer that keeps it thin is kept. A site start's plan\n * is given its fewest pieces in code first (`aiSettlePlanGalleries`,\n * AGL-3754), so this is the backstop for a section that settle leaves: one\n * item of a list split into sections.\n */\nexport function aiSiteEmptyGalleryCheck(): (plan: AiBuildPlan) => AiDoctrineViolation[] {\n let answers = 0\n return (plan) => {\n answers += 1\n if (answers >= AI_GENERATION_MAX_ATTEMPTS) return []\n return aiSiteEmptyGalleryViolations(plan)\n }\n}\n\n/**\n * A site plan's page standing in for the blog its start writes (AGL-3660),\n * asked about on the FIRST answer only, like a thin home: the re-ask names\n * the page, and an answer that keeps it is kept — the posts then take the\n * next address the page leaves free, and the header links them all the same.\n */\nexport function aiSiteBlogStandInCheck(): (plan: AiBuildPlan) => AiDoctrineViolation[] {\n let answers = 0\n return (plan) => {\n answers += 1\n if (answers >= AI_GENERATION_MAX_ATTEMPTS) return []\n return aiSiteBlogStandInViolations(plan)\n }\n}\n\n/**\n * What a TEMPLATE job's plan is told its creation's `fields` are (AGL-3143\n * §11): the subject's binding tokens, listed so the planner promises only\n * what the page can fill and the build can be held to it\n * (`aiPlanTemplateTokenViolations`).\n *\n * ⛔ It rides the job's own turn and NOT the `fields` description in the plan\n * tool, which every kind's request caches. Written there it read better and\n * cost 40 tokens of the shared prefix — 2,980 to 3,020 — which is one credit\n * of the Free page's 300-credit wall, taking the room it keeps for a re-asked\n * section from 45 credits to 44 and breaking `ai-job-free-page.spec.ts`. A\n * page job must not pay for a sentence about templates.\n *\n * The list is the tokens a reader sees, never the whole catalog (AGL-3143\n * §16). Offered a slug and the publish timestamp, a live plan promised them,\n * and its build kept the promise the only way a slug can be kept: it printed\n * \"entry slug: {{entry.slug}}\" on every article.\n */\nexport function aiPlanTemplateTokenLines(job: Pick<AiJob, 'kind' | 'inputs'>): string[] {\n if (job.kind !== 'template') return []\n const subject = (job.inputs ?? {})['subject']\n const definition =\n typeof subject === 'string'\n ? AI_TEMPLATE_SUBJECT_DEFINITIONS[subject as AiTemplateSubject]\n : undefined\n if (!definition) return []\n return [\n `The template's fields are the binding tokens its page shows a reader, each one of: ${aiTemplateShownTokens(\n definition,\n ).join(', ')}. A link or picture token is shown by the link or picture holding it. Promise only what the page fills.`,\n ]\n}\n\n/** A kind that builds only some plans: the creations it makes, and the shapes it refuses. */\nexport interface AiJobPlanScope extends AiPlanJobScope {\n /** Why a job of the kind cannot build a plan of this shape; `null` when it can. Pure. */\n shapeRefusal: (plan: AiBuildPlan, options?: { freeTaste?: boolean }) => string | null\n}\n\n/**\n * The kinds whose confirm door builds only some plans (AGL-3030). A page job\n * builds one screen and a scaffold four to eight; each builds only the\n * creations its list names. A template, layout, component, form or email job\n * builds its own record and what that record places (AGL-3143 §15,\n * `AI_JOB_CREATE_KINDS`), and holds no plan to a shape.\n */\nexport const AI_JOB_PLAN_SCOPES: Readonly<Partial<Record<AiJobKind, AiJobPlanScope>>> = {\n page: { noun: 'a page job', creates: AI_PAGE_CREATE_KINDS, shapeRefusal: aiPagePlanShapeRefusal },\n // A build from one request (AGL-3616): its operations are judged against\n // the site's registry by the step, which reads it (`aiBuildPlanViolations`).\n build: {\n noun: 'a build',\n creates: AI_BUILD_CREATE_KINDS,\n shapeRefusal: (plan, options) => aiBuildPlanShapeRefusal(plan, { freeTaste: options?.freeTaste }),\n },\n site: { noun: 'a site scaffold', creates: AI_SITE_CREATE_KINDS, shapeRefusal: aiSitePlanShapeRefusal },\n ...Object.fromEntries(\n Object.entries(AI_JOB_CREATE_KINDS).map(([kind, creates]) => [\n kind,\n { noun: AI_JOB_CREATE_NOUNS[kind as AiJobKind] as string, creates, shapeRefusal: () => null },\n ]),\n ),\n}\n\n/**\n * What the plan step holds beyond the doctrine's plan rules, as the\n * violations the plan's one re-ask names: the players the plan lists against\n * the brief it answers (AGL-3433), which only this step reads beside the plan,\n * and a kind's shape refusal.\n */\nfunction planViolations(\n scope: AiJobPlanScope | null,\n brief: string,\n freeTaste = false,\n ops: AiBuildOps | null = null,\n home: { min: number; across: number | null } | null = null,\n blog = false,\n site = false,\n store = false,\n): (plan: AiBuildPlan) => AiDoctrineViolation[] {\n const thinHome = home && home.min > 0 ? aiSiteThinHomeCheck(home) : null\n const storeShop = store ? aiSiteStoreShopCheck() : null\n const blogStandIn = blog ? aiSiteBlogStandInCheck() : null\n const emptyGallery = site ? aiSiteEmptyGalleryCheck() : null\n return (plan) => {\n // A build's plan is held to the operations this site has (AGL-3616):\n // an unknown op, arguments its schema refuses, a cycle or a reference\n // nothing makes are re-asked once like every other plan rule.\n const message = ops\n ? aiBuildPlanShapeRefusal(plan, { freeTaste, ops })\n : (scope?.shapeRefusal(plan, { freeTaste }) ?? null)\n return [\n ...aiPlanEmbedBriefViolations(plan, brief),\n ...(message ? [{ rule: null, code: 'plan-job-shape', message }] : []),\n ...(thinHome ? thinHome(plan) : []),\n ...(blogStandIn ? blogStandIn(plan) : []),\n ...(emptyGallery ? emptyGallery(plan) : []),\n ...(storeShop ? storeShop(plan) : []),\n ]\n }\n}\n\n/** The layout a site's pages render in (AGL-3755): its home page's, else its only layout, else none. */\nfunction aiSiteLayoutOf(inventory: AiSiteInventory | null): string | null {\n const home = inventory?.screens.find((screen) => !screen.template && aiPlanSlugKey(screen.slug) === '/')\n if (home?.layoutId) return home.layoutId\n return inventory?.layouts.length === 1 ? (inventory.layouts[0]?.id ?? null) : null\n}\n\n/**\n * A site plan's violations with its events' (AGL-3755): its items are only\n * the events it may plan. Every other kind's are as they are.\n */\nfunction aiSiteWithEventItemsCheck(\n violations: (plan: AiBuildPlan) => AiDoctrineViolation[],\n siteEvents: AiSiteEventPlanning | null | undefined,\n ops: AiBuildOps | null = null,\n): (plan: AiBuildPlan) => AiDoctrineViolation[] {\n // A build's events left on no page are re-asked once (AGL-3755).\n const unlisted = ops ? aiBuildUnlistedEventsCheck(ops) : null\n const events = siteEvents === undefined ? null : aiSiteEventItemsCheck(siteEvents)\n if (!unlisted && !events) return violations\n return (plan) => [...violations(plan), ...(events ? events(plan) : []), ...(unlisted ? unlisted(plan) : [])]\n}\n\n/**\n * The inventory names of every id the plan references, so the proposal reads\n * \"the Services layout\" rather than an id — kept on the plan because the\n * inventory is not read again when the member opens it.\n */\nexport function aiPlanLabels(\n plan: AiBuildPlan,\n inventory: AiSiteInventory | null,\n): Record<string, string> {\n const names = new Map<string, string>()\n for (const rows of [\n inventory?.components,\n inventory?.layouts,\n inventory?.templates,\n inventory?.forms,\n inventory?.datasets,\n inventory?.collections,\n inventory?.screens,\n ]) {\n for (const row of rows ?? []) names.set(row.id, row.name)\n }\n const refs = [\n ...plan.reuse.map((entry) => entry.id),\n ...plan.create.map((entry) => entry.duplicateOf),\n ...plan.screens.flatMap((screen) => [\n screen.layout,\n screen.template,\n screen.duplicateOf,\n ...screen.sections.flatMap((section) => section.uses),\n ]),\n ]\n const labels: Record<string, string> = {}\n for (const ref of refs) {\n if (!ref || isAiPlanNewRef(ref)) continue\n const name = names.get(ref)\n if (name) labels[ref] = name\n }\n return labels\n}\n\n/* ------------------------------------------------------------------------ *\n * Reusing an identical brief's plan\n * ------------------------------------------------------------------------ */\n\n/**\n * How long a plan may be reused for (AGL-2937).\n *\n * The window is what makes reuse safe rather than merely cheap. The key\n * already covers everything the request said — the brief, the inputs, the\n * model and the prompt as rendered, site inventory included — so a plan is\n * reused only when nothing the model was shown has changed. What the key\n * cannot see is the world outside the request: a page published, a component\n * renamed after the inventory was read, a member who meant something\n * different the second time. Fifteen minutes is short enough that the answer\n * is still the one the site would give, and long enough to cover what reuse\n * is actually for — the same brief run twice in a sitting, a job resubmitted\n * after a refused reservation, two members starting the same work.\n */\nexport const AI_PLAN_REUSE_WINDOW_MS = 15 * 60 * 1_000\n\n/** Jobs one lookup reads. More than one can share a key; the newest plan wins. */\nexport const AI_PLAN_REUSE_CANDIDATES = 5\n\n/** A job whose plan might be reused, as the finder reports it. */\nexport interface AiJobPlanCandidate {\n jobId: string\n status: AiJobStatus\n plan: AiJobPlan\n}\n\n/**\n * The digest a plan is reused by: the whole request, never the answer.\n *\n * It hashes what the model was asked and what it was shown — the job's kind\n * and site, the user turn (which carries the trimmed brief and every scalar\n * input), the model that would answer, every rendered system block including\n * the site inventory, and the tool's schema. Two requests that hash the same\n * would have been sent the same bytes to the same model, so the second can\n * keep the first's answer.\n *\n * The version tag is the escape hatch: anything that changes what a key\n * MEANS, rather than what it covers, bumps it and strands every old key\n * harmlessly, since a key nothing matches simply asks the model.\n */\nexport function aiJobPlanKey(input: {\n job: Pick<AiJob, 'kind' | 'hostId'>\n prompt: string\n model: string\n system: readonly AiSystemBlock[]\n /** The plan tool the request offers (AGL-3433); the plain one when absent. */\n tool?: AiTool\n}): string {\n const digest = createHash('sha256')\n for (const part of [\n 'plan.v1',\n input.job.kind,\n input.job.hostId ?? '',\n input.model,\n input.prompt,\n ...input.system.map((block) => block.text),\n JSON.stringify(input.tool ?? AI_BUILD_PLAN_TOOL),\n ]) {\n // Length-prefixed, so two different splits of the same characters cannot\n // collide by running into one another.\n digest.update(`${part.length}:${part}\\u0000`)\n }\n return digest.digest('hex')\n}\n\n/** A job that could not have produced a reusable plan, whatever its key says. */\nconst UNREUSABLE: readonly AiJobStatus[] = ['canceled', 'failed']\n\nfunction planMillis(plan: AiJobPlan): number | null {\n const at = plan.proposedAt as unknown\n if (at instanceof Date) return at.getTime()\n if (at && typeof (at as { toMillis?: unknown }).toMillis === 'function') {\n return (at as { toMillis(): number }).toMillis()\n }\n return null\n}\n\n/**\n * The newest plan worth reusing out of what the finder returned: another\n * job's, still proposed or already confirmed, on a job that was neither\n * canceled nor failed, proposed inside the window.\n *\n * A canceled or failed job is excluded even though its plan may be perfectly\n * good: those are the jobs a member walked away from, and handing their plan\n * to the next one would make a rejected answer look like a fresh one.\n */\nexport function aiReusablePlan(\n candidates: readonly AiJobPlanCandidate[],\n context: { jobId: string; now: Date },\n): AiJobPlanCandidate | null {\n const floor = context.now.getTime() - AI_PLAN_REUSE_WINDOW_MS\n let best: AiJobPlanCandidate | null = null\n let bestAt = -1\n for (const candidate of candidates) {\n if (candidate.jobId === context.jobId) continue\n if (UNREUSABLE.includes(candidate.status)) continue\n if (candidate.plan.status !== 'proposed' && candidate.plan.status !== 'confirmed') continue\n const at = planMillis(candidate.plan)\n if (at === null || at < floor || at > context.now.getTime()) continue\n if (at > bestAt) {\n best = candidate\n bestAt = at\n }\n }\n return best\n}\n\nexport type AiJobPlanFinder = (\n orgId: string,\n key: string,\n firestore?: FirebaseFirestore.Firestore,\n) => Promise<AiJobPlanCandidate[]>\n\n/**\n * The finder the step uses in production: an equality on `plan.key` inside\n * the org's own jobs.\n *\n * One equality on one field, with no ordering beside it, so Firestore's\n * automatic single-field index answers it and no composite index is deployed.\n * The window and the statuses are applied in memory instead, which is what\n * keeps it that way.\n */\nexport const findAiJobsByPlanKey: AiJobPlanFinder = async (orgId, key, firestore) => {\n if (!firestore) return []\n const snapshot = await firestore\n .collection('orgs')\n .doc(orgId)\n .collection(AI_JOBS_COLLECTION)\n .where('plan.key', '==', key)\n .limit(AI_PLAN_REUSE_CANDIDATES)\n .get()\n return snapshot.docs.flatMap((doc) => {\n const data = doc.data() as { status?: AiJobStatus; plan?: AiJobPlan | null }\n return data.plan\n ? [{ jobId: doc.id, status: data.status ?? 'queued', plan: data.plan }]\n : []\n })\n}\n\n/** What a job may create on its site, read for the plan step; `null` for no site. */\nexport type AiPlanCapabilitiesReader = (input: {\n job: AiJob\n org: Partial<AglynOrgBilling> | null\n firestore: FirebaseFirestore.Firestore\n}) => Promise<AiPlanCapabilities | null>\n\n/** The reader the step uses in production: the draft bands' own arithmetic, for the job's site. */\nexport const readAiJobPlanCapabilities: AiPlanCapabilitiesReader = async ({ job, org, firestore }) => {\n if (!job.hostId) return null\n const capabilities = await readAiPlanCapabilities(firestore, { hostId: job.hostId, org })\n // A site start creates datasets only where the data plugin runs and its\n // writer admits this member now (AGL-3616), and at most a start's few.\n return job.kind === 'site'\n ? aiSitePlanDatasetCapability(capabilities, { firestore, org, now: new Date(), job })\n : capabilities\n}\n\nexport interface AiJobPlanStepDeps {\n /** The inventory reader; specs hand in a fake. */\n readInventory?: typeof readSiteInventory\n /** The reuse lookup; specs hand in a fake, and `null` turns reuse off. */\n findPlansByKey?: AiJobPlanFinder | null\n /** What the job may create on its site (AGL-3030); specs and the eval recorder hand in their own. */\n readCapabilities?: AiPlanCapabilitiesReader\n /** The kind's admission, asked of a plan before it is kept; the registry's otherwise. */\n admissionRefusal?: typeof aiJobAdmissionRefusal\n /** A build's operations on its site (AGL-3616); the registry and its gates otherwise. */\n readOps?: AiBuildOpsReader\n /**\n * The site's business profile, status and remembered preferences\n * (AGL-3661); specs and the eval recorder hand in their own, and `null`\n * turns it off.\n */\n readSiteContext?: AiSiteContextReader | null\n /**\n * What a Free workspace has left (AGL-3722), read when a build's plan is\n * kept so its card can say whether it fits; `null` turns it off.\n */\n readFreeCredits?: typeof readFreeAiCreditsLeft | null\n /**\n * What an event-led site start may plan of events (AGL-3755); specs hand in\n * their own, and `null` turns it off.\n */\n readSiteEvents?: typeof readAiSiteEventPlanning | null\n}\n\n/** What a job's site context is read with (AGL-3661). */\nexport type AiSiteContextReader = (input: {\n job: AiJob\n firestore: FirebaseFirestore.Firestore\n}) => Promise<AiSiteContextInput | null>\n\n/** The reader the step uses in production: fail-soft, org-scoped, `null` for a job with no site. */\nexport const readAiJobSiteContext: AiSiteContextReader = ({ job, firestore }) =>\n readAiSiteContext(firestore, { orgId: job.orgId, hostId: job.hostId })\n\n/** What a build may plan on its site (AGL-3616). */\nexport type AiBuildOpsReader = (input: {\n job: AiJob\n org: Partial<AglynOrgBilling> | null\n firestore: FirebaseFirestore.Firestore\n freeTaste: boolean\n}) => Promise<AiBuildOps>\n\n/** The reader the step uses in production: the registry, gated for the job's site. */\nexport const readAiBuildOps: AiBuildOpsReader = async ({ job, org, firestore, freeTaste }) => {\n const host = job.hostId\n ? (((await firestore.collection('hosts').doc(job.hostId).get()).data() ?? null) as Record<string, unknown> | null)\n : null\n return aiBuildOps({ orgId: job.orgId, hostId: job.hostId ?? null, org, host, freeTaste })\n}\n\n/**\n * A build plan with the store pages it adds (AGL-3676): where it turns the\n * site into a store and the site can have one — its operations include the\n * commerce plugin's products, which they do only on a plan with commerce and\n * a site Commerce runs on — the account, cart and policy pages neither the\n * plan nor the site already has. Worked out here by code, never by the\n * model, so the plan card shows them before Confirm. Every other plan, and a\n * build that adds none, carries none.\n */\nexport function aiPlanWithStorePages<T extends AiBuildPlan>(\n kind: AiJob['kind'],\n ops: AiBuildOps | null,\n inventory: Pick<AiSiteInventory, 'screens'> | null,\n plan: T,\n): T {\n if (kind !== 'build') return plan\n // A reused plan's store pages were the other job's site's; this one's are worked out again.\n const rest: T = { ...plan }\n delete rest.storePages\n const storePages = aiBuildStorePagesFor(plan, {\n store: Boolean(ops?.has(AI_BUILD_PRODUCT_OP)),\n pages: (inventory?.screens ?? []).map((screen) => ({ id: screen.id, slug: screen.slug, title: screen.name, template: screen.template })),\n })\n return storePages ? { ...rest, storePages } : rest\n}\n\n/**\n * A build plan's items with what each can cost (AGL-3616), from its\n * capability, recorded as the plan is kept so the console's estimate and the\n * confirm door read one figure without the registry.\n */\nexport function aiPlanWithItemCredits<T extends AiBuildPlan>(ops: AiBuildOps | null, plan: T): T {\n if (!ops || !plan.items?.length) return plan\n return {\n ...plan,\n items: plan.items.map((item) => {\n const capability = ops.get(item.op)\n return capability ? { ...item, credits: Math.max(0, Math.floor(capability.estimateCredits(item.args))) } : item\n }),\n }\n}\n\nexport function createAiJobPlanStep(deps: AiJobPlanStepDeps = {}): AiJobStepRunner {\n const readInventory = deps.readInventory ?? readSiteInventory\n const findPlansByKey =\n deps.findPlansByKey === undefined ? findAiJobsByPlanKey : deps.findPlansByKey\n const readCapabilities = deps.readCapabilities ?? readAiJobPlanCapabilities\n const admissionRefusal = deps.admissionRefusal ?? aiJobAdmissionRefusal\n const readOps = deps.readOps ?? readAiBuildOps\n const readContext = deps.readSiteContext === undefined ? readAiJobSiteContext : deps.readSiteContext\n const readFreeCredits = deps.readFreeCredits === undefined ? readFreeAiCreditsLeft : deps.readFreeCredits\n const readSiteEvents = deps.readSiteEvents === undefined ? readAiSiteEventPlanning : deps.readSiteEvents\n return async ({ job, now, signal, firestore, modelFor, org: orgDocument }) => {\n const org = (orgDocument ?? null) as Partial<AglynOrgBilling> | null\n const [inventory, workspace, siteContext] = await Promise.all([\n job.hostId ? readInventory(job.orgId, job.hostId, { firestore }) : Promise.resolve(null),\n readCapabilities({ job, org, firestore }),\n job.hostId && readContext ? readContext({ job, firestore }) : Promise.resolve(null),\n ])\n // The site's profile, status and memory (AGL-3661), as a per-site cached\n // block after the plan rules. The inventory block already lists what the\n // site holds, so the context carries no index of its own here.\n const instructions = aiInstructionsWithSiteContext(AI_JOB_PLAN_INSTRUCTIONS, siteContext)\n const scope = AI_JOB_PLAN_SCOPES[job.kind] ?? null\n const capabilities = aiSitePlanCapabilities(\n job,\n workspace ? aiPlanCapabilitiesForJob(workspace, scope) : null,\n )\n const freeTaste = capabilities?.freeTaste === true\n // A build's operations, as this site has them (AGL-3616).\n const ops = job.kind === 'build' ? await readOps({ job, org, firestore, freeTaste }) : null\n const site = job.kind === 'site'\n // An event-led start's first events (AGL-3755), only where the build's\n // gate admits the events plugin's operation here and its writer this member.\n const siteEvents =\n site && readSiteEvents\n ? await readSiteEvents({ job, org, firestore, now, readOps: () => readOps({ job, org, firestore, freeTaste }) })\n : null\n const itemOps = ops ?? siteEvents?.ops ?? null\n // A kept plan waits for its confirmation; a Free build's card is told what\n // is left, so it says before Confirm whether the build fits (AGL-3722).\n // Net of what THIS plan just spent: the machine meters the step after it\n // returns, so the meters read here do not hold it yet, and a card told\n // the figure from before the plan (101) offered Confirm to a person with\n // 48 left, who met the prompt only after pressing it (2026-10-10).\n const planReview = async (spentUsd = 0): Promise<AiJobReview> => {\n const review: AiJobReview = { reason: 'plan', message: AI_JOB_PLAN_REVIEW_COPY, findings: [] }\n if (job.kind !== 'build' || !freeTaste || !readFreeCredits) return review\n const credits = await readFreeCredits(firestore, { orgId: job.orgId, org, now, pendingUsd: spentUsd })\n return credits ? { ...review, freeCredits: credits } : review\n }\n // The model switch's answer for this job (AGL-2942): the creator's pick\n // where the plan, the org restriction and the allotment allowlists allow\n // it, and Auto held to those same lists otherwise. Without a resolver the\n // doctrine asks the routing table itself. A Free site plans on its\n // provider's fast tier (AGL-3594), unless the creator picked a model.\n const model = aiSitePlanModel(job, modelFor?.('job.plan'), freeTaste)\n const route = AI_ROUTING_TABLE['job.plan']\n const prompt = [aiJobPlanPrompt(job, capabilities, inventory, ops, now), ...(siteEvents ? aiSiteEventPlanLines(siteEvents, now) : [])].join('\\n')\n\n /**\n * The confirm door's answer for this plan, asked before the plan is kept\n * (AGL-3030): the kind's own admission, handed the plan as the resume\n * door hands it. A refusal fails the job with the door's sentence; a door\n * that could not answer keeps the plan, since the resume door asks again\n * before anything is built.\n *\n * Two answers are about the month's Free credits, not the plan (AGL-3722):\n * - the 409 credits prompt KEEPS the plan. The card is told what is left\n * (`planReview`) and offers Build what fits / the home page first /\n * Upgrade, and the confirm door asks with the person's choice. Failing\n * here instead failed every Free build past its p90 before the card\n * could offer them — and paged staff with `ai.jobFailed`, as at\n * 2026-10-09 16:38Z (600 credits estimated, 164 left).\n * - the 429 \"nothing left\" still fails the job with its sentence, but as\n * the customer's limit, which raises no staff alert.\n */\n const refusalOnKeep = async (plan: AiJobPlan, outcome: AiJobStepOutcome): Promise<AiJobStepOutcome | null> => {\n let refusal: Awaited<ReturnType<typeof aiJobAdmissionRefusal>> = null\n try {\n refusal = await admissionRefusal(job.kind, {\n firestore,\n orgId: job.orgId,\n hostId: job.hostId ?? null,\n inputs: job.inputs ?? {},\n org,\n plan,\n uid: job.createdBy,\n })\n } catch (error) {\n console.error('ai plan admission failed', { orgId: job.orgId, jobId: job.$id, error })\n }\n if (!refusal || refusal.code === AI_CREDITS_CONFIRM_CODE) return null\n return {\n ...outcome,\n failure: refusal.error,\n ...(refusal.status === 429 ? { customerLimit: true } : {}),\n }\n }\n\n // Reuse before asking (AGL-2937). The key covers the whole request, so a\n // hit is a request that would have been sent the same bytes to the same\n // model; the window covers what the key cannot see. A reused plan spends\n // nothing, so the machine's spent-nothing branch releases the reservation\n // and meters no credit, and the job still stops for the member to confirm\n // — this job's plan is proposed to this member, whatever the last one did\n // with theirs.\n const resolved = model ?? aiModelForStep('job.plan')\n // A brief that asks for a video is offered the plan's list of players\n // (AGL-3433), and a job that may bind a dataset a record template on\n // each screen (AGL-3475). A site's plan says the language its brief is\n // written in, which its guided start sets the site's language from (AGL-3750).\n const tool = aiBuildPlanToolFor(job.brief, {\n records: Boolean(inventory?.datasets.length) || capabilities?.create.dataset.allowed === true,\n items: job.kind === 'build' || Boolean(siteEvents),\n language: site,\n })\n const key = aiJobPlanKey({\n job,\n prompt,\n model: resolved,\n system: aiDoctrineSystemBlocks(inventory, { instructions }),\n tool,\n })\n const reused = findPlansByKey\n ? aiReusablePlan(await findPlansByKey(job.orgId, key, firestore), {\n jobId: job.$id,\n now,\n })\n : null\n if (reused) {\n // The reused plan's draft ids name the other job's drafts; this job's are its own.\n const plan: AiJobPlan = aiPlanWithStorePages(job.kind, ops, inventory, aiPlanWithItemCredits(itemOps, aiPlanWithDraftIds(job.kind, {\n ...reused.plan,\n status: 'proposed',\n labels: aiPlanLabels(reused.plan, inventory),\n proposedAt: now as unknown as AiJobPlan['proposedAt'],\n confirmedAt: null,\n confirmedBy: null,\n key,\n reusedFrom: reused.jobId,\n })))\n const unspent = aiUnspentOutcome(resolved)\n return (\n (await refusalOnKeep(plan, unspent)) ?? {\n ...unspent,\n plan,\n review: await planReview(),\n }\n )\n }\n\n const result = await runValidatedGeneration('plan', {\n step: 'job.plan',\n ...(model ? { model } : {}),\n instructions,\n inventory,\n messages: [{ role: 'user', content: prompt }],\n tool,\n // The routing ceiling, lowered on a tier too slow to look records up,\n // answer and ask again inside the least time the step registered; a\n // scaffold's outline is held to its own, far lower ceiling (AGL-3594).\n maxTokens: aiSitePlanMaxTokens(job, resolved, freeTaste),\n // A scaffold plans an outline without thinking (AGL-3594): the thinking\n // was nearly all of the 13,491 tokens a Free site's plan spent.\n ...(site ? { thinking: 'off' as const } : route.thinking ? { thinking: route.thinking } : {}),\n ...(route.effort ? { effort: route.effort } : {}),\n ...(signal ? { signal } : {}),\n capabilities,\n // A build's events are listed on a page (AGL-3755): on the one it makes\n // about them, else on an Events page of its own where there is room.\n ...(ops\n ? {\n settle: (answered: AiBuildPlan) =>\n aiSettleBuildEventPage(answered, {\n ops,\n siteSlugs: (inventory?.screens ?? []).map((screen) => screen.slug),\n pageLimit: aiBuildPageLimit(freeTaste),\n siteLayout: aiSiteLayoutOf(inventory),\n }),\n }\n : {}),\n extend: aiSiteWithEventItemsCheck(\n planViolations(\n scope,\n job.brief,\n freeTaste,\n ops,\n site ? aiSitePlanHomeRule(inventory, capabilities) : null,\n site && aiSiteContentPart(job.inputs ?? null, freeTaste) === 'posts',\n site,\n site && aiSiteKindOfInputs(job.inputs)?.id === 'store',\n ),\n site ? siteEvents : undefined,\n ops,\n ),\n })\n const spent: AiJobStepOutcome = {\n outputs: [],\n usage: result.usage,\n estCostUsd: result.estCostUsd,\n model: result.model,\n stopReason: result.stopReason,\n ...(result.effort ? { effort: result.effort } : {}),\n }\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') {\n // Refused by our own checks, after the one re-ask (AGL-3594): the member\n // reads a sentence and an action, staff the rules; the machine gives the\n // credits back (`uncredited`), and on the Free taste Try again says when\n // it cannot work. Whether the give-back was made is the job's to say\n // (`refundedCredits`), so this sentence does not promise it.\n const review = aiDoctrineReview(result)\n const retryRefusal = freeTaste\n ? await aiFreePlanRetryRefusal({ job, org, firestore, now, site })\n : null\n return {\n ...spent,\n uncredited: true,\n review: {\n ...review,\n message: aiPlanFailureCopy({\n kind: job.kind,\n codes: result.violations.map((violation) => violation.code),\n refunded: false,\n }),\n detail: review.detail ?? review.message,\n ...(retryRefusal ? { retryRefusal } : {}),\n },\n }\n }\n // A store's plan is a storefront's (AGL-3676): settled before it is kept and priced.\n const answered = site && aiSiteKindOfInputs(job.inputs)?.id === 'store' ? aiSiteStorefrontPlan(result.value, { paid: !freeTaste }) : result.value\n // Each draft the plan decides is named as the plan is kept, before anything is built (AGL-3079).\n const plan: AiJobPlan = aiPlanWithStorePages(job.kind, ops, inventory, aiPlanWithItemCredits(itemOps, aiPlanWithDraftIds(job.kind, {\n ...answered,\n status: 'proposed',\n labels: aiPlanLabels(answered, inventory),\n // A Date the Admin SDK stores as a timestamp, like every instant the machine writes.\n proposedAt: now as unknown as AiJobPlan['proposedAt'],\n confirmedAt: null,\n confirmedBy: null,\n key,\n })))\n return (\n (await refusalOnKeep(plan, spent)) ?? {\n ...spent,\n plan,\n review: await planReview(result.estCostUsd),\n }\n )\n }\n}\n\n/**\n * A site job's capabilities (AGL-3594, AGL-3660): it changes no theme in its\n * plan — every scaffold designs its look first, in a unit of its own — and on\n * the Free taste its plan holds at most the pages the member asked for within\n * the Free band, which the Free wall then holds it to.\n *\n * A build's pages are written in the layout language (`aiBuildUnitJob`), whose\n * compiler draws a section's repeated items itself, so its plan is not asked\n * for a component for them (`repeatsCompiled`, AGL-3616). It still may plan\n * one, and a section two pages share is still one. Every other job's\n * capabilities pass through.\n */\nexport function aiSitePlanCapabilities(\n job: Pick<AiJob, 'kind' | 'inputs'>,\n capabilities: AiPlanCapabilities | null,\n): AiPlanCapabilities | null {\n if (job.kind === 'build' && capabilities) return { ...capabilities, repeatsCompiled: true }\n if (job.kind !== 'site' || !capabilities) return capabilities\n const create = {\n ...capabilities.create,\n 'theme-change': {\n allowed: false,\n left: null,\n reason: 'a site start designs its own look before its pages',\n },\n // A site start builds its look, one layout and one form, never a\n // component (`AI_SITE_CREATE_KINDS`), so a paid workspace's plan draws\n // repeated items in their sections, as a Free one's does. Held to rule 1\n // instead, every paid plan was refused: for the component it left out,\n // or, re-asked, for the one it could not build (AGL-3660).\n component: {\n allowed: false,\n left: null,\n reason: 'a site start draws its repeated items in their sections',\n },\n }\n // At most a start's few datasets, settled in code where a plan names more\n // (AGL-3616); a section that shows the work with its fewest pieces (AGL-3754).\n const site = {\n ...capabilities,\n reusableComponents: false,\n create,\n datasetsMax: AI_SITE_DATASETS_MAX,\n galleryMinItems: AI_SITE_GALLERY_MIN_ITEMS,\n }\n if (!capabilities.freeTaste) return site\n const asked = Number((job.inputs ?? {})['pages'])\n const pages =\n Number.isInteger(asked) && asked >= AI_SITE_FREE_PAGES.min\n ? Math.min(asked, AI_SITE_FREE_PAGES.max)\n : AI_SITE_FREE_PAGES.max\n return { ...site, freeSitePages: pages }\n}\n\n/**\n * The model a plan runs on (AGL-3594): the resolved one, except a Free site\n * plans on its provider's fast tier — an outline the plan rules hold and\n * re-ask, at a fifth of the balanced tier's output rate. A model the creator\n * picked by name stays theirs.\n */\nexport function aiSitePlanModel(\n job: Pick<AiJob, 'kind' | 'model'>,\n resolved: string | undefined,\n freeTaste: boolean,\n): string | undefined {\n if (job.kind !== 'site' || !freeTaste) return resolved\n const picked = typeof job.model === 'string' ? job.model.trim() : ''\n if (picked && picked !== 'auto') return resolved\n const entry = aiCatalogEntry(resolved ?? aiModelForStep('job.plan'))\n return (entry ? aiDefaultModelFor(entry.provider, 'fast') : undefined) ?? resolved\n}\n\n/** The plan's answer ceiling: the step's own, and a scaffold's outline ceiling under it (AGL-3594). */\nexport function aiSitePlanMaxTokens(\n job: Pick<AiJob, 'kind'>,\n model: string,\n freeTaste: boolean,\n): number {\n const ceiling = aiJobPlanMaxTokens(model)\n if (job.kind !== 'site') return ceiling\n return Math.min(ceiling, freeTaste ? AI_SITE_PLAN_MAX_TOKENS.free : AI_SITE_PLAN_MAX_TOKENS.paid)\n}\n\n/**\n * Why trying a Free plan again cannot work this month, or `null` (AGL-3594):\n * the owner's Free allowance left, read off the account month the meter\n * writes net of give-backs, against what a plan of this kind costs at its\n * worst. Read before this step is metered, and the machine gives a refused\n * plan's credits back, so the figure read is the figure left. A read that fails\n * leaves the button on: the reservation still refuses at the wall.\n */\nasync function aiFreePlanRetryRefusal(input: {\n job: AiJob\n org: Partial<AglynOrgBilling> | null\n firestore: FirebaseFirestore.Firestore\n now: Date\n site: boolean\n}): Promise<string | null> {\n const account = freeAssistAccount(input.org as AssistMeteredOrg | null)\n if (!account?.accountUid) return null\n try {\n // The meter's month key, `YYYY-MM` in UTC (`assistUsageMonth`).\n const month = input.now.toISOString().slice(0, 7)\n const snapshot = await freeAccountUsageRef(input.firestore, account.accountUid, month).get()\n const spent = assistCreditsFromUsd(\n assistSpendAfterReturnsUsd(snapshot.get('estCostUsd'), snapshot.get(ASSIST_RETURNED_USD_FIELD)),\n )\n return aiPlanRetryRefusal({\n creditsLeft: FREE_AI_TASTE_CREDITS_PER_MONTH - spent,\n planCredits: input.site ? AI_FREE_SITE_WORST_CASE_CREDITS.plan : AI_FREE_PAGE_WORST_CASE_CREDITS.plan,\n })\n } catch (error) {\n console.error('ai plan retry allowance read failed', { orgId: input.job.orgId, jobId: input.job.$id, error })\n return null\n }\n}\n\nexport const runAiJobPlanStep = createAiJobPlanStep()\n\n/**\n * Registers the plan step every planned kind runs first, with the least time\n * a plan needs; the plugin's console surface calls it.\n */\nexport function registerAiJobPlan(): void {\n registerAiJobPlanStep(runAiJobPlanStep, { minimumMs: AI_JOB_PLAN_STEP_MINIMUM_MS })\n}\n"],"names":["aiSiteKindOfInputs","createHash","AI_BUILD_PLAN_TOOL","aiBuildPlanToolFor","aiPlanSlugKey","isAiPlanNewRef","AI_BUILD_PRODUCT_OP","aiBuildStorePagesFor","readFreeAiCreditsLeft","AI_TEMPLATE_SUBJECT_DEFINITIONS","aiTemplateShownTokens","AI_JOB_CREATE_KINDS","AI_JOB_CREATE_NOUNS","AI_PAGE_CREATE_KINDS","aiPagePlanShapeRefusal","AI_PLAN_COMPILED_REPEATS_SENTENCE","AI_PLAN_NO_FORM_SENTENCE","aiPlanCanPlaceForm","aiPlanCapabilitiesForJob","aiPlanCapabilityLines","aiSitePlanDatasetCapability","AI_FREE_SITE_WORST_CASE_CREDITS","AI_SITE_CREATE_KINDS","AI_SITE_FREE_PAGES","AI_SITE_PLAN_MAX_TOKENS","AI_SITE_HOME_BANDS","aiFreeSiteSectionsWithin","aiSiteFullPlanSentence","aiSiteHomeMinSections","aiSiteNameSentence","aiSitePlanIsHome","aiSitePlanShapeRefusal","aiSiteBlogStandInViolations","aiSiteThinHomeViolations","AI_SITE_GALLERY_SENTENCE","aiSiteEmptyGalleryViolations","AI_SITE_DATASETS_MAX","AI_SITE_GALLERY_MIN_ITEMS","aiSiteContentPart","aiLayoutIsShopPage","aiLayoutShopGridSection","aiStorefrontPlanScreens","AI_GENERATION_MAX_ATTEMPTS","aiPlanFailureCopy","aiPlanRetryRefusal","FREE_AI_TASTE_CREDITS_PER_MONTH","aiDoctrineSystemBlocks","runValidatedGeneration","AI_STEP_TIERS","aiCatalogEntry","aiDefaultModelFor","AI_FREE_PAGE_WORST_CASE_CREDITS","aiPlanEmbedBriefViolations","assistCreditsFromUsd","ASSIST_RETURNED_USD_FIELD","assistSpendAfterReturnsUsd","freeAccountUsageRef","freeAssistAccount","AI_ROUTING_TABLE","aiModelForStep","readSiteInventory","readAiSiteContext","aiInstructionsWithSiteContext","AI_BUILD_CREATE_KINDS","AI_BUILD_STRUCTURAL_OPS","aiBuildPageLimit","aiBuildPlanShapeRefusal","aiBuildOpLines","aiBuildOps","aiBuildUnlistedEventsCheck","aiSettleBuildEventPage","aiSiteEventItemsCheck","aiSiteEventPlanLines","readAiSiteEventPlanning","aiJobAdmissionRefusal","AI_CREDITS_CONFIRM_CODE","readAiPlanCapabilities","AI_JOB_BRIEF_MAX_CHARS","aiJobStepBudget","aiDoctrineReview","aiUnspentOutcome","aiPlanWithDraftIds","AI_JOBS_COLLECTION","registerAiJobPlanStep","AI_JOB_PLAN_INSTRUCTIONS","text","AI_JOB_PLAN_STEP_BUDGET","tier","maxTokens","AI_JOB_PLAN_STEP_MINIMUM_MS","minimumMs","aiJobPlanMaxTokens","model","AI_JOB_PLAN_REVIEW_COPY","aiJobPlanPrompt","job","capabilities","inventory","ops","now","lines","kind","brief","slice","key","value","Object","entries","inputs","push","String","aiPlanTemplateTokenLines","aiPlanSiteLines","aiPlanBuildLines","join","pages","freeTaste","repeatsCompiled","aiPlanTodaySentence","toISOString","name","trim","screens","filter","screen","template","starter","find","replaceable","length","page","slug","some","cap","freeSitePages","home","aiSitePlanHomeRule","undefined","across","plansHome","min","bands","id","AI_SITE_STORE_HOME_BANDS","AI_SITE_STORE_PAGES_SENTENCE","AI_SITE_FORM_SENTENCE","create","dataset","allowed","aiSiteDatasetSentence","Math","datasetsMax","left","label","toLowerCase","AI_SITE_STORE_SHOP_SENTENCE","max","AI_SITE_FORM_DATASET_SENTENCE","layouts","forms","aiSiteThinHomeCheck","rule","answers","plan","aiSiteStorefrontPlan","options","paid","isShopPage","gridSection","every","index","AI_SITE_STORE_SHOP_CODE","aiSiteStoreShopPageViolations","code","message","paths","aiSiteStoreShopCheck","aiSiteEmptyGalleryCheck","aiSiteBlogStandInCheck","subject","definition","AI_JOB_PLAN_SCOPES","noun","creates","shapeRefusal","build","site","fromEntries","map","planViolations","scope","blog","store","thinHome","storeShop","blogStandIn","emptyGallery","aiSiteLayoutOf","layoutId","aiSiteWithEventItemsCheck","violations","siteEvents","unlisted","events","aiPlanLabels","names","Map","rows","components","templates","datasets","collections","row","set","refs","reuse","entry","duplicateOf","flatMap","layout","sections","section","uses","labels","ref","get","AI_PLAN_REUSE_WINDOW_MS","AI_PLAN_REUSE_CANDIDATES","aiJobPlanKey","input","digest","part","hostId","prompt","system","block","JSON","stringify","tool","update","UNREUSABLE","planMillis","at","proposedAt","Date","getTime","toMillis","aiReusablePlan","candidates","context","floor","best","bestAt","candidate","jobId","includes","status","findAiJobsByPlanKey","orgId","firestore","snapshot","collection","doc","where","limit","docs","data","readAiJobPlanCapabilities","org","readAiJobSiteContext","readAiBuildOps","host","aiPlanWithStorePages","rest","storePages","Boolean","has","title","aiPlanWithItemCredits","items","item","capability","op","credits","estimateCredits","args","createAiJobPlanStep","deps","readInventory","findPlansByKey","readCapabilities","admissionRefusal","readOps","readContext","readSiteContext","readFreeCredits","readSiteEvents","signal","modelFor","orgDocument","workspace","siteContext","Promise","all","resolve","instructions","aiSitePlanCapabilities","itemOps","planReview","spentUsd","review","reason","findings","pendingUsd","freeCredits","aiSitePlanModel","route","refusalOnKeep","outcome","refusal","uid","createdBy","error","console","$id","failure","customerLimit","resolved","records","language","reused","confirmedAt","confirmedBy","reusedFrom","unspent","result","step","messages","role","content","aiSitePlanMaxTokens","thinking","effort","settle","answered","siteSlugs","pageLimit","siteLayout","extend","spent","outputs","usage","estCostUsd","stopReason","refused","retryRefusal","aiFreePlanRetryRefusal","uncredited","codes","violation","refunded","detail","component","reusableComponents","galleryMinItems","asked","Number","isInteger","picked","provider","ceiling","free","account","accountUid","month","creditsLeft","planCredits","runAiJobPlanStep","registerAiJobPlan"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-plan-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 { aiSiteKindOfInputs } from '../model/ai-site-kinds'\nimport { createHash } from 'node:crypto'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport {\n AI_BUILD_PLAN_TOOL,\n aiBuildPlanToolFor,\n aiPlanSlugKey,\n isAiPlanNewRef,\n type AiBuildPlan,\n} from '../model/ai-build-plan'\nimport type { AiJob, AiJobKind, AiJobPlan, AiJobReview, AiJobStatus } from '../model/ai-jobs.types'\nimport { AI_BUILD_PRODUCT_OP, aiBuildStorePagesFor } from '../model/ai-build-store-pages'\nimport { readFreeAiCreditsLeft } from '../usage/free-ai-credits-left'\nimport {\n AI_TEMPLATE_SUBJECT_DEFINITIONS,\n aiTemplateShownTokens,\n type AiTemplateSubject,\n} from '../model/ai-template-subjects'\nimport { AI_JOB_CREATE_KINDS, AI_JOB_CREATE_NOUNS } from '../model/ai-job-creations'\nimport { AI_PAGE_CREATE_KINDS, aiPagePlanShapeRefusal } from '../model/ai-page-job'\nimport {\n AI_PLAN_COMPILED_REPEATS_SENTENCE,\n AI_PLAN_NO_FORM_SENTENCE,\n aiPlanCanPlaceForm,\n aiPlanCapabilitiesForJob,\n aiPlanCapabilityLines,\n type AiPlanCapabilities,\n type AiPlanJobScope,\n} from '../model/ai-plan-capabilities'\nimport type { AiSiteInventory } from '../model/ai-site-inventory'\nimport { aiSitePlanDatasetCapability } from './ai-job-site-datasets'\nimport {\n AI_FREE_SITE_WORST_CASE_CREDITS,\n AI_SITE_CREATE_KINDS,\n AI_SITE_FREE_PAGES,\n AI_SITE_PLAN_MAX_TOKENS,\n AI_SITE_HOME_BANDS,\n aiFreeSiteSectionsWithin,\n aiSiteFullPlanSentence,\n aiSiteHomeMinSections,\n aiSiteNameSentence,\n aiSitePlanIsHome,\n aiSitePlanShapeRefusal,\n aiSiteBlogStandInViolations,\n aiSiteThinHomeViolations,\n AI_SITE_GALLERY_SENTENCE,\n aiSiteEmptyGalleryViolations,\n AI_SITE_DATASETS_MAX,\n AI_SITE_GALLERY_MIN_ITEMS,\n} from '../model/ai-site-job'\nimport { aiSiteContentPart } from './ai-job-site-content'\nimport { aiLayoutIsShopPage, aiLayoutShopGridSection } from '../layout-language/ai-layout-listings'\nimport { aiLayoutSpeaksOtherThanEnglish } from '../layout-language/ai-layout-labels'\nimport { aiStorefrontPlanScreens } from '../layout-language/ai-layout-storefront'\nimport { AI_GENERATION_MAX_ATTEMPTS } from '../runtime/ai-generation-bounds'\nimport { aiPlanFailureCopy, aiPlanRetryRefusal } from '../model/ai-job-failure-copy'\nimport { FREE_AI_TASTE_CREDITS_PER_MONTH } from '../plan-entitlements'\nimport { aiDoctrineSystemBlocks, runValidatedGeneration } from '../runtime/ai-doctrine'\nimport { AI_STEP_TIERS, aiCatalogEntry, aiDefaultModelFor } from '../providers/catalog'\nimport {\n AI_FREE_PAGE_WORST_CASE_CREDITS,\n aiPlanEmbedBriefViolations,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport { assistCreditsFromUsd } from '../usage/assist-credits'\nimport { ASSIST_RETURNED_USD_FIELD, assistSpendAfterReturnsUsd } from '../usage/assist-credit-returns'\nimport { freeAccountUsageRef, freeAssistAccount, type AssistMeteredOrg } from '../usage/assist-free-taste'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport type { AiTool } from '../providers/contract'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { readSiteInventory } from '../runtime/site-inventory'\nimport { readAiSiteContext } from '../runtime/site-context'\nimport { aiInstructionsWithSiteContext, type AiSiteContextInput } from '../model/ai-site-context'\nimport {\n AI_BUILD_CREATE_KINDS,\n AI_BUILD_STRUCTURAL_OPS,\n aiBuildPageLimit,\n aiBuildPlanShapeRefusal,\n type AiBuildOps,\n} from '../model/ai-build-job'\nimport { aiBuildOpLines, aiBuildOps } from './ai-build-capabilities'\nimport { aiBuildUnlistedEventsCheck, aiSettleBuildEventPage } from '../model/ai-build-event-page'\nimport {\n aiSiteEventItemsCheck,\n aiSiteEventPlanLines,\n readAiSiteEventPlanning,\n type AiSiteEventPlanning,\n} from './ai-job-site-events'\nimport { aiJobAdmissionRefusal } from './ai-job-admission'\nimport { AI_CREDITS_CONFIRM_CODE } from '../model/ai-credit-estimate'\nimport { readAiPlanCapabilities } from './ai-job-drafts'\nimport {\n AI_JOB_BRIEF_MAX_CHARS,\n type AiJobStepOutcome,\n type AiJobStepRunner,\n} from './ai-job-text-step'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiDoctrineReview, aiUnspentOutcome } from './ai-job-generation'\nimport { aiPlanWithDraftIds } from './ai-job-draft-ids'\nimport { AI_JOBS_COLLECTION, registerAiJobPlanStep } from './ai-jobs'\n\n/**\n * The plan step (AGL-2935): the first step of every job that builds site\n * structure. Before a node is generated, the model answers with a typed plan\n * — what the site already has that the job reuses, what it creates and why,\n * and the screens it builds from them — held to the doctrine's plan rules\n * against the site's inventory and re-asked once when it breaks one.\n *\n * A plan that passes is kept on the job, and the job stops for review: the\n * member reads what it will build before a credit is spent building it, and\n * confirms through the resume door. A plan that still breaks a rule on its\n * re-ask stops the job the same way, with the rules named. Either way the\n * step writes nothing itself; the machine records the plan, the spend and\n * the stop, as it records every step.\n *\n * ── Told what it may create, refused what it cannot build (AGL-3030) ─────\n *\n * Before it asks, the step reads what the job may create on its site: the\n * workspace's plan, the site's counts against its limits, and — for a kind\n * that builds only some plans — what the job builds itself. The user turn\n * states it, the plan rules refuse a creation outside it with the one re-ask,\n * and where the workspace keeps no reusable components they accept the page\n * built inline. A plan the confirm door would still refuse — the kind's own\n * admission, handed the plan, as the resume door hands it — is refused HERE,\n * before a member is shown a Confirm: the job fails with the door's sentence,\n * its plan is not kept, and it holds nothing.\n */\n\n/** The plan step's own instructions, cached after the doctrine. */\nexport const AI_JOB_PLAN_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text:\n 'You plan what a website builder job will build, before anything is built. You are given the kind of job and the brief from the person who owns the site. ' +\n 'Answer with submit_build_plan: what the site inventory already has that the job reuses, what it must create and why nothing listed will do, and each page it builds (the screens list) with its layout, template, slug, search title, search description and sections from top to bottom. ' +\n 'Refer to inventory records by id and to what the plan creates as new:<name>. Plan only what the brief asks for: a component, layout, template, form or email job plans no pages unless the brief asks for them, and a page job plans exactly one page.',\n },\n]\n\n/**\n * The plan step's time (AGL-3026, AGL-3036): its two inventory-lookup rounds,\n * its answer and its re-ask at the routing table's ceiling for `job.plan`, on\n * the tier that step kind is served from, with the step's reads and writes,\n * at the rates `ai-job-budget.ts` assumes — fitted to what a beat can give a\n * step, so the served tier asks a little under the routing ceiling.\n */\nexport const AI_JOB_PLAN_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.plan'],\n maxTokens: AI_ROUTING_TABLE['job.plan'].maxTokens,\n})\n\n/**\n * The least time a plan needs before it starts. Registered with the step, so\n * an inline door never starts a plan. A plan thinks before it answers and\n * routinely runs past an inline door's budget, and a provider call that\n * budget cuts off is still generated and billed upstream while the meter\n * records nothing. The beat starts a plan only with this much of its own\n * budget left, and a spec holds it inside that budget.\n */\nexport const AI_JOB_PLAN_STEP_MINIMUM_MS = AI_JOB_PLAN_STEP_BUDGET.minimumMs\n\n/**\n * The plan's answer ceiling on the model a job runs: the most whose worst\n * case fits the least time the step registered, and never more than the\n * routing table's. A model the catalog does not know is planned at the\n * slowest.\n */\nexport function aiJobPlanMaxTokens(model: string): number {\n return AI_JOB_PLAN_STEP_BUDGET.maxTokens(model)\n}\n\n/** What the member reads while a plan waits for them. */\nexport const AI_JOB_PLAN_REVIEW_COPY =\n 'The plan is ready. Review what the job will reuse and create, then confirm it to build.'\n\n/**\n * The job as the plan step's user turn: its kind, its brief, its scalar\n * inputs and, where the job read them, what it may create on its site\n * (AGL-3030). Per workspace and per site, which is why they ride here and\n * never in a system block the platform's cache entry is keyed on.\n */\nexport function aiJobPlanPrompt(\n job: Pick<AiJob, 'kind' | 'brief' | 'inputs'>,\n capabilities: AiPlanCapabilities | null = null,\n inventory: AiSiteInventory | null = null,\n ops: AiBuildOps | null = null,\n now: Date | null = null,\n): string {\n const lines = [`Job kind: ${job.kind}`, `Brief: ${job.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)}`]\n for (const [key, value] of Object.entries(job.inputs ?? {})) {\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n lines.push(`${key}: ${String(value)}`)\n }\n }\n if (capabilities) lines.push(...aiPlanCapabilityLines(capabilities))\n // A form is a saved form or none (AGL-3596): said where the site read has\n // none to place and the job may create none.\n if (capabilities && inventory && !aiPlanCanPlaceForm(inventory, capabilities)) {\n lines.push(AI_PLAN_NO_FORM_SENTENCE)\n }\n lines.push(...aiPlanTemplateTokenLines(job))\n lines.push(...aiPlanSiteLines(job, inventory, capabilities))\n lines.push(...aiPlanBuildLines(job, capabilities, ops, now))\n return lines.join('\\n')\n}\n\n/**\n * What a BUILD job's plan is told (AGL-3616): that one request becomes pages,\n * creations and items, how many pages it may hold, and the item operations\n * this site has with their arguments. On the job's own turn, never a cached\n * system block, because the operations are per site.\n */\nexport function aiPlanBuildLines(\n job: Pick<AiJob, 'kind'>,\n capabilities: AiPlanCapabilities | null,\n ops: AiBuildOps | null,\n now: Date | null = null,\n): string[] {\n if (job.kind !== 'build') return []\n const pages = aiBuildPageLimit(capabilities?.freeTaste === true)\n return [\n `This is a build from one request: plan each page it asks for in screens (at most ${pages}), each new layout, form, component or email design in create, and anything else in items. Plan only what the request asks for; reuse what the site has.`,\n 'A page section that shows a form or an item places it: put its new:<name> in that section\\'s uses. An item built after another lists it in dependsOn.',\n // The layout language draws a section's repeated items (AGL-3660), so the\n // doctrine's \"repeated items place a component\" is not asked of a build.\n ...(capabilities?.repeatsCompiled ? [AI_PLAN_COMPILED_REPEATS_SENTENCE] : []),\n ...(ops ? aiBuildOpLines(ops, AI_BUILD_STRUCTURAL_OPS) : []),\n // An item with a date (an event, AGL-3743) is dated after the build.\n ...(now ? [aiPlanTodaySentence(now)] : []),\n ]\n}\n\n/** The build's date, so a dated item falls after it (AGL-3743). */\nexport function aiPlanTodaySentence(now: Date): string {\n return `Today is ${now.toISOString().slice(0, 10)} (UTC). Anything an item dates, such as an event, falls after today.`\n}\n\n/**\n * What a SITE job's plan is told beside the inventory (AGL-3594): the pages\n * the site already has and that their addresses are taken, which one — the\n * untouched starter home — a planned home page at `/` replaces, the Free page\n * cap and its guidance, and that a plan is an outline.\n *\n * A site job only. A new site is born with a published home page at `/`\n * (AGL-3497), and a plan that did not know it could replace it planned its own\n * home there and was refused for the clash on every new site. Kept off a page\n * job's turn, whose length the Free page's wall is proven at.\n */\nexport function aiPlanSiteLines(\n job: Pick<AiJob, 'kind'> & Partial<Pick<AiJob, 'inputs'>>,\n inventory: AiSiteInventory | null,\n capabilities: AiPlanCapabilities | null,\n): string[] {\n if (job.kind !== 'site') return []\n const lines: string[] = []\n // The business's own name, as the name to use (AGL-3596): the plan's search\n // titles carry it, and an input line alone left the model free to coin one.\n const name = job.inputs?.['businessName']\n if (typeof name === 'string' && name.trim()) lines.push(aiSiteNameSentence(name.trim()))\n const pages = (inventory?.screens ?? []).filter((screen) => !screen.template)\n const starter = pages.find((screen) => screen.replaceable)\n if (pages.length) {\n lines.push('Pages the site already has; their addresses are taken unless a line says otherwise:')\n for (const page of pages) {\n lines.push(\n page.replaceable\n ? `- ${page.name} at ${page.slug}: the starter home page the site was created with. Plan this site's home page at / and it replaces this one.`\n : `- ${page.name} at ${page.slug}`,\n )\n }\n if (!starter && pages.some((page) => page.slug.trim() === '/' || page.slug.trim() === '')) {\n lines.push(\"The home page at / is the owner's own and stays: give every page you plan another address.\")\n }\n }\n const cap = capabilities?.freeSitePages\n const home = aiSitePlanHomeRule(inventory, capabilities)\n if (cap !== undefined) {\n lines.push(\n `This is a Free workspace: plan at most ${cap} ${cap === 1 ? 'page' : 'pages'} — the home page at / and the one page the brief most needs, such as services, booking or contact — and ${home.across} sections across them.${aiPlanCanPlaceForm(inventory, capabilities) ? ' Put the contact form on one of them.' : ''}`,\n )\n }\n // A new site is a full website (AGL-3660): its home is planned as one, not\n // left to a ceiling the outline is free to stay far under.\n const plansHome = !pages.some((page) => !page.replaceable && aiSitePlanIsHome(page))\n if (home.min > 0 && plansHome) {\n // A store's home is a storefront (AGL-3676): its own parts, in its own order.\n const bands = aiSiteKindOfInputs(job.inputs)?.id === 'store' ? AI_SITE_STORE_HOME_BANDS : AI_SITE_HOME_BANDS\n lines.push(`The home page at / reads as a full website: at least ${home.min} sections — ${bands}.`)\n }\n // The sections are a budget to use (AGL-3660): a prod start of 2026-10-08\n // read \"at most 8\" as leave to plan a home of two.\n lines.push(aiSiteFullPlanSentence({ pages: cap ?? 1, across: home.across, min: plansHome ? home.min : 0 }))\n // A paid blog's first posts are written at /blog and linked from the\n // header (AGL-3660, AGL-3676), so the plan plans no page standing in for it.\n if (aiSiteContentPart(job.inputs ?? null, capabilities?.freeTaste === true) === 'posts') {\n lines.push(\n \"This site's blog is written for it with its first posts at /blog, and the header links it. Plan no page that stands in for it (no Blog, Articles, Journal, Posts or Stories page); feature the posts in a section of the home page instead.\",\n )\n }\n // A paid store's account, cart and policy pages are added by the platform,\n // outside the plan's pages (AGL-3676), so the plan spends none of its pages on them.\n if (aiSiteContentPart(job.inputs ?? null, capabilities?.freeTaste === true) === 'products') {\n lines.push(AI_SITE_STORE_PAGES_SENTENCE)\n }\n // A section that shows the work counts its pieces (AGL-3660): a planned\n // gallery of no items let a page about the work show none.\n lines.push(AI_SITE_GALLERY_SENTENCE)\n // A section that asks for something places the form (AGL-3660): a live\n // portfolio start planned a commissions inquiry beside the form it made\n // for its Contact page, without placing it.\n if (aiPlanCanPlaceForm(inventory, capabilities)) lines.push(AI_SITE_FORM_SENTENCE)\n // Structured content is kept as a dataset where one may be made (AGL-3616).\n if (capabilities?.create.dataset.allowed) {\n lines.push(aiSiteDatasetSentence(Math.min(capabilities.datasetsMax ?? AI_SITE_DATASETS_MAX, capabilities.create.dataset.left ?? AI_SITE_DATASETS_MAX)))\n }\n // The kind of site the person picked (AGL-3660): the pages it usually has.\n const kind = aiSiteKindOfInputs(job.inputs)\n if (kind) lines.push(`This is a ${kind.label.toLowerCase()} site. ${kind.pages}`)\n // A store's Shop page IS its storefront (AGL-3676): the platform lists the\n // real products there, so the plan names the section and draws no range.\n if (kind?.id === 'store') lines.push(AI_SITE_STORE_SHOP_SENTENCE)\n lines.push(\n \"Keep the plan an outline: each page's title, address, a short search title and description, and its sections named in a few words. The build writes the copy.\",\n )\n return lines\n}\n\n/**\n * The sentence a site plan's turn states about a section that collects\n * answers (AGL-3660): it places the saved form by reference, and one form\n * may be placed on more than one page.\n */\nexport const AI_SITE_FORM_SENTENCE =\n \"A section that asks visitors for something (contact, inquiry, commissions, booking, sign-up) places the form: put new:<the form's name> or the site's form id in its uses. One form may be placed on several pages.\"\n\n/**\n * The sentence a site plan's turn states where it may create datasets, with\n * how many it may (the live eval of 2026-10-10 planned four past a cap of\n * three it was never told)\n * (AGL-3616): a list of like things is kept as one, named in the sections\n * that list it, and a page of its own per record is that dataset's record\n * template. Only where datasets may be made, so a Free plan's turn, whose\n * length its wall is proven at, is unchanged.\n */\nexport function aiSiteDatasetSentence(max: number): string {\n return `Keep structured content as a dataset, at most ${max} ${max === 1 ? 'dataset' : 'datasets'}, and only for a list of 4 or more similar items: a menu, a team, services, portfolio pieces, events, questions and answers; never reviews or testimonials. Create the dataset, its fields the items' field names, and put new:<the dataset's name> in the uses of each section that lists them; the platform lists its records there. Where each item deserves a page of its own, plan one page as the dataset's record template. Anything else stays written on its page. ${AI_SITE_FORM_DATASET_SENTENCE}`\n}\n\n/**\n * What a paid site plan is told about a form that writes to a dataset\n * (AGL-3616, Zach 2026-10-10): only where the person would want each\n * submission kept as a record, the dataset created with the form's own\n * field names, and never listed, because its records are the people who\n * sent the form.\n */\nexport const AI_SITE_FORM_DATASET_SENTENCE =\n \"Where each submission of the form should also be kept as a record (volunteer sign-ups, RSVPs, inquiries), the form writes to a dataset: create the dataset, its fields the form's field names, and set the form's writesTo to new:<the dataset's name>; that dataset counts toward the same limit and no section lists it. Otherwise writesTo is null.\"\n\n\n/**\n * What a site start's home page is held to (AGL-3660): its fewest sections,\n * and on the Free taste the sections the wall fits across the pages, which\n * the home's count is shared out of. The plan's turn and its re-ask read this\n * one figure, so the sentence the model is told is the check it is held to.\n */\nexport function aiSitePlanHomeRule(\n inventory: AiSiteInventory | null,\n capabilities: AiPlanCapabilities | null,\n): { min: number; across: number | null } {\n const cap = capabilities?.freeSitePages\n if (cap === undefined) return { min: aiSiteHomeMinSections({ pages: 1, across: null }), across: null }\n // Dear: a guided start that places a form makes one (AGL-3596).\n const across = aiFreeSiteSectionsWithin(\n { layouts: (inventory?.layouts.length ?? 0) ? 0 : 1, pages: cap, forms: 1 },\n FREE_AI_TASTE_CREDITS_PER_MONTH,\n )\n return { min: aiSiteHomeMinSections({ pages: cap, across }), across }\n}\n\n/**\n * A site plan's thin home, asked about on the FIRST answer only (AGL-3660).\n * The re-ask names the count; an answer that still plans a thinner home is\n * kept, since a home with four sections is a site and a stopped start is\n * none — the same trade a language page makes with its last answer's gaps.\n */\nexport function aiSiteThinHomeCheck(\n rule: { min: number; across: number | null },\n): (plan: AiBuildPlan) => AiDoctrineViolation[] {\n let answers = 0\n return (plan) => {\n answers += 1\n if (answers >= AI_GENERATION_MAX_ATTEMPTS) return []\n return aiSiteThinHomeViolations(plan, rule)\n }\n}\n\n/** What a store's plan is told about its Shop page (AGL-3676). */\nexport const AI_SITE_STORE_SHOP_SENTENCE =\n 'Plan a Shop page at /shop: a short title section, then a section named \"Product grid\" second, where the platform lists the store\\'s real products with sort, filters, photos, prices and cart, then at most one or two short sections (care, shipping, gifting help). Never open it with a hero, and never plan the products themselves as cards of your own.'\n\n/** What a paid store's plan is told about the pages the platform adds beside it (AGL-3676). */\nexport const AI_SITE_STORE_PAGES_SENTENCE =\n \"The platform adds this store's account page (sign in, orders, saved items), its cart page, and its Shipping & returns, Privacy policy and Terms of sale pages, and links them in the header and footer; they do not count toward this plan's pages. Plan none of them: spend every page on what the brief is about.\"\n\n/** What a store's home is composed of (AGL-3676): a storefront's parts, in a storefront's order. */\nexport const AI_SITE_STORE_HOME_BANDS =\n 'a hero that sells first, then \"Bestsellers\" (the platform lists the real products), \"Shop by collection\" with 3 or 4 items, the brand story, why buy here with 3 or 4 items, \"Customer reviews\" (the platform shows the store\\'s real ones) and a \"Newsletter sign-up\" band last'\n\n/**\n * A store plan held to a storefront (AGL-3676, `ai-layout-storefront.ts`):\n * on a paid start its home gains the parts a shopper expects that the plan\n * left out — bestsellers, collections, reviews, a newsletter sign-up —\n * while the page has room; on any start its Shop page opens on its grid.\n * Settled, never re-asked: each part has one answer, and the Free wall's\n * home keeps the sections it planned.\n */\nexport function aiSiteStorefrontPlan<P extends Pick<AiBuildPlan, 'screens'>>(plan: P, options: { paid: boolean }): P {\n const screens = aiStorefrontPlanScreens(plan.screens, {\n paid: options.paid,\n isShopPage: aiLayoutIsShopPage,\n gridSection: aiLayoutShopGridSection,\n })\n return screens.every((screen, index) => screen === plan.screens[index]) ? plan : { ...plan, screens }\n}\n\n/** The code a store plan with no Shop page is re-asked under. */\nexport const AI_SITE_STORE_SHOP_CODE = 'plan-store-shop-page'\n\n/**\n * A store plan of two or more pages with no Shop page (AGL-3676): the page\n * the platform lists the catalog on is the one a shopper looks for. A\n * one-page Free taste lists its products on the home instead.\n */\nexport function aiSiteStoreShopPageViolations(\n plan: Pick<AiBuildPlan, 'screens'>,\n): Array<{ rule: null; code: string; message: string; paths: string[] }> {\n if (plan.screens.length < 2 || plan.screens.some((screen) => aiLayoutIsShopPage(screen))) return []\n return [\n {\n rule: null,\n code: AI_SITE_STORE_SHOP_CODE,\n message:\n 'This store has no Shop page. Plan one at /shop, its second section named \"Product grid\": the platform lists the real products there.',\n paths: ['screens'],\n },\n ]\n}\n\n/** A store plan's missing Shop page, asked about on the FIRST answer only, like a thin home. */\nexport function aiSiteStoreShopCheck(): (plan: AiBuildPlan) => AiDoctrineViolation[] {\n let answers = 0\n return (plan) => {\n answers += 1\n if (answers >= AI_GENERATION_MAX_ATTEMPTS) return []\n return aiSiteStoreShopPageViolations(plan)\n }\n}\n\n/**\n * A site plan's section that shows the work without its pieces (AGL-3660),\n * asked about on the FIRST answer only, like a thin home: the re-ask names\n * the section, and an answer that keeps it thin is kept. A site start's plan\n * is given its fewest pieces in code first (`aiSettlePlanGalleries`,\n * AGL-3754), so this is the backstop for a section that settle leaves: one\n * item of a list split into sections.\n */\nexport function aiSiteEmptyGalleryCheck(): (plan: AiBuildPlan) => AiDoctrineViolation[] {\n let answers = 0\n return (plan) => {\n answers += 1\n if (answers >= AI_GENERATION_MAX_ATTEMPTS) return []\n return aiSiteEmptyGalleryViolations(plan)\n }\n}\n\n/**\n * A site plan's page standing in for the blog its start writes (AGL-3660),\n * asked about on the FIRST answer only, like a thin home: the re-ask names\n * the page, and an answer that keeps it is kept — the posts then take the\n * next address the page leaves free, and the header links them all the same.\n */\nexport function aiSiteBlogStandInCheck(): (plan: AiBuildPlan) => AiDoctrineViolation[] {\n let answers = 0\n return (plan) => {\n answers += 1\n if (answers >= AI_GENERATION_MAX_ATTEMPTS) return []\n return aiSiteBlogStandInViolations(plan)\n }\n}\n\n/** The code a site plan in another language with no English photo words is re-asked under. */\nexport const AI_SITE_PHOTO_WORDS_CODE = 'plan-photo-words'\n\n/**\n * A site plan in another language that gave no English photo words\n * (AGL-3779): its stock photos have nothing in English to be searched by, and\n * two Spanish bakeries' heroes, searched by their kind of site, took a coffee\n * mug. Missing covers null, left out, and words the plan's reader dropped as\n * not plain English (\"panadería pasteles\"). An English plan, and one whose\n * language is unknown, owes none.\n */\nexport function aiSitePhotoWordsViolations(plan: Pick<AiBuildPlan, 'language' | 'photoWords'>): AiDoctrineViolation[] {\n if (!aiLayoutSpeaksOtherThanEnglish(plan.language) || plan.photoWords) return []\n return [\n {\n rule: null,\n code: AI_SITE_PHOTO_WORDS_CODE,\n message: `This plan's language is ${plan.language}, so photoWords is required: 2-4 plain English words, the kind of business first, then what it sells (e.g. florist wedding bouquets), never a place or the business name.`,\n paths: ['photoWords'],\n },\n ]\n}\n\n/**\n * A site plan's missing English photo words, asked about on the FIRST answer\n * only, like a thin home: an answer that still gives none is kept, and its\n * photos fall back to its business type in English, else its kind of site\n * (`aiStockSourceTerms`), since a start stopped over its photo words would be\n * no site at all.\n */\nexport function aiSitePhotoWordsCheck(): (plan: AiBuildPlan) => AiDoctrineViolation[] {\n let answers = 0\n return (plan) => {\n answers += 1\n if (answers >= AI_GENERATION_MAX_ATTEMPTS) return []\n return aiSitePhotoWordsViolations(plan)\n }\n}\n\n/**\n * What a TEMPLATE job's plan is told its creation's `fields` are (AGL-3143\n * §11): the subject's binding tokens, listed so the planner promises only\n * what the page can fill and the build can be held to it\n * (`aiPlanTemplateTokenViolations`).\n *\n * ⛔ It rides the job's own turn and NOT the `fields` description in the plan\n * tool, which every kind's request caches. Written there it read better and\n * cost 40 tokens of the shared prefix — 2,980 to 3,020 — which is one credit\n * of the Free page's 300-credit wall, taking the room it keeps for a re-asked\n * section from 45 credits to 44 and breaking `ai-job-free-page.spec.ts`. A\n * page job must not pay for a sentence about templates.\n *\n * The list is the tokens a reader sees, never the whole catalog (AGL-3143\n * §16). Offered a slug and the publish timestamp, a live plan promised them,\n * and its build kept the promise the only way a slug can be kept: it printed\n * \"entry slug: {{entry.slug}}\" on every article.\n */\nexport function aiPlanTemplateTokenLines(job: Pick<AiJob, 'kind' | 'inputs'>): string[] {\n if (job.kind !== 'template') return []\n const subject = (job.inputs ?? {})['subject']\n const definition =\n typeof subject === 'string'\n ? AI_TEMPLATE_SUBJECT_DEFINITIONS[subject as AiTemplateSubject]\n : undefined\n if (!definition) return []\n return [\n `The template's fields are the binding tokens its page shows a reader, each one of: ${aiTemplateShownTokens(\n definition,\n ).join(', ')}. A link or picture token is shown by the link or picture holding it. Promise only what the page fills.`,\n ]\n}\n\n/** A kind that builds only some plans: the creations it makes, and the shapes it refuses. */\nexport interface AiJobPlanScope extends AiPlanJobScope {\n /** Why a job of the kind cannot build a plan of this shape; `null` when it can. Pure. */\n shapeRefusal: (plan: AiBuildPlan, options?: { freeTaste?: boolean }) => string | null\n}\n\n/**\n * The kinds whose confirm door builds only some plans (AGL-3030). A page job\n * builds one screen and a scaffold four to eight; each builds only the\n * creations its list names. A template, layout, component, form or email job\n * builds its own record and what that record places (AGL-3143 §15,\n * `AI_JOB_CREATE_KINDS`), and holds no plan to a shape.\n */\nexport const AI_JOB_PLAN_SCOPES: Readonly<Partial<Record<AiJobKind, AiJobPlanScope>>> = {\n page: { noun: 'a page job', creates: AI_PAGE_CREATE_KINDS, shapeRefusal: aiPagePlanShapeRefusal },\n // A build from one request (AGL-3616): its operations are judged against\n // the site's registry by the step, which reads it (`aiBuildPlanViolations`).\n build: {\n noun: 'a build',\n creates: AI_BUILD_CREATE_KINDS,\n shapeRefusal: (plan, options) => aiBuildPlanShapeRefusal(plan, { freeTaste: options?.freeTaste }),\n },\n site: { noun: 'a site scaffold', creates: AI_SITE_CREATE_KINDS, shapeRefusal: aiSitePlanShapeRefusal },\n ...Object.fromEntries(\n Object.entries(AI_JOB_CREATE_KINDS).map(([kind, creates]) => [\n kind,\n { noun: AI_JOB_CREATE_NOUNS[kind as AiJobKind] as string, creates, shapeRefusal: () => null },\n ]),\n ),\n}\n\n/**\n * What the plan step holds beyond the doctrine's plan rules, as the\n * violations the plan's one re-ask names: the players the plan lists against\n * the brief it answers (AGL-3433), which only this step reads beside the plan,\n * and a kind's shape refusal.\n */\nfunction planViolations(\n scope: AiJobPlanScope | null,\n brief: string,\n freeTaste = false,\n ops: AiBuildOps | null = null,\n home: { min: number; across: number | null } | null = null,\n blog = false,\n site = false,\n store = false,\n): (plan: AiBuildPlan) => AiDoctrineViolation[] {\n const thinHome = home && home.min > 0 ? aiSiteThinHomeCheck(home) : null\n const storeShop = store ? aiSiteStoreShopCheck() : null\n const blogStandIn = blog ? aiSiteBlogStandInCheck() : null\n const emptyGallery = site ? aiSiteEmptyGalleryCheck() : null\n const photoWords = site ? aiSitePhotoWordsCheck() : null\n return (plan) => {\n // A build's plan is held to the operations this site has (AGL-3616):\n // an unknown op, arguments its schema refuses, a cycle or a reference\n // nothing makes are re-asked once like every other plan rule.\n const message = ops\n ? aiBuildPlanShapeRefusal(plan, { freeTaste, ops })\n : (scope?.shapeRefusal(plan, { freeTaste }) ?? null)\n return [\n ...aiPlanEmbedBriefViolations(plan, brief),\n ...(message ? [{ rule: null, code: 'plan-job-shape', message }] : []),\n ...(thinHome ? thinHome(plan) : []),\n ...(blogStandIn ? blogStandIn(plan) : []),\n ...(emptyGallery ? emptyGallery(plan) : []),\n ...(storeShop ? storeShop(plan) : []),\n ...(photoWords ? photoWords(plan) : []),\n ]\n }\n}\n\n/** The layout a site's pages render in (AGL-3755): its home page's, else its only layout, else none. */\nfunction aiSiteLayoutOf(inventory: AiSiteInventory | null): string | null {\n const home = inventory?.screens.find((screen) => !screen.template && aiPlanSlugKey(screen.slug) === '/')\n if (home?.layoutId) return home.layoutId\n return inventory?.layouts.length === 1 ? (inventory.layouts[0]?.id ?? null) : null\n}\n\n/**\n * A site plan's violations with its events' (AGL-3755): its items are only\n * the events it may plan. Every other kind's are as they are.\n */\nfunction aiSiteWithEventItemsCheck(\n violations: (plan: AiBuildPlan) => AiDoctrineViolation[],\n siteEvents: AiSiteEventPlanning | null | undefined,\n ops: AiBuildOps | null = null,\n): (plan: AiBuildPlan) => AiDoctrineViolation[] {\n // A build's events left on no page are re-asked once (AGL-3755).\n const unlisted = ops ? aiBuildUnlistedEventsCheck(ops) : null\n const events = siteEvents === undefined ? null : aiSiteEventItemsCheck(siteEvents)\n if (!unlisted && !events) return violations\n return (plan) => [...violations(plan), ...(events ? events(plan) : []), ...(unlisted ? unlisted(plan) : [])]\n}\n\n/**\n * The inventory names of every id the plan references, so the proposal reads\n * \"the Services layout\" rather than an id — kept on the plan because the\n * inventory is not read again when the member opens it.\n */\nexport function aiPlanLabels(\n plan: AiBuildPlan,\n inventory: AiSiteInventory | null,\n): Record<string, string> {\n const names = new Map<string, string>()\n for (const rows of [\n inventory?.components,\n inventory?.layouts,\n inventory?.templates,\n inventory?.forms,\n inventory?.datasets,\n inventory?.collections,\n inventory?.screens,\n ]) {\n for (const row of rows ?? []) names.set(row.id, row.name)\n }\n const refs = [\n ...plan.reuse.map((entry) => entry.id),\n ...plan.create.map((entry) => entry.duplicateOf),\n ...plan.screens.flatMap((screen) => [\n screen.layout,\n screen.template,\n screen.duplicateOf,\n ...screen.sections.flatMap((section) => section.uses),\n ]),\n ]\n const labels: Record<string, string> = {}\n for (const ref of refs) {\n if (!ref || isAiPlanNewRef(ref)) continue\n const name = names.get(ref)\n if (name) labels[ref] = name\n }\n return labels\n}\n\n/* ------------------------------------------------------------------------ *\n * Reusing an identical brief's plan\n * ------------------------------------------------------------------------ */\n\n/**\n * How long a plan may be reused for (AGL-2937).\n *\n * The window is what makes reuse safe rather than merely cheap. The key\n * already covers everything the request said — the brief, the inputs, the\n * model and the prompt as rendered, site inventory included — so a plan is\n * reused only when nothing the model was shown has changed. What the key\n * cannot see is the world outside the request: a page published, a component\n * renamed after the inventory was read, a member who meant something\n * different the second time. Fifteen minutes is short enough that the answer\n * is still the one the site would give, and long enough to cover what reuse\n * is actually for — the same brief run twice in a sitting, a job resubmitted\n * after a refused reservation, two members starting the same work.\n */\nexport const AI_PLAN_REUSE_WINDOW_MS = 15 * 60 * 1_000\n\n/** Jobs one lookup reads. More than one can share a key; the newest plan wins. */\nexport const AI_PLAN_REUSE_CANDIDATES = 5\n\n/** A job whose plan might be reused, as the finder reports it. */\nexport interface AiJobPlanCandidate {\n jobId: string\n status: AiJobStatus\n plan: AiJobPlan\n}\n\n/**\n * The digest a plan is reused by: the whole request, never the answer.\n *\n * It hashes what the model was asked and what it was shown — the job's kind\n * and site, the user turn (which carries the trimmed brief and every scalar\n * input), the model that would answer, every rendered system block including\n * the site inventory, and the tool's schema. Two requests that hash the same\n * would have been sent the same bytes to the same model, so the second can\n * keep the first's answer.\n *\n * The version tag is the escape hatch: anything that changes what a key\n * MEANS, rather than what it covers, bumps it and strands every old key\n * harmlessly, since a key nothing matches simply asks the model.\n */\nexport function aiJobPlanKey(input: {\n job: Pick<AiJob, 'kind' | 'hostId'>\n prompt: string\n model: string\n system: readonly AiSystemBlock[]\n /** The plan tool the request offers (AGL-3433); the plain one when absent. */\n tool?: AiTool\n}): string {\n const digest = createHash('sha256')\n for (const part of [\n 'plan.v1',\n input.job.kind,\n input.job.hostId ?? '',\n input.model,\n input.prompt,\n ...input.system.map((block) => block.text),\n JSON.stringify(input.tool ?? AI_BUILD_PLAN_TOOL),\n ]) {\n // Length-prefixed, so two different splits of the same characters cannot\n // collide by running into one another.\n digest.update(`${part.length}:${part}\\u0000`)\n }\n return digest.digest('hex')\n}\n\n/** A job that could not have produced a reusable plan, whatever its key says. */\nconst UNREUSABLE: readonly AiJobStatus[] = ['canceled', 'failed']\n\nfunction planMillis(plan: AiJobPlan): number | null {\n const at = plan.proposedAt as unknown\n if (at instanceof Date) return at.getTime()\n if (at && typeof (at as { toMillis?: unknown }).toMillis === 'function') {\n return (at as { toMillis(): number }).toMillis()\n }\n return null\n}\n\n/**\n * The newest plan worth reusing out of what the finder returned: another\n * job's, still proposed or already confirmed, on a job that was neither\n * canceled nor failed, proposed inside the window.\n *\n * A canceled or failed job is excluded even though its plan may be perfectly\n * good: those are the jobs a member walked away from, and handing their plan\n * to the next one would make a rejected answer look like a fresh one.\n */\nexport function aiReusablePlan(\n candidates: readonly AiJobPlanCandidate[],\n context: { jobId: string; now: Date },\n): AiJobPlanCandidate | null {\n const floor = context.now.getTime() - AI_PLAN_REUSE_WINDOW_MS\n let best: AiJobPlanCandidate | null = null\n let bestAt = -1\n for (const candidate of candidates) {\n if (candidate.jobId === context.jobId) continue\n if (UNREUSABLE.includes(candidate.status)) continue\n if (candidate.plan.status !== 'proposed' && candidate.plan.status !== 'confirmed') continue\n const at = planMillis(candidate.plan)\n if (at === null || at < floor || at > context.now.getTime()) continue\n if (at > bestAt) {\n best = candidate\n bestAt = at\n }\n }\n return best\n}\n\nexport type AiJobPlanFinder = (\n orgId: string,\n key: string,\n firestore?: FirebaseFirestore.Firestore,\n) => Promise<AiJobPlanCandidate[]>\n\n/**\n * The finder the step uses in production: an equality on `plan.key` inside\n * the org's own jobs.\n *\n * One equality on one field, with no ordering beside it, so Firestore's\n * automatic single-field index answers it and no composite index is deployed.\n * The window and the statuses are applied in memory instead, which is what\n * keeps it that way.\n */\nexport const findAiJobsByPlanKey: AiJobPlanFinder = async (orgId, key, firestore) => {\n if (!firestore) return []\n const snapshot = await firestore\n .collection('orgs')\n .doc(orgId)\n .collection(AI_JOBS_COLLECTION)\n .where('plan.key', '==', key)\n .limit(AI_PLAN_REUSE_CANDIDATES)\n .get()\n return snapshot.docs.flatMap((doc) => {\n const data = doc.data() as { status?: AiJobStatus; plan?: AiJobPlan | null }\n return data.plan\n ? [{ jobId: doc.id, status: data.status ?? 'queued', plan: data.plan }]\n : []\n })\n}\n\n/** What a job may create on its site, read for the plan step; `null` for no site. */\nexport type AiPlanCapabilitiesReader = (input: {\n job: AiJob\n org: Partial<AglynOrgBilling> | null\n firestore: FirebaseFirestore.Firestore\n}) => Promise<AiPlanCapabilities | null>\n\n/** The reader the step uses in production: the draft bands' own arithmetic, for the job's site. */\nexport const readAiJobPlanCapabilities: AiPlanCapabilitiesReader = async ({ job, org, firestore }) => {\n if (!job.hostId) return null\n const capabilities = await readAiPlanCapabilities(firestore, { hostId: job.hostId, org })\n // A site start creates datasets only where the data plugin runs and its\n // writer admits this member now (AGL-3616), and at most a start's few.\n return job.kind === 'site'\n ? aiSitePlanDatasetCapability(capabilities, { firestore, org, now: new Date(), job })\n : capabilities\n}\n\nexport interface AiJobPlanStepDeps {\n /** The inventory reader; specs hand in a fake. */\n readInventory?: typeof readSiteInventory\n /** The reuse lookup; specs hand in a fake, and `null` turns reuse off. */\n findPlansByKey?: AiJobPlanFinder | null\n /** What the job may create on its site (AGL-3030); specs and the eval recorder hand in their own. */\n readCapabilities?: AiPlanCapabilitiesReader\n /** The kind's admission, asked of a plan before it is kept; the registry's otherwise. */\n admissionRefusal?: typeof aiJobAdmissionRefusal\n /** A build's operations on its site (AGL-3616); the registry and its gates otherwise. */\n readOps?: AiBuildOpsReader\n /**\n * The site's business profile, status and remembered preferences\n * (AGL-3661); specs and the eval recorder hand in their own, and `null`\n * turns it off.\n */\n readSiteContext?: AiSiteContextReader | null\n /**\n * What a Free workspace has left (AGL-3722), read when a build's plan is\n * kept so its card can say whether it fits; `null` turns it off.\n */\n readFreeCredits?: typeof readFreeAiCreditsLeft | null\n /**\n * What an event-led site start may plan of events (AGL-3755); specs hand in\n * their own, and `null` turns it off.\n */\n readSiteEvents?: typeof readAiSiteEventPlanning | null\n}\n\n/** What a job's site context is read with (AGL-3661). */\nexport type AiSiteContextReader = (input: {\n job: AiJob\n firestore: FirebaseFirestore.Firestore\n}) => Promise<AiSiteContextInput | null>\n\n/** The reader the step uses in production: fail-soft, org-scoped, `null` for a job with no site. */\nexport const readAiJobSiteContext: AiSiteContextReader = ({ job, firestore }) =>\n readAiSiteContext(firestore, { orgId: job.orgId, hostId: job.hostId })\n\n/** What a build may plan on its site (AGL-3616). */\nexport type AiBuildOpsReader = (input: {\n job: AiJob\n org: Partial<AglynOrgBilling> | null\n firestore: FirebaseFirestore.Firestore\n freeTaste: boolean\n}) => Promise<AiBuildOps>\n\n/** The reader the step uses in production: the registry, gated for the job's site. */\nexport const readAiBuildOps: AiBuildOpsReader = async ({ job, org, firestore, freeTaste }) => {\n const host = job.hostId\n ? (((await firestore.collection('hosts').doc(job.hostId).get()).data() ?? null) as Record<string, unknown> | null)\n : null\n return aiBuildOps({ orgId: job.orgId, hostId: job.hostId ?? null, org, host, freeTaste })\n}\n\n/**\n * A build plan with the store pages it adds (AGL-3676): where it turns the\n * site into a store and the site can have one — its operations include the\n * commerce plugin's products, which they do only on a plan with commerce and\n * a site Commerce runs on — the account, cart and policy pages neither the\n * plan nor the site already has. Worked out here by code, never by the\n * model, so the plan card shows them before Confirm. Every other plan, and a\n * build that adds none, carries none.\n */\nexport function aiPlanWithStorePages<T extends AiBuildPlan>(\n kind: AiJob['kind'],\n ops: AiBuildOps | null,\n inventory: Pick<AiSiteInventory, 'screens'> | null,\n plan: T,\n): T {\n if (kind !== 'build') return plan\n // A reused plan's store pages were the other job's site's; this one's are worked out again.\n const rest: T = { ...plan }\n delete rest.storePages\n const storePages = aiBuildStorePagesFor(plan, {\n store: Boolean(ops?.has(AI_BUILD_PRODUCT_OP)),\n pages: (inventory?.screens ?? []).map((screen) => ({ id: screen.id, slug: screen.slug, title: screen.name, template: screen.template })),\n })\n return storePages ? { ...rest, storePages } : rest\n}\n\n/**\n * A build plan's items with what each can cost (AGL-3616), from its\n * capability, recorded as the plan is kept so the console's estimate and the\n * confirm door read one figure without the registry.\n */\nexport function aiPlanWithItemCredits<T extends AiBuildPlan>(ops: AiBuildOps | null, plan: T): T {\n if (!ops || !plan.items?.length) return plan\n return {\n ...plan,\n items: plan.items.map((item) => {\n const capability = ops.get(item.op)\n return capability ? { ...item, credits: Math.max(0, Math.floor(capability.estimateCredits(item.args))) } : item\n }),\n }\n}\n\nexport function createAiJobPlanStep(deps: AiJobPlanStepDeps = {}): AiJobStepRunner {\n const readInventory = deps.readInventory ?? readSiteInventory\n const findPlansByKey =\n deps.findPlansByKey === undefined ? findAiJobsByPlanKey : deps.findPlansByKey\n const readCapabilities = deps.readCapabilities ?? readAiJobPlanCapabilities\n const admissionRefusal = deps.admissionRefusal ?? aiJobAdmissionRefusal\n const readOps = deps.readOps ?? readAiBuildOps\n const readContext = deps.readSiteContext === undefined ? readAiJobSiteContext : deps.readSiteContext\n const readFreeCredits = deps.readFreeCredits === undefined ? readFreeAiCreditsLeft : deps.readFreeCredits\n const readSiteEvents = deps.readSiteEvents === undefined ? readAiSiteEventPlanning : deps.readSiteEvents\n return async ({ job, now, signal, firestore, modelFor, org: orgDocument }) => {\n const org = (orgDocument ?? null) as Partial<AglynOrgBilling> | null\n const [inventory, workspace, siteContext] = await Promise.all([\n job.hostId ? readInventory(job.orgId, job.hostId, { firestore }) : Promise.resolve(null),\n readCapabilities({ job, org, firestore }),\n job.hostId && readContext ? readContext({ job, firestore }) : Promise.resolve(null),\n ])\n // The site's profile, status and memory (AGL-3661), as a per-site cached\n // block after the plan rules. The inventory block already lists what the\n // site holds, so the context carries no index of its own here.\n const instructions = aiInstructionsWithSiteContext(AI_JOB_PLAN_INSTRUCTIONS, siteContext)\n const scope = AI_JOB_PLAN_SCOPES[job.kind] ?? null\n const capabilities = aiSitePlanCapabilities(\n job,\n workspace ? aiPlanCapabilitiesForJob(workspace, scope) : null,\n )\n const freeTaste = capabilities?.freeTaste === true\n // A build's operations, as this site has them (AGL-3616).\n const ops = job.kind === 'build' ? await readOps({ job, org, firestore, freeTaste }) : null\n const site = job.kind === 'site'\n // An event-led start's first events (AGL-3755), only where the build's\n // gate admits the events plugin's operation here and its writer this member.\n const siteEvents =\n site && readSiteEvents\n ? await readSiteEvents({ job, org, firestore, now, readOps: () => readOps({ job, org, firestore, freeTaste }) })\n : null\n const itemOps = ops ?? siteEvents?.ops ?? null\n // A kept plan waits for its confirmation; a Free build's card is told what\n // is left, so it says before Confirm whether the build fits (AGL-3722).\n // Net of what THIS plan just spent: the machine meters the step after it\n // returns, so the meters read here do not hold it yet, and a card told\n // the figure from before the plan (101) offered Confirm to a person with\n // 48 left, who met the prompt only after pressing it (2026-10-10).\n const planReview = async (spentUsd = 0): Promise<AiJobReview> => {\n const review: AiJobReview = { reason: 'plan', message: AI_JOB_PLAN_REVIEW_COPY, findings: [] }\n if (job.kind !== 'build' || !freeTaste || !readFreeCredits) return review\n const credits = await readFreeCredits(firestore, { orgId: job.orgId, org, now, pendingUsd: spentUsd })\n return credits ? { ...review, freeCredits: credits } : review\n }\n // The model switch's answer for this job (AGL-2942): the creator's pick\n // where the plan, the org restriction and the allotment allowlists allow\n // it, and Auto held to those same lists otherwise. Without a resolver the\n // doctrine asks the routing table itself. A Free site plans on its\n // provider's fast tier (AGL-3594), unless the creator picked a model.\n const model = aiSitePlanModel(job, modelFor?.('job.plan'), freeTaste)\n const route = AI_ROUTING_TABLE['job.plan']\n const prompt = [aiJobPlanPrompt(job, capabilities, inventory, ops, now), ...(siteEvents ? aiSiteEventPlanLines(siteEvents, now) : [])].join('\\n')\n\n /**\n * The confirm door's answer for this plan, asked before the plan is kept\n * (AGL-3030): the kind's own admission, handed the plan as the resume\n * door hands it. A refusal fails the job with the door's sentence; a door\n * that could not answer keeps the plan, since the resume door asks again\n * before anything is built.\n *\n * Two answers are about the month's Free credits, not the plan (AGL-3722):\n * - the 409 credits prompt KEEPS the plan. The card is told what is left\n * (`planReview`) and offers Build what fits / the home page first /\n * Upgrade, and the confirm door asks with the person's choice. Failing\n * here instead failed every Free build past its p90 before the card\n * could offer them — and paged staff with `ai.jobFailed`, as at\n * 2026-10-09 16:38Z (600 credits estimated, 164 left).\n * - the 429 \"nothing left\" still fails the job with its sentence, but as\n * the customer's limit, which raises no staff alert.\n */\n const refusalOnKeep = async (plan: AiJobPlan, outcome: AiJobStepOutcome): Promise<AiJobStepOutcome | null> => {\n let refusal: Awaited<ReturnType<typeof aiJobAdmissionRefusal>> = null\n try {\n refusal = await admissionRefusal(job.kind, {\n firestore,\n orgId: job.orgId,\n hostId: job.hostId ?? null,\n inputs: job.inputs ?? {},\n org,\n plan,\n uid: job.createdBy,\n })\n } catch (error) {\n console.error('ai plan admission failed', { orgId: job.orgId, jobId: job.$id, error })\n }\n if (!refusal || refusal.code === AI_CREDITS_CONFIRM_CODE) return null\n return {\n ...outcome,\n failure: refusal.error,\n ...(refusal.status === 429 ? { customerLimit: true } : {}),\n }\n }\n\n // Reuse before asking (AGL-2937). The key covers the whole request, so a\n // hit is a request that would have been sent the same bytes to the same\n // model; the window covers what the key cannot see. A reused plan spends\n // nothing, so the machine's spent-nothing branch releases the reservation\n // and meters no credit, and the job still stops for the member to confirm\n // — this job's plan is proposed to this member, whatever the last one did\n // with theirs.\n const resolved = model ?? aiModelForStep('job.plan')\n // A brief that asks for a video is offered the plan's list of players\n // (AGL-3433), and a job that may bind a dataset a record template on\n // each screen (AGL-3475). A site's plan says the language its brief is\n // written in, which its guided start sets the site's language from (AGL-3750).\n const tool = aiBuildPlanToolFor(job.brief, {\n records: Boolean(inventory?.datasets.length) || capabilities?.create.dataset.allowed === true,\n items: job.kind === 'build' || Boolean(siteEvents),\n language: site,\n })\n const key = aiJobPlanKey({\n job,\n prompt,\n model: resolved,\n system: aiDoctrineSystemBlocks(inventory, { instructions }),\n tool,\n })\n const reused = findPlansByKey\n ? aiReusablePlan(await findPlansByKey(job.orgId, key, firestore), {\n jobId: job.$id,\n now,\n })\n : null\n if (reused) {\n // The reused plan's draft ids name the other job's drafts; this job's are its own.\n const plan: AiJobPlan = aiPlanWithStorePages(job.kind, ops, inventory, aiPlanWithItemCredits(itemOps, aiPlanWithDraftIds(job.kind, {\n ...reused.plan,\n status: 'proposed',\n labels: aiPlanLabels(reused.plan, inventory),\n proposedAt: now as unknown as AiJobPlan['proposedAt'],\n confirmedAt: null,\n confirmedBy: null,\n key,\n reusedFrom: reused.jobId,\n })))\n const unspent = aiUnspentOutcome(resolved)\n return (\n (await refusalOnKeep(plan, unspent)) ?? {\n ...unspent,\n plan,\n review: await planReview(),\n }\n )\n }\n\n const result = await runValidatedGeneration('plan', {\n step: 'job.plan',\n ...(model ? { model } : {}),\n instructions,\n inventory,\n messages: [{ role: 'user', content: prompt }],\n tool,\n // The routing ceiling, lowered on a tier too slow to look records up,\n // answer and ask again inside the least time the step registered; a\n // scaffold's outline is held to its own, far lower ceiling (AGL-3594).\n maxTokens: aiSitePlanMaxTokens(job, resolved, freeTaste),\n // A scaffold plans an outline without thinking (AGL-3594): the thinking\n // was nearly all of the 13,491 tokens a Free site's plan spent.\n ...(site ? { thinking: 'off' as const } : route.thinking ? { thinking: route.thinking } : {}),\n ...(route.effort ? { effort: route.effort } : {}),\n ...(signal ? { signal } : {}),\n capabilities,\n // A build's events are listed on a page (AGL-3755): on the one it makes\n // about them, else on an Events page of its own where there is room.\n ...(ops\n ? {\n settle: (answered: AiBuildPlan) =>\n aiSettleBuildEventPage(answered, {\n ops,\n siteSlugs: (inventory?.screens ?? []).map((screen) => screen.slug),\n pageLimit: aiBuildPageLimit(freeTaste),\n siteLayout: aiSiteLayoutOf(inventory),\n }),\n }\n : {}),\n extend: aiSiteWithEventItemsCheck(\n planViolations(\n scope,\n job.brief,\n freeTaste,\n ops,\n site ? aiSitePlanHomeRule(inventory, capabilities) : null,\n site && aiSiteContentPart(job.inputs ?? null, freeTaste) === 'posts',\n site,\n site && aiSiteKindOfInputs(job.inputs)?.id === 'store',\n ),\n site ? siteEvents : undefined,\n ops,\n ),\n })\n const spent: AiJobStepOutcome = {\n outputs: [],\n usage: result.usage,\n estCostUsd: result.estCostUsd,\n model: result.model,\n stopReason: result.stopReason,\n ...(result.effort ? { effort: result.effort } : {}),\n }\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') {\n // Refused by our own checks, after the one re-ask (AGL-3594): the member\n // reads a sentence and an action, staff the rules; the machine gives the\n // credits back (`uncredited`), and on the Free taste Try again says when\n // it cannot work. Whether the give-back was made is the job's to say\n // (`refundedCredits`), so this sentence does not promise it.\n const review = aiDoctrineReview(result)\n const retryRefusal = freeTaste\n ? await aiFreePlanRetryRefusal({ job, org, firestore, now, site })\n : null\n return {\n ...spent,\n uncredited: true,\n review: {\n ...review,\n message: aiPlanFailureCopy({\n kind: job.kind,\n codes: result.violations.map((violation) => violation.code),\n refunded: false,\n }),\n detail: review.detail ?? review.message,\n ...(retryRefusal ? { retryRefusal } : {}),\n },\n }\n }\n // A store's plan is a storefront's (AGL-3676): settled before it is kept and priced.\n const answered = site && aiSiteKindOfInputs(job.inputs)?.id === 'store' ? aiSiteStorefrontPlan(result.value, { paid: !freeTaste }) : result.value\n // Each draft the plan decides is named as the plan is kept, before anything is built (AGL-3079).\n const plan: AiJobPlan = aiPlanWithStorePages(job.kind, ops, inventory, aiPlanWithItemCredits(itemOps, aiPlanWithDraftIds(job.kind, {\n ...answered,\n status: 'proposed',\n labels: aiPlanLabels(answered, inventory),\n // A Date the Admin SDK stores as a timestamp, like every instant the machine writes.\n proposedAt: now as unknown as AiJobPlan['proposedAt'],\n confirmedAt: null,\n confirmedBy: null,\n key,\n })))\n return (\n (await refusalOnKeep(plan, spent)) ?? {\n ...spent,\n plan,\n review: await planReview(result.estCostUsd),\n }\n )\n }\n}\n\n/**\n * A site job's capabilities (AGL-3594, AGL-3660): it changes no theme in its\n * plan — every scaffold designs its look first, in a unit of its own — and on\n * the Free taste its plan holds at most the pages the member asked for within\n * the Free band, which the Free wall then holds it to.\n *\n * A build's pages are written in the layout language (`aiBuildUnitJob`), whose\n * compiler draws a section's repeated items itself, so its plan is not asked\n * for a component for them (`repeatsCompiled`, AGL-3616). It still may plan\n * one, and a section two pages share is still one. Every other job's\n * capabilities pass through.\n */\nexport function aiSitePlanCapabilities(\n job: Pick<AiJob, 'kind' | 'inputs'>,\n capabilities: AiPlanCapabilities | null,\n): AiPlanCapabilities | null {\n if (job.kind === 'build' && capabilities) return { ...capabilities, repeatsCompiled: true }\n if (job.kind !== 'site' || !capabilities) return capabilities\n const create = {\n ...capabilities.create,\n 'theme-change': {\n allowed: false,\n left: null,\n reason: 'a site start designs its own look before its pages',\n },\n // A site start builds its look, one layout and one form, never a\n // component (`AI_SITE_CREATE_KINDS`), so a paid workspace's plan draws\n // repeated items in their sections, as a Free one's does. Held to rule 1\n // instead, every paid plan was refused: for the component it left out,\n // or, re-asked, for the one it could not build (AGL-3660).\n component: {\n allowed: false,\n left: null,\n reason: 'a site start draws its repeated items in their sections',\n },\n }\n // At most a start's few datasets, settled in code where a plan names more\n // (AGL-3616); a section that shows the work with its fewest pieces (AGL-3754).\n const site = {\n ...capabilities,\n reusableComponents: false,\n create,\n datasetsMax: AI_SITE_DATASETS_MAX,\n galleryMinItems: AI_SITE_GALLERY_MIN_ITEMS,\n }\n if (!capabilities.freeTaste) return site\n const asked = Number((job.inputs ?? {})['pages'])\n const pages =\n Number.isInteger(asked) && asked >= AI_SITE_FREE_PAGES.min\n ? Math.min(asked, AI_SITE_FREE_PAGES.max)\n : AI_SITE_FREE_PAGES.max\n return { ...site, freeSitePages: pages }\n}\n\n/**\n * The model a plan runs on (AGL-3594): the resolved one, except a Free site\n * plans on its provider's fast tier — an outline the plan rules hold and\n * re-ask, at a fifth of the balanced tier's output rate. A model the creator\n * picked by name stays theirs.\n */\nexport function aiSitePlanModel(\n job: Pick<AiJob, 'kind' | 'model'>,\n resolved: string | undefined,\n freeTaste: boolean,\n): string | undefined {\n if (job.kind !== 'site' || !freeTaste) return resolved\n const picked = typeof job.model === 'string' ? job.model.trim() : ''\n if (picked && picked !== 'auto') return resolved\n const entry = aiCatalogEntry(resolved ?? aiModelForStep('job.plan'))\n return (entry ? aiDefaultModelFor(entry.provider, 'fast') : undefined) ?? resolved\n}\n\n/** The plan's answer ceiling: the step's own, and a scaffold's outline ceiling under it (AGL-3594). */\nexport function aiSitePlanMaxTokens(\n job: Pick<AiJob, 'kind'>,\n model: string,\n freeTaste: boolean,\n): number {\n const ceiling = aiJobPlanMaxTokens(model)\n if (job.kind !== 'site') return ceiling\n return Math.min(ceiling, freeTaste ? AI_SITE_PLAN_MAX_TOKENS.free : AI_SITE_PLAN_MAX_TOKENS.paid)\n}\n\n/**\n * Why trying a Free plan again cannot work this month, or `null` (AGL-3594):\n * the owner's Free allowance left, read off the account month the meter\n * writes net of give-backs, against what a plan of this kind costs at its\n * worst. Read before this step is metered, and the machine gives a refused\n * plan's credits back, so the figure read is the figure left. A read that fails\n * leaves the button on: the reservation still refuses at the wall.\n */\nasync function aiFreePlanRetryRefusal(input: {\n job: AiJob\n org: Partial<AglynOrgBilling> | null\n firestore: FirebaseFirestore.Firestore\n now: Date\n site: boolean\n}): Promise<string | null> {\n const account = freeAssistAccount(input.org as AssistMeteredOrg | null)\n if (!account?.accountUid) return null\n try {\n // The meter's month key, `YYYY-MM` in UTC (`assistUsageMonth`).\n const month = input.now.toISOString().slice(0, 7)\n const snapshot = await freeAccountUsageRef(input.firestore, account.accountUid, month).get()\n const spent = assistCreditsFromUsd(\n assistSpendAfterReturnsUsd(snapshot.get('estCostUsd'), snapshot.get(ASSIST_RETURNED_USD_FIELD)),\n )\n return aiPlanRetryRefusal({\n creditsLeft: FREE_AI_TASTE_CREDITS_PER_MONTH - spent,\n planCredits: input.site ? AI_FREE_SITE_WORST_CASE_CREDITS.plan : AI_FREE_PAGE_WORST_CASE_CREDITS.plan,\n })\n } catch (error) {\n console.error('ai plan retry allowance read failed', { orgId: input.job.orgId, jobId: input.job.$id, error })\n return null\n }\n}\n\nexport const runAiJobPlanStep = createAiJobPlanStep()\n\n/**\n * Registers the plan step every planned kind runs first, with the least time\n * a plan needs; the plugin's console surface calls it.\n */\nexport function registerAiJobPlan(): void {\n registerAiJobPlanStep(runAiJobPlanStep, { minimumMs: AI_JOB_PLAN_STEP_MINIMUM_MS })\n}\n"],"names":["aiSiteKindOfInputs","createHash","AI_BUILD_PLAN_TOOL","aiBuildPlanToolFor","aiPlanSlugKey","isAiPlanNewRef","AI_BUILD_PRODUCT_OP","aiBuildStorePagesFor","readFreeAiCreditsLeft","AI_TEMPLATE_SUBJECT_DEFINITIONS","aiTemplateShownTokens","AI_JOB_CREATE_KINDS","AI_JOB_CREATE_NOUNS","AI_PAGE_CREATE_KINDS","aiPagePlanShapeRefusal","AI_PLAN_COMPILED_REPEATS_SENTENCE","AI_PLAN_NO_FORM_SENTENCE","aiPlanCanPlaceForm","aiPlanCapabilitiesForJob","aiPlanCapabilityLines","aiSitePlanDatasetCapability","AI_FREE_SITE_WORST_CASE_CREDITS","AI_SITE_CREATE_KINDS","AI_SITE_FREE_PAGES","AI_SITE_PLAN_MAX_TOKENS","AI_SITE_HOME_BANDS","aiFreeSiteSectionsWithin","aiSiteFullPlanSentence","aiSiteHomeMinSections","aiSiteNameSentence","aiSitePlanIsHome","aiSitePlanShapeRefusal","aiSiteBlogStandInViolations","aiSiteThinHomeViolations","AI_SITE_GALLERY_SENTENCE","aiSiteEmptyGalleryViolations","AI_SITE_DATASETS_MAX","AI_SITE_GALLERY_MIN_ITEMS","aiSiteContentPart","aiLayoutIsShopPage","aiLayoutShopGridSection","aiLayoutSpeaksOtherThanEnglish","aiStorefrontPlanScreens","AI_GENERATION_MAX_ATTEMPTS","aiPlanFailureCopy","aiPlanRetryRefusal","FREE_AI_TASTE_CREDITS_PER_MONTH","aiDoctrineSystemBlocks","runValidatedGeneration","AI_STEP_TIERS","aiCatalogEntry","aiDefaultModelFor","AI_FREE_PAGE_WORST_CASE_CREDITS","aiPlanEmbedBriefViolations","assistCreditsFromUsd","ASSIST_RETURNED_USD_FIELD","assistSpendAfterReturnsUsd","freeAccountUsageRef","freeAssistAccount","AI_ROUTING_TABLE","aiModelForStep","readSiteInventory","readAiSiteContext","aiInstructionsWithSiteContext","AI_BUILD_CREATE_KINDS","AI_BUILD_STRUCTURAL_OPS","aiBuildPageLimit","aiBuildPlanShapeRefusal","aiBuildOpLines","aiBuildOps","aiBuildUnlistedEventsCheck","aiSettleBuildEventPage","aiSiteEventItemsCheck","aiSiteEventPlanLines","readAiSiteEventPlanning","aiJobAdmissionRefusal","AI_CREDITS_CONFIRM_CODE","readAiPlanCapabilities","AI_JOB_BRIEF_MAX_CHARS","aiJobStepBudget","aiDoctrineReview","aiUnspentOutcome","aiPlanWithDraftIds","AI_JOBS_COLLECTION","registerAiJobPlanStep","AI_JOB_PLAN_INSTRUCTIONS","text","AI_JOB_PLAN_STEP_BUDGET","tier","maxTokens","AI_JOB_PLAN_STEP_MINIMUM_MS","minimumMs","aiJobPlanMaxTokens","model","AI_JOB_PLAN_REVIEW_COPY","aiJobPlanPrompt","job","capabilities","inventory","ops","now","lines","kind","brief","slice","key","value","Object","entries","inputs","push","String","aiPlanTemplateTokenLines","aiPlanSiteLines","aiPlanBuildLines","join","pages","freeTaste","repeatsCompiled","aiPlanTodaySentence","toISOString","name","trim","screens","filter","screen","template","starter","find","replaceable","length","page","slug","some","cap","freeSitePages","home","aiSitePlanHomeRule","undefined","across","plansHome","min","bands","id","AI_SITE_STORE_HOME_BANDS","AI_SITE_STORE_PAGES_SENTENCE","AI_SITE_FORM_SENTENCE","create","dataset","allowed","aiSiteDatasetSentence","Math","datasetsMax","left","label","toLowerCase","AI_SITE_STORE_SHOP_SENTENCE","max","AI_SITE_FORM_DATASET_SENTENCE","layouts","forms","aiSiteThinHomeCheck","rule","answers","plan","aiSiteStorefrontPlan","options","paid","isShopPage","gridSection","every","index","AI_SITE_STORE_SHOP_CODE","aiSiteStoreShopPageViolations","code","message","paths","aiSiteStoreShopCheck","aiSiteEmptyGalleryCheck","aiSiteBlogStandInCheck","AI_SITE_PHOTO_WORDS_CODE","aiSitePhotoWordsViolations","language","photoWords","aiSitePhotoWordsCheck","subject","definition","AI_JOB_PLAN_SCOPES","noun","creates","shapeRefusal","build","site","fromEntries","map","planViolations","scope","blog","store","thinHome","storeShop","blogStandIn","emptyGallery","aiSiteLayoutOf","layoutId","aiSiteWithEventItemsCheck","violations","siteEvents","unlisted","events","aiPlanLabels","names","Map","rows","components","templates","datasets","collections","row","set","refs","reuse","entry","duplicateOf","flatMap","layout","sections","section","uses","labels","ref","get","AI_PLAN_REUSE_WINDOW_MS","AI_PLAN_REUSE_CANDIDATES","aiJobPlanKey","input","digest","part","hostId","prompt","system","block","JSON","stringify","tool","update","UNREUSABLE","planMillis","at","proposedAt","Date","getTime","toMillis","aiReusablePlan","candidates","context","floor","best","bestAt","candidate","jobId","includes","status","findAiJobsByPlanKey","orgId","firestore","snapshot","collection","doc","where","limit","docs","data","readAiJobPlanCapabilities","org","readAiJobSiteContext","readAiBuildOps","host","aiPlanWithStorePages","rest","storePages","Boolean","has","title","aiPlanWithItemCredits","items","item","capability","op","credits","estimateCredits","args","createAiJobPlanStep","deps","readInventory","findPlansByKey","readCapabilities","admissionRefusal","readOps","readContext","readSiteContext","readFreeCredits","readSiteEvents","signal","modelFor","orgDocument","workspace","siteContext","Promise","all","resolve","instructions","aiSitePlanCapabilities","itemOps","planReview","spentUsd","review","reason","findings","pendingUsd","freeCredits","aiSitePlanModel","route","refusalOnKeep","outcome","refusal","uid","createdBy","error","console","$id","failure","customerLimit","resolved","records","reused","confirmedAt","confirmedBy","reusedFrom","unspent","result","step","messages","role","content","aiSitePlanMaxTokens","thinking","effort","settle","answered","siteSlugs","pageLimit","siteLayout","extend","spent","outputs","usage","estCostUsd","stopReason","refused","retryRefusal","aiFreePlanRetryRefusal","uncredited","codes","violation","refunded","detail","component","reusableComponents","galleryMinItems","asked","Number","isInteger","picked","provider","ceiling","free","account","accountUid","month","creditsLeft","planCredits","runAiJobPlanStep","registerAiJobPlan"],"mappings":"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"}
|
|
@@ -28,7 +28,7 @@ import { readRepeatRows } from "@aglyn/aglyn/plugin-manager/repeat-rows";
|
|
|
28
28
|
import { decodeStoredNodes } from "@aglyn/aglyn/app-utils/stored-nodes";
|
|
29
29
|
import { getTemplateScreenIds } from "@aglyn/tenant-runtime/template-screens";
|
|
30
30
|
import { AI_SEO_OUTPUT_IDS, aiSeoAuditView, aiSeoJobTarget } from "../model/ai-seo.js";
|
|
31
|
-
import { aiModelForStep } from "../providers/routing.js";
|
|
31
|
+
import { aiModelForStep, aiRouteSettings } from "../providers/routing.js";
|
|
32
32
|
import { aiSeoAudit, aiSeoAuditBatches, aiSeoHeadingDemotions } from "../runtime/seo-audit.js";
|
|
33
33
|
import { runValidatedGeneration } from "../runtime/ai-doctrine.js";
|
|
34
34
|
import { AI_SEO_FIELDS_MAX_TOKENS, generateSeoFields } from "../runtime/seo-fields.js";
|
|
@@ -478,7 +478,8 @@ async function proposeSite(context, host, scan) {
|
|
|
478
478
|
].filter((line)=>line !== '').join('\n');
|
|
479
479
|
const siteText = scan.pages.map((page)=>page.facts.text).join('\n');
|
|
480
480
|
const generation = await runValidatedGeneration('seo-site', _extends({
|
|
481
|
-
step: 'job.seo'
|
|
481
|
+
step: 'job.seo'
|
|
482
|
+
}, aiRouteSettings('job.seo'), {
|
|
482
483
|
model,
|
|
483
484
|
instructions: AI_SEO_SITE_INSTRUCTIONS,
|
|
484
485
|
messages: [
|
|
@@ -643,7 +644,8 @@ async function proposeFixes(context, report, unit) {
|
|
|
643
644
|
const notes = [];
|
|
644
645
|
if (pages.length) {
|
|
645
646
|
const generation = await runValidatedGeneration('seo-fixes', _extends({
|
|
646
|
-
step: 'job.seo'
|
|
647
|
+
step: 'job.seo'
|
|
648
|
+
}, aiRouteSettings('job.seo'), {
|
|
647
649
|
model,
|
|
648
650
|
instructions: AI_SEO_FIXES_INSTRUCTIONS,
|
|
649
651
|
messages: [
|