@aglyn/plugins-ai 1.0.0-beta.149 → 1.0.0-beta.151

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-ai",
3
- "version": "1.0.0-beta.149",
3
+ "version": "1.0.0-beta.151",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,22 +25,22 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.149",
29
- "@aglyn/shared-data-enums": "1.0.0-beta.149",
30
- "@aglyn/shared-data-mdi": "1.0.0-beta.149",
31
- "@aglyn/shared-data-types": "1.0.0-beta.149",
32
- "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.149",
33
- "@aglyn/shared-ui-jsx": "1.0.0-beta.149",
34
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.149",
35
- "@aglyn/shared-ui-theme": "1.0.0-beta.149",
36
- "@aglyn/shared-util-email": "1.0.0-beta.149",
37
- "@aglyn/shared-util-errors": "1.0.0-beta.149",
38
- "@aglyn/shared-util-http": "1.0.0-beta.149",
39
- "@aglyn/shared-util-timestamp": "1.0.0-beta.149",
40
- "@aglyn/shared-util-tools": "1.0.0-beta.149",
41
- "@aglyn/tenant-data-admin": "1.0.0-beta.149",
42
- "@aglyn/tenant-feature-instance": "1.0.0-beta.149",
43
- "@aglyn/tenant-runtime": "1.0.0-beta.149",
28
+ "@aglyn/aglyn": "1.0.0-beta.151",
29
+ "@aglyn/shared-data-enums": "1.0.0-beta.151",
30
+ "@aglyn/shared-data-mdi": "1.0.0-beta.151",
31
+ "@aglyn/shared-data-types": "1.0.0-beta.151",
32
+ "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.151",
33
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.151",
34
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.151",
35
+ "@aglyn/shared-ui-theme": "1.0.0-beta.151",
36
+ "@aglyn/shared-util-email": "1.0.0-beta.151",
37
+ "@aglyn/shared-util-errors": "1.0.0-beta.151",
38
+ "@aglyn/shared-util-http": "1.0.0-beta.151",
39
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.151",
40
+ "@aglyn/shared-util-tools": "1.0.0-beta.151",
41
+ "@aglyn/tenant-data-admin": "1.0.0-beta.151",
42
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.151",
43
+ "@aglyn/tenant-runtime": "1.0.0-beta.151",
44
44
  "@swc/helpers": "0.5.23"
45
45
  },
46
46
  "peerDependencies": {
@@ -117,6 +117,7 @@ export function AiJobPlan({ job, onResume, busy = false, staff = false }) {
117
117
  screen.title,
118
118
  " at ",
119
119
  screen.slug,
120
+ screen.duplicateOf ? `, from a copy of ${named(screen.duplicateOf)}` : '',
120
121
  screen.layout ? ` in ${named(screen.layout)}` : '',
121
122
  screen.sections.length ? `: ${screen.sections.map((section)=>section.name).join(', ')}` : ''
122
123
  ]
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-job-plan.component.tsx"],"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 { AI_BUILD_PLAN_CREATION_NOUNS, isAiPlanNewRef } from '../model/ai-build-plan'\nimport type { AiJobReview, AiJobSummary } from '../model/ai-jobs.types'\nimport { aiJobPlanCreditEstimate } from '../model/ai-site-job'\nimport { Box, Button, Collapse, Stack, Typography } from '@mui/material'\nimport { useState } from 'react'\n\n/**\n * A job's plan and what it waits for (AGL-2935), inside the AI jobs drawer:\n * what the job reuses from the site, what it creates and why, the screens it\n * builds — and, while the job waits for a person, the one button that\n * confirms the plan or tries a refused step again. The drawer owns the\n * request; this renders.\n */\nexport interface AiJobPlanProps {\n job: AiJobSummary\n /** Confirms the plan, or tries the refused step again. */\n onResume: (job: AiJobSummary) => void\n /** A resume for this job is in flight. */\n busy?: boolean\n /**\n * The viewer is staff (AGL-3078), who can open where a refused answer broke\n * its rules and the outline of those parts, to tell a shape the rules should\n * allow from an answer that ignored its re-ask. A member reads the findings.\n */\n staff?: boolean\n}\n\n/**\n * Where a refused answer broke its rules, and the outline of those parts, as\n * lines staff read (AGL-3078): each finding with the nodes or plan entries it\n * names, then each outlined node indented by its depth, with its element, a\n * Grid's layout as written, and the names of what it sets and holds.\n */\nexport function aiJobReviewDetails(review: AiJobReview | null): string[] {\n if (review?.reason !== 'doctrine') return []\n const lines = review.findings.flatMap((finding) => {\n const where = finding.nodeIds?.length\n ? `nodes ${finding.nodeIds.join(', ')}`\n : finding.paths?.length\n ? `at ${finding.paths.join(', ')}`\n : null\n return where ? [`${finding.rule === null ? '' : `Rule ${finding.rule} `}${finding.code}: ${where}`] : []\n })\n for (const node of review.outline ?? []) {\n lines.push(\n [\n `${' '.repeat(node.depth)}${node.id} ${node.componentId}`,\n ...Object.entries(node.grid ?? {}).map(([name, value]) => `${name}=${JSON.stringify(value)}`),\n ...(node.props.length ? [`props ${node.props.join(', ')}`] : []),\n ...(node.sx?.length ? [`sx ${node.sx.join(', ')}`] : []),\n ...(node.children.length ? [`holds ${node.children.join(', ')}`] : []),\n ].join(' · '),\n )\n }\n return lines\n}\n\nexport function AiJobPlan({\n job,\n onResume,\n busy = false,\n staff = false,\n}: AiJobPlanProps): JSX.Element | null {\n const [detailsOpen, setDetailsOpen] = useState(false)\n const { plan, review } = job\n if (!plan && !review) return null\n const details = staff ? aiJobReviewDetails(review) : []\n /** A reference as a person reads it: the record's name, or what the plan creates. */\n const named = (ref: string | null): string =>\n !ref\n ? ''\n : isAiPlanNewRef(ref)\n ? ref.slice('new:'.length)\n : (plan?.labels[ref] ?? ref)\n const waiting = job.status === 'needs_review' && review !== null\n // The guard rail (AGL-2911): what the plan is estimated to cost is read\n // before it is confirmed, not after it has been spent. An estimate, and\n // said to be one — the plan's own passes at the nominal credits a step\n // holds, where what a step really costs is its model's tokens. A page job\n // counts the creations it builds before its page (AGL-3031).\n const estimate =\n plan && waiting && review.reason === 'plan' ? aiJobPlanCreditEstimate(job.kind, plan) : 0\n return (\n <Box sx={{ mt: 1 }}>\n {plan && (\n <Stack spacing={0.5} role=\"list\" aria-label=\"Plan\">\n <Typography variant=\"caption\" color=\"text.secondary\">\n {plan.status === 'confirmed' ? 'Confirmed plan' : 'Proposed plan'}\n </Typography>\n {plan.reuse.map((entry, index) => (\n <Typography key={`reuse-${index}`} variant=\"body2\" role=\"listitem\">\n Reuses the {entry.kind} {named(entry.id)} — {entry.purpose}\n </Typography>\n ))}\n {plan.create.map((entry, index) => (\n <Typography key={`create-${index}`} variant=\"body2\" role=\"listitem\">\n Creates the {AI_BUILD_PLAN_CREATION_NOUNS[entry.kind].noun} {entry.name}\n {entry.duplicateOf\n ? `, from a copy of ${named(entry.duplicateOf)}`\n : ''}{' '}\n — {entry.why}\n </Typography>\n ))}\n {plan.screens.map((screen, index) => (\n <Typography key={`screen-${index}`} variant=\"body2\" role=\"listitem\">\n Builds the screen {screen.title} at {screen.slug}\n {screen.layout ? ` in ${named(screen.layout)}` : ''}\n {screen.sections.length\n ? `: ${screen.sections.map((section) => section.name).join(', ')}`\n : ''}\n </Typography>\n ))}\n </Stack>\n )}\n {review?.reason === 'doctrine' && review.findings.length > 0 && (\n <Box\n component=\"ul\"\n sx={{ my: 0.5, pl: 2.5 }}\n aria-label=\"Building rules broken\"\n >\n {review.findings.map((finding, index) => (\n <Typography\n key={`finding-${index}`}\n component=\"li\"\n variant=\"caption\"\n >\n {finding.message}\n </Typography>\n ))}\n </Box>\n )}\n {details.length > 0 && (\n <Box sx={{ mt: 0.5 }}>\n <Button\n size=\"small\"\n aria-expanded={detailsOpen}\n onClick={() => setDetailsOpen((prior) => !prior)}\n >\n {detailsOpen ? 'Hide what was refused' : 'Show what was refused'}\n </Button>\n <Collapse in={detailsOpen} unmountOnExit>\n <Typography\n variant=\"caption\"\n component=\"pre\"\n aria-label=\"What was refused\"\n sx={{ m: 0, fontFamily: 'monospace', whiteSpace: 'pre', overflowX: 'auto' }}\n >\n {details.join('\\n')}\n </Typography>\n </Collapse>\n </Box>\n )}\n {estimate > 0 && (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mt: 1 }}\n >\n {`Estimated cost: about ${estimate.toLocaleString('en-US')} credits. What it costs is what its steps spend.`}\n </Typography>\n )}\n {waiting && (\n <Button\n size=\"small\"\n variant=\"contained\"\n disabled={busy}\n onClick={() => onResume(job)}\n sx={{ mt: 1 }}\n >\n {review.reason === 'plan' ? 'Confirm plan' : 'Try again'}\n </Button>\n )}\n </Box>\n )\n}\n\nexport default AiJobPlan\n"],"names":["AI_BUILD_PLAN_CREATION_NOUNS","isAiPlanNewRef","aiJobPlanCreditEstimate","Box","Button","Collapse","Stack","Typography","useState","aiJobReviewDetails","review","reason","lines","findings","flatMap","finding","where","nodeIds","length","join","paths","rule","code","node","outline","push","repeat","depth","id","componentId","Object","entries","grid","map","name","value","JSON","stringify","props","sx","children","AiJobPlan","job","onResume","busy","staff","detailsOpen","setDetailsOpen","plan","details","named","ref","slice","labels","waiting","status","estimate","kind","mt","spacing","role","aria-label","variant","color","reuse","entry","index","purpose","create","noun","duplicateOf","why","screens","screen","title","slug","layout","sections","section","component","my","pl","message","size","aria-expanded","onClick","prior","in","unmountOnExit","m","fontFamily","whiteSpace","overflowX","display","toLocaleString","disabled"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-job-plan.component.tsx"],"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 { AI_BUILD_PLAN_CREATION_NOUNS, isAiPlanNewRef } from '../model/ai-build-plan'\nimport type { AiJobReview, AiJobSummary } from '../model/ai-jobs.types'\nimport { aiJobPlanCreditEstimate } from '../model/ai-site-job'\nimport { Box, Button, Collapse, Stack, Typography } from '@mui/material'\nimport { useState } from 'react'\n\n/**\n * A job's plan and what it waits for (AGL-2935), inside the AI jobs drawer:\n * what the job reuses from the site, what it creates and why, the screens it\n * builds — and, while the job waits for a person, the one button that\n * confirms the plan or tries a refused step again. The drawer owns the\n * request; this renders.\n */\nexport interface AiJobPlanProps {\n job: AiJobSummary\n /** Confirms the plan, or tries the refused step again. */\n onResume: (job: AiJobSummary) => void\n /** A resume for this job is in flight. */\n busy?: boolean\n /**\n * The viewer is staff (AGL-3078), who can open where a refused answer broke\n * its rules and the outline of those parts, to tell a shape the rules should\n * allow from an answer that ignored its re-ask. A member reads the findings.\n */\n staff?: boolean\n}\n\n/**\n * Where a refused answer broke its rules, and the outline of those parts, as\n * lines staff read (AGL-3078): each finding with the nodes or plan entries it\n * names, then each outlined node indented by its depth, with its element, a\n * Grid's layout as written, and the names of what it sets and holds.\n */\nexport function aiJobReviewDetails(review: AiJobReview | null): string[] {\n if (review?.reason !== 'doctrine') return []\n const lines = review.findings.flatMap((finding) => {\n const where = finding.nodeIds?.length\n ? `nodes ${finding.nodeIds.join(', ')}`\n : finding.paths?.length\n ? `at ${finding.paths.join(', ')}`\n : null\n return where ? [`${finding.rule === null ? '' : `Rule ${finding.rule} `}${finding.code}: ${where}`] : []\n })\n for (const node of review.outline ?? []) {\n lines.push(\n [\n `${' '.repeat(node.depth)}${node.id} ${node.componentId}`,\n ...Object.entries(node.grid ?? {}).map(([name, value]) => `${name}=${JSON.stringify(value)}`),\n ...(node.props.length ? [`props ${node.props.join(', ')}`] : []),\n ...(node.sx?.length ? [`sx ${node.sx.join(', ')}`] : []),\n ...(node.children.length ? [`holds ${node.children.join(', ')}`] : []),\n ].join(' · '),\n )\n }\n return lines\n}\n\nexport function AiJobPlan({\n job,\n onResume,\n busy = false,\n staff = false,\n}: AiJobPlanProps): JSX.Element | null {\n const [detailsOpen, setDetailsOpen] = useState(false)\n const { plan, review } = job\n if (!plan && !review) return null\n const details = staff ? aiJobReviewDetails(review) : []\n /** A reference as a person reads it: the record's name, or what the plan creates. */\n const named = (ref: string | null): string =>\n !ref\n ? ''\n : isAiPlanNewRef(ref)\n ? ref.slice('new:'.length)\n : (plan?.labels[ref] ?? ref)\n const waiting = job.status === 'needs_review' && review !== null\n // The guard rail (AGL-2911): what the plan is estimated to cost is read\n // before it is confirmed, not after it has been spent. An estimate, and\n // said to be one — the plan's own passes at the nominal credits a step\n // holds, where what a step really costs is its model's tokens. A page job\n // counts the creations it builds before its page (AGL-3031).\n const estimate =\n plan && waiting && review.reason === 'plan' ? aiJobPlanCreditEstimate(job.kind, plan) : 0\n return (\n <Box sx={{ mt: 1 }}>\n {plan && (\n <Stack spacing={0.5} role=\"list\" aria-label=\"Plan\">\n <Typography variant=\"caption\" color=\"text.secondary\">\n {plan.status === 'confirmed' ? 'Confirmed plan' : 'Proposed plan'}\n </Typography>\n {plan.reuse.map((entry, index) => (\n <Typography key={`reuse-${index}`} variant=\"body2\" role=\"listitem\">\n Reuses the {entry.kind} {named(entry.id)} — {entry.purpose}\n </Typography>\n ))}\n {plan.create.map((entry, index) => (\n <Typography key={`create-${index}`} variant=\"body2\" role=\"listitem\">\n Creates the {AI_BUILD_PLAN_CREATION_NOUNS[entry.kind].noun} {entry.name}\n {entry.duplicateOf\n ? `, from a copy of ${named(entry.duplicateOf)}`\n : ''}{' '}\n — {entry.why}\n </Typography>\n ))}\n {plan.screens.map((screen, index) => (\n <Typography key={`screen-${index}`} variant=\"body2\" role=\"listitem\">\n Builds the screen {screen.title} at {screen.slug}\n {screen.duplicateOf\n ? `, from a copy of ${named(screen.duplicateOf)}`\n : ''}\n {screen.layout ? ` in ${named(screen.layout)}` : ''}\n {screen.sections.length\n ? `: ${screen.sections.map((section) => section.name).join(', ')}`\n : ''}\n </Typography>\n ))}\n </Stack>\n )}\n {review?.reason === 'doctrine' && review.findings.length > 0 && (\n <Box\n component=\"ul\"\n sx={{ my: 0.5, pl: 2.5 }}\n aria-label=\"Building rules broken\"\n >\n {review.findings.map((finding, index) => (\n <Typography\n key={`finding-${index}`}\n component=\"li\"\n variant=\"caption\"\n >\n {finding.message}\n </Typography>\n ))}\n </Box>\n )}\n {details.length > 0 && (\n <Box sx={{ mt: 0.5 }}>\n <Button\n size=\"small\"\n aria-expanded={detailsOpen}\n onClick={() => setDetailsOpen((prior) => !prior)}\n >\n {detailsOpen ? 'Hide what was refused' : 'Show what was refused'}\n </Button>\n <Collapse in={detailsOpen} unmountOnExit>\n <Typography\n variant=\"caption\"\n component=\"pre\"\n aria-label=\"What was refused\"\n sx={{ m: 0, fontFamily: 'monospace', whiteSpace: 'pre', overflowX: 'auto' }}\n >\n {details.join('\\n')}\n </Typography>\n </Collapse>\n </Box>\n )}\n {estimate > 0 && (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mt: 1 }}\n >\n {`Estimated cost: about ${estimate.toLocaleString('en-US')} credits. What it costs is what its steps spend.`}\n </Typography>\n )}\n {waiting && (\n <Button\n size=\"small\"\n variant=\"contained\"\n disabled={busy}\n onClick={() => onResume(job)}\n sx={{ mt: 1 }}\n >\n {review.reason === 'plan' ? 'Confirm plan' : 'Try again'}\n </Button>\n )}\n </Box>\n )\n}\n\nexport default AiJobPlan\n"],"names":["AI_BUILD_PLAN_CREATION_NOUNS","isAiPlanNewRef","aiJobPlanCreditEstimate","Box","Button","Collapse","Stack","Typography","useState","aiJobReviewDetails","review","reason","lines","findings","flatMap","finding","where","nodeIds","length","join","paths","rule","code","node","outline","push","repeat","depth","id","componentId","Object","entries","grid","map","name","value","JSON","stringify","props","sx","children","AiJobPlan","job","onResume","busy","staff","detailsOpen","setDetailsOpen","plan","details","named","ref","slice","labels","waiting","status","estimate","kind","mt","spacing","role","aria-label","variant","color","reuse","entry","index","purpose","create","noun","duplicateOf","why","screens","screen","title","slug","layout","sections","section","component","my","pl","message","size","aria-expanded","onClick","prior","in","unmountOnExit","m","fontFamily","whiteSpace","overflowX","display","toLocaleString","disabled"],"mappings":"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"}
@@ -370,14 +370,37 @@ export function createAiJobPageStep(deps = {}) {
370
370
  // is the draft — unrouted, as every copy is — and nothing is generated.
371
371
  // Only a page is a page's start: a collection entry template's copy stays
372
372
  // a template, so a plan naming one builds the page instead.
373
- if (!written && screen.duplicateOf && inventory.screens.some((row)=>row.id === screen.duplicateOf && !row.template)) {
373
+ // A copy shows what its SOURCE shows, and this branch generates nothing.
374
+ // So if the source cannot already show what the plan promised, copying it
375
+ // can only produce a page that breaks the promise — the refusal below
376
+ // would be certain before the copy was ever minted. Read the source and
377
+ // build instead, rather than leave the member a page they must throw away
378
+ // and a Try again that can only copy the same thing (AGL-3024).
379
+ // Only a source we could READ and found short diverts: one we cannot read
380
+ // is one we cannot judge, and the review after the copy still answers for
381
+ // that. A short source carries findings; an unreadable one carries none.
382
+ const sourceReview = !written && screen.duplicateOf ? await aiCopiedPageReview(firestore, {
383
+ hostId,
384
+ id: screen.duplicateOf,
385
+ name,
386
+ screen
387
+ }) : null;
388
+ const sourceShort = Boolean(sourceReview == null ? void 0 : sourceReview.findings.length);
389
+ if (!written && !sourceShort && screen.duplicateOf && inventory.screens.some((row)=>row.id === screen.duplicateOf && !row.template)) {
374
390
  const copy = await duplicate('screen', {
375
391
  orgId: job.orgId,
376
392
  hostId,
377
393
  sourceId: screen.duplicateOf,
378
394
  name,
379
395
  uid: job.createdBy,
380
- org: org
396
+ org: org,
397
+ // A copy core mints is written under core's id, never `draftId`, so
398
+ // `written` above cannot find it and this branch would fire on every
399
+ // pass — and the pass after a refused copy is the common one, since
400
+ // the review below offers to try again. The job's recorded id is the
401
+ // claim, so a second pass replays the one copy instead of leaving
402
+ // another orphan screen behind it (AGL-3024).
403
+ attemptKey: draftId
381
404
  });
382
405
  if (copy.ok) {
383
406
  // A copy is not a build (AGL-3024). Nothing is generated here, so
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-page-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { buildPageMarkdown } from '@aglyn/aglyn/app-utils/page-markdown'\nimport { checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { SCREEN_SEO_TEXT_GUIDANCE } from '@aglyn/aglyn/app-utils/screen-seo-fields'\nimport { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { duplicateResource } from '@aglyn/tenant-data-admin/server/duplicate-resource'\nimport { AI_BUILD_PLAN_LIMITS, type AiBuildPlanScreen } from '../model/ai-build-plan'\nimport {\n AI_PAGE_CREATE_KINDS,\n aiPageCreationRefusal,\n aiPagePlanRefusal,\n parseAiPageJobInputs,\n} from '../model/ai-page-job'\nimport { aiPlanCapabilitiesForJob, aiPlanUncreatable } from '../model/ai-plan-capabilities'\nimport { aiSiteWords } from '../model/ai-site-job'\nimport type { AiJob, AiJobOutput, AiJobPlan, AiJobReview } from '../model/ai-jobs.types'\nimport { aiModelForStep } from '../providers/routing'\nimport { aiDoctrineNeedsInputMessage, runValidatedGeneration } from '../runtime/ai-doctrine'\nimport { validateAiDoctrineTree, type AiDoctrineTree } from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport { AI_SEO_FIELDS_MAX_TOKENS, generateSeoFields } from '../runtime/seo-fields'\nimport { readSiteInventory } from '../runtime/site-inventory'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport { aiGenerationWorstCaseMs } from './ai-job-budget'\nimport { aiJobDraftId } from './ai-job-draft-ids'\nimport {\n aiDraftAdmissionRefusal,\n aiDraftAllowanceRefusal,\n aiSiteSubdomain,\n readAiDraft,\n readAiDraftNodes,\n readAiPlanCapabilities,\n updateAiDraftNodes,\n writeAiDraft,\n writeAiDraftScreenSeo,\n type AiDraftRecord,\n} from './ai-job-drafts'\nimport {\n aiBracketedFactsNote,\n aiConfirmedPlan,\n aiDoctrineReview,\n aiGenerationSpent,\n aiLimitReview,\n aiModelNodeIds,\n aiUnspentOutcome,\n} from './ai-job-generation'\nimport {\n AI_JOB_PAGE_SECTION_MAX_TOKENS,\n AI_JOB_PAGE_SECTION_TOKENS,\n AI_JOB_PAGE_STEP_BUDGET,\n AI_JOB_PAGE_STEP_MINIMUM_MS,\n aiJobPageSectionMaxTokens,\n} from './ai-job-page-budget'\nimport {\n AI_JOB_PAGE_INSTRUCTIONS,\n AI_PAGE_SECTION_TOOL,\n aiEmptyPage,\n aiPageCheckContext,\n aiPageSectionCheck,\n aiPageSectionNodeId,\n aiPageSectionPrompt,\n aiPageSectionSmaller,\n aiPageWithSection,\n type AiPageSection,\n} from './ai-job-page-sections'\nimport { AI_PLAN_ITEMS_MIN, aiPlanCopiedPageViolations } from './ai-job-plan-conformance'\nimport {\n aiCreationUnit,\n aiRunJobUnit,\n aiSiteBuiltRefs,\n aiSitePendingUnits,\n aiSiteUnitJob,\n type AiSiteUnit,\n} from './ai-job-site-step'\nimport type { AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport {\n aiJobStepRunMinimumMs,\n aiJobStepRunnerFor,\n registerAiJobStep,\n registerAiJobStepPasses,\n} from './ai-jobs'\n\n/**\n * The page step (AGL-2907): the generation step of a `page` job, run once a\n * member confirmed the job's plan. It builds ONE screen, section by section,\n * from what the site already has, and leaves it an unpublished draft.\n *\n * ── Why a page is built in passes ────────────────────────────────────────\n *\n * A page is far larger than one answer the job beat can wait for: the beat\n * gives a step what is left of its budget, and a whole page, answered and re-asked,\n * takes several times that. The confirmed plan already names the page's\n * sections, so the step builds one section per pass and asks the machine to\n * continue. Every pass is one reservation and one generation — the section's\n * lookup rounds, its answer and, when it breaks a rule, its one re-ask — sized\n * so that worst case fits `AI_JOB_PAGE_STEP_MINIMUM_MS`, which the step\n * registers as the least time a section pass needs before it starts.\n *\n * - The first pass builds the top section, which holds the page's h1, and\n * writes the draft screen and its first version.\n * - Each later pass builds the next section and adds it to that version.\n * - The last pass generates no tree: it holds the whole page to the\n * doctrine, writes the search title and description, and reports the\n * draft with its measured weight and a navigation proposal.\n *\n * How a section is asked for and held to the rules as part of the page is\n * `ai-job-page-sections.ts`.\n *\n * ── What the plan creates comes first (AGL-3031) ─────────────────────────\n *\n * A plan that creates a layout, forms or components is built in the same job,\n * before its page, one creation a pass, through the site scaffold's unit\n * machinery: each creation is handed to the step that builds that kind, under\n * a job derived from this one, and lands as the draft that step writes. Where\n * the job stands is read from its outputs, so nothing is kept anywhere else.\n * Once every creation is built, the page's passes run on the plan with each\n * `new:<name>` resolved to the record that was built, which the site's\n * inventory now lists — so the page places the new component and binds the new\n * form by id, and renders inside the new layout, exactly as it would one the\n * site always had. A pass that builds a creation needs the time the step that\n * builds it registers, not a section's (`aiPageJobRunMinimumMs`, AGL-3035), so\n * neither the beat nor a door starts it with only a section's time left.\n *\n * ── Never published ──────────────────────────────────────────────────────\n *\n * The draft is written the way `/api/hosts/resources` and\n * `/api/hosts/versions` write a screen and its first version, and nothing\n * else is touched: not the host document, whose routing map is what the live\n * site serves, and no collection, store, layout or navigation pointer\n * (`ai-job-drafts.ts`). A screen the routing map does not name serves nothing\n * until a member publishes it.\n */\n\n/**\n * A section pass's time, and the ceiling each pass asks on each model, are\n * declared apart from the step (`ai-job-page-budget.ts`): the site scaffold\n * builds its pages through this step and reads them without loading it.\n */\nexport {\n AI_JOB_PAGE_SECTION_MAX_TOKENS,\n AI_JOB_PAGE_SECTION_TOKENS,\n AI_JOB_PAGE_STEP_BUDGET,\n AI_JOB_PAGE_STEP_MINIMUM_MS,\n aiJobPageSectionMaxTokens,\n}\n\n/**\n * Tokens one element takes in a section's answer, ESTIMATED: the tree as JSON\n * text inside the tool call, at four characters a token. Measured on the ten\n * golden pages at a median of 38 and a most of 43 over their 39 sections, and\n * `ai-job-page-evals.spec.ts` holds every golden section under it.\n */\nexport const AI_JOB_PAGE_TOKENS_PER_ELEMENT = 45\n\n/**\n * Real tokens to every token estimated at four characters (AGL-3042). A\n * section's ceiling is counted by the provider in its own tokens, which run\n * above that estimate. MEASURED on the first live document run on a Free\n * workspace (AGL-3024, test-org, 2026-09-16), as the cache read of a request\n * over the estimate of the same cached prefix: 4,059 over 2,825 for the page\n * plan and 6,649 over 4,310 for the layout, the higher of the two, 1.5427 —\n * the ratio `ai-job-free-page.spec.ts` prices a Free page at. It is measured on\n * prompt text; an answer's own is read off a recorded pass, whose output\n * tokens the step's record keeps (`lastRuns`), beside the section it stored.\n */\nexport const AI_JOB_PAGE_REAL_TOKENS_PER_ESTIMATED = Math.max(4_059 / 2_825, 6_649 / 4_310)\n\n/**\n * Real tokens one element takes in a section's answer: the estimate at its\n * golden most, in real tokens, rounded up — 45 × 1.5427 → 70 (AGL-3042).\n */\nexport const AI_JOB_PAGE_REAL_TOKENS_PER_ELEMENT = Math.ceil(\n AI_JOB_PAGE_TOKENS_PER_ELEMENT * AI_JOB_PAGE_REAL_TOKENS_PER_ESTIMATED,\n)\n\n/**\n * The most elements a section's request asks it to keep under at an answer\n * ceiling: 15 at the balanced tier's 1,050 (AGL-3042). Counted in real\n * tokens, because the ceiling is: counted in the estimate, the budget is 23\n * elements, which a section of the goldens' largest elements fills at 1,597\n * real tokens — half as much again as the ceiling that cuts it off.\n */\nexport function aiJobPageSectionMaxElements(maxTokens: number): number {\n return Math.max(1, Math.floor(maxTokens / AI_JOB_PAGE_REAL_TOKENS_PER_ELEMENT))\n}\n\n/** A page's name when the plan names none. */\nexport const AI_JOB_PAGE_DEFAULT_NAME = 'New page'\n\nexport const AI_JOB_PAGE_NO_PLAN_COPY = 'This page job has no confirmed plan to build.'\nexport const AI_JOB_PAGE_DELETED_COPY = 'The draft page was deleted while it was being built.'\n\n/** A creation the plan names that this deployment has no step to build. */\nexport const AI_JOB_PAGE_CREATION_UNAVAILABLE_COPY =\n 'This page needs something that cannot be built here yet. Describe the page again.'\n\n/** A creation that finished without reporting what it built; the page cannot place it. */\nexport const AI_JOB_PAGE_CREATION_EMPTY_COPY =\n 'Part of this page could not be built. Describe the page again.'\n\n/**\n * What a page refused on its last pass tells the member beside the rules it\n * broke (AGL-3143). The draft is reported with the review, and mending what\n * the findings name in it is what lets the next pass finish the job.\n */\nexport const AI_JOB_PAGE_REFUSED_DRAFT_COPY =\n 'The draft is yours to open: mend what these name in it, then try again.'\n\n/**\n * The most passes a page job may take (AGL-3031): every creation the plan\n * limits admit, every section they admit, and the last pass that reports the\n * draft. A creation's step builds it in one pass, and a page one section a\n * pass, so a runner that never finishes is still bounded while a real page is\n * not.\n */\nexport const AI_JOB_PAGE_MAX_PASSES = AI_BUILD_PLAN_LIMITS.create + AI_BUILD_PLAN_LIMITS.sections + 1\n\n/**\n * The units a plan's creations are built as, in the order a scaffold builds\n * them: the layout, then the forms, then the components. Each is addressed by\n * its place in the plan, so a unit run again finds its own draft.\n */\nexport function aiPageJobUnits(plan: Pick<AiJobPlan, 'create'>): AiSiteUnit[] {\n return AI_PAGE_CREATE_KINDS.flatMap((kind) =>\n plan.create.flatMap((creation, index) => {\n if (creation.kind !== kind) return []\n const unit = aiCreationUnit(creation, `c${index}`)\n return unit ? [unit] : []\n }),\n )\n}\n\n/**\n * The least time this page job's next pass needs (AGL-3035). A pass that\n * builds a creation hands it to the step registered for that kind, under the\n * job derived for it, so it needs what that step registers for that job — a\n * layout's time, not a section's. Every other pass — a section, the last one —\n * needs a section pass's.\n */\nexport function aiPageJobRunMinimumMs(job: AiJob): number {\n const confirmed = aiConfirmedPlan(job)\n if (!confirmed) return AI_JOB_PAGE_STEP_MINIMUM_MS\n const units = aiPageJobUnits(confirmed)\n const outputs = job.outputs ?? []\n const [unit] = aiSitePendingUnits(units, outputs)\n if (!unit) return AI_JOB_PAGE_STEP_MINIMUM_MS\n return aiJobStepRunMinimumMs(aiSiteUnitJob(job, unit, aiSiteBuiltRefs(units, outputs)))\n}\n\n/** The one-segment address a draft asks for, from the plan's slug. */\nexport function aiPageDraftSlug(screen: Pick<AiBuildPlanScreen, 'slug'>): string {\n const segments = screen.slug.split('/').map((part) => part.trim()).filter(Boolean)\n return segments[segments.length - 1] ?? ''\n}\n\n/** A plan's listing value held to the length the SEO editor guides it to, cut at a word. */\nfunction clip(value: string, max: number): string {\n const text = value.trim()\n if (text.length <= max) return text\n const cut = text.slice(0, max + 1)\n const space = cut.lastIndexOf(' ')\n return (space > max / 2 ? cut.slice(0, space) : text.slice(0, max)).trim()\n}\n\n/** The site's name as its visitors see it, read the way the SEO step reads it. */\nfunction siteNameOf(host: FirebaseFirestore.DocumentSnapshot): string {\n const text = (value: unknown) => (typeof value === 'string' ? value.trim() : '')\n return text(host.get('seo.title')) || text(host.get('displayName')) || text(host.get('subdomain'))\n}\n\n/**\n * The counts the confirmed plan's sections promised, against the page a COPY\n * actually produced (AGL-3024); `null` when the copy keeps the plan, or when\n * the plan promised no count to keep.\n *\n * The same hole the layout copy had (`aiCopiedLayoutReview`), on the kind the\n * defect was measured on. This branch generates nothing, so no model ever\n * answers for the plan: a plan reading \"start from the Services page, one\n * card per practice area\" gets the Services page and whatever IT had, and\n * until now reported `Done` — the one report that tells a customer there is\n * no reason to re-read the page.\n *\n * A copy that cannot be read back settles nothing, and a promise this step\n * cannot settle is reviewed by a person rather than reported as kept.\n */\nexport async function aiCopiedPageReview(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; id: string; name: string; screen: Pick<AiBuildPlanScreen, 'sections'> },\n): Promise<AiJobReview | null> {\n if (!input.screen.sections.some((section) => section.items >= AI_PLAN_ITEMS_MIN)) return null\n const stored = await readAiDraftNodes(firestore, { kind: 'screen', hostId: input.hostId, id: input.id })\n const nodes = (stored?.nodes ?? {}) as unknown as AiDoctrineTree['nodes']\n if (!nodes[CANVAS_ROOT_ELEMENT_ID]) {\n return {\n reason: 'doctrine',\n message: `\"${input.name}\" was copied from the page the plan names, and this job could not read the copy back to check it shows what the plan says it shows. Open the page and check it before you put it in front of anyone.`,\n findings: [],\n }\n }\n const violations = aiPlanCopiedPageViolations(input.screen, { rootId: CANVAS_ROOT_ELEMENT_ID, nodes })\n if (!violations.length) return null\n return aiDoctrineReview({\n message: `\"${input.name}\" is a copy of the page the plan names, and a copy is all it is: ${violations[0].message}`,\n violations,\n })\n}\n\n/**\n * A page job is admitted with inputs that read, for a site of the job's own\n * org with a screen to spare; and confirmed only for a plan it can build —\n * one screen, whose creations are ones a page job builds, this deployment has\n * a step for, and this workspace may still make on this site (AGL-3031).\n */\nexport const aiPageJobAdmission: AiJobAdmission = async (context) => {\n const inputs = parseAiPageJobInputs(context.inputs)\n if (typeof inputs === 'string') return { status: 400, error: inputs }\n const plan = context.plan\n if (plan) {\n const refusal = aiPagePlanRefusal(plan)\n if (refusal) return { status: 400, error: refusal }\n if (aiPageJobUnits(plan).some((unit) => !aiJobStepRunnerFor(unit.jobKind))) {\n return { status: 400, error: AI_JOB_PAGE_CREATION_UNAVAILABLE_COPY }\n }\n }\n const org = context.org as Partial<AglynOrgBilling> | null\n return aiDraftAdmissionRefusal(context.firestore, {\n orgId: context.orgId,\n hostId: context.hostId,\n kind: 'screen',\n noun: 'page',\n org,\n // What the workspace may still make here, read once the site is known to\n // be the org's: the site may have changed since the plan was told.\n ...(plan?.create.length\n ? {\n ownCheck: async (hostId: string) => {\n const capabilities = aiPlanCapabilitiesForJob(\n await readAiPlanCapabilities(context.firestore, { hostId, org }),\n { noun: 'a page job', creates: AI_PAGE_CREATE_KINDS },\n )\n const refusal = aiPageCreationRefusal(aiPlanUncreatable(plan, capabilities))\n return refusal ? { status: 403 as const, error: refusal } : null\n },\n }\n : {}),\n })\n}\n\nexport interface AiJobPageStepDeps {\n /** The inventory reader; specs hand in a fake. */\n readInventory?: typeof readSiteInventory\n /** The platform's duplicate module, for a plan that starts from a copy. */\n duplicate?: typeof duplicateResource\n /** The listing generator (AGL-2910). */\n seoFields?: typeof generateSeoFields\n /** The runners a plan's creations are built by (AGL-3031); the registry's otherwise. */\n runnerFor?: typeof aiJobStepRunnerFor\n}\n\nexport function createAiJobPageStep(deps: AiJobPageStepDeps = {}): AiJobStepRunner {\n const readInventory = deps.readInventory ?? readSiteInventory\n const duplicate = deps.duplicate ?? duplicateResource\n const seoFields = deps.seoFields ?? generateSeoFields\n const runnerFor = deps.runnerFor ?? aiJobStepRunnerFor\n return async (stepContext): Promise<AiJobStepOutcome> => {\n const { job, now, signal, firestore, modelFor } = stepContext\n const hostId = job.hostId\n if (!hostId) throw new Error('a page job names no site, and its admission refuses one')\n // The switch's answer for this job, else the routing table's (AGL-2942).\n const model = modelFor?.('job.page') ?? aiModelForStep('job.page')\n const confirmed = aiConfirmedPlan(job)\n if (!confirmed) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_NO_PLAN_COPY }\n // The resume door refuses such a plan at confirmation; one confirmed\n // before a site changed still stops here, before any spend.\n const refusal = aiPagePlanRefusal(confirmed)\n if (refusal) return { ...aiUnspentOutcome(model), failure: refusal }\n\n // ── The creations first (AGL-3031): one a pass, by the steps that own them ──\n const units = aiPageJobUnits(confirmed)\n const outputs = job.outputs ?? []\n const [unit] = aiSitePendingUnits(units, outputs)\n if (unit) {\n const runner = runnerFor(unit.jobKind)\n if (!runner) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_CREATION_UNAVAILABLE_COPY }\n const pass = await aiRunJobUnit(stepContext, {\n unit,\n units,\n runner,\n emptyCopy: AI_JOB_PAGE_CREATION_EMPTY_COPY,\n })\n // A built creation is followed by the next, or by the page's first section.\n return pass.built ? { ...pass.outcome, continue: true } : pass.outcome\n }\n // The page's plan with every creation resolved to the record it built, as\n // a scaffold's page is planned against what the scaffold built.\n const plan: AiJobPlan = units.length\n ? (aiSiteUnitJob(\n job,\n {\n kind: 'page',\n jobKind: 'page',\n resource: 'screen',\n slot: 'page',\n screen: confirmed.screens[0],\n label: confirmed.screens[0].title,\n },\n aiSiteBuiltRefs(units, outputs),\n ).plan as AiJobPlan)\n : confirmed\n const screen = plan.screens[0] as AiBuildPlanScreen\n const name = screen.title || AI_JOB_PAGE_DEFAULT_NAME\n const slug = aiPageDraftSlug(screen)\n const sectionIds = screen.sections.map((_, index) => aiPageSectionNodeId(job.$id, index))\n // The draft screen, by the id the job recorded for it: every pass writes and finds the same one.\n const draftId = aiJobDraftId(job, 'screen')\n\n const output = (\n draft: Pick<AiDraftRecord, 'id' | 'versionId' | 'name' | 'hostSubdomain'>,\n load?: AiLoadEstimate | null,\n note?: string | null,\n ): AiJobOutput => ({\n resource: 'screen',\n id: draft.id,\n versionId: draft.versionId,\n hostId,\n hostSubdomain: draft.hostSubdomain,\n label: draft.name,\n ...(load ? { load } : {}),\n ...(note ? { note } : {}),\n // Rule 10: a navigation entry travels with the page as a proposal, for\n // the member to add once the page is live; no menu is written.\n ...(screen.nav ? { proposal: { navigation: { label: name, slug } } } : {}),\n })\n\n const [inventory, orgSnapshot, written] = await Promise.all([\n readInventory(job.orgId, hostId, { firestore }),\n firestore.collection('orgs').doc(job.orgId).get(),\n readAiDraft(firestore, { kind: 'screen', hostId, id: draftId }),\n ])\n const org = (orgSnapshot.data() ?? null) as Partial<AglynOrgBilling> | null\n if (written?.deleted) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_DELETED_COPY }\n\n // The plan starts from a copy of a screen the site has (rule 15): the copy\n // is the draft — unrouted, as every copy is — and nothing is generated.\n // Only a page is a page's start: a collection entry template's copy stays\n // a template, so a plan naming one builds the page instead.\n if (\n !written &&\n screen.duplicateOf &&\n inventory.screens.some((row) => row.id === screen.duplicateOf && !row.template)\n ) {\n const copy = await duplicate('screen', {\n orgId: job.orgId,\n hostId,\n sourceId: screen.duplicateOf,\n name,\n uid: job.createdBy,\n org: org as Record<string, unknown> | null,\n })\n if (copy.ok) {\n // A copy is not a build (AGL-3024). Nothing is generated here, so\n // whatever the plan promised BEYOND the source is in the draft only\n // if the source already had it, and no model is left to answer for\n // the difference — the copy itself has to.\n const review = await aiCopiedPageReview(firestore, { hostId, id: copy.id, name, screen })\n if (review) return aiUnspentOutcome(model, { review })\n const hostSubdomain = await aiSiteSubdomain(firestore, hostId)\n return aiUnspentOutcome(model, {\n outputs: [output({ id: copy.id, versionId: copy.versionId, name: copy.name, hostSubdomain })],\n })\n }\n if (copy.ok === false && copy.status === 403) {\n return aiUnspentOutcome(model, { review: aiLimitReview(copy.error) })\n }\n }\n\n const stored = written ? await readAiDraftNodes(firestore, { kind: 'screen', hostId, id: draftId }) : null\n if (written && !stored) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_DELETED_COPY }\n const page = stored?.nodes ?? aiEmptyPage()\n const index = sectionIds.findIndex((id) => !(id in page))\n // A workspace that keeps no reusable components or saved forms builds its\n // page inline, and every pass is held to the rules that way (AGL-3030).\n const reusableComponents = checkEntitlement(org, 'reusableComponents')\n // A link may take a visitor to a section of this page, by its name in the plan (AGL-3097).\n const sections = screen.sections.map((section) => section.name)\n const context = aiPageCheckContext(inventory, { reusableComponents, sections })\n\n // ── The last pass: the whole page, its listing, and the draft reported ──\n if (index === -1 && written) {\n // A link the page carries to one of its sections goes there, now every section is built.\n const report = validateAiDoctrineTree({ rootId: CANVAS_ROOT_ELEMENT_ID, nodes: page }, 'page', {\n ...context,\n scrollTargetIds: sectionIds,\n })\n // The facts the brief did not give, on the page or in the defaults its components show (AGL-3056).\n const note = aiBracketedFactsNote({\n tree: { rootId: CANVAS_ROOT_ELEMENT_ID, nodes: page as unknown as AiDoctrineTree['nodes'] },\n inventory,\n })\n // The page's own draft, reported once however many times the last pass\n // runs (AGL-3143): a refused pass reports it and a later one that\n // passes does not report it twice.\n const reported = outputs.some((entry) => entry.resource === 'screen' && entry.id === draftId)\n const reports = reported ? [] : [output(written, report.load, note)]\n if (report.violations.length) {\n // The check mints its own ids; the review names the nodes by the ids the draft stores them under.\n const storedIds = report.tree?.sourceIds ?? {}\n const violations = report.violations.map((violation) =>\n violation.nodeIds ? { ...violation, nodeIds: aiModelNodeIds(violation.nodeIds, storedIds) } : violation,\n )\n // THE DRAFT TRAVELS WITH THE REFUSAL (AGL-3143). Every other review\n // parks on a model's answer, so trying again asks the model again and\n // can come back different. This one reads the STORED page and spends\n // nothing, so trying again refuses the same page for the same reasons\n // forever — and a job parked here having reported no output names no\n // draft at all, which leaves the member nowhere to mend the nodes the\n // findings name and no way to finish the job from the console. The\n // draft is reported now instead, and the job still parks: what the\n // review names is mended in the draft, and the next pass then passes.\n return aiUnspentOutcome(model, {\n outputs: reports,\n review: aiDoctrineReview({\n message: `${aiDoctrineNeedsInputMessage(violations)} ${AI_JOB_PAGE_REFUSED_DRAFT_COPY}`,\n violations,\n answer: { tree: { rootId: CANVAS_ROOT_ELEMENT_ID, nodes: page } },\n }),\n })\n }\n let spent: AiJobStepOutcome = aiUnspentOutcome(model)\n if (!written.seo?.title?.trim() || !written.seo?.description?.trim()) {\n const seoModel = modelFor?.('job.seo') ?? aiModelForStep('job.seo')\n // The plan's own listing, held to the editor's guidance, when the\n // listing cannot be written inside this pass's time on the model the\n // job runs.\n let values = {\n title: clip(screen.seoTitle, SCREEN_SEO_TEXT_GUIDANCE.title),\n description: clip(screen.seoDescription, SCREEN_SEO_TEXT_GUIDANCE.description),\n }\n if (\n // A listing is written from the page's own text, with no inventory\n // and so no lookup round.\n aiGenerationWorstCaseMs({ maxTokens: AI_SEO_FIELDS_MAX_TOKENS, model: seoModel, lookups: 0 }) <=\n AI_JOB_PAGE_STEP_MINIMUM_MS\n ) {\n const host = await firestore.collection('hosts').doc(hostId).get()\n const result = await seoFields({\n subject: { kind: 'screen', name, path: `/${slug}` },\n brand: siteNameOf(host),\n // What the site is and who it is for (AGL-2918). The page's own\n // Markdown says what the page is about and cannot say who it was\n // written for, so the listing borrows the two answers the job was\n // started from — the scaffold's derived page job carries them,\n // and a page job started from the Screens page carries neither.\n site: aiSiteWords(job.inputs),\n text: buildPageMarkdown({ nodes: page as never, rootId: CANVAS_ROOT_ELEMENT_ID }),\n fields: ['title', 'description'],\n // Other screens by name only: their own listings are their content.\n otherTitles: inventory.screens.map((row) => row.name),\n model: seoModel,\n ...(signal ? { signal } : {}),\n })\n spent = { ...spent, ...aiGenerationSpent(result) }\n if (result.status === 'ok') {\n values = {\n title: result.value.title?.trim() || values.title,\n description: result.value.description?.trim() || values.description,\n }\n }\n }\n await writeAiDraftScreenSeo(firestore, { hostId, id: draftId, seo: values, now })\n }\n return { ...spent, outputs: reports }\n }\n\n // ── A section pass ──\n if (!written) {\n const allowance = await aiDraftAllowanceRefusal(firestore, { kind: 'screen', hostId, org })\n if (allowance) return aiUnspentOutcome(model, { review: aiLimitReview(allowance) })\n }\n const maxTokens = aiJobPageSectionMaxTokens(model)\n const maxElements = aiJobPageSectionMaxElements(maxTokens)\n const result = await runValidatedGeneration<AiPageSection>('page-section', {\n step: 'job.page',\n model,\n instructions: AI_JOB_PAGE_INSTRUCTIONS,\n inventory,\n messages: [\n {\n role: 'user',\n content: aiPageSectionPrompt({ job, plan, screen, index, maxElements, reusableComponents }),\n },\n ],\n tool: AI_PAGE_SECTION_TOOL,\n maxTokens,\n // A section cut off at its ceiling is asked for smaller, and a member\n // whose section still does not fit reads that it was too large (AGL-3042).\n cutOff: { noun: 'section', smaller: aiPageSectionSmaller({ maxElements, reusableComponents }) },\n thinking: 'off',\n check: aiPageSectionCheck({\n page,\n sectionIds,\n index,\n context,\n section: screen.sections[index],\n inventory,\n }),\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result) }\n\n if (!written) {\n const draft = await writeAiDraft(firestore, {\n kind: 'screen',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: aiPageWithSection(aiEmptyPage(), result.value, sectionIds),\n slug,\n layoutId:\n screen.layout && inventory.layouts.some((row) => row.id === screen.layout) ? screen.layout : null,\n now,\n })\n if (draft.ok === false) {\n if (draft.status === 404) throw new Error(`site ${hostId} vanished while its page was generated`)\n return { ...spent, review: aiLimitReview(draft.error) }\n }\n } else {\n const update = await updateAiDraftNodes(firestore, {\n kind: 'screen',\n hostId,\n id: draftId,\n now,\n // A pass run again after its write finds its section and changes nothing.\n update: (nodes) =>\n result.value.rootId in nodes ? null : aiPageWithSection(nodes, result.value, sectionIds),\n })\n if (update.ok === false) return { ...spent, failure: AI_JOB_PAGE_DELETED_COPY }\n }\n return { ...spent, continue: true }\n }\n}\n\nexport const runAiJobPageStep = createAiJobPageStep()\n\n/**\n * Registers the page step with the least time a section pass needs and the\n * time its next pass needs, the passes a page with its creations may take, and\n * the check a page job passes when it is created or its plan is confirmed.\n */\nexport function registerAiPageJob(): void {\n registerAiJobStep('page', runAiJobPageStep, {\n minimumMs: AI_JOB_PAGE_STEP_MINIMUM_MS,\n minimumMsFor: aiPageJobRunMinimumMs,\n })\n registerAiJobStepPasses('page', AI_JOB_PAGE_MAX_PASSES)\n registerAiJobAdmission('page', aiPageJobAdmission)\n}\n"],"names":["buildPageMarkdown","checkEntitlement","SCREEN_SEO_TEXT_GUIDANCE","CANVAS_ROOT_ELEMENT_ID","duplicateResource","AI_BUILD_PLAN_LIMITS","AI_PAGE_CREATE_KINDS","aiPageCreationRefusal","aiPagePlanRefusal","parseAiPageJobInputs","aiPlanCapabilitiesForJob","aiPlanUncreatable","aiSiteWords","aiModelForStep","aiDoctrineNeedsInputMessage","runValidatedGeneration","validateAiDoctrineTree","AI_SEO_FIELDS_MAX_TOKENS","generateSeoFields","readSiteInventory","registerAiJobAdmission","aiGenerationWorstCaseMs","aiJobDraftId","aiDraftAdmissionRefusal","aiDraftAllowanceRefusal","aiSiteSubdomain","readAiDraft","readAiDraftNodes","readAiPlanCapabilities","updateAiDraftNodes","writeAiDraft","writeAiDraftScreenSeo","aiBracketedFactsNote","aiConfirmedPlan","aiDoctrineReview","aiGenerationSpent","aiLimitReview","aiModelNodeIds","aiUnspentOutcome","AI_JOB_PAGE_SECTION_MAX_TOKENS","AI_JOB_PAGE_SECTION_TOKENS","AI_JOB_PAGE_STEP_BUDGET","AI_JOB_PAGE_STEP_MINIMUM_MS","aiJobPageSectionMaxTokens","AI_JOB_PAGE_INSTRUCTIONS","AI_PAGE_SECTION_TOOL","aiEmptyPage","aiPageCheckContext","aiPageSectionCheck","aiPageSectionNodeId","aiPageSectionPrompt","aiPageSectionSmaller","aiPageWithSection","AI_PLAN_ITEMS_MIN","aiPlanCopiedPageViolations","aiCreationUnit","aiRunJobUnit","aiSiteBuiltRefs","aiSitePendingUnits","aiSiteUnitJob","aiJobStepRunMinimumMs","aiJobStepRunnerFor","registerAiJobStep","registerAiJobStepPasses","AI_JOB_PAGE_TOKENS_PER_ELEMENT","AI_JOB_PAGE_REAL_TOKENS_PER_ESTIMATED","Math","max","AI_JOB_PAGE_REAL_TOKENS_PER_ELEMENT","ceil","aiJobPageSectionMaxElements","maxTokens","floor","AI_JOB_PAGE_DEFAULT_NAME","AI_JOB_PAGE_NO_PLAN_COPY","AI_JOB_PAGE_DELETED_COPY","AI_JOB_PAGE_CREATION_UNAVAILABLE_COPY","AI_JOB_PAGE_CREATION_EMPTY_COPY","AI_JOB_PAGE_REFUSED_DRAFT_COPY","AI_JOB_PAGE_MAX_PASSES","create","sections","aiPageJobUnits","plan","flatMap","kind","creation","index","unit","aiPageJobRunMinimumMs","job","confirmed","units","outputs","aiPageDraftSlug","screen","segments","slug","split","map","part","trim","filter","Boolean","length","clip","value","text","cut","slice","space","lastIndexOf","siteNameOf","host","get","aiCopiedPageReview","firestore","input","some","section","items","stored","hostId","id","nodes","reason","message","name","findings","violations","rootId","aiPageJobAdmission","context","inputs","status","error","refusal","jobKind","org","orgId","noun","ownCheck","capabilities","creates","createAiJobPageStep","deps","readInventory","duplicate","seoFields","runnerFor","stepContext","orgSnapshot","now","signal","modelFor","Error","model","failure","runner","pass","emptyCopy","built","outcome","continue","resource","slot","screens","label","title","sectionIds","_","$id","draftId","output","draft","load","note","versionId","hostSubdomain","nav","proposal","navigation","inventory","written","Promise","all","collection","doc","data","deleted","duplicateOf","row","template","copy","sourceId","uid","createdBy","ok","review","page","findIndex","reusableComponents","report","scrollTargetIds","tree","reported","entry","reports","storedIds","sourceIds","violation","nodeIds","answer","spent","seo","description","seoModel","values","seoTitle","seoDescription","lookups","result","subject","path","brand","site","fields","otherTitles","allowance","maxElements","step","instructions","messages","role","content","tool","cutOff","smaller","thinking","check","refused","layoutId","layout","layouts","update","runAiJobPageStep","registerAiPageJob","minimumMs","minimumMsFor"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,iBAAiB,QAAQ,uCAAsC;AACxE,SAASC,gBAAgB,QAAQ,2CAA0C;AAC3E,SAASC,wBAAwB,QAAQ,2CAA0C;AACnF,SAASC,sBAAsB,QAAQ,2CAA0C;AAEjF,SAASC,iBAAiB,QAAQ,qDAAoD;AACtF,SAASC,oBAAoB,QAAgC,4BAAwB;AACrF,SACEC,oBAAoB,EACpBC,qBAAqB,EACrBC,iBAAiB,EACjBC,oBAAoB,QACf,0BAAsB;AAC7B,SAASC,wBAAwB,EAAEC,iBAAiB,QAAQ,mCAA+B;AAC3F,SAASC,WAAW,QAAQ,0BAAsB;AAElD,SAASC,cAAc,QAAQ,0BAAsB;AACrD,SAASC,2BAA2B,EAAEC,sBAAsB,QAAQ,4BAAwB;AAC5F,SAASC,sBAAsB,QAA6B,uCAAmC;AAE/F,SAASC,wBAAwB,EAAEC,iBAAiB,QAAQ,2BAAuB;AACnF,SAASC,iBAAiB,QAAQ,+BAA2B;AAC7D,SAASC,sBAAsB,QAA6B,wBAAoB;AAChF,SAASC,uBAAuB,QAAQ,qBAAiB;AACzD,SAASC,YAAY,QAAQ,wBAAoB;AACjD,SACEC,uBAAuB,EACvBC,uBAAuB,EACvBC,eAAe,EACfC,WAAW,EACXC,gBAAgB,EAChBC,sBAAsB,EACtBC,kBAAkB,EAClBC,YAAY,EACZC,qBAAqB,QAEhB,qBAAiB;AACxB,SACEC,oBAAoB,EACpBC,eAAe,EACfC,gBAAgB,EAChBC,iBAAiB,EACjBC,aAAa,EACbC,cAAc,EACdC,gBAAgB,QACX,yBAAqB;AAC5B,SACEC,8BAA8B,EAC9BC,0BAA0B,EAC1BC,uBAAuB,EACvBC,2BAA2B,EAC3BC,yBAAyB,QACpB,0BAAsB;AAC7B,SACEC,wBAAwB,EACxBC,oBAAoB,EACpBC,WAAW,EACXC,kBAAkB,EAClBC,kBAAkB,EAClBC,mBAAmB,EACnBC,mBAAmB,EACnBC,oBAAoB,EACpBC,iBAAiB,QAEZ,4BAAwB;AAC/B,SAASC,iBAAiB,EAAEC,0BAA0B,QAAQ,+BAA2B;AACzF,SACEC,cAAc,EACdC,YAAY,EACZC,eAAe,EACfC,kBAAkB,EAClBC,aAAa,QAER,wBAAoB;AAE3B,SACEC,qBAAqB,EACrBC,kBAAkB,EAClBC,iBAAiB,EACjBC,uBAAuB,QAClB,eAAW;AAElB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiDC,GAED;;;;CAIC,GACD,SACExB,8BAA8B,EAC9BC,0BAA0B,EAC1BC,uBAAuB,EACvBC,2BAA2B,EAC3BC,yBAAyB,KAC1B;AAED;;;;;CAKC,GACD,OAAO,MAAMqB,iCAAiC,GAAE;AAEhD;;;;;;;;;;CAUC,GACD,OAAO,MAAMC,wCAAwCC,KAAKC,GAAG,CAAC,OAAQ,MAAO,OAAQ,MAAM;AAE3F;;;CAGC,GACD,OAAO,MAAMC,sCAAsCF,KAAKG,IAAI,CAC1DL,iCAAiCC,uCAClC;AAED;;;;;;CAMC,GACD,OAAO,SAASK,4BAA4BC,SAAiB;IAC3D,OAAOL,KAAKC,GAAG,CAAC,GAAGD,KAAKM,KAAK,CAACD,YAAYH;AAC5C;AAEA,4CAA4C,GAC5C,OAAO,MAAMK,2BAA2B,WAAU;AAElD,OAAO,MAAMC,2BAA2B,gDAA+C;AACvF,OAAO,MAAMC,2BAA2B,uDAAsD;AAE9F,yEAAyE,GACzE,OAAO,MAAMC,wCACX,oFAAmF;AAErF,wFAAwF,GACxF,OAAO,MAAMC,kCACX,iEAAgE;AAElE;;;;CAIC,GACD,OAAO,MAAMC,iCACX,0EAAyE;AAE3E;;;;;;CAMC,GACD,OAAO,MAAMC,yBAAyB1E,qBAAqB2E,MAAM,GAAG3E,qBAAqB4E,QAAQ,GAAG,EAAC;AAErG;;;;CAIC,GACD,OAAO,SAASC,eAAeC,IAA+B;IAC5D,OAAO7E,qBAAqB8E,OAAO,CAAC,CAACC,OACnCF,KAAKH,MAAM,CAACI,OAAO,CAAC,CAACE,UAAUC;YAC7B,IAAID,SAASD,IAAI,KAAKA,MAAM,OAAO,EAAE;YACrC,MAAMG,OAAOjC,eAAe+B,UAAU,CAAC,CAAC,EAAEC,OAAO;YACjD,OAAOC,OAAO;gBAACA;aAAK,GAAG,EAAE;QAC3B;AAEJ;AAEA;;;;;;CAMC,GACD,OAAO,SAASC,sBAAsBC,GAAU;QAI9BA;IAHhB,MAAMC,YAAY1D,gBAAgByD;IAClC,IAAI,CAACC,WAAW,OAAOjD;IACvB,MAAMkD,QAAQV,eAAeS;IAC7B,MAAME,WAAUH,eAAAA,IAAIG,OAAO,YAAXH,eAAe,EAAE;IACjC,MAAM,CAACF,KAAK,GAAG9B,mBAAmBkC,OAAOC;IACzC,IAAI,CAACL,MAAM,OAAO9C;IAClB,OAAOkB,sBAAsBD,cAAc+B,KAAKF,MAAM/B,gBAAgBmC,OAAOC;AAC/E;AAEA,oEAAoE,GACpE,OAAO,SAASC,gBAAgBC,MAAuC;QAE9DC;IADP,MAAMA,WAAWD,OAAOE,IAAI,CAACC,KAAK,CAAC,KAAKC,GAAG,CAAC,CAACC,OAASA,KAAKC,IAAI,IAAIC,MAAM,CAACC;IAC1E,QAAOP,aAAAA,QAAQ,CAACA,SAASQ,MAAM,GAAG,EAAE,YAA7BR,aAAiC;AAC1C;AAEA,0FAA0F,GAC1F,SAASS,KAAKC,KAAa,EAAEvC,GAAW;IACtC,MAAMwC,OAAOD,MAAML,IAAI;IACvB,IAAIM,KAAKH,MAAM,IAAIrC,KAAK,OAAOwC;IAC/B,MAAMC,MAAMD,KAAKE,KAAK,CAAC,GAAG1C,MAAM;IAChC,MAAM2C,QAAQF,IAAIG,WAAW,CAAC;IAC9B,OAAO,AAACD,CAAAA,QAAQ3C,MAAM,IAAIyC,IAAIC,KAAK,CAAC,GAAGC,SAASH,KAAKE,KAAK,CAAC,GAAG1C,IAAG,EAAGkC,IAAI;AAC1E;AAEA,gFAAgF,GAChF,SAASW,WAAWC,IAAwC;IAC1D,MAAMN,OAAO,CAACD,QAAoB,OAAOA,UAAU,WAAWA,MAAML,IAAI,KAAK;IAC7E,OAAOM,KAAKM,KAAKC,GAAG,CAAC,iBAAiBP,KAAKM,KAAKC,GAAG,CAAC,mBAAmBP,KAAKM,KAAKC,GAAG,CAAC;AACvF;AAEA;;;;;;;;;;;;;;CAcC,GACD,OAAO,eAAeC,mBACpBC,SAAsC,EACtCC,KAAgG;;IAEhG,IAAI,CAACA,MAAMtB,MAAM,CAACd,QAAQ,CAACqC,IAAI,CAAC,CAACC,UAAYA,QAAQC,KAAK,IAAInE,oBAAoB,OAAO;IACzF,MAAMoE,SAAS,MAAM9F,iBAAiByF,WAAW;QAAE/B,MAAM;QAAUqC,QAAQL,MAAMK,MAAM;QAAEC,IAAIN,MAAMM,EAAE;IAAC;IACtG,MAAMC,gBAASH,0BAAAA,OAAQG,KAAK,mBAAI,CAAC;IACjC,IAAI,CAACA,KAAK,CAACzH,uBAAuB,EAAE;QAClC,OAAO;YACL0H,QAAQ;YACRC,SAAS,CAAC,CAAC,EAAET,MAAMU,IAAI,CAAC,oMAAoM,CAAC;YAC7NC,UAAU,EAAE;QACd;IACF;IACA,MAAMC,aAAa3E,2BAA2B+D,MAAMtB,MAAM,EAAE;QAAEmC,QAAQ/H;QAAwByH;IAAM;IACpG,IAAI,CAACK,WAAWzB,MAAM,EAAE,OAAO;IAC/B,OAAOtE,iBAAiB;QACtB4F,SAAS,CAAC,CAAC,EAAET,MAAMU,IAAI,CAAC,iEAAiE,EAAEE,UAAU,CAAC,EAAE,CAACH,OAAO,EAAE;QAClHG;IACF;AACF;AAEA;;;;;CAKC,GACD,OAAO,MAAME,qBAAqC,OAAOC;IACvD,MAAMC,SAAS5H,qBAAqB2H,QAAQC,MAAM;IAClD,IAAI,OAAOA,WAAW,UAAU,OAAO;QAAEC,QAAQ;QAAKC,OAAOF;IAAO;IACpE,MAAMlD,OAAOiD,QAAQjD,IAAI;IACzB,IAAIA,MAAM;QACR,MAAMqD,UAAUhI,kBAAkB2E;QAClC,IAAIqD,SAAS,OAAO;YAAEF,QAAQ;YAAKC,OAAOC;QAAQ;QAClD,IAAItD,eAAeC,MAAMmC,IAAI,CAAC,CAAC9B,OAAS,CAAC3B,mBAAmB2B,KAAKiD,OAAO,IAAI;YAC1E,OAAO;gBAAEH,QAAQ;gBAAKC,OAAO3D;YAAsC;QACrE;IACF;IACA,MAAM8D,MAAMN,QAAQM,GAAG;IACvB,OAAOnH,wBAAwB6G,QAAQhB,SAAS,EAAE;QAChDuB,OAAOP,QAAQO,KAAK;QACpBjB,QAAQU,QAAQV,MAAM;QACtBrC,MAAM;QACNuD,MAAM;QACNF;OAGIvD,CAAAA,wBAAAA,KAAMH,MAAM,CAACwB,MAAM,IACnB;QACEqC,UAAU,OAAOnB;YACf,MAAMoB,eAAepI,yBACnB,MAAMkB,uBAAuBwG,QAAQhB,SAAS,EAAE;gBAAEM;gBAAQgB;YAAI,IAC9D;gBAAEE,MAAM;gBAAcG,SAASzI;YAAqB;YAEtD,MAAMkI,UAAUjI,sBAAsBI,kBAAkBwE,MAAM2D;YAC9D,OAAON,UAAU;gBAAEF,QAAQ;gBAAcC,OAAOC;YAAQ,IAAI;QAC9D;IACF,IACA,CAAC;AAET,EAAC;AAaD,OAAO,SAASQ,oBAAoBC,OAA0B,CAAC,CAAC;QACxCA,qBACJA,iBACAA,iBACAA;IAHlB,MAAMC,iBAAgBD,sBAAAA,KAAKC,aAAa,YAAlBD,sBAAsB9H;IAC5C,MAAMgI,aAAYF,kBAAAA,KAAKE,SAAS,YAAdF,kBAAkB7I;IACpC,MAAMgJ,aAAYH,kBAAAA,KAAKG,SAAS,YAAdH,kBAAkB/H;IACpC,MAAMmI,aAAYJ,kBAAAA,KAAKI,SAAS,YAAdJ,kBAAkBpF;IACpC,OAAO,OAAOyF;kBAeI5D,cA4DH6D;QA1Eb,MAAM,EAAE7D,GAAG,EAAE8D,GAAG,EAAEC,MAAM,EAAErC,SAAS,EAAEsC,QAAQ,EAAE,GAAGJ;QAClD,MAAM5B,SAAShC,IAAIgC,MAAM;QACzB,IAAI,CAACA,QAAQ,MAAM,IAAIiC,MAAM;QAC7B,yEAAyE;QACzE,MAAMC,gBAAQF,4BAAAA,SAAW,8BAAe7I,eAAe;QACvD,MAAM8E,YAAY1D,gBAAgByD;QAClC,IAAI,CAACC,WAAW,OAAO,aAAKrD,iBAAiBsH;YAAQC,SAASnF;;QAC9D,qEAAqE;QACrE,4DAA4D;QAC5D,MAAM8D,UAAUhI,kBAAkBmF;QAClC,IAAI6C,SAAS,OAAO,aAAKlG,iBAAiBsH;YAAQC,SAASrB;;QAE3D,+EAA+E;QAC/E,MAAM5C,QAAQV,eAAeS;QAC7B,MAAME,WAAUH,eAAAA,IAAIG,OAAO,YAAXH,eAAe,EAAE;QACjC,MAAM,CAACF,KAAK,GAAG9B,mBAAmBkC,OAAOC;QACzC,IAAIL,MAAM;YACR,MAAMsE,SAAST,UAAU7D,KAAKiD,OAAO;YACrC,IAAI,CAACqB,QAAQ,OAAO,aAAKxH,iBAAiBsH;gBAAQC,SAASjF;;YAC3D,MAAMmF,OAAO,MAAMvG,aAAa8F,aAAa;gBAC3C9D;gBACAI;gBACAkE;gBACAE,WAAWnF;YACb;YACA,4EAA4E;YAC5E,OAAOkF,KAAKE,KAAK,GAAG,aAAKF,KAAKG,OAAO;gBAAEC,UAAU;iBAASJ,KAAKG,OAAO;QACxE;QACA,0EAA0E;QAC1E,gEAAgE;QAChE,MAAM/E,OAAkBS,MAAMY,MAAM,GAC/B7C,cACC+B,KACA;YACEL,MAAM;YACNoD,SAAS;YACT2B,UAAU;YACVC,MAAM;YACNtE,QAAQJ,UAAU2E,OAAO,CAAC,EAAE;YAC5BC,OAAO5E,UAAU2E,OAAO,CAAC,EAAE,CAACE,KAAK;QACnC,GACA/G,gBAAgBmC,OAAOC,UACvBV,IAAI,GACNQ;QACJ,MAAMI,SAASZ,KAAKmF,OAAO,CAAC,EAAE;QAC9B,MAAMvC,OAAOhC,OAAOyE,KAAK,IAAI/F;QAC7B,MAAMwB,OAAOH,gBAAgBC;QAC7B,MAAM0E,aAAa1E,OAAOd,QAAQ,CAACkB,GAAG,CAAC,CAACuE,GAAGnF,QAAUtC,oBAAoByC,IAAIiF,GAAG,EAAEpF;QAClF,iGAAiG;QACjG,MAAMqF,UAAUtJ,aAAaoE,KAAK;QAElC,MAAMmF,SAAS,CACbC,OACAC,MACAC,OACiB;gBACjBZ,UAAU;gBACVzC,IAAImD,MAAMnD,EAAE;gBACZsD,WAAWH,MAAMG,SAAS;gBAC1BvD;gBACAwD,eAAeJ,MAAMI,aAAa;gBAClCX,OAAOO,MAAM/C,IAAI;eACbgD,OAAO;gBAAEA;YAAK,IAAI,CAAC,GACnBC,OAAO;gBAAEA;YAAK,IAAI,CAAC,GAGnBjF,OAAOoF,GAAG,GAAG;gBAAEC,UAAU;oBAAEC,YAAY;wBAAEd,OAAOxC;wBAAM9B;oBAAK;gBAAE;YAAE,IAAI,CAAC;QAG1E,MAAM,CAACqF,WAAW/B,aAAagC,QAAQ,GAAG,MAAMC,QAAQC,GAAG,CAAC;YAC1DvC,cAAcxD,IAAIiD,KAAK,EAAEjB,QAAQ;gBAAEN;YAAU;YAC7CA,UAAUsE,UAAU,CAAC,QAAQC,GAAG,CAACjG,IAAIiD,KAAK,EAAEzB,GAAG;YAC/CxF,YAAY0F,WAAW;gBAAE/B,MAAM;gBAAUqC;gBAAQC,IAAIiD;YAAQ;SAC9D;QACD,MAAMlC,OAAOa,oBAAAA,YAAYqC,IAAI,cAAhBrC,oBAAsB;QACnC,IAAIgC,2BAAAA,QAASM,OAAO,EAAE,OAAO,aAAKvJ,iBAAiBsH;YAAQC,SAASlF;;QAEpE,2EAA2E;QAC3E,wEAAwE;QACxE,0EAA0E;QAC1E,4DAA4D;QAC5D,IACE,CAAC4G,WACDxF,OAAO+F,WAAW,IAClBR,UAAUhB,OAAO,CAAChD,IAAI,CAAC,CAACyE,MAAQA,IAAIpE,EAAE,KAAK5B,OAAO+F,WAAW,IAAI,CAACC,IAAIC,QAAQ,GAC9E;YACA,MAAMC,OAAO,MAAM9C,UAAU,UAAU;gBACrCR,OAAOjD,IAAIiD,KAAK;gBAChBjB;gBACAwE,UAAUnG,OAAO+F,WAAW;gBAC5B/D;gBACAoE,KAAKzG,IAAI0G,SAAS;gBAClB1D,KAAKA;YACP;YACA,IAAIuD,KAAKI,EAAE,EAAE;gBACX,kEAAkE;gBAClE,oEAAoE;gBACpE,mEAAmE;gBACnE,2CAA2C;gBAC3C,MAAMC,SAAS,MAAMnF,mBAAmBC,WAAW;oBAAEM;oBAAQC,IAAIsE,KAAKtE,EAAE;oBAAEI;oBAAMhC;gBAAO;gBACvF,IAAIuG,QAAQ,OAAOhK,iBAAiBsH,OAAO;oBAAE0C;gBAAO;gBACpD,MAAMpB,gBAAgB,MAAMzJ,gBAAgB2F,WAAWM;gBACvD,OAAOpF,iBAAiBsH,OAAO;oBAC7B/D,SAAS;wBAACgF,OAAO;4BAAElD,IAAIsE,KAAKtE,EAAE;4BAAEsD,WAAWgB,KAAKhB,SAAS;4BAAElD,MAAMkE,KAAKlE,IAAI;4BAAEmD;wBAAc;qBAAG;gBAC/F;YACF;YACA,IAAIe,KAAKI,EAAE,KAAK,SAASJ,KAAK3D,MAAM,KAAK,KAAK;gBAC5C,OAAOhG,iBAAiBsH,OAAO;oBAAE0C,QAAQlK,cAAc6J,KAAK1D,KAAK;gBAAE;YACrE;QACF;QAEA,MAAMd,SAAS8D,UAAU,MAAM5J,iBAAiByF,WAAW;YAAE/B,MAAM;YAAUqC;YAAQC,IAAIiD;QAAQ,KAAK;QACtG,IAAIW,WAAW,CAAC9D,QAAQ,OAAO,aAAKnF,iBAAiBsH;YAAQC,SAASlF;;QACtE,MAAM4H,gBAAO9E,0BAAAA,OAAQG,KAAK,oBAAI9E;QAC9B,MAAMyC,QAAQkF,WAAW+B,SAAS,CAAC,CAAC7E,KAAO,CAAEA,CAAAA,MAAM4E,IAAG;QACtD,0EAA0E;QAC1E,wEAAwE;QACxE,MAAME,qBAAqBxM,iBAAiByI,KAAK;QACjD,2FAA2F;QAC3F,MAAMzD,WAAWc,OAAOd,QAAQ,CAACkB,GAAG,CAAC,CAACoB,UAAYA,QAAQQ,IAAI;QAC9D,MAAMK,UAAUrF,mBAAmBuI,WAAW;YAAEmB;YAAoBxH;QAAS;QAE7E,2EAA2E;QAC3E,IAAIM,UAAU,CAAC,KAAKgG,SAAS;gBAyCtBA,oBAAAA,cAA+BA,0BAAAA;YAxCpC,yFAAyF;YACzF,MAAMmB,SAAS1L,uBAAuB;gBAAEkH,QAAQ/H;gBAAwByH,OAAO2E;YAAK,GAAG,QAAQ,aAC1FnE;gBACHuE,iBAAiBlC;;YAEnB,mGAAmG;YACnG,MAAMO,OAAOhJ,qBAAqB;gBAChC4K,MAAM;oBAAE1E,QAAQ/H;oBAAwByH,OAAO2E;gBAA2C;gBAC1FjB;YACF;YACA,uEAAuE;YACvE,kEAAkE;YAClE,mCAAmC;YACnC,MAAMuB,WAAWhH,QAAQyB,IAAI,CAAC,CAACwF,QAAUA,MAAM1C,QAAQ,KAAK,YAAY0C,MAAMnF,EAAE,KAAKiD;YACrF,MAAMmC,UAAUF,WAAW,EAAE,GAAG;gBAAChC,OAAOU,SAASmB,OAAO3B,IAAI,EAAEC;aAAM;YACpE,IAAI0B,OAAOzE,UAAU,CAACzB,MAAM,EAAE;;oBAEVkG;gBADlB,kGAAkG;gBAClG,MAAMM,sBAAYN,eAAAA,OAAOE,IAAI,qBAAXF,aAAaO,SAAS,oBAAI,CAAC;gBAC7C,MAAMhF,aAAayE,OAAOzE,UAAU,CAAC9B,GAAG,CAAC,CAAC+G,YACxCA,UAAUC,OAAO,GAAG,aAAKD;wBAAWC,SAAS9K,eAAe6K,UAAUC,OAAO,EAAEH;yBAAeE;gBAEhG,oEAAoE;gBACpE,sEAAsE;gBACtE,qEAAqE;gBACrE,sEAAsE;gBACtE,qEAAqE;gBACrE,sEAAsE;gBACtE,mEAAmE;gBACnE,mEAAmE;gBACnE,sEAAsE;gBACtE,OAAO5K,iBAAiBsH,OAAO;oBAC7B/D,SAASkH;oBACTT,QAAQpK,iBAAiB;wBACvB4F,SAAS,GAAGhH,4BAA4BmH,YAAY,CAAC,EAAEnD,gCAAgC;wBACvFmD;wBACAmF,QAAQ;4BAAER,MAAM;gCAAE1E,QAAQ/H;gCAAwByH,OAAO2E;4BAAK;wBAAE;oBAClE;gBACF;YACF;YACA,IAAIc,QAA0B/K,iBAAiBsH;YAC/C,IAAI,GAAC2B,eAAAA,QAAQ+B,GAAG,sBAAX/B,qBAAAA,aAAaf,KAAK,qBAAlBe,mBAAoBlF,IAAI,OAAM,GAACkF,gBAAAA,QAAQ+B,GAAG,sBAAX/B,2BAAAA,cAAagC,WAAW,qBAAxBhC,yBAA0BlF,IAAI,KAAI;;gBACpE,MAAMmH,oBAAW9D,4BAAAA,SAAW,8BAAc7I,eAAe;gBACzD,kEAAkE;gBAClE,qEAAqE;gBACrE,YAAY;gBACZ,IAAI4M,SAAS;oBACXjD,OAAO/D,KAAKV,OAAO2H,QAAQ,EAAExN,yBAAyBsK,KAAK;oBAC3D+C,aAAa9G,KAAKV,OAAO4H,cAAc,EAAEzN,yBAAyBqN,WAAW;gBAC/E;gBACA,IACE,mEAAmE;gBACnE,0BAA0B;gBAC1BlM,wBAAwB;oBAAEkD,WAAWtD;oBAA0B2I,OAAO4D;oBAAUI,SAAS;gBAAE,MAC3FlL,6BACA;oBACA,MAAMuE,OAAO,MAAMG,UAAUsE,UAAU,CAAC,SAASC,GAAG,CAACjE,QAAQR,GAAG;oBAChE,MAAM2G,SAAS,MAAMzE,UAAU;wBAC7B0E,SAAS;4BAAEzI,MAAM;4BAAU0C;4BAAMgG,MAAM,CAAC,CAAC,EAAE9H,MAAM;wBAAC;wBAClD+H,OAAOhH,WAAWC;wBAClB,gEAAgE;wBAChE,iEAAiE;wBACjE,kEAAkE;wBAClE,+DAA+D;wBAC/D,gEAAgE;wBAChEgH,MAAMrN,YAAY8E,IAAI2C,MAAM;wBAC5B1B,MAAM3G,kBAAkB;4BAAE4H,OAAO2E;4BAAerE,QAAQ/H;wBAAuB;wBAC/E+N,QAAQ;4BAAC;4BAAS;yBAAc;wBAChC,oEAAoE;wBACpEC,aAAa7C,UAAUhB,OAAO,CAACnE,GAAG,CAAC,CAAC4F,MAAQA,IAAIhE,IAAI;wBACpD6B,OAAO4D;uBACH/D,SAAS;wBAAEA;oBAAO,IAAI,CAAC;oBAE7B4D,QAAQ,aAAKA,OAAUlL,kBAAkB0L;oBACzC,IAAIA,OAAOvF,MAAM,KAAK,MAAM;4BAEjBuF,qBACMA;wBAFfJ,SAAS;4BACPjD,OAAOqD,EAAAA,sBAAAA,OAAOnH,KAAK,CAAC8D,KAAK,qBAAlBqD,oBAAoBxH,IAAI,OAAMoH,OAAOjD,KAAK;4BACjD+C,aAAaM,EAAAA,4BAAAA,OAAOnH,KAAK,CAAC6G,WAAW,qBAAxBM,0BAA0BxH,IAAI,OAAMoH,OAAOF,WAAW;wBACrE;oBACF;gBACF;gBACA,MAAMxL,sBAAsBqF,WAAW;oBAAEM;oBAAQC,IAAIiD;oBAAS0C,KAAKG;oBAAQjE;gBAAI;YACjF;YACA,OAAO,aAAK6D;gBAAOxH,SAASkH;;QAC9B;QAEA,uBAAuB;QACvB,IAAI,CAACxB,SAAS;YACZ,MAAM6C,YAAY,MAAM5M,wBAAwB4F,WAAW;gBAAE/B,MAAM;gBAAUqC;gBAAQgB;YAAI;YACzF,IAAI0F,WAAW,OAAO9L,iBAAiBsH,OAAO;gBAAE0C,QAAQlK,cAAcgM;YAAW;QACnF;QACA,MAAM7J,YAAY5B,0BAA0BiH;QAC5C,MAAMyE,cAAc/J,4BAA4BC;QAChD,MAAMsJ,SAAS,MAAM9M,uBAAsC,gBAAgB;YACzEuN,MAAM;YACN1E;YACA2E,cAAc3L;YACd0I;YACAkD,UAAU;gBACR;oBACEC,MAAM;oBACNC,SAASxL,oBAAoB;wBAAEwC;wBAAKP;wBAAMY;wBAAQR;wBAAO8I;wBAAa5B;oBAAmB;gBAC3F;aACD;YACDkC,MAAM9L;YACN0B;YACA,sEAAsE;YACtE,2EAA2E;YAC3EqK,QAAQ;gBAAEhG,MAAM;gBAAWiG,SAAS1L,qBAAqB;oBAAEkL;oBAAa5B;gBAAmB;YAAG;YAC9FqC,UAAU;YACVC,OAAO/L,mBAAmB;gBACxBuJ;gBACA9B;gBACAlF;gBACA6C;gBACAb,SAASxB,OAAOd,QAAQ,CAACM,MAAM;gBAC/B+F;YACF;WACI7B,SAAS;YAAEA;QAAO,IAAI,CAAC;QAE7B,MAAM4D,QAAQlL,kBAAkB0L;QAChC,IAAIA,OAAOvF,MAAM,KAAK,WAAW,OAAO,aAAK+E;YAAO2B,SAAS;;QAC7D,IAAInB,OAAOvF,MAAM,KAAK,eAAe,OAAO,aAAK+E;YAAOf,QAAQpK,iBAAiB2L;;QAEjF,IAAI,CAACtC,SAAS;YACZ,MAAMT,QAAQ,MAAMhJ,aAAasF,WAAW;gBAC1C/B,MAAM;gBACNqC;gBACAC,IAAIiD;gBACJuB,KAAKzG,IAAI0G,SAAS;gBAClB1D;gBACAX;gBACAH,OAAOxE,kBAAkBN,eAAe+K,OAAOnH,KAAK,EAAE+D;gBACtDxE;gBACAgJ,UACElJ,OAAOmJ,MAAM,IAAI5D,UAAU6D,OAAO,CAAC7H,IAAI,CAAC,CAACyE,MAAQA,IAAIpE,EAAE,KAAK5B,OAAOmJ,MAAM,IAAInJ,OAAOmJ,MAAM,GAAG;gBAC/F1F;YACF;YACA,IAAIsB,MAAMuB,EAAE,KAAK,OAAO;gBACtB,IAAIvB,MAAMxC,MAAM,KAAK,KAAK,MAAM,IAAIqB,MAAM,CAAC,KAAK,EAAEjC,OAAO,sCAAsC,CAAC;gBAChG,OAAO,aAAK2F;oBAAOf,QAAQlK,cAAc0I,MAAMvC,KAAK;;YACtD;QACF,OAAO;YACL,MAAM6G,SAAS,MAAMvN,mBAAmBuF,WAAW;gBACjD/B,MAAM;gBACNqC;gBACAC,IAAIiD;gBACJpB;gBACA,0EAA0E;gBAC1E4F,QAAQ,CAACxH,QACPiG,OAAOnH,KAAK,CAACwB,MAAM,IAAIN,QAAQ,OAAOxE,kBAAkBwE,OAAOiG,OAAOnH,KAAK,EAAE+D;YACjF;YACA,IAAI2E,OAAO/C,EAAE,KAAK,OAAO,OAAO,aAAKgB;gBAAOxD,SAASlF;;QACvD;QACA,OAAO,aAAK0I;YAAOlD,UAAU;;IAC/B;AACF;AAEA,OAAO,MAAMkF,mBAAmBrG,sBAAqB;AAErD;;;;CAIC,GACD,OAAO,SAASsG;IACdxL,kBAAkB,QAAQuL,kBAAkB;QAC1CE,WAAW7M;QACX8M,cAAc/J;IAChB;IACA1B,wBAAwB,QAAQgB;IAChC3D,uBAAuB,QAAQ+G;AACjC"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-page-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { buildPageMarkdown } from '@aglyn/aglyn/app-utils/page-markdown'\nimport { checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { SCREEN_SEO_TEXT_GUIDANCE } from '@aglyn/aglyn/app-utils/screen-seo-fields'\nimport { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { duplicateResource } from '@aglyn/tenant-data-admin/server/duplicate-resource'\nimport { AI_BUILD_PLAN_LIMITS, type AiBuildPlanScreen } from '../model/ai-build-plan'\nimport {\n AI_PAGE_CREATE_KINDS,\n aiPageCreationRefusal,\n aiPagePlanRefusal,\n parseAiPageJobInputs,\n} from '../model/ai-page-job'\nimport { aiPlanCapabilitiesForJob, aiPlanUncreatable } from '../model/ai-plan-capabilities'\nimport { aiSiteWords } from '../model/ai-site-job'\nimport type { AiJob, AiJobOutput, AiJobPlan, AiJobReview } from '../model/ai-jobs.types'\nimport { aiModelForStep } from '../providers/routing'\nimport { aiDoctrineNeedsInputMessage, runValidatedGeneration } from '../runtime/ai-doctrine'\nimport { validateAiDoctrineTree, type AiDoctrineTree } from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport { AI_SEO_FIELDS_MAX_TOKENS, generateSeoFields } from '../runtime/seo-fields'\nimport { readSiteInventory } from '../runtime/site-inventory'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport { aiGenerationWorstCaseMs } from './ai-job-budget'\nimport { aiJobDraftId } from './ai-job-draft-ids'\nimport {\n aiDraftAdmissionRefusal,\n aiDraftAllowanceRefusal,\n aiSiteSubdomain,\n readAiDraft,\n readAiDraftNodes,\n readAiPlanCapabilities,\n updateAiDraftNodes,\n writeAiDraft,\n writeAiDraftScreenSeo,\n type AiDraftRecord,\n} from './ai-job-drafts'\nimport {\n aiBracketedFactsNote,\n aiConfirmedPlan,\n aiDoctrineReview,\n aiGenerationSpent,\n aiLimitReview,\n aiModelNodeIds,\n aiUnspentOutcome,\n} from './ai-job-generation'\nimport {\n AI_JOB_PAGE_SECTION_MAX_TOKENS,\n AI_JOB_PAGE_SECTION_TOKENS,\n AI_JOB_PAGE_STEP_BUDGET,\n AI_JOB_PAGE_STEP_MINIMUM_MS,\n aiJobPageSectionMaxTokens,\n} from './ai-job-page-budget'\nimport {\n AI_JOB_PAGE_INSTRUCTIONS,\n AI_PAGE_SECTION_TOOL,\n aiEmptyPage,\n aiPageCheckContext,\n aiPageSectionCheck,\n aiPageSectionNodeId,\n aiPageSectionPrompt,\n aiPageSectionSmaller,\n aiPageWithSection,\n type AiPageSection,\n} from './ai-job-page-sections'\nimport { AI_PLAN_ITEMS_MIN, aiPlanCopiedPageViolations } from './ai-job-plan-conformance'\nimport {\n aiCreationUnit,\n aiRunJobUnit,\n aiSiteBuiltRefs,\n aiSitePendingUnits,\n aiSiteUnitJob,\n type AiSiteUnit,\n} from './ai-job-site-step'\nimport type { AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport {\n aiJobStepRunMinimumMs,\n aiJobStepRunnerFor,\n registerAiJobStep,\n registerAiJobStepPasses,\n} from './ai-jobs'\n\n/**\n * The page step (AGL-2907): the generation step of a `page` job, run once a\n * member confirmed the job's plan. It builds ONE screen, section by section,\n * from what the site already has, and leaves it an unpublished draft.\n *\n * ── Why a page is built in passes ────────────────────────────────────────\n *\n * A page is far larger than one answer the job beat can wait for: the beat\n * gives a step what is left of its budget, and a whole page, answered and re-asked,\n * takes several times that. The confirmed plan already names the page's\n * sections, so the step builds one section per pass and asks the machine to\n * continue. Every pass is one reservation and one generation — the section's\n * lookup rounds, its answer and, when it breaks a rule, its one re-ask — sized\n * so that worst case fits `AI_JOB_PAGE_STEP_MINIMUM_MS`, which the step\n * registers as the least time a section pass needs before it starts.\n *\n * - The first pass builds the top section, which holds the page's h1, and\n * writes the draft screen and its first version.\n * - Each later pass builds the next section and adds it to that version.\n * - The last pass generates no tree: it holds the whole page to the\n * doctrine, writes the search title and description, and reports the\n * draft with its measured weight and a navigation proposal.\n *\n * How a section is asked for and held to the rules as part of the page is\n * `ai-job-page-sections.ts`.\n *\n * ── What the plan creates comes first (AGL-3031) ─────────────────────────\n *\n * A plan that creates a layout, forms or components is built in the same job,\n * before its page, one creation a pass, through the site scaffold's unit\n * machinery: each creation is handed to the step that builds that kind, under\n * a job derived from this one, and lands as the draft that step writes. Where\n * the job stands is read from its outputs, so nothing is kept anywhere else.\n * Once every creation is built, the page's passes run on the plan with each\n * `new:<name>` resolved to the record that was built, which the site's\n * inventory now lists — so the page places the new component and binds the new\n * form by id, and renders inside the new layout, exactly as it would one the\n * site always had. A pass that builds a creation needs the time the step that\n * builds it registers, not a section's (`aiPageJobRunMinimumMs`, AGL-3035), so\n * neither the beat nor a door starts it with only a section's time left.\n *\n * ── Never published ──────────────────────────────────────────────────────\n *\n * The draft is written the way `/api/hosts/resources` and\n * `/api/hosts/versions` write a screen and its first version, and nothing\n * else is touched: not the host document, whose routing map is what the live\n * site serves, and no collection, store, layout or navigation pointer\n * (`ai-job-drafts.ts`). A screen the routing map does not name serves nothing\n * until a member publishes it.\n */\n\n/**\n * A section pass's time, and the ceiling each pass asks on each model, are\n * declared apart from the step (`ai-job-page-budget.ts`): the site scaffold\n * builds its pages through this step and reads them without loading it.\n */\nexport {\n AI_JOB_PAGE_SECTION_MAX_TOKENS,\n AI_JOB_PAGE_SECTION_TOKENS,\n AI_JOB_PAGE_STEP_BUDGET,\n AI_JOB_PAGE_STEP_MINIMUM_MS,\n aiJobPageSectionMaxTokens,\n}\n\n/**\n * Tokens one element takes in a section's answer, ESTIMATED: the tree as JSON\n * text inside the tool call, at four characters a token. Measured on the ten\n * golden pages at a median of 38 and a most of 43 over their 39 sections, and\n * `ai-job-page-evals.spec.ts` holds every golden section under it.\n */\nexport const AI_JOB_PAGE_TOKENS_PER_ELEMENT = 45\n\n/**\n * Real tokens to every token estimated at four characters (AGL-3042). A\n * section's ceiling is counted by the provider in its own tokens, which run\n * above that estimate. MEASURED on the first live document run on a Free\n * workspace (AGL-3024, test-org, 2026-09-16), as the cache read of a request\n * over the estimate of the same cached prefix: 4,059 over 2,825 for the page\n * plan and 6,649 over 4,310 for the layout, the higher of the two, 1.5427 —\n * the ratio `ai-job-free-page.spec.ts` prices a Free page at. It is measured on\n * prompt text; an answer's own is read off a recorded pass, whose output\n * tokens the step's record keeps (`lastRuns`), beside the section it stored.\n */\nexport const AI_JOB_PAGE_REAL_TOKENS_PER_ESTIMATED = Math.max(4_059 / 2_825, 6_649 / 4_310)\n\n/**\n * Real tokens one element takes in a section's answer: the estimate at its\n * golden most, in real tokens, rounded up — 45 × 1.5427 → 70 (AGL-3042).\n */\nexport const AI_JOB_PAGE_REAL_TOKENS_PER_ELEMENT = Math.ceil(\n AI_JOB_PAGE_TOKENS_PER_ELEMENT * AI_JOB_PAGE_REAL_TOKENS_PER_ESTIMATED,\n)\n\n/**\n * The most elements a section's request asks it to keep under at an answer\n * ceiling: 15 at the balanced tier's 1,050 (AGL-3042). Counted in real\n * tokens, because the ceiling is: counted in the estimate, the budget is 23\n * elements, which a section of the goldens' largest elements fills at 1,597\n * real tokens — half as much again as the ceiling that cuts it off.\n */\nexport function aiJobPageSectionMaxElements(maxTokens: number): number {\n return Math.max(1, Math.floor(maxTokens / AI_JOB_PAGE_REAL_TOKENS_PER_ELEMENT))\n}\n\n/** A page's name when the plan names none. */\nexport const AI_JOB_PAGE_DEFAULT_NAME = 'New page'\n\nexport const AI_JOB_PAGE_NO_PLAN_COPY = 'This page job has no confirmed plan to build.'\nexport const AI_JOB_PAGE_DELETED_COPY = 'The draft page was deleted while it was being built.'\n\n/** A creation the plan names that this deployment has no step to build. */\nexport const AI_JOB_PAGE_CREATION_UNAVAILABLE_COPY =\n 'This page needs something that cannot be built here yet. Describe the page again.'\n\n/** A creation that finished without reporting what it built; the page cannot place it. */\nexport const AI_JOB_PAGE_CREATION_EMPTY_COPY =\n 'Part of this page could not be built. Describe the page again.'\n\n/**\n * What a page refused on its last pass tells the member beside the rules it\n * broke (AGL-3143). The draft is reported with the review, and mending what\n * the findings name in it is what lets the next pass finish the job.\n */\nexport const AI_JOB_PAGE_REFUSED_DRAFT_COPY =\n 'The draft is yours to open: mend what these name in it, then try again.'\n\n/**\n * The most passes a page job may take (AGL-3031): every creation the plan\n * limits admit, every section they admit, and the last pass that reports the\n * draft. A creation's step builds it in one pass, and a page one section a\n * pass, so a runner that never finishes is still bounded while a real page is\n * not.\n */\nexport const AI_JOB_PAGE_MAX_PASSES = AI_BUILD_PLAN_LIMITS.create + AI_BUILD_PLAN_LIMITS.sections + 1\n\n/**\n * The units a plan's creations are built as, in the order a scaffold builds\n * them: the layout, then the forms, then the components. Each is addressed by\n * its place in the plan, so a unit run again finds its own draft.\n */\nexport function aiPageJobUnits(plan: Pick<AiJobPlan, 'create'>): AiSiteUnit[] {\n return AI_PAGE_CREATE_KINDS.flatMap((kind) =>\n plan.create.flatMap((creation, index) => {\n if (creation.kind !== kind) return []\n const unit = aiCreationUnit(creation, `c${index}`)\n return unit ? [unit] : []\n }),\n )\n}\n\n/**\n * The least time this page job's next pass needs (AGL-3035). A pass that\n * builds a creation hands it to the step registered for that kind, under the\n * job derived for it, so it needs what that step registers for that job — a\n * layout's time, not a section's. Every other pass — a section, the last one —\n * needs a section pass's.\n */\nexport function aiPageJobRunMinimumMs(job: AiJob): number {\n const confirmed = aiConfirmedPlan(job)\n if (!confirmed) return AI_JOB_PAGE_STEP_MINIMUM_MS\n const units = aiPageJobUnits(confirmed)\n const outputs = job.outputs ?? []\n const [unit] = aiSitePendingUnits(units, outputs)\n if (!unit) return AI_JOB_PAGE_STEP_MINIMUM_MS\n return aiJobStepRunMinimumMs(aiSiteUnitJob(job, unit, aiSiteBuiltRefs(units, outputs)))\n}\n\n/** The one-segment address a draft asks for, from the plan's slug. */\nexport function aiPageDraftSlug(screen: Pick<AiBuildPlanScreen, 'slug'>): string {\n const segments = screen.slug.split('/').map((part) => part.trim()).filter(Boolean)\n return segments[segments.length - 1] ?? ''\n}\n\n/** A plan's listing value held to the length the SEO editor guides it to, cut at a word. */\nfunction clip(value: string, max: number): string {\n const text = value.trim()\n if (text.length <= max) return text\n const cut = text.slice(0, max + 1)\n const space = cut.lastIndexOf(' ')\n return (space > max / 2 ? cut.slice(0, space) : text.slice(0, max)).trim()\n}\n\n/** The site's name as its visitors see it, read the way the SEO step reads it. */\nfunction siteNameOf(host: FirebaseFirestore.DocumentSnapshot): string {\n const text = (value: unknown) => (typeof value === 'string' ? value.trim() : '')\n return text(host.get('seo.title')) || text(host.get('displayName')) || text(host.get('subdomain'))\n}\n\n/**\n * The counts the confirmed plan's sections promised, against the page a COPY\n * actually produced (AGL-3024); `null` when the copy keeps the plan, or when\n * the plan promised no count to keep.\n *\n * The same hole the layout copy had (`aiCopiedLayoutReview`), on the kind the\n * defect was measured on. This branch generates nothing, so no model ever\n * answers for the plan: a plan reading \"start from the Services page, one\n * card per practice area\" gets the Services page and whatever IT had, and\n * until now reported `Done` — the one report that tells a customer there is\n * no reason to re-read the page.\n *\n * A copy that cannot be read back settles nothing, and a promise this step\n * cannot settle is reviewed by a person rather than reported as kept.\n */\nexport async function aiCopiedPageReview(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; id: string; name: string; screen: Pick<AiBuildPlanScreen, 'sections'> },\n): Promise<AiJobReview | null> {\n if (!input.screen.sections.some((section) => section.items >= AI_PLAN_ITEMS_MIN)) return null\n const stored = await readAiDraftNodes(firestore, { kind: 'screen', hostId: input.hostId, id: input.id })\n const nodes = (stored?.nodes ?? {}) as unknown as AiDoctrineTree['nodes']\n if (!nodes[CANVAS_ROOT_ELEMENT_ID]) {\n return {\n reason: 'doctrine',\n message: `\"${input.name}\" was copied from the page the plan names, and this job could not read the copy back to check it shows what the plan says it shows. Open the page and check it before you put it in front of anyone.`,\n findings: [],\n }\n }\n const violations = aiPlanCopiedPageViolations(input.screen, { rootId: CANVAS_ROOT_ELEMENT_ID, nodes })\n if (!violations.length) return null\n return aiDoctrineReview({\n message: `\"${input.name}\" is a copy of the page the plan names, and a copy is all it is: ${violations[0].message}`,\n violations,\n })\n}\n\n/**\n * A page job is admitted with inputs that read, for a site of the job's own\n * org with a screen to spare; and confirmed only for a plan it can build —\n * one screen, whose creations are ones a page job builds, this deployment has\n * a step for, and this workspace may still make on this site (AGL-3031).\n */\nexport const aiPageJobAdmission: AiJobAdmission = async (context) => {\n const inputs = parseAiPageJobInputs(context.inputs)\n if (typeof inputs === 'string') return { status: 400, error: inputs }\n const plan = context.plan\n if (plan) {\n const refusal = aiPagePlanRefusal(plan)\n if (refusal) return { status: 400, error: refusal }\n if (aiPageJobUnits(plan).some((unit) => !aiJobStepRunnerFor(unit.jobKind))) {\n return { status: 400, error: AI_JOB_PAGE_CREATION_UNAVAILABLE_COPY }\n }\n }\n const org = context.org as Partial<AglynOrgBilling> | null\n return aiDraftAdmissionRefusal(context.firestore, {\n orgId: context.orgId,\n hostId: context.hostId,\n kind: 'screen',\n noun: 'page',\n org,\n // What the workspace may still make here, read once the site is known to\n // be the org's: the site may have changed since the plan was told.\n ...(plan?.create.length\n ? {\n ownCheck: async (hostId: string) => {\n const capabilities = aiPlanCapabilitiesForJob(\n await readAiPlanCapabilities(context.firestore, { hostId, org }),\n { noun: 'a page job', creates: AI_PAGE_CREATE_KINDS },\n )\n const refusal = aiPageCreationRefusal(aiPlanUncreatable(plan, capabilities))\n return refusal ? { status: 403 as const, error: refusal } : null\n },\n }\n : {}),\n })\n}\n\nexport interface AiJobPageStepDeps {\n /** The inventory reader; specs hand in a fake. */\n readInventory?: typeof readSiteInventory\n /** The platform's duplicate module, for a plan that starts from a copy. */\n duplicate?: typeof duplicateResource\n /** The listing generator (AGL-2910). */\n seoFields?: typeof generateSeoFields\n /** The runners a plan's creations are built by (AGL-3031); the registry's otherwise. */\n runnerFor?: typeof aiJobStepRunnerFor\n}\n\nexport function createAiJobPageStep(deps: AiJobPageStepDeps = {}): AiJobStepRunner {\n const readInventory = deps.readInventory ?? readSiteInventory\n const duplicate = deps.duplicate ?? duplicateResource\n const seoFields = deps.seoFields ?? generateSeoFields\n const runnerFor = deps.runnerFor ?? aiJobStepRunnerFor\n return async (stepContext): Promise<AiJobStepOutcome> => {\n const { job, now, signal, firestore, modelFor } = stepContext\n const hostId = job.hostId\n if (!hostId) throw new Error('a page job names no site, and its admission refuses one')\n // The switch's answer for this job, else the routing table's (AGL-2942).\n const model = modelFor?.('job.page') ?? aiModelForStep('job.page')\n const confirmed = aiConfirmedPlan(job)\n if (!confirmed) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_NO_PLAN_COPY }\n // The resume door refuses such a plan at confirmation; one confirmed\n // before a site changed still stops here, before any spend.\n const refusal = aiPagePlanRefusal(confirmed)\n if (refusal) return { ...aiUnspentOutcome(model), failure: refusal }\n\n // ── The creations first (AGL-3031): one a pass, by the steps that own them ──\n const units = aiPageJobUnits(confirmed)\n const outputs = job.outputs ?? []\n const [unit] = aiSitePendingUnits(units, outputs)\n if (unit) {\n const runner = runnerFor(unit.jobKind)\n if (!runner) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_CREATION_UNAVAILABLE_COPY }\n const pass = await aiRunJobUnit(stepContext, {\n unit,\n units,\n runner,\n emptyCopy: AI_JOB_PAGE_CREATION_EMPTY_COPY,\n })\n // A built creation is followed by the next, or by the page's first section.\n return pass.built ? { ...pass.outcome, continue: true } : pass.outcome\n }\n // The page's plan with every creation resolved to the record it built, as\n // a scaffold's page is planned against what the scaffold built.\n const plan: AiJobPlan = units.length\n ? (aiSiteUnitJob(\n job,\n {\n kind: 'page',\n jobKind: 'page',\n resource: 'screen',\n slot: 'page',\n screen: confirmed.screens[0],\n label: confirmed.screens[0].title,\n },\n aiSiteBuiltRefs(units, outputs),\n ).plan as AiJobPlan)\n : confirmed\n const screen = plan.screens[0] as AiBuildPlanScreen\n const name = screen.title || AI_JOB_PAGE_DEFAULT_NAME\n const slug = aiPageDraftSlug(screen)\n const sectionIds = screen.sections.map((_, index) => aiPageSectionNodeId(job.$id, index))\n // The draft screen, by the id the job recorded for it: every pass writes and finds the same one.\n const draftId = aiJobDraftId(job, 'screen')\n\n const output = (\n draft: Pick<AiDraftRecord, 'id' | 'versionId' | 'name' | 'hostSubdomain'>,\n load?: AiLoadEstimate | null,\n note?: string | null,\n ): AiJobOutput => ({\n resource: 'screen',\n id: draft.id,\n versionId: draft.versionId,\n hostId,\n hostSubdomain: draft.hostSubdomain,\n label: draft.name,\n ...(load ? { load } : {}),\n ...(note ? { note } : {}),\n // Rule 10: a navigation entry travels with the page as a proposal, for\n // the member to add once the page is live; no menu is written.\n ...(screen.nav ? { proposal: { navigation: { label: name, slug } } } : {}),\n })\n\n const [inventory, orgSnapshot, written] = await Promise.all([\n readInventory(job.orgId, hostId, { firestore }),\n firestore.collection('orgs').doc(job.orgId).get(),\n readAiDraft(firestore, { kind: 'screen', hostId, id: draftId }),\n ])\n const org = (orgSnapshot.data() ?? null) as Partial<AglynOrgBilling> | null\n if (written?.deleted) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_DELETED_COPY }\n\n // The plan starts from a copy of a screen the site has (rule 15): the copy\n // is the draft — unrouted, as every copy is — and nothing is generated.\n // Only a page is a page's start: a collection entry template's copy stays\n // a template, so a plan naming one builds the page instead.\n // A copy shows what its SOURCE shows, and this branch generates nothing.\n // So if the source cannot already show what the plan promised, copying it\n // can only produce a page that breaks the promise — the refusal below\n // would be certain before the copy was ever minted. Read the source and\n // build instead, rather than leave the member a page they must throw away\n // and a Try again that can only copy the same thing (AGL-3024).\n // Only a source we could READ and found short diverts: one we cannot read\n // is one we cannot judge, and the review after the copy still answers for\n // that. A short source carries findings; an unreadable one carries none.\n const sourceReview =\n !written && screen.duplicateOf\n ? await aiCopiedPageReview(firestore, { hostId, id: screen.duplicateOf, name, screen })\n : null\n const sourceShort = Boolean(sourceReview?.findings.length)\n if (\n !written &&\n !sourceShort &&\n screen.duplicateOf &&\n inventory.screens.some((row) => row.id === screen.duplicateOf && !row.template)\n ) {\n const copy = await duplicate('screen', {\n orgId: job.orgId,\n hostId,\n sourceId: screen.duplicateOf,\n name,\n uid: job.createdBy,\n org: org as Record<string, unknown> | null,\n // A copy core mints is written under core's id, never `draftId`, so\n // `written` above cannot find it and this branch would fire on every\n // pass — and the pass after a refused copy is the common one, since\n // the review below offers to try again. The job's recorded id is the\n // claim, so a second pass replays the one copy instead of leaving\n // another orphan screen behind it (AGL-3024).\n attemptKey: draftId,\n })\n if (copy.ok) {\n // A copy is not a build (AGL-3024). Nothing is generated here, so\n // whatever the plan promised BEYOND the source is in the draft only\n // if the source already had it, and no model is left to answer for\n // the difference — the copy itself has to.\n const review = await aiCopiedPageReview(firestore, { hostId, id: copy.id, name, screen })\n if (review) return aiUnspentOutcome(model, { review })\n const hostSubdomain = await aiSiteSubdomain(firestore, hostId)\n return aiUnspentOutcome(model, {\n outputs: [output({ id: copy.id, versionId: copy.versionId, name: copy.name, hostSubdomain })],\n })\n }\n if (copy.ok === false && copy.status === 403) {\n return aiUnspentOutcome(model, { review: aiLimitReview(copy.error) })\n }\n }\n\n const stored = written ? await readAiDraftNodes(firestore, { kind: 'screen', hostId, id: draftId }) : null\n if (written && !stored) return { ...aiUnspentOutcome(model), failure: AI_JOB_PAGE_DELETED_COPY }\n const page = stored?.nodes ?? aiEmptyPage()\n const index = sectionIds.findIndex((id) => !(id in page))\n // A workspace that keeps no reusable components or saved forms builds its\n // page inline, and every pass is held to the rules that way (AGL-3030).\n const reusableComponents = checkEntitlement(org, 'reusableComponents')\n // A link may take a visitor to a section of this page, by its name in the plan (AGL-3097).\n const sections = screen.sections.map((section) => section.name)\n const context = aiPageCheckContext(inventory, { reusableComponents, sections })\n\n // ── The last pass: the whole page, its listing, and the draft reported ──\n if (index === -1 && written) {\n // A link the page carries to one of its sections goes there, now every section is built.\n const report = validateAiDoctrineTree({ rootId: CANVAS_ROOT_ELEMENT_ID, nodes: page }, 'page', {\n ...context,\n scrollTargetIds: sectionIds,\n })\n // The facts the brief did not give, on the page or in the defaults its components show (AGL-3056).\n const note = aiBracketedFactsNote({\n tree: { rootId: CANVAS_ROOT_ELEMENT_ID, nodes: page as unknown as AiDoctrineTree['nodes'] },\n inventory,\n })\n // The page's own draft, reported once however many times the last pass\n // runs (AGL-3143): a refused pass reports it and a later one that\n // passes does not report it twice.\n const reported = outputs.some((entry) => entry.resource === 'screen' && entry.id === draftId)\n const reports = reported ? [] : [output(written, report.load, note)]\n if (report.violations.length) {\n // The check mints its own ids; the review names the nodes by the ids the draft stores them under.\n const storedIds = report.tree?.sourceIds ?? {}\n const violations = report.violations.map((violation) =>\n violation.nodeIds ? { ...violation, nodeIds: aiModelNodeIds(violation.nodeIds, storedIds) } : violation,\n )\n // THE DRAFT TRAVELS WITH THE REFUSAL (AGL-3143). Every other review\n // parks on a model's answer, so trying again asks the model again and\n // can come back different. This one reads the STORED page and spends\n // nothing, so trying again refuses the same page for the same reasons\n // forever — and a job parked here having reported no output names no\n // draft at all, which leaves the member nowhere to mend the nodes the\n // findings name and no way to finish the job from the console. The\n // draft is reported now instead, and the job still parks: what the\n // review names is mended in the draft, and the next pass then passes.\n return aiUnspentOutcome(model, {\n outputs: reports,\n review: aiDoctrineReview({\n message: `${aiDoctrineNeedsInputMessage(violations)} ${AI_JOB_PAGE_REFUSED_DRAFT_COPY}`,\n violations,\n answer: { tree: { rootId: CANVAS_ROOT_ELEMENT_ID, nodes: page } },\n }),\n })\n }\n let spent: AiJobStepOutcome = aiUnspentOutcome(model)\n if (!written.seo?.title?.trim() || !written.seo?.description?.trim()) {\n const seoModel = modelFor?.('job.seo') ?? aiModelForStep('job.seo')\n // The plan's own listing, held to the editor's guidance, when the\n // listing cannot be written inside this pass's time on the model the\n // job runs.\n let values = {\n title: clip(screen.seoTitle, SCREEN_SEO_TEXT_GUIDANCE.title),\n description: clip(screen.seoDescription, SCREEN_SEO_TEXT_GUIDANCE.description),\n }\n if (\n // A listing is written from the page's own text, with no inventory\n // and so no lookup round.\n aiGenerationWorstCaseMs({ maxTokens: AI_SEO_FIELDS_MAX_TOKENS, model: seoModel, lookups: 0 }) <=\n AI_JOB_PAGE_STEP_MINIMUM_MS\n ) {\n const host = await firestore.collection('hosts').doc(hostId).get()\n const result = await seoFields({\n subject: { kind: 'screen', name, path: `/${slug}` },\n brand: siteNameOf(host),\n // What the site is and who it is for (AGL-2918). The page's own\n // Markdown says what the page is about and cannot say who it was\n // written for, so the listing borrows the two answers the job was\n // started from — the scaffold's derived page job carries them,\n // and a page job started from the Screens page carries neither.\n site: aiSiteWords(job.inputs),\n text: buildPageMarkdown({ nodes: page as never, rootId: CANVAS_ROOT_ELEMENT_ID }),\n fields: ['title', 'description'],\n // Other screens by name only: their own listings are their content.\n otherTitles: inventory.screens.map((row) => row.name),\n model: seoModel,\n ...(signal ? { signal } : {}),\n })\n spent = { ...spent, ...aiGenerationSpent(result) }\n if (result.status === 'ok') {\n values = {\n title: result.value.title?.trim() || values.title,\n description: result.value.description?.trim() || values.description,\n }\n }\n }\n await writeAiDraftScreenSeo(firestore, { hostId, id: draftId, seo: values, now })\n }\n return { ...spent, outputs: reports }\n }\n\n // ── A section pass ──\n if (!written) {\n const allowance = await aiDraftAllowanceRefusal(firestore, { kind: 'screen', hostId, org })\n if (allowance) return aiUnspentOutcome(model, { review: aiLimitReview(allowance) })\n }\n const maxTokens = aiJobPageSectionMaxTokens(model)\n const maxElements = aiJobPageSectionMaxElements(maxTokens)\n const result = await runValidatedGeneration<AiPageSection>('page-section', {\n step: 'job.page',\n model,\n instructions: AI_JOB_PAGE_INSTRUCTIONS,\n inventory,\n messages: [\n {\n role: 'user',\n content: aiPageSectionPrompt({ job, plan, screen, index, maxElements, reusableComponents }),\n },\n ],\n tool: AI_PAGE_SECTION_TOOL,\n maxTokens,\n // A section cut off at its ceiling is asked for smaller, and a member\n // whose section still does not fit reads that it was too large (AGL-3042).\n cutOff: { noun: 'section', smaller: aiPageSectionSmaller({ maxElements, reusableComponents }) },\n thinking: 'off',\n check: aiPageSectionCheck({\n page,\n sectionIds,\n index,\n context,\n section: screen.sections[index],\n inventory,\n }),\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result) }\n\n if (!written) {\n const draft = await writeAiDraft(firestore, {\n kind: 'screen',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: aiPageWithSection(aiEmptyPage(), result.value, sectionIds),\n slug,\n layoutId:\n screen.layout && inventory.layouts.some((row) => row.id === screen.layout) ? screen.layout : null,\n now,\n })\n if (draft.ok === false) {\n if (draft.status === 404) throw new Error(`site ${hostId} vanished while its page was generated`)\n return { ...spent, review: aiLimitReview(draft.error) }\n }\n } else {\n const update = await updateAiDraftNodes(firestore, {\n kind: 'screen',\n hostId,\n id: draftId,\n now,\n // A pass run again after its write finds its section and changes nothing.\n update: (nodes) =>\n result.value.rootId in nodes ? null : aiPageWithSection(nodes, result.value, sectionIds),\n })\n if (update.ok === false) return { ...spent, failure: AI_JOB_PAGE_DELETED_COPY }\n }\n return { ...spent, continue: true }\n }\n}\n\nexport const runAiJobPageStep = createAiJobPageStep()\n\n/**\n * Registers the page step with the least time a section pass needs and the\n * time its next pass needs, the passes a page with its creations may take, and\n * the check a page job passes when it is created or its plan is confirmed.\n */\nexport function registerAiPageJob(): void {\n registerAiJobStep('page', runAiJobPageStep, {\n minimumMs: AI_JOB_PAGE_STEP_MINIMUM_MS,\n minimumMsFor: aiPageJobRunMinimumMs,\n })\n registerAiJobStepPasses('page', AI_JOB_PAGE_MAX_PASSES)\n registerAiJobAdmission('page', aiPageJobAdmission)\n}\n"],"names":["buildPageMarkdown","checkEntitlement","SCREEN_SEO_TEXT_GUIDANCE","CANVAS_ROOT_ELEMENT_ID","duplicateResource","AI_BUILD_PLAN_LIMITS","AI_PAGE_CREATE_KINDS","aiPageCreationRefusal","aiPagePlanRefusal","parseAiPageJobInputs","aiPlanCapabilitiesForJob","aiPlanUncreatable","aiSiteWords","aiModelForStep","aiDoctrineNeedsInputMessage","runValidatedGeneration","validateAiDoctrineTree","AI_SEO_FIELDS_MAX_TOKENS","generateSeoFields","readSiteInventory","registerAiJobAdmission","aiGenerationWorstCaseMs","aiJobDraftId","aiDraftAdmissionRefusal","aiDraftAllowanceRefusal","aiSiteSubdomain","readAiDraft","readAiDraftNodes","readAiPlanCapabilities","updateAiDraftNodes","writeAiDraft","writeAiDraftScreenSeo","aiBracketedFactsNote","aiConfirmedPlan","aiDoctrineReview","aiGenerationSpent","aiLimitReview","aiModelNodeIds","aiUnspentOutcome","AI_JOB_PAGE_SECTION_MAX_TOKENS","AI_JOB_PAGE_SECTION_TOKENS","AI_JOB_PAGE_STEP_BUDGET","AI_JOB_PAGE_STEP_MINIMUM_MS","aiJobPageSectionMaxTokens","AI_JOB_PAGE_INSTRUCTIONS","AI_PAGE_SECTION_TOOL","aiEmptyPage","aiPageCheckContext","aiPageSectionCheck","aiPageSectionNodeId","aiPageSectionPrompt","aiPageSectionSmaller","aiPageWithSection","AI_PLAN_ITEMS_MIN","aiPlanCopiedPageViolations","aiCreationUnit","aiRunJobUnit","aiSiteBuiltRefs","aiSitePendingUnits","aiSiteUnitJob","aiJobStepRunMinimumMs","aiJobStepRunnerFor","registerAiJobStep","registerAiJobStepPasses","AI_JOB_PAGE_TOKENS_PER_ELEMENT","AI_JOB_PAGE_REAL_TOKENS_PER_ESTIMATED","Math","max","AI_JOB_PAGE_REAL_TOKENS_PER_ELEMENT","ceil","aiJobPageSectionMaxElements","maxTokens","floor","AI_JOB_PAGE_DEFAULT_NAME","AI_JOB_PAGE_NO_PLAN_COPY","AI_JOB_PAGE_DELETED_COPY","AI_JOB_PAGE_CREATION_UNAVAILABLE_COPY","AI_JOB_PAGE_CREATION_EMPTY_COPY","AI_JOB_PAGE_REFUSED_DRAFT_COPY","AI_JOB_PAGE_MAX_PASSES","create","sections","aiPageJobUnits","plan","flatMap","kind","creation","index","unit","aiPageJobRunMinimumMs","job","confirmed","units","outputs","aiPageDraftSlug","screen","segments","slug","split","map","part","trim","filter","Boolean","length","clip","value","text","cut","slice","space","lastIndexOf","siteNameOf","host","get","aiCopiedPageReview","firestore","input","some","section","items","stored","hostId","id","nodes","reason","message","name","findings","violations","rootId","aiPageJobAdmission","context","inputs","status","error","refusal","jobKind","org","orgId","noun","ownCheck","capabilities","creates","createAiJobPageStep","deps","readInventory","duplicate","seoFields","runnerFor","stepContext","orgSnapshot","now","signal","modelFor","Error","model","failure","runner","pass","emptyCopy","built","outcome","continue","resource","slot","screens","label","title","sectionIds","_","$id","draftId","output","draft","load","note","versionId","hostSubdomain","nav","proposal","navigation","inventory","written","Promise","all","collection","doc","data","deleted","sourceReview","duplicateOf","sourceShort","row","template","copy","sourceId","uid","createdBy","attemptKey","ok","review","page","findIndex","reusableComponents","report","scrollTargetIds","tree","reported","entry","reports","storedIds","sourceIds","violation","nodeIds","answer","spent","seo","description","seoModel","values","seoTitle","seoDescription","lookups","result","subject","path","brand","site","fields","otherTitles","allowance","maxElements","step","instructions","messages","role","content","tool","cutOff","smaller","thinking","check","refused","layoutId","layout","layouts","update","runAiJobPageStep","registerAiPageJob","minimumMs","minimumMsFor"],"mappings":"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"}
@@ -28,7 +28,7 @@ import { readSiteInventory } from "../runtime/site-inventory.js";
28
28
  import { registerAiJobAdmission } from "./ai-job-admission.js";
29
29
  import { aiJobDraftId } from "./ai-job-draft-ids.js";
30
30
  import { aiDraftAdmissionRefusal, aiDraftAllowanceRefusal, aiSiteSubdomain, readAiDraft, writeAiDraft } from "./ai-job-drafts.js";
31
- import { aiConfirmedPlan, aiDoctrineReview, aiGenerationSpent, aiJobBriefLine, aiLimitReview, aiModelNodeIds, aiPlanCreation, aiPlanReferenceLines, aiUnspentOutcome } from "./ai-job-generation.js";
31
+ import { aiConfirmedPlan, aiDoctrineReview, aiGenerationSpent, aiJobBriefLine, aiLimitReview, aiModelNodeIds, aiPlacedComponentIds, aiPlanCreation, aiPlanReferenceLines, aiPlanReuseViolations, aiUnspentOutcome } from "./ai-job-generation.js";
32
32
  import { aiPlanTemplateTokenViolations } from "./ai-job-plan-conformance.js";
33
33
  import { aiJobStepBudget } from "./ai-job-budget.js";
34
34
  import { registerAiJobStep } from "./ai-jobs.js";
@@ -346,14 +346,19 @@ export function createAiJobTemplateStep(deps = {}) {
346
346
  context: {
347
347
  bindingTokens: aiTemplateAddressTokens(definition)
348
348
  },
349
- // Rule 8's binding check, and the confirmed plan kept: a token the
350
- // plan promised that the template does not bind (AGL-3143 §11).
349
+ // Rule 8's binding check, and the confirmed plan kept in both halves: a
350
+ // token the plan promised that the template does not bind (AGL-3143
351
+ // §11), and a component the plan reuses that it does not place. The
352
+ // layout and component doors hold their builds to that reuse; the
353
+ // template door did not, so it could drop a promised component and
354
+ // still answer `Done` (AGL-3024).
351
355
  extend: (tree)=>[
352
356
  ...aiTemplateBindingCheck(definition)(tree),
353
357
  ...aiPlanTemplateTokenViolations(plan, definition, {
354
358
  rootId: tree.rootId,
355
359
  nodes: tree.nodes
356
- })
360
+ }),
361
+ ...aiPlanReuseViolations(inventory, plan, aiPlacedComponentIds(tree), 'template')
357
362
  ]
358
363
  }, signal ? {
359
364
  signal
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-template-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 { hostCollectionKind } from '@aglyn/aglyn/app-utils/collection-kind'\nimport { normalizeScreenSlug } from '@aglyn/aglyn/app-utils/screen-route'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { duplicateResource } from '@aglyn/tenant-data-admin/server/duplicate-resource'\nimport type { AiJob, AiJobOutput, AiJobPlan } from '../model/ai-jobs.types'\nimport {\n AI_TEMPLATE_SUBJECT_DEFINITIONS,\n AI_TEMPLATE_SUBJECTS,\n aiBindingTokensIn,\n aiTemplateAddressTokens,\n parseAiTemplateJobInputs,\n type AiTemplateSubjectDefinition,\n} from '../model/ai-template-subjects'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n aiDoctrineTreeTool,\n runValidatedGeneration,\n type AiValidatedTree,\n} from '../runtime/ai-doctrine'\nimport {\n walkTree,\n type AiDoctrineNode,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport { AI_PALETTE } from '../runtime/ai-palette.generated'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { aiTemplateExamplesSystemBlock } from '../runtime/ai-template-examples'\nimport { readSiteInventory } from '../runtime/site-inventory'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport { aiJobDraftId } from './ai-job-draft-ids'\nimport {\n aiDraftAdmissionRefusal,\n aiDraftAllowanceRefusal,\n aiSiteSubdomain,\n readAiDraft,\n writeAiDraft,\n type AiDraftRecord,\n} from './ai-job-drafts'\nimport {\n aiConfirmedPlan,\n aiDoctrineReview,\n aiGenerationSpent,\n aiJobBriefLine,\n aiLimitReview,\n aiModelNodeIds,\n aiPlanCreation,\n aiPlanReferenceLines,\n aiUnspentOutcome,\n} from './ai-job-generation'\nimport { aiPlanTemplateTokenViolations } from './ai-job-plan-conformance'\nimport type { AiJobStepRunner } from './ai-job-text-step'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The template step (AGL-2909): the generation step of a `template` job, run\n * once a member confirmed the job's plan.\n *\n * It builds ONE page template — the page a site renders once per record: a\n * content collection's entry, a product, an author — through\n * `runValidatedGeneration('template', …)`. Everything that differs per\n * record is a binding token from the subject's vocabulary\n * (`model/ai-template-subjects.ts`), which is the list the besigner's insert\n * picker offers for that page; a link or media prop may hold one of those\n * tokens whole, which the palette validator admits only because this door\n * names them. The door adds one check of its own through `extend` (rule 8):\n * no token the page does not fill, an h1 bound to the subject's title, and no\n * block that fills itself only on another subject's page.\n *\n * The model is shown the platform's starter pages as examples, in a cached\n * block (`runtime/ai-template-examples.ts`), and writes against them.\n *\n * The template lands in the site's library as a new draft\n * (`ai-job-drafts.ts`), inert until a member uses it: bound to no\n * collection, set as no product or author page, served nowhere. A plan that\n * starts from a copy of a template the site has gets that copy instead.\n */\n\n/** The longest answer a template may run to; the tree is held to the template budget either way. */\nexport const AI_JOB_TEMPLATE_MAX_TOKENS = AI_ROUTING_TABLE['job.template'].maxTokens\n\n/**\n * The template step's time (AGL-3035, AGL-3036): its two inventory-lookup\n * rounds, its answer and its re-ask at the routing table's ceiling on the tier\n * `job.template` is served from, fitted to what a beat can give a step. It\n * runs on the job beat, never at an inline door, and asks for no more of the\n * ceiling than fits that time on the model it runs.\n */\nexport const AI_JOB_TEMPLATE_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.template'],\n maxTokens: AI_JOB_TEMPLATE_MAX_TOKENS,\n})\n\n/** The least time one template step needs before it starts. */\nexport const AI_JOB_TEMPLATE_STEP_MINIMUM_MS = AI_JOB_TEMPLATE_STEP_BUDGET.minimumMs\n\n/** The template step's own instructions, cached after the doctrine and before the examples. */\nexport const AI_JOB_TEMPLATE_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n 'You build one page template: the page a website renders once for each record it keeps, such as each entry of a content collection, each product or each author. Answer with submit_template: the whole page as one flat node map.',\n 'Everything that differs from one record to the next is a binding token, never typed copy. The request names what the page is for, the tokens that page fills and the blocks that fill themselves there; bind only those tokens.',\n 'The page’s one h1 holds the title token the request names. Copy that reads the same on every record, such as a section heading, is written in the site’s voice.',\n 'A link or media prop holds exactly one token from the request’s link and media tokens, such as an Image whose src is {{entry.coverImage}}; its alt text may combine words and tokens.',\n 'The page renders inside the site’s layout, so it carries no header, navigation or footer of its own.',\n ].join('\\n'),\n },\n]\n\n/** A content collection an entry template is for, as the prompt and the draft name it. */\nexport interface AiTemplateCollection {\n id: string\n name: string\n slug: string\n}\n\n/** The content collection by that id on the site, or `null` when there is none. */\nexport async function readAiTemplateCollection(\n firestore: FirebaseFirestore.Firestore,\n hostId: string,\n collectionId: string,\n): Promise<AiTemplateCollection | null> {\n const snapshot = await firestore\n .collection('hosts')\n .doc(hostId)\n .collection('collections')\n .doc(collectionId)\n .get()\n if (!snapshot.exists) return null\n const data = (snapshot.data() ?? {}) as Record<string, unknown>\n if (hostCollectionKind(data) !== 'content') return null\n const text = (value: unknown) => (typeof value === 'string' ? value.trim() : '')\n const slug = text(data['slug'])\n return {\n id: collectionId,\n name: text(data['displayName']) || text(data['name']) || slug || collectionId,\n slug,\n }\n}\n\n/** The template's name: the plan's, else the collection's or the subject's. */\nexport function aiTemplateDraftName(\n definition: AiTemplateSubjectDefinition,\n collection: AiTemplateCollection | null,\n planned: string | null | undefined,\n): string {\n return planned || (collection ? `${collection.name} entry template` : definition.defaultName)\n}\n\n/** The address Use template offers for the page it creates. */\nexport function aiTemplateDraftSlug(\n definition: AiTemplateSubjectDefinition,\n collection: AiTemplateCollection | null,\n): string | null {\n return (\n normalizeScreenSlug(\n collection\n ? `${collection.slug || collection.id}-entry-template`\n : `${definition.subject}-page-template`,\n ) ?? null\n )\n}\n\n/** The template job as the generation's user turn. */\nexport function aiJobTemplatePrompt(\n job: Pick<AiJob, 'brief'>,\n definition: AiTemplateSubjectDefinition,\n collection: AiTemplateCollection | null,\n plan: AiJobPlan | null,\n name: string,\n): string {\n const of = collection\n ? ` of the \"${collection.name}\" collection${collection.slug ? ` (/${collection.slug})` : ''}`\n : ''\n return [\n `Template name: ${name}`,\n `The page is for ${definition.noun}${of}.`,\n `Title token: ${definition.titleToken}`,\n `Tokens this page fills: ${definition.tokens.map((entry) => `${entry.token} ${entry.label}`).join('; ')}`,\n `Link and media tokens: ${aiTemplateAddressTokens(definition).join(', ')}`,\n definition.blocks.length\n ? `Blocks that fill themselves on this page: ${definition.blocks.join(', ')}`\n : 'No block fills itself on this page: bind the tokens.',\n aiJobBriefLine(job),\n ...aiPlanReferenceLines(plan),\n ].join('\\n')\n}\n\n/** Which subject's page each self-filling block belongs to. */\nconst BLOCK_SUBJECTS = new Map<string, AiTemplateSubjectDefinition>(\n AI_TEMPLATE_SUBJECTS.flatMap((subject) =>\n AI_TEMPLATE_SUBJECT_DEFINITIONS[subject].blocks.map(\n (block) => [block, AI_TEMPLATE_SUBJECT_DEFINITIONS[subject]] as const,\n ),\n ),\n)\n\n/** Every string a prop value holds, however deeply. */\nfunction stringsIn(value: unknown, out: string[] = []): string[] {\n if (typeof value === 'string') out.push(value)\n else if (Array.isArray(value)) for (const inner of value) stringsIn(inner, out)\n else if (value && typeof value === 'object') {\n for (const inner of Object.values(value as Record<string, unknown>)) stringsIn(inner, out)\n }\n return out\n}\n\n/** The page's title: a Typography that renders as an h1. */\nfunction isPageTitle(node: AiDoctrineNode): boolean {\n if (node.componentId !== 'muiTypography') return false\n const element = typeof node.props?.['component'] === 'string' ? String(node.props['component']) : ''\n return (element || String(node.props?.['variant'] ?? '')) === 'h1'\n}\n\n/**\n * The template door's check on a tree the doctrine admitted (rule 8): every\n * token is one the page fills, the h1 binds the subject's title, and no block\n * that fills itself on another subject's page is placed — except inside a\n * Collection Entries card, where each fills from the card's own entry.\n */\nexport function aiTemplateBindingCheck(\n definition: AiTemplateSubjectDefinition,\n): (tree: AiValidatedTree) => AiDoctrineViolation[] {\n const allowed = new Set(definition.tokens.map((entry) => entry.token))\n return (tree) => {\n const nodes = tree.nodes as unknown as Record<string, AiDoctrineNode>\n const unknown = new Set<string>()\n const unknownAt: string[] = []\n const foreign: Array<{ id: string; componentId: string; owner: AiTemplateSubjectDefinition }> = []\n const titles: string[] = []\n let titleBound = false\n for (const { id, node, ancestors } of walkTree({ rootId: tree.rootId, nodes })) {\n const tokens = stringsIn(node.props).flatMap(aiBindingTokensIn)\n const strays = tokens.filter((token) => !allowed.has(token))\n if (strays.length) {\n for (const token of strays) unknown.add(token)\n unknownAt.push(id)\n }\n const owner = BLOCK_SUBJECTS.get(node.componentId)\n const inCard = ancestors.some((ancestor) => nodes[ancestor]?.componentId === 'collectionEntries')\n if (owner && owner.subject !== definition.subject && !inCard) {\n foreign.push({ id, componentId: node.componentId, owner })\n }\n if (isPageTitle(node)) {\n titles.push(id)\n if (tokens.includes(definition.titleToken)) titleBound = true\n }\n }\n const violations: AiDoctrineViolation[] = []\n if (unknown.size) {\n violations.push({\n rule: 8,\n code: 'unknown-binding',\n message: `This binds ${[...unknown].slice(0, 3).join(', ')}, which ${definition.noun}’s page does not fill. Bind only the tokens listed for this page.`,\n nodeIds: aiModelNodeIds(unknownAt, tree.sourceIds),\n })\n }\n // The doctrine already refuses a page with no h1 or with several.\n if (titles.length === 1 && !titleBound) {\n violations.push({\n rule: 8,\n code: 'typed-title',\n message: `The page title is typed, so every record would show the same one. Put ${definition.titleToken} in the h1.`,\n nodeIds: aiModelNodeIds(titles, tree.sourceIds),\n })\n }\n if (foreign.length) {\n const [first] = foreign\n violations.push({\n rule: 8,\n code: 'foreign-block',\n message: `The ${AI_PALETTE[first.componentId]?.displayName ?? first.componentId} block fills itself only on ${first.owner.noun}’s page, and this template is for ${definition.noun}. Remove it.`,\n nodeIds: aiModelNodeIds(\n foreign.map((entry) => entry.id),\n tree.sourceIds,\n ),\n })\n }\n return violations\n }\n}\n\nexport interface AiTemplateJobAdmissionDeps {\n readCollection?: typeof readAiTemplateCollection\n}\n\n/**\n * A template job is admitted with inputs that say what the page is for, for\n * a site of the job's own org that holds the collection it names, with a\n * template to spare.\n */\nexport function createAiTemplateJobAdmission(deps: AiTemplateJobAdmissionDeps = {}): AiJobAdmission {\n const readCollection = deps.readCollection ?? readAiTemplateCollection\n return async (context) => {\n const inputs = parseAiTemplateJobInputs(context.inputs)\n if (typeof inputs === 'string') return { status: 400, error: inputs }\n return aiDraftAdmissionRefusal(context.firestore, {\n orgId: context.orgId,\n hostId: context.hostId,\n kind: 'template',\n org: context.org,\n ownCheck: async (hostId) =>\n inputs.collectionId && !(await readCollection(context.firestore, hostId, inputs.collectionId))\n ? { status: 404, error: 'That content collection is not on this site' }\n : null,\n })\n }\n}\n\nexport interface AiJobTemplateStepDeps {\n /** The inventory reader; specs hand in a fake. */\n readInventory?: typeof readSiteInventory\n /** The platform's duplicate module, for a plan that starts from a copy. */\n duplicate?: typeof duplicateResource\n readCollection?: typeof readAiTemplateCollection\n /** The starter examples block; built from the starters once per process otherwise. */\n examples?: () => AiSystemBlock\n}\n\nexport function createAiJobTemplateStep(deps: AiJobTemplateStepDeps = {}): AiJobStepRunner {\n const readInventory = deps.readInventory ?? readSiteInventory\n const duplicate = deps.duplicate ?? duplicateResource\n const readCollection = deps.readCollection ?? readAiTemplateCollection\n const examples = deps.examples ?? aiTemplateExamplesSystemBlock\n return async ({ job, now, signal, firestore, modelFor }) => {\n const hostId = job.hostId\n if (!hostId) throw new Error('a template job names no site, and its admission refuses one')\n const inputs = parseAiTemplateJobInputs(job.inputs)\n if (typeof inputs === 'string') {\n throw new Error(`a template job's inputs no longer read as they were admitted: ${inputs}`)\n }\n const definition = AI_TEMPLATE_SUBJECT_DEFINITIONS[inputs.subject]\n const output = (draft: AiDraftRecord, load?: AiLoadEstimate | null): AiJobOutput => ({\n resource: 'template',\n id: draft.id,\n versionId: null,\n hostId,\n hostSubdomain: draft.hostSubdomain,\n label: draft.name,\n ...(load ? { load } : {}),\n })\n // The switch's answer for this job, else the routing table's (AGL-2942).\n const model = modelFor?.('job.template') ?? aiModelForStep('job.template')\n\n // A run cut off after its draft was written reports that draft, found by the id the job recorded.\n const draftId = aiJobDraftId(job, 'template')\n const written = await readAiDraft(firestore, { kind: 'template', hostId, id: draftId })\n if (written) return aiUnspentOutcome(model, { outputs: [output(written)] })\n\n const [inventory, orgSnapshot, collection] = await Promise.all([\n readInventory(job.orgId, hostId, { firestore }),\n firestore.collection('orgs').doc(job.orgId).get(),\n inputs.collectionId\n ? readCollection(firestore, hostId, inputs.collectionId)\n : Promise.resolve(null),\n ])\n if (inputs.collectionId && !collection) {\n throw new Error(`collection ${inputs.collectionId} left site ${hostId} after its template job was admitted`)\n }\n const org = (orgSnapshot.data() ?? null) as Partial<AglynOrgBilling> | null\n const plan = aiConfirmedPlan(job)\n const creation = aiPlanCreation(plan, 'template')\n const name = aiTemplateDraftName(definition, collection, creation?.name)\n\n // The plan starts from a copy of a template the site has (rule 15): the\n // copy is the draft, and nothing is generated. A source gone since the\n // plan was made is built from the brief instead.\n if (\n creation?.duplicateOf &&\n inventory.templates.some((template) => template.id === creation.duplicateOf)\n ) {\n const copy = await duplicate('template', {\n orgId: job.orgId,\n hostId,\n sourceId: creation.duplicateOf,\n name,\n uid: job.createdBy,\n org: org as Record<string, unknown> | null,\n })\n if (copy.ok) {\n const hostSubdomain = await aiSiteSubdomain(firestore, hostId)\n return aiUnspentOutcome(model, {\n outputs: [output({ id: copy.id, versionId: null, name: copy.name, hostSubdomain })],\n })\n }\n if (copy.ok === false && copy.status === 403) {\n return aiUnspentOutcome(model, { review: aiLimitReview(copy.error) })\n }\n }\n\n const allowance = await aiDraftAllowanceRefusal(firestore, { kind: 'template', hostId, org })\n if (allowance) return aiUnspentOutcome(model, { review: aiLimitReview(allowance) })\n\n const result = await runValidatedGeneration('template', {\n step: 'job.template',\n model,\n instructions: [...AI_JOB_TEMPLATE_INSTRUCTIONS, examples()],\n inventory,\n messages: [\n {\n role: 'user',\n content: aiJobTemplatePrompt(job, definition, collection, plan, name),\n },\n ],\n tool: aiDoctrineTreeTool('template'),\n maxTokens: AI_JOB_TEMPLATE_STEP_BUDGET.maxTokens(model),\n ...(AI_ROUTING_TABLE['job.template'].thinking ? { thinking: AI_ROUTING_TABLE['job.template'].thinking } : {}),\n ...(AI_ROUTING_TABLE['job.template'].effort ? { effort: AI_ROUTING_TABLE['job.template'].effort } : {}),\n context: { bindingTokens: aiTemplateAddressTokens(definition) },\n // Rule 8's binding check, and the confirmed plan kept: a token the\n // plan promised that the template does not bind (AGL-3143 §11).\n extend: (tree) => [\n ...aiTemplateBindingCheck(definition)(tree),\n ...aiPlanTemplateTokenViolations(plan, definition, {\n rootId: tree.rootId,\n nodes: tree.nodes as unknown as Record<string, AiDoctrineNode>,\n }),\n ],\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result) }\n\n const draft = await writeAiDraft(firestore, {\n kind: 'template',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: result.value.nodes,\n slug: aiTemplateDraftSlug(definition, collection),\n now,\n })\n if (draft.ok === false) {\n if (draft.status === 404) throw new Error(`site ${hostId} vanished while its template was generated`)\n return { ...spent, review: aiLimitReview(draft.error) }\n }\n return { ...spent, outputs: [output(draft, result.value.load)] }\n }\n}\n\nexport const runAiJobTemplateStep = createAiJobTemplateStep()\n\n/** Registers the template step and the check a template job passes before it is created or resumed. */\nexport function registerAiTemplateJob(): void {\n registerAiJobStep('template', runAiJobTemplateStep, { minimumMs: AI_JOB_TEMPLATE_STEP_MINIMUM_MS })\n registerAiJobAdmission('template', createAiTemplateJobAdmission())\n}\n"],"names":["hostCollectionKind","normalizeScreenSlug","duplicateResource","AI_TEMPLATE_SUBJECT_DEFINITIONS","AI_TEMPLATE_SUBJECTS","aiBindingTokensIn","aiTemplateAddressTokens","parseAiTemplateJobInputs","AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","aiDoctrineTreeTool","runValidatedGeneration","walkTree","AI_PALETTE","aiTemplateExamplesSystemBlock","readSiteInventory","registerAiJobAdmission","aiJobDraftId","aiDraftAdmissionRefusal","aiDraftAllowanceRefusal","aiSiteSubdomain","readAiDraft","writeAiDraft","aiConfirmedPlan","aiDoctrineReview","aiGenerationSpent","aiJobBriefLine","aiLimitReview","aiModelNodeIds","aiPlanCreation","aiPlanReferenceLines","aiUnspentOutcome","aiPlanTemplateTokenViolations","aiJobStepBudget","registerAiJobStep","AI_JOB_TEMPLATE_MAX_TOKENS","maxTokens","AI_JOB_TEMPLATE_STEP_BUDGET","tier","AI_JOB_TEMPLATE_STEP_MINIMUM_MS","minimumMs","AI_JOB_TEMPLATE_INSTRUCTIONS","text","join","readAiTemplateCollection","firestore","hostId","collectionId","snapshot","collection","doc","get","exists","data","value","trim","slug","id","name","aiTemplateDraftName","definition","planned","defaultName","aiTemplateDraftSlug","subject","aiJobTemplatePrompt","job","plan","of","noun","titleToken","tokens","map","entry","token","label","blocks","length","BLOCK_SUBJECTS","Map","flatMap","block","stringsIn","out","push","Array","isArray","inner","Object","values","isPageTitle","node","componentId","element","props","String","aiTemplateBindingCheck","allowed","Set","tree","nodes","unknown","unknownAt","foreign","titles","titleBound","ancestors","rootId","strays","filter","has","add","owner","inCard","some","ancestor","includes","violations","size","rule","code","message","slice","nodeIds","sourceIds","first","displayName","createAiTemplateJobAdmission","deps","readCollection","context","inputs","status","error","orgId","kind","org","ownCheck","createAiJobTemplateStep","readInventory","duplicate","examples","now","signal","modelFor","orgSnapshot","Error","output","draft","load","resource","versionId","hostSubdomain","model","draftId","written","outputs","inventory","Promise","all","resolve","creation","duplicateOf","templates","template","copy","sourceId","uid","createdBy","ok","review","allowance","result","step","instructions","messages","role","content","tool","thinking","effort","bindingTokens","extend","spent","refused","runAiJobTemplateStep","registerAiTemplateJob"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,kBAAkB,QAAQ,yCAAwC;AAC3E,SAASC,mBAAmB,QAAQ,sCAAqC;AAEzE,SAASC,iBAAiB,QAAQ,qDAAoD;AAEtF,SACEC,+BAA+B,EAC/BC,oBAAoB,EACpBC,iBAAiB,EACjBC,uBAAuB,EACvBC,wBAAwB,QAEnB,mCAA+B;AACtC,SAASC,aAAa,QAAQ,0BAAsB;AACpD,SAASC,gBAAgB,EAAEC,cAAc,QAAQ,0BAAsB;AACvE,SACEC,kBAAkB,EAClBC,sBAAsB,QAEjB,4BAAwB;AAC/B,SACEC,QAAQ,QAGH,uCAAmC;AAE1C,SAASC,UAAU,QAAQ,qCAAiC;AAE5D,SAASC,6BAA6B,QAAQ,qCAAiC;AAC/E,SAASC,iBAAiB,QAAQ,+BAA2B;AAC7D,SAASC,sBAAsB,QAA6B,wBAAoB;AAChF,SAASC,YAAY,QAAQ,wBAAoB;AACjD,SACEC,uBAAuB,EACvBC,uBAAuB,EACvBC,eAAe,EACfC,WAAW,EACXC,YAAY,QAEP,qBAAiB;AACxB,SACEC,eAAe,EACfC,gBAAgB,EAChBC,iBAAiB,EACjBC,cAAc,EACdC,aAAa,EACbC,cAAc,EACdC,cAAc,EACdC,oBAAoB,EACpBC,gBAAgB,QACX,yBAAqB;AAC5B,SAASC,6BAA6B,QAAQ,+BAA2B;AAEzE,SAASC,eAAe,QAAQ,qBAAiB;AACjD,SAASC,iBAAiB,QAAQ,eAAW;AAE7C;;;;;;;;;;;;;;;;;;;;;;CAsBC,GAED,kGAAkG,GAClG,OAAO,MAAMC,6BAA6B3B,gBAAgB,CAAC,eAAe,CAAC4B,SAAS,CAAA;AAEpF;;;;;;CAMC,GACD,OAAO,MAAMC,8BAA8BJ,gBAAgB;IACzDK,MAAM/B,aAAa,CAAC,eAAe;IACnC6B,WAAWD;AACb,GAAE;AAEF,6DAA6D,GAC7D,OAAO,MAAMI,kCAAkCF,4BAA4BG,SAAS,CAAA;AAEpF,6FAA6F,GAC7F,OAAO,MAAMC,+BAAyD;IACpE;QACEC,MAAM;YACJ;YACA;YACA;YACA;YACA;SACD,CAACC,IAAI,CAAC;IACT;CACD,CAAA;AASD,iFAAiF,GACjF,OAAO,eAAeC,yBACpBC,SAAsC,EACtCC,MAAc,EACdC,YAAoB;QASNC;IAPd,MAAMA,WAAW,MAAMH,UACpBI,UAAU,CAAC,SACXC,GAAG,CAACJ,QACJG,UAAU,CAAC,eACXC,GAAG,CAACH,cACJI,GAAG;IACN,IAAI,CAACH,SAASI,MAAM,EAAE,OAAO;IAC7B,MAAMC,QAAQL,iBAAAA,SAASK,IAAI,cAAbL,iBAAmB,CAAC;IAClC,IAAIjD,mBAAmBsD,UAAU,WAAW,OAAO;IACnD,MAAMX,OAAO,CAACY,QAAoB,OAAOA,UAAU,WAAWA,MAAMC,IAAI,KAAK;IAC7E,MAAMC,OAAOd,KAAKW,IAAI,CAAC,OAAO;IAC9B,OAAO;QACLI,IAAIV;QACJW,MAAMhB,KAAKW,IAAI,CAAC,cAAc,KAAKX,KAAKW,IAAI,CAAC,OAAO,KAAKG,QAAQT;QACjES;IACF;AACF;AAEA,6EAA6E,GAC7E,OAAO,SAASG,oBACdC,UAAuC,EACvCX,UAAuC,EACvCY,OAAkC;IAElC,OAAOA,WAAYZ,CAAAA,aAAa,GAAGA,WAAWS,IAAI,CAAC,eAAe,CAAC,GAAGE,WAAWE,WAAW,AAAD;AAC7F;AAEA,6DAA6D,GAC7D,OAAO,SAASC,oBACdH,UAAuC,EACvCX,UAAuC;QAGrCjD;IADF,QACEA,uBAAAA,oBACEiD,aACI,GAAGA,WAAWO,IAAI,IAAIP,WAAWQ,EAAE,CAAC,eAAe,CAAC,GACpD,GAAGG,WAAWI,OAAO,CAAC,cAAc,CAAC,aAH3ChE,uBAIK;AAET;AAEA,oDAAoD,GACpD,OAAO,SAASiE,oBACdC,GAAyB,EACzBN,UAAuC,EACvCX,UAAuC,EACvCkB,IAAsB,EACtBT,IAAY;IAEZ,MAAMU,KAAKnB,aACP,CAAC,SAAS,EAAEA,WAAWS,IAAI,CAAC,YAAY,EAAET,WAAWO,IAAI,GAAG,CAAC,GAAG,EAAEP,WAAWO,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,GAC3F;IACJ,OAAO;QACL,CAAC,eAAe,EAAEE,MAAM;QACxB,CAAC,gBAAgB,EAAEE,WAAWS,IAAI,GAAGD,GAAG,CAAC,CAAC;QAC1C,CAAC,aAAa,EAAER,WAAWU,UAAU,EAAE;QACvC,CAAC,wBAAwB,EAAEV,WAAWW,MAAM,CAACC,GAAG,CAAC,CAACC,QAAU,GAAGA,MAAMC,KAAK,CAAC,CAAC,EAAED,MAAME,KAAK,EAAE,EAAEhC,IAAI,CAAC,OAAO;QACzG,CAAC,uBAAuB,EAAEtC,wBAAwBuD,YAAYjB,IAAI,CAAC,OAAO;QAC1EiB,WAAWgB,MAAM,CAACC,MAAM,GACpB,CAAC,0CAA0C,EAAEjB,WAAWgB,MAAM,CAACjC,IAAI,CAAC,OAAO,GAC3E;QACJjB,eAAewC;WACZpC,qBAAqBqC;KACzB,CAACxB,IAAI,CAAC;AACT;AAEA,6DAA6D,GAC7D,MAAMmC,iBAAiB,IAAIC,IACzB5E,qBAAqB6E,OAAO,CAAC,CAAChB,UAC5B9D,+BAA+B,CAAC8D,QAAQ,CAACY,MAAM,CAACJ,GAAG,CACjD,CAACS,QAAU;YAACA;YAAO/E,+BAA+B,CAAC8D,QAAQ;SAAC;AAKlE,qDAAqD,GACrD,SAASkB,UAAU5B,KAAc,EAAE6B,MAAgB,EAAE;IACnD,IAAI,OAAO7B,UAAU,UAAU6B,IAAIC,IAAI,CAAC9B;SACnC,IAAI+B,MAAMC,OAAO,CAAChC,QAAQ,KAAK,MAAMiC,SAASjC,MAAO4B,UAAUK,OAAOJ;SACtE,IAAI7B,SAAS,OAAOA,UAAU,UAAU;QAC3C,KAAK,MAAMiC,SAASC,OAAOC,MAAM,CAACnC,OAAmC4B,UAAUK,OAAOJ;IACxF;IACA,OAAOA;AACT;AAEA,0DAA0D,GAC1D,SAASO,YAAYC,IAAoB;;QAEhBA,aACGA;IAF1B,IAAIA,KAAKC,WAAW,KAAK,iBAAiB,OAAO;IACjD,MAAMC,UAAU,SAAOF,cAAAA,KAAKG,KAAK,qBAAVH,WAAY,CAAC,YAAY,MAAK,WAAWI,OAAOJ,KAAKG,KAAK,CAAC,YAAY,IAAI;IAClG,OAAO,AAACD,CAAAA,WAAWE,gBAAOJ,eAAAA,KAAKG,KAAK,qBAAVH,YAAY,CAAC,UAAU,mBAAI,GAAE,MAAO;AAChE;AAEA;;;;;CAKC,GACD,OAAO,SAASK,uBACdpC,UAAuC;IAEvC,MAAMqC,UAAU,IAAIC,IAAItC,WAAWW,MAAM,CAACC,GAAG,CAAC,CAACC,QAAUA,MAAMC,KAAK;IACpE,OAAO,CAACyB;QACN,MAAMC,QAAQD,KAAKC,KAAK;QACxB,MAAMC,UAAU,IAAIH;QACpB,MAAMI,YAAsB,EAAE;QAC9B,MAAMC,UAA0F,EAAE;QAClG,MAAMC,SAAmB,EAAE;QAC3B,IAAIC,aAAa;QACjB,KAAK,MAAM,EAAEhD,EAAE,EAAEkC,IAAI,EAAEe,SAAS,EAAE,IAAI9F,SAAS;YAAE+F,QAAQR,KAAKQ,MAAM;YAAEP;QAAM,GAAI;YAC9E,MAAM7B,SAASW,UAAUS,KAAKG,KAAK,EAAEd,OAAO,CAAC5E;YAC7C,MAAMwG,SAASrC,OAAOsC,MAAM,CAAC,CAACnC,QAAU,CAACuB,QAAQa,GAAG,CAACpC;YACrD,IAAIkC,OAAO/B,MAAM,EAAE;gBACjB,KAAK,MAAMH,SAASkC,OAAQP,QAAQU,GAAG,CAACrC;gBACxC4B,UAAUlB,IAAI,CAAC3B;YACjB;YACA,MAAMuD,QAAQlC,eAAe3B,GAAG,CAACwC,KAAKC,WAAW;YACjD,MAAMqB,SAASP,UAAUQ,IAAI,CAAC,CAACC;oBAAaf;uBAAAA,EAAAA,kBAAAA,KAAK,CAACe,SAAS,qBAAff,gBAAiBR,WAAW,MAAK;;YAC7E,IAAIoB,SAASA,MAAMhD,OAAO,KAAKJ,WAAWI,OAAO,IAAI,CAACiD,QAAQ;gBAC5DV,QAAQnB,IAAI,CAAC;oBAAE3B;oBAAImC,aAAaD,KAAKC,WAAW;oBAAEoB;gBAAM;YAC1D;YACA,IAAItB,YAAYC,OAAO;gBACrBa,OAAOpB,IAAI,CAAC3B;gBACZ,IAAIc,OAAO6C,QAAQ,CAACxD,WAAWU,UAAU,GAAGmC,aAAa;YAC3D;QACF;QACA,MAAMY,aAAoC,EAAE;QAC5C,IAAIhB,QAAQiB,IAAI,EAAE;YAChBD,WAAWjC,IAAI,CAAC;gBACdmC,MAAM;gBACNC,MAAM;gBACNC,SAAS,CAAC,WAAW,EAAE;uBAAIpB;iBAAQ,CAACqB,KAAK,CAAC,GAAG,GAAG/E,IAAI,CAAC,MAAM,QAAQ,EAAEiB,WAAWS,IAAI,CAAC,iEAAiE,CAAC;gBACvJsD,SAAS/F,eAAe0E,WAAWH,KAAKyB,SAAS;YACnD;QACF;QACA,kEAAkE;QAClE,IAAIpB,OAAO3B,MAAM,KAAK,KAAK,CAAC4B,YAAY;YACtCY,WAAWjC,IAAI,CAAC;gBACdmC,MAAM;gBACNC,MAAM;gBACNC,SAAS,CAAC,sEAAsE,EAAE7D,WAAWU,UAAU,CAAC,WAAW,CAAC;gBACpHqD,SAAS/F,eAAe4E,QAAQL,KAAKyB,SAAS;YAChD;QACF;QACA,IAAIrB,QAAQ1B,MAAM,EAAE;;gBAKAhE;YAJlB,MAAM,CAACgH,MAAM,GAAGtB;YAChBc,WAAWjC,IAAI,CAAC;gBACdmC,MAAM;gBACNC,MAAM;gBACNC,SAAS,CAAC,IAAI,WAAE5G,gCAAAA,UAAU,CAACgH,MAAMjC,WAAW,CAAC,qBAA7B/E,8BAA+BiH,WAAW,mBAAID,MAAMjC,WAAW,CAAC,4BAA4B,EAAEiC,MAAMb,KAAK,CAAC3C,IAAI,CAAC,kCAAkC,EAAET,WAAWS,IAAI,CAAC,YAAY,CAAC;gBAChMsD,SAAS/F,eACP2E,QAAQ/B,GAAG,CAAC,CAACC,QAAUA,MAAMhB,EAAE,GAC/B0C,KAAKyB,SAAS;YAElB;QACF;QACA,OAAOP;IACT;AACF;AAMA;;;;CAIC,GACD,OAAO,SAASU,6BAA6BC,OAAmC,CAAC,CAAC;QACzDA;IAAvB,MAAMC,kBAAiBD,uBAAAA,KAAKC,cAAc,YAAnBD,uBAAuBpF;IAC9C,OAAO,OAAOsF;QACZ,MAAMC,SAAS7H,yBAAyB4H,QAAQC,MAAM;QACtD,IAAI,OAAOA,WAAW,UAAU,OAAO;YAAEC,QAAQ;YAAKC,OAAOF;QAAO;QACpE,OAAOjH,wBAAwBgH,QAAQrF,SAAS,EAAE;YAChDyF,OAAOJ,QAAQI,KAAK;YACpBxF,QAAQoF,QAAQpF,MAAM;YACtByF,MAAM;YACNC,KAAKN,QAAQM,GAAG;YAChBC,UAAU,OAAO3F,SACfqF,OAAOpF,YAAY,IAAI,CAAE,MAAMkF,eAAeC,QAAQrF,SAAS,EAAEC,QAAQqF,OAAOpF,YAAY,IACxF;oBAAEqF,QAAQ;oBAAKC,OAAO;gBAA8C,IACpE;QACR;IACF;AACF;AAYA,OAAO,SAASK,wBAAwBV,OAA8B,CAAC,CAAC;QAChDA,qBACJA,iBACKA,sBACNA;IAHjB,MAAMW,iBAAgBX,sBAAAA,KAAKW,aAAa,YAAlBX,sBAAsBjH;IAC5C,MAAM6H,aAAYZ,kBAAAA,KAAKY,SAAS,YAAdZ,kBAAkB/H;IACpC,MAAMgI,kBAAiBD,uBAAAA,KAAKC,cAAc,YAAnBD,uBAAuBpF;IAC9C,MAAMiG,YAAWb,iBAAAA,KAAKa,QAAQ,YAAbb,iBAAiBlH;IAClC,OAAO,OAAO,EAAEoD,GAAG,EAAE4E,GAAG,EAAEC,MAAM,EAAElG,SAAS,EAAEmG,QAAQ,EAAE;kBAmCxCC;QAlCb,MAAMnG,SAASoB,IAAIpB,MAAM;QACzB,IAAI,CAACA,QAAQ,MAAM,IAAIoG,MAAM;QAC7B,MAAMf,SAAS7H,yBAAyB4D,IAAIiE,MAAM;QAClD,IAAI,OAAOA,WAAW,UAAU;YAC9B,MAAM,IAAIe,MAAM,CAAC,8DAA8D,EAAEf,QAAQ;QAC3F;QACA,MAAMvE,aAAa1D,+BAA+B,CAACiI,OAAOnE,OAAO,CAAC;QAClE,MAAMmF,SAAS,CAACC,OAAsBC,OAA+C;gBACnFC,UAAU;gBACV7F,IAAI2F,MAAM3F,EAAE;gBACZ8F,WAAW;gBACXzG;gBACA0G,eAAeJ,MAAMI,aAAa;gBAClC7E,OAAOyE,MAAM1F,IAAI;eACb2F,OAAO;gBAAEA;YAAK,IAAI,CAAC;QAEzB,yEAAyE;QACzE,MAAMI,gBAAQT,4BAAAA,SAAW,kCAAmBvI,eAAe;QAE3D,kGAAkG;QAClG,MAAMiJ,UAAUzI,aAAaiD,KAAK;QAClC,MAAMyF,UAAU,MAAMtI,YAAYwB,WAAW;YAAE0F,MAAM;YAAYzF;YAAQW,IAAIiG;QAAQ;QACrF,IAAIC,SAAS,OAAO5H,iBAAiB0H,OAAO;YAAEG,SAAS;gBAACT,OAAOQ;aAAS;QAAC;QAEzE,MAAM,CAACE,WAAWZ,aAAahG,WAAW,GAAG,MAAM6G,QAAQC,GAAG,CAAC;YAC7DpB,cAAczE,IAAIoE,KAAK,EAAExF,QAAQ;gBAAED;YAAU;YAC7CA,UAAUI,UAAU,CAAC,QAAQC,GAAG,CAACgB,IAAIoE,KAAK,EAAEnF,GAAG;YAC/CgF,OAAOpF,YAAY,GACfkF,eAAepF,WAAWC,QAAQqF,OAAOpF,YAAY,IACrD+G,QAAQE,OAAO,CAAC;SACrB;QACD,IAAI7B,OAAOpF,YAAY,IAAI,CAACE,YAAY;YACtC,MAAM,IAAIiG,MAAM,CAAC,WAAW,EAAEf,OAAOpF,YAAY,CAAC,WAAW,EAAED,OAAO,oCAAoC,CAAC;QAC7G;QACA,MAAM0F,OAAOS,oBAAAA,YAAY5F,IAAI,cAAhB4F,oBAAsB;QACnC,MAAM9E,OAAO5C,gBAAgB2C;QAC7B,MAAM+F,WAAWpI,eAAesC,MAAM;QACtC,MAAMT,OAAOC,oBAAoBC,YAAYX,YAAYgH,4BAAAA,SAAUvG,IAAI;QAEvE,wEAAwE;QACxE,uEAAuE;QACvE,iDAAiD;QACjD,IACEuG,CAAAA,4BAAAA,SAAUC,WAAW,KACrBL,UAAUM,SAAS,CAACjD,IAAI,CAAC,CAACkD,WAAaA,SAAS3G,EAAE,KAAKwG,SAASC,WAAW,GAC3E;YACA,MAAMG,OAAO,MAAMzB,UAAU,YAAY;gBACvCN,OAAOpE,IAAIoE,KAAK;gBAChBxF;gBACAwH,UAAUL,SAASC,WAAW;gBAC9BxG;gBACA6G,KAAKrG,IAAIsG,SAAS;gBAClBhC,KAAKA;YACP;YACA,IAAI6B,KAAKI,EAAE,EAAE;gBACX,MAAMjB,gBAAgB,MAAMpI,gBAAgByB,WAAWC;gBACvD,OAAOf,iBAAiB0H,OAAO;oBAC7BG,SAAS;wBAACT,OAAO;4BAAE1F,IAAI4G,KAAK5G,EAAE;4BAAE8F,WAAW;4BAAM7F,MAAM2G,KAAK3G,IAAI;4BAAE8F;wBAAc;qBAAG;gBACrF;YACF;YACA,IAAIa,KAAKI,EAAE,KAAK,SAASJ,KAAKjC,MAAM,KAAK,KAAK;gBAC5C,OAAOrG,iBAAiB0H,OAAO;oBAAEiB,QAAQ/I,cAAc0I,KAAKhC,KAAK;gBAAE;YACrE;QACF;QAEA,MAAMsC,YAAY,MAAMxJ,wBAAwB0B,WAAW;YAAE0F,MAAM;YAAYzF;YAAQ0F;QAAI;QAC3F,IAAImC,WAAW,OAAO5I,iBAAiB0H,OAAO;YAAEiB,QAAQ/I,cAAcgJ;QAAW;QAEjF,MAAMC,SAAS,MAAMjK,uBAAuB,YAAY;YACtDkK,MAAM;YACNpB;YACAqB,cAAc;mBAAIrI;gBAA8BoG;aAAW;YAC3DgB;YACAkB,UAAU;gBACR;oBACEC,MAAM;oBACNC,SAAShH,oBAAoBC,KAAKN,YAAYX,YAAYkB,MAAMT;gBAClE;aACD;YACDwH,MAAMxK,mBAAmB;YACzB0B,WAAWC,4BAA4BD,SAAS,CAACqH;WAC7CjJ,gBAAgB,CAAC,eAAe,CAAC2K,QAAQ,GAAG;YAAEA,UAAU3K,gBAAgB,CAAC,eAAe,CAAC2K,QAAQ;QAAC,IAAI,CAAC,GACvG3K,gBAAgB,CAAC,eAAe,CAAC4K,MAAM,GAAG;YAAEA,QAAQ5K,gBAAgB,CAAC,eAAe,CAAC4K,MAAM;QAAC,IAAI,CAAC;YACrGlD,SAAS;gBAAEmD,eAAehL,wBAAwBuD;YAAY;YAC9D,mEAAmE;YACnE,gEAAgE;YAChE0H,QAAQ,CAACnF,OAAS;uBACbH,uBAAuBpC,YAAYuC;uBACnCnE,8BAA8BmC,MAAMP,YAAY;wBACjD+C,QAAQR,KAAKQ,MAAM;wBACnBP,OAAOD,KAAKC,KAAK;oBACnB;iBACD;WACG2C,SAAS;YAAEA;QAAO,IAAI,CAAC;QAE7B,MAAMwC,QAAQ9J,kBAAkBmJ;QAChC,IAAIA,OAAOxC,MAAM,KAAK,WAAW,OAAO,aAAKmD;YAAOC,SAAS;;QAC7D,IAAIZ,OAAOxC,MAAM,KAAK,eAAe,OAAO,aAAKmD;YAAOb,QAAQlJ,iBAAiBoJ;;QAEjF,MAAMxB,QAAQ,MAAM9H,aAAauB,WAAW;YAC1C0F,MAAM;YACNzF;YACAW,IAAIiG;YACJa,KAAKrG,IAAIsG,SAAS;YAClBhC;YACA9E;YACA0C,OAAOwE,OAAOtH,KAAK,CAAC8C,KAAK;YACzB5C,MAAMO,oBAAoBH,YAAYX;YACtC6F;QACF;QACA,IAAIM,MAAMqB,EAAE,KAAK,OAAO;YACtB,IAAIrB,MAAMhB,MAAM,KAAK,KAAK,MAAM,IAAIc,MAAM,CAAC,KAAK,EAAEpG,OAAO,0CAA0C,CAAC;YACpG,OAAO,aAAKyI;gBAAOb,QAAQ/I,cAAcyH,MAAMf,KAAK;;QACtD;QACA,OAAO,aAAKkD;YAAO3B,SAAS;gBAACT,OAAOC,OAAOwB,OAAOtH,KAAK,CAAC+F,IAAI;aAAE;;IAChE;AACF;AAEA,OAAO,MAAMoC,uBAAuB/C,0BAAyB;AAE7D,qGAAqG,GACrG,OAAO,SAASgD;IACdxJ,kBAAkB,YAAYuJ,sBAAsB;QAAEjJ,WAAWD;IAAgC;IACjGvB,uBAAuB,YAAY+G;AACrC"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-template-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 { hostCollectionKind } from '@aglyn/aglyn/app-utils/collection-kind'\nimport { normalizeScreenSlug } from '@aglyn/aglyn/app-utils/screen-route'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { duplicateResource } from '@aglyn/tenant-data-admin/server/duplicate-resource'\nimport type { AiJob, AiJobOutput, AiJobPlan } from '../model/ai-jobs.types'\nimport {\n AI_TEMPLATE_SUBJECT_DEFINITIONS,\n AI_TEMPLATE_SUBJECTS,\n aiBindingTokensIn,\n aiTemplateAddressTokens,\n parseAiTemplateJobInputs,\n type AiTemplateSubjectDefinition,\n} from '../model/ai-template-subjects'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n aiDoctrineTreeTool,\n runValidatedGeneration,\n type AiValidatedTree,\n} from '../runtime/ai-doctrine'\nimport {\n walkTree,\n type AiDoctrineNode,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport { AI_PALETTE } from '../runtime/ai-palette.generated'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { aiTemplateExamplesSystemBlock } from '../runtime/ai-template-examples'\nimport { readSiteInventory } from '../runtime/site-inventory'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport { aiJobDraftId } from './ai-job-draft-ids'\nimport {\n aiDraftAdmissionRefusal,\n aiDraftAllowanceRefusal,\n aiSiteSubdomain,\n readAiDraft,\n writeAiDraft,\n type AiDraftRecord,\n} from './ai-job-drafts'\nimport {\n aiConfirmedPlan,\n aiDoctrineReview,\n aiGenerationSpent,\n aiJobBriefLine,\n aiLimitReview,\n aiModelNodeIds,\n aiPlacedComponentIds,\n aiPlanCreation,\n aiPlanReferenceLines,\n aiPlanReuseViolations,\n aiUnspentOutcome,\n} from './ai-job-generation'\nimport { aiPlanTemplateTokenViolations } from './ai-job-plan-conformance'\nimport type { AiJobStepRunner } from './ai-job-text-step'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The template step (AGL-2909): the generation step of a `template` job, run\n * once a member confirmed the job's plan.\n *\n * It builds ONE page template — the page a site renders once per record: a\n * content collection's entry, a product, an author — through\n * `runValidatedGeneration('template', …)`. Everything that differs per\n * record is a binding token from the subject's vocabulary\n * (`model/ai-template-subjects.ts`), which is the list the besigner's insert\n * picker offers for that page; a link or media prop may hold one of those\n * tokens whole, which the palette validator admits only because this door\n * names them. The door adds one check of its own through `extend` (rule 8):\n * no token the page does not fill, an h1 bound to the subject's title, and no\n * block that fills itself only on another subject's page.\n *\n * The model is shown the platform's starter pages as examples, in a cached\n * block (`runtime/ai-template-examples.ts`), and writes against them.\n *\n * The template lands in the site's library as a new draft\n * (`ai-job-drafts.ts`), inert until a member uses it: bound to no\n * collection, set as no product or author page, served nowhere. A plan that\n * starts from a copy of a template the site has gets that copy instead.\n */\n\n/** The longest answer a template may run to; the tree is held to the template budget either way. */\nexport const AI_JOB_TEMPLATE_MAX_TOKENS = AI_ROUTING_TABLE['job.template'].maxTokens\n\n/**\n * The template step's time (AGL-3035, AGL-3036): its two inventory-lookup\n * rounds, its answer and its re-ask at the routing table's ceiling on the tier\n * `job.template` is served from, fitted to what a beat can give a step. It\n * runs on the job beat, never at an inline door, and asks for no more of the\n * ceiling than fits that time on the model it runs.\n */\nexport const AI_JOB_TEMPLATE_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.template'],\n maxTokens: AI_JOB_TEMPLATE_MAX_TOKENS,\n})\n\n/** The least time one template step needs before it starts. */\nexport const AI_JOB_TEMPLATE_STEP_MINIMUM_MS = AI_JOB_TEMPLATE_STEP_BUDGET.minimumMs\n\n/** The template step's own instructions, cached after the doctrine and before the examples. */\nexport const AI_JOB_TEMPLATE_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n 'You build one page template: the page a website renders once for each record it keeps, such as each entry of a content collection, each product or each author. Answer with submit_template: the whole page as one flat node map.',\n 'Everything that differs from one record to the next is a binding token, never typed copy. The request names what the page is for, the tokens that page fills and the blocks that fill themselves there; bind only those tokens.',\n 'The page’s one h1 holds the title token the request names. Copy that reads the same on every record, such as a section heading, is written in the site’s voice.',\n 'A link or media prop holds exactly one token from the request’s link and media tokens, such as an Image whose src is {{entry.coverImage}}; its alt text may combine words and tokens.',\n 'The page renders inside the site’s layout, so it carries no header, navigation or footer of its own.',\n ].join('\\n'),\n },\n]\n\n/** A content collection an entry template is for, as the prompt and the draft name it. */\nexport interface AiTemplateCollection {\n id: string\n name: string\n slug: string\n}\n\n/** The content collection by that id on the site, or `null` when there is none. */\nexport async function readAiTemplateCollection(\n firestore: FirebaseFirestore.Firestore,\n hostId: string,\n collectionId: string,\n): Promise<AiTemplateCollection | null> {\n const snapshot = await firestore\n .collection('hosts')\n .doc(hostId)\n .collection('collections')\n .doc(collectionId)\n .get()\n if (!snapshot.exists) return null\n const data = (snapshot.data() ?? {}) as Record<string, unknown>\n if (hostCollectionKind(data) !== 'content') return null\n const text = (value: unknown) => (typeof value === 'string' ? value.trim() : '')\n const slug = text(data['slug'])\n return {\n id: collectionId,\n name: text(data['displayName']) || text(data['name']) || slug || collectionId,\n slug,\n }\n}\n\n/** The template's name: the plan's, else the collection's or the subject's. */\nexport function aiTemplateDraftName(\n definition: AiTemplateSubjectDefinition,\n collection: AiTemplateCollection | null,\n planned: string | null | undefined,\n): string {\n return planned || (collection ? `${collection.name} entry template` : definition.defaultName)\n}\n\n/** The address Use template offers for the page it creates. */\nexport function aiTemplateDraftSlug(\n definition: AiTemplateSubjectDefinition,\n collection: AiTemplateCollection | null,\n): string | null {\n return (\n normalizeScreenSlug(\n collection\n ? `${collection.slug || collection.id}-entry-template`\n : `${definition.subject}-page-template`,\n ) ?? null\n )\n}\n\n/** The template job as the generation's user turn. */\nexport function aiJobTemplatePrompt(\n job: Pick<AiJob, 'brief'>,\n definition: AiTemplateSubjectDefinition,\n collection: AiTemplateCollection | null,\n plan: AiJobPlan | null,\n name: string,\n): string {\n const of = collection\n ? ` of the \"${collection.name}\" collection${collection.slug ? ` (/${collection.slug})` : ''}`\n : ''\n return [\n `Template name: ${name}`,\n `The page is for ${definition.noun}${of}.`,\n `Title token: ${definition.titleToken}`,\n `Tokens this page fills: ${definition.tokens.map((entry) => `${entry.token} ${entry.label}`).join('; ')}`,\n `Link and media tokens: ${aiTemplateAddressTokens(definition).join(', ')}`,\n definition.blocks.length\n ? `Blocks that fill themselves on this page: ${definition.blocks.join(', ')}`\n : 'No block fills itself on this page: bind the tokens.',\n aiJobBriefLine(job),\n ...aiPlanReferenceLines(plan),\n ].join('\\n')\n}\n\n/** Which subject's page each self-filling block belongs to. */\nconst BLOCK_SUBJECTS = new Map<string, AiTemplateSubjectDefinition>(\n AI_TEMPLATE_SUBJECTS.flatMap((subject) =>\n AI_TEMPLATE_SUBJECT_DEFINITIONS[subject].blocks.map(\n (block) => [block, AI_TEMPLATE_SUBJECT_DEFINITIONS[subject]] as const,\n ),\n ),\n)\n\n/** Every string a prop value holds, however deeply. */\nfunction stringsIn(value: unknown, out: string[] = []): string[] {\n if (typeof value === 'string') out.push(value)\n else if (Array.isArray(value)) for (const inner of value) stringsIn(inner, out)\n else if (value && typeof value === 'object') {\n for (const inner of Object.values(value as Record<string, unknown>)) stringsIn(inner, out)\n }\n return out\n}\n\n/** The page's title: a Typography that renders as an h1. */\nfunction isPageTitle(node: AiDoctrineNode): boolean {\n if (node.componentId !== 'muiTypography') return false\n const element = typeof node.props?.['component'] === 'string' ? String(node.props['component']) : ''\n return (element || String(node.props?.['variant'] ?? '')) === 'h1'\n}\n\n/**\n * The template door's check on a tree the doctrine admitted (rule 8): every\n * token is one the page fills, the h1 binds the subject's title, and no block\n * that fills itself on another subject's page is placed — except inside a\n * Collection Entries card, where each fills from the card's own entry.\n */\nexport function aiTemplateBindingCheck(\n definition: AiTemplateSubjectDefinition,\n): (tree: AiValidatedTree) => AiDoctrineViolation[] {\n const allowed = new Set(definition.tokens.map((entry) => entry.token))\n return (tree) => {\n const nodes = tree.nodes as unknown as Record<string, AiDoctrineNode>\n const unknown = new Set<string>()\n const unknownAt: string[] = []\n const foreign: Array<{ id: string; componentId: string; owner: AiTemplateSubjectDefinition }> = []\n const titles: string[] = []\n let titleBound = false\n for (const { id, node, ancestors } of walkTree({ rootId: tree.rootId, nodes })) {\n const tokens = stringsIn(node.props).flatMap(aiBindingTokensIn)\n const strays = tokens.filter((token) => !allowed.has(token))\n if (strays.length) {\n for (const token of strays) unknown.add(token)\n unknownAt.push(id)\n }\n const owner = BLOCK_SUBJECTS.get(node.componentId)\n const inCard = ancestors.some((ancestor) => nodes[ancestor]?.componentId === 'collectionEntries')\n if (owner && owner.subject !== definition.subject && !inCard) {\n foreign.push({ id, componentId: node.componentId, owner })\n }\n if (isPageTitle(node)) {\n titles.push(id)\n if (tokens.includes(definition.titleToken)) titleBound = true\n }\n }\n const violations: AiDoctrineViolation[] = []\n if (unknown.size) {\n violations.push({\n rule: 8,\n code: 'unknown-binding',\n message: `This binds ${[...unknown].slice(0, 3).join(', ')}, which ${definition.noun}’s page does not fill. Bind only the tokens listed for this page.`,\n nodeIds: aiModelNodeIds(unknownAt, tree.sourceIds),\n })\n }\n // The doctrine already refuses a page with no h1 or with several.\n if (titles.length === 1 && !titleBound) {\n violations.push({\n rule: 8,\n code: 'typed-title',\n message: `The page title is typed, so every record would show the same one. Put ${definition.titleToken} in the h1.`,\n nodeIds: aiModelNodeIds(titles, tree.sourceIds),\n })\n }\n if (foreign.length) {\n const [first] = foreign\n violations.push({\n rule: 8,\n code: 'foreign-block',\n message: `The ${AI_PALETTE[first.componentId]?.displayName ?? first.componentId} block fills itself only on ${first.owner.noun}’s page, and this template is for ${definition.noun}. Remove it.`,\n nodeIds: aiModelNodeIds(\n foreign.map((entry) => entry.id),\n tree.sourceIds,\n ),\n })\n }\n return violations\n }\n}\n\nexport interface AiTemplateJobAdmissionDeps {\n readCollection?: typeof readAiTemplateCollection\n}\n\n/**\n * A template job is admitted with inputs that say what the page is for, for\n * a site of the job's own org that holds the collection it names, with a\n * template to spare.\n */\nexport function createAiTemplateJobAdmission(deps: AiTemplateJobAdmissionDeps = {}): AiJobAdmission {\n const readCollection = deps.readCollection ?? readAiTemplateCollection\n return async (context) => {\n const inputs = parseAiTemplateJobInputs(context.inputs)\n if (typeof inputs === 'string') return { status: 400, error: inputs }\n return aiDraftAdmissionRefusal(context.firestore, {\n orgId: context.orgId,\n hostId: context.hostId,\n kind: 'template',\n org: context.org,\n ownCheck: async (hostId) =>\n inputs.collectionId && !(await readCollection(context.firestore, hostId, inputs.collectionId))\n ? { status: 404, error: 'That content collection is not on this site' }\n : null,\n })\n }\n}\n\nexport interface AiJobTemplateStepDeps {\n /** The inventory reader; specs hand in a fake. */\n readInventory?: typeof readSiteInventory\n /** The platform's duplicate module, for a plan that starts from a copy. */\n duplicate?: typeof duplicateResource\n readCollection?: typeof readAiTemplateCollection\n /** The starter examples block; built from the starters once per process otherwise. */\n examples?: () => AiSystemBlock\n}\n\nexport function createAiJobTemplateStep(deps: AiJobTemplateStepDeps = {}): AiJobStepRunner {\n const readInventory = deps.readInventory ?? readSiteInventory\n const duplicate = deps.duplicate ?? duplicateResource\n const readCollection = deps.readCollection ?? readAiTemplateCollection\n const examples = deps.examples ?? aiTemplateExamplesSystemBlock\n return async ({ job, now, signal, firestore, modelFor }) => {\n const hostId = job.hostId\n if (!hostId) throw new Error('a template job names no site, and its admission refuses one')\n const inputs = parseAiTemplateJobInputs(job.inputs)\n if (typeof inputs === 'string') {\n throw new Error(`a template job's inputs no longer read as they were admitted: ${inputs}`)\n }\n const definition = AI_TEMPLATE_SUBJECT_DEFINITIONS[inputs.subject]\n const output = (draft: AiDraftRecord, load?: AiLoadEstimate | null): AiJobOutput => ({\n resource: 'template',\n id: draft.id,\n versionId: null,\n hostId,\n hostSubdomain: draft.hostSubdomain,\n label: draft.name,\n ...(load ? { load } : {}),\n })\n // The switch's answer for this job, else the routing table's (AGL-2942).\n const model = modelFor?.('job.template') ?? aiModelForStep('job.template')\n\n // A run cut off after its draft was written reports that draft, found by the id the job recorded.\n const draftId = aiJobDraftId(job, 'template')\n const written = await readAiDraft(firestore, { kind: 'template', hostId, id: draftId })\n if (written) return aiUnspentOutcome(model, { outputs: [output(written)] })\n\n const [inventory, orgSnapshot, collection] = await Promise.all([\n readInventory(job.orgId, hostId, { firestore }),\n firestore.collection('orgs').doc(job.orgId).get(),\n inputs.collectionId\n ? readCollection(firestore, hostId, inputs.collectionId)\n : Promise.resolve(null),\n ])\n if (inputs.collectionId && !collection) {\n throw new Error(`collection ${inputs.collectionId} left site ${hostId} after its template job was admitted`)\n }\n const org = (orgSnapshot.data() ?? null) as Partial<AglynOrgBilling> | null\n const plan = aiConfirmedPlan(job)\n const creation = aiPlanCreation(plan, 'template')\n const name = aiTemplateDraftName(definition, collection, creation?.name)\n\n // The plan starts from a copy of a template the site has (rule 15): the\n // copy is the draft, and nothing is generated. A source gone since the\n // plan was made is built from the brief instead.\n if (\n creation?.duplicateOf &&\n inventory.templates.some((template) => template.id === creation.duplicateOf)\n ) {\n const copy = await duplicate('template', {\n orgId: job.orgId,\n hostId,\n sourceId: creation.duplicateOf,\n name,\n uid: job.createdBy,\n org: org as Record<string, unknown> | null,\n })\n if (copy.ok) {\n const hostSubdomain = await aiSiteSubdomain(firestore, hostId)\n return aiUnspentOutcome(model, {\n outputs: [output({ id: copy.id, versionId: null, name: copy.name, hostSubdomain })],\n })\n }\n if (copy.ok === false && copy.status === 403) {\n return aiUnspentOutcome(model, { review: aiLimitReview(copy.error) })\n }\n }\n\n const allowance = await aiDraftAllowanceRefusal(firestore, { kind: 'template', hostId, org })\n if (allowance) return aiUnspentOutcome(model, { review: aiLimitReview(allowance) })\n\n const result = await runValidatedGeneration('template', {\n step: 'job.template',\n model,\n instructions: [...AI_JOB_TEMPLATE_INSTRUCTIONS, examples()],\n inventory,\n messages: [\n {\n role: 'user',\n content: aiJobTemplatePrompt(job, definition, collection, plan, name),\n },\n ],\n tool: aiDoctrineTreeTool('template'),\n maxTokens: AI_JOB_TEMPLATE_STEP_BUDGET.maxTokens(model),\n ...(AI_ROUTING_TABLE['job.template'].thinking ? { thinking: AI_ROUTING_TABLE['job.template'].thinking } : {}),\n ...(AI_ROUTING_TABLE['job.template'].effort ? { effort: AI_ROUTING_TABLE['job.template'].effort } : {}),\n context: { bindingTokens: aiTemplateAddressTokens(definition) },\n // Rule 8's binding check, and the confirmed plan kept in both halves: a\n // token the plan promised that the template does not bind (AGL-3143\n // §11), and a component the plan reuses that it does not place. The\n // layout and component doors hold their builds to that reuse; the\n // template door did not, so it could drop a promised component and\n // still answer `Done` (AGL-3024).\n extend: (tree) => [\n ...aiTemplateBindingCheck(definition)(tree),\n ...aiPlanTemplateTokenViolations(plan, definition, {\n rootId: tree.rootId,\n nodes: tree.nodes as unknown as Record<string, AiDoctrineNode>,\n }),\n ...aiPlanReuseViolations(inventory, plan, aiPlacedComponentIds(tree), 'template'),\n ],\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result) }\n\n const draft = await writeAiDraft(firestore, {\n kind: 'template',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: result.value.nodes,\n slug: aiTemplateDraftSlug(definition, collection),\n now,\n })\n if (draft.ok === false) {\n if (draft.status === 404) throw new Error(`site ${hostId} vanished while its template was generated`)\n return { ...spent, review: aiLimitReview(draft.error) }\n }\n return { ...spent, outputs: [output(draft, result.value.load)] }\n }\n}\n\nexport const runAiJobTemplateStep = createAiJobTemplateStep()\n\n/** Registers the template step and the check a template job passes before it is created or resumed. */\nexport function registerAiTemplateJob(): void {\n registerAiJobStep('template', runAiJobTemplateStep, { minimumMs: AI_JOB_TEMPLATE_STEP_MINIMUM_MS })\n registerAiJobAdmission('template', createAiTemplateJobAdmission())\n}\n"],"names":["hostCollectionKind","normalizeScreenSlug","duplicateResource","AI_TEMPLATE_SUBJECT_DEFINITIONS","AI_TEMPLATE_SUBJECTS","aiBindingTokensIn","aiTemplateAddressTokens","parseAiTemplateJobInputs","AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","aiDoctrineTreeTool","runValidatedGeneration","walkTree","AI_PALETTE","aiTemplateExamplesSystemBlock","readSiteInventory","registerAiJobAdmission","aiJobDraftId","aiDraftAdmissionRefusal","aiDraftAllowanceRefusal","aiSiteSubdomain","readAiDraft","writeAiDraft","aiConfirmedPlan","aiDoctrineReview","aiGenerationSpent","aiJobBriefLine","aiLimitReview","aiModelNodeIds","aiPlacedComponentIds","aiPlanCreation","aiPlanReferenceLines","aiPlanReuseViolations","aiUnspentOutcome","aiPlanTemplateTokenViolations","aiJobStepBudget","registerAiJobStep","AI_JOB_TEMPLATE_MAX_TOKENS","maxTokens","AI_JOB_TEMPLATE_STEP_BUDGET","tier","AI_JOB_TEMPLATE_STEP_MINIMUM_MS","minimumMs","AI_JOB_TEMPLATE_INSTRUCTIONS","text","join","readAiTemplateCollection","firestore","hostId","collectionId","snapshot","collection","doc","get","exists","data","value","trim","slug","id","name","aiTemplateDraftName","definition","planned","defaultName","aiTemplateDraftSlug","subject","aiJobTemplatePrompt","job","plan","of","noun","titleToken","tokens","map","entry","token","label","blocks","length","BLOCK_SUBJECTS","Map","flatMap","block","stringsIn","out","push","Array","isArray","inner","Object","values","isPageTitle","node","componentId","element","props","String","aiTemplateBindingCheck","allowed","Set","tree","nodes","unknown","unknownAt","foreign","titles","titleBound","ancestors","rootId","strays","filter","has","add","owner","inCard","some","ancestor","includes","violations","size","rule","code","message","slice","nodeIds","sourceIds","first","displayName","createAiTemplateJobAdmission","deps","readCollection","context","inputs","status","error","orgId","kind","org","ownCheck","createAiJobTemplateStep","readInventory","duplicate","examples","now","signal","modelFor","orgSnapshot","Error","output","draft","load","resource","versionId","hostSubdomain","model","draftId","written","outputs","inventory","Promise","all","resolve","creation","duplicateOf","templates","template","copy","sourceId","uid","createdBy","ok","review","allowance","result","step","instructions","messages","role","content","tool","thinking","effort","bindingTokens","extend","spent","refused","runAiJobTemplateStep","registerAiTemplateJob"],"mappings":"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"}