@aglyn/plugins-ai 1.0.0-beta.243 → 1.0.0-beta.246

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.js +3 -1
  3. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  4. package/src/lib/components/ai-job-links.js +7 -0
  5. package/src/lib/components/ai-job-links.js.map +1 -1
  6. package/src/lib/components/ai-product-zones.d.ts +3 -1
  7. package/src/lib/components/ai-product-zones.js.map +1 -1
  8. package/src/lib/components/ai-products-hub-card.component.js +1 -1
  9. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  10. package/src/lib/components/ai-products-proposals.component.js +17 -9
  11. package/src/lib/components/ai-products-proposals.component.js.map +1 -1
  12. package/src/lib/components/ai-site-start-card.component.js +6 -4
  13. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  14. package/src/lib/components/ai-upsell-dialog.component.js +1 -1
  15. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  16. package/src/lib/jobs/ai-build-capabilities.js +162 -2
  17. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  18. package/src/lib/jobs/ai-job-build-step.d.ts +7 -0
  19. package/src/lib/jobs/ai-job-build-step.js +85 -16
  20. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  21. package/src/lib/jobs/ai-job-duplicate-step.d.ts +123 -0
  22. package/src/lib/jobs/ai-job-duplicate-step.js +269 -0
  23. package/src/lib/jobs/ai-job-duplicate-step.js.map +1 -0
  24. package/src/lib/jobs/ai-job-layout-language.js +24 -6
  25. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  26. package/src/lib/jobs/ai-job-navigation-step.d.ts +157 -0
  27. package/src/lib/jobs/ai-job-navigation-step.js +439 -0
  28. package/src/lib/jobs/ai-job-navigation-step.js.map +1 -0
  29. package/src/lib/jobs/ai-job-page-language.d.ts +5 -2
  30. package/src/lib/jobs/ai-job-page-language.js +38 -10
  31. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  32. package/src/lib/jobs/ai-job-page-sections.js +1 -1
  33. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  34. package/src/lib/jobs/ai-job-page-step.js +11 -4
  35. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  36. package/src/lib/jobs/ai-job-plan-step.d.ts +4 -2
  37. package/src/lib/jobs/ai-job-plan-step.js +16 -7
  38. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  39. package/src/lib/jobs/ai-job-products-step.d.ts +2 -2
  40. package/src/lib/jobs/ai-job-products-step.js +2 -2
  41. package/src/lib/jobs/ai-job-products-step.js.map +1 -1
  42. package/src/lib/jobs/ai-job-settings-step.d.ts +272 -0
  43. package/src/lib/jobs/ai-job-settings-step.js +695 -0
  44. package/src/lib/jobs/ai-job-settings-step.js.map +1 -0
  45. package/src/lib/jobs/ai-job-site-content.d.ts +20 -6
  46. package/src/lib/jobs/ai-job-site-content.js +28 -12
  47. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  48. package/src/lib/jobs/ai-job-site-datasets.d.ts +2 -0
  49. package/src/lib/jobs/ai-job-site-datasets.js +10 -3
  50. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  51. package/src/lib/jobs/ai-job-site-step.d.ts +3 -1
  52. package/src/lib/jobs/ai-job-site-step.js +86 -12
  53. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  54. package/src/lib/jobs/ai-site-publish.d.ts +76 -4
  55. package/src/lib/jobs/ai-site-publish.js +231 -59
  56. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  57. package/src/lib/layout-language/ai-layout-compiler.d.ts +7 -4
  58. package/src/lib/layout-language/ai-layout-compiler.js +26 -25
  59. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  60. package/src/lib/layout-language/ai-layout-frame-hollows.d.ts +41 -0
  61. package/src/lib/layout-language/ai-layout-frame-hollows.js +330 -0
  62. package/src/lib/layout-language/ai-layout-frame-hollows.js.map +1 -0
  63. package/src/lib/layout-language/ai-layout-frame.d.ts +18 -1
  64. package/src/lib/layout-language/ai-layout-frame.js +83 -13
  65. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  66. package/src/lib/layout-language/ai-layout-links.js +5 -2
  67. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  68. package/src/lib/layout-language/ai-layout-listings.d.ts +35 -0
  69. package/src/lib/layout-language/ai-layout-listings.js +34 -0
  70. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  71. package/src/lib/layout-language/ai-layout-tree.d.ts +2 -0
  72. package/src/lib/layout-language/ai-layout-tree.js +17 -0
  73. package/src/lib/layout-language/ai-layout-tree.js.map +1 -1
  74. package/src/lib/model/ai-build-job.d.ts +23 -0
  75. package/src/lib/model/ai-build-job.js +102 -13
  76. package/src/lib/model/ai-build-job.js.map +1 -1
  77. package/src/lib/model/ai-build-plan.d.ts +25 -0
  78. package/src/lib/model/ai-build-plan.js +45 -1
  79. package/src/lib/model/ai-build-plan.js.map +1 -1
  80. package/src/lib/model/ai-credit-estimate.d.ts +52 -0
  81. package/src/lib/model/ai-credit-estimate.js +51 -0
  82. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  83. package/src/lib/model/ai-job-activity.js +4 -1
  84. package/src/lib/model/ai-job-activity.js.map +1 -1
  85. package/src/lib/model/ai-jobs.types.d.ts +10 -2
  86. package/src/lib/model/ai-jobs.types.js +4 -1
  87. package/src/lib/model/ai-jobs.types.js.map +1 -1
  88. package/src/lib/model/ai-layout-menus.d.ts +104 -0
  89. package/src/lib/model/ai-layout-menus.js +518 -0
  90. package/src/lib/model/ai-layout-menus.js.map +1 -0
  91. package/src/lib/model/ai-products.d.ts +31 -1
  92. package/src/lib/model/ai-products.js +34 -1
  93. package/src/lib/model/ai-products.js.map +1 -1
  94. package/src/lib/model/ai-site-inventory.d.ts +15 -0
  95. package/src/lib/model/ai-site-inventory.js.map +1 -1
  96. package/src/lib/model/ai-site-job.d.ts +10 -0
  97. package/src/lib/model/ai-site-job.js +36 -9
  98. package/src/lib/model/ai-site-job.js.map +1 -1
  99. package/src/lib/model/ai-site-store-pages.js +1 -1
  100. package/src/lib/model/ai-site-store-pages.js.map +1 -1
  101. package/src/lib/runtime/ai-doctrine-validators.d.ts +95 -1
  102. package/src/lib/runtime/ai-doctrine-validators.js +264 -36
  103. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  104. package/src/lib/runtime/ai-doctrine.js +7 -2
  105. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  106. package/src/lib/runtime/ai-palette.generated.js +1 -1
  107. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  108. package/src/lib/runtime/ai-products-generation.d.ts +4 -2
  109. package/src/lib/runtime/ai-products-generation.js +12 -6
  110. package/src/lib/runtime/ai-products-generation.js.map +1 -1
  111. package/src/lib/runtime/site-inventory.d.ts +12 -0
  112. package/src/lib/runtime/site-inventory.js +29 -1
  113. package/src/lib/runtime/site-inventory.js.map +1 -1
  114. package/src/lib/server.js +12 -0
  115. package/src/lib/server.js.map +1 -1
  116. package/src/lib/testing/stand-in-dataset-index.js +37 -3
  117. package/src/lib/testing/stand-in-dataset-index.js.map +1 -1
  118. package/src/lib/tools/ai-products-tool.d.ts +3 -1
  119. package/src/lib/tools/ai-products-tool.js +15 -5
  120. package/src/lib/tools/ai-products-tool.js.map +1 -1
  121. package/src/lib/usage/ai-quota-checks.d.ts +13 -3
  122. package/src/lib/usage/ai-quota-checks.js +46 -8
  123. package/src/lib/usage/ai-quota-checks.js.map +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-navigation-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 { hostRoleCanWrite } from '@aglyn/aglyn/app-utils/organizations'\nimport { checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { decodeStoredNodes, encodeStoredNodes } from '@aglyn/aglyn/app-utils/stored-nodes'\nimport { getLockdownVerdict } from '@aglyn/tenant-data-admin/server/lockdown'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport type { Firestore } from 'firebase-admin/firestore'\nimport { AI_EDIT_GONE_COPY, AI_EDIT_NEEDS_VERSIONING_COPY, AI_EDIT_NO_NODES_COPY, AI_EDIT_VERSION_GONE_COPY } from '../model/ai-edit-job'\nimport { isAiPlanNewRef } from '../model/ai-build-plan'\nimport { AI_BUILD_MADE_INPUT, type AiBuildMade } from '../model/ai-build-job'\nimport { aiBuildStoreLayoutOf } from '../model/ai-build-store-pages'\nimport type { AiJob, AiJobOutput } from '../model/ai-jobs.types'\nimport {\n aiLayoutWithMenuChanges,\n aiMenuChangesEmpty,\n aiMenuChangesOf,\n aiMenusOf,\n type AiMenuChanges,\n type AiMenuName,\n type AiMenuTarget,\n} from '../model/ai-layout-menus'\nimport { registerAiJobAdmission, type AiJobAdmission, type AiJobAdmissionRefusal } from './ai-job-admission'\nimport { aiOriginJobId } from './ai-job-draft-ids'\nimport { aiEditPlacement, aiEditVersionId, aiJobEditModel, readAiEditTarget } from './ai-job-edit-step'\nimport { aiLimitReview, aiUnspentOutcome } from './ai-job-generation'\nimport type { AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The `navigation` step (AGL-3728): a change to a site's header or footer\n * menu — add, take out, rename, relink or reorder its items — as a\n * first-class build operation, so Assist's one plan card can say \"add About\n * and Pricing to the menu, and take Blog out\" and have it done.\n *\n * NO MODEL. The planner already wrote the change as the item's arguments, in\n * words (`aiMenuChangesOf`); this applies it to the layout's own Besigner\n * elements in code (`aiLayoutWithMenuChanges`), so it costs no credits and\n * cannot invent a link: every destination is one of the site's pages, a page\n * this build made, a path on the site or an https address.\n *\n * ── Where it lands ───────────────────────────────────────────────────────\n *\n * As the `edit` step's change to a layout does: a NEW version beside the\n * layout's current one, which visitors keep seeing until a person publishes\n * the new one. A layout is always served, so without version history there is\n * nowhere for it to land and it is refused as a plan limit, charged nothing.\n * The version is named by the job's id, so a pass asked again finds what it\n * wrote.\n *\n * ── Who may ──────────────────────────────────────────────────────────────\n *\n * The editor's own rule for a layout's versions (`canWriteHostContent` and\n * `hostWritesFrozen` in the Firestore rules): a member with a write role on\n * the site (admin, editor or author), while no lockdown freezes the site, its\n * org or the platform. Asked at the door, again before the write, and the\n * role once more inside the transaction that writes.\n *\n * ── Audit ────────────────────────────────────────────────────────────────\n *\n * The layout it wrote is the job's output, which the job machine records as\n * an `ai.job.output` activity row on the site, as every other operation's is.\n */\n\nexport const AI_NAVIGATION_KIND = 'navigation' as const\n\n/**\n * ASSUMED: the least time the step needs — the site's pages, the layout and\n * its version, the org, and the one transaction that writes. No model, so it\n * fits an inline door.\n */\nexport const AI_JOB_NAVIGATION_STEP_MINIMUM_MS = 5_000\n\nexport const AI_NAVIGATION_NO_SITE_COPY = 'Open the site whose menu should change.'\nexport const AI_NAVIGATION_ROLE_COPY = 'Changing the site’s menus needs the admin, editor or author role on this site.'\nexport const AI_NAVIGATION_LOCKED_COPY = 'This site is not accepting changes right now.'\nexport const AI_NAVIGATION_NO_LAYOUT_COPY = 'This site has no layout with a header or footer to change. Make one in Layouts first.'\nexport const AI_NAVIGATION_NOTHING_COPY = 'The request named no menu change this could make.'\n\n/** The name the new version carries in the layout's versions. */\nexport const AI_NAVIGATION_VERSION_NAME = 'Menu changes'\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** A path on the site, as a menu item may link it. */\nconst SITE_PATH = /^\\/(?:[A-Za-z0-9._~!$&'()*+,;=:@%-]+(?:\\/[A-Za-z0-9._~!$&'()*+,;=:@%-]+)*)?\\/?(?:[?#][^\\s]*)?$/\nconst SCREEN_ID = /^[A-Za-z0-9_-]{1,128}$/\n\nfunction buildMadeOf(inputs: Readonly<Record<string, unknown>> | null | undefined): AiBuildMade {\n const raw = (inputs?.[AI_BUILD_MADE_INPUT] ?? {}) as Partial<Record<'pages' | 'layouts', unknown>>\n const rows = (value: unknown, name: 'title' | 'name') =>\n (Array.isArray(value) ? value : []).flatMap((entry) => {\n const row = (entry ?? {}) as Record<string, unknown>\n return text(row[name]) && SCREEN_ID.test(text(row['id'])) ? [{ [name]: text(row[name]), id: text(row['id']) }] : []\n })\n return { pages: rows(raw.pages, 'title') as AiBuildMade['pages'], layouts: rows(raw.layouts, 'name') as AiBuildMade['layouts'] }\n}\n\n/** The site's pages a menu may link, by id, with the layout each renders inside. */\nexport interface AiNavigationSitePage {\n id: string\n title: string\n slug: string\n template: boolean\n layoutId: string | null\n}\n\n/** The site's pages: not deleted, not an email design. */\nexport async function readAiNavigationSitePages(firestore: Firestore, hostId: string): Promise<AiNavigationSitePage[]> {\n const snapshot = await firestore.collection('hosts').doc(hostId).collection('screens').select('displayName', 'slug', 'kind', 'deletedAt', 'layoutId').get()\n return snapshot.docs.flatMap((doc) => {\n const kind = doc.get('kind')\n if (doc.get('deletedAt') || (kind && kind !== 'template')) return []\n const layoutId = doc.get('layoutId')\n return [\n {\n id: doc.id,\n slug: String(doc.get('slug') ?? ''),\n title: String(doc.get('displayName') ?? ''),\n template: kind === 'template',\n layoutId: typeof layoutId === 'string' && layoutId ? layoutId : null,\n },\n ]\n })\n}\n\n/**\n * What a destination in an item's words links (AGL-3728): a page this build\n * made (`new:<title>`), one of the site's pages by id (`page:<id>` or the bare\n * id), a path on the site, or an https address; `null` for anything else.\n */\nexport function aiNavigationTarget(\n destination: string,\n context: { pages: readonly Pick<AiNavigationSitePage, 'id' | 'template'>[]; made: AiBuildMade },\n): AiMenuTarget | null {\n const value = destination.trim()\n const pageIds = new Set(context.pages.filter((page) => !page.template).map((page) => page.id))\n if (isAiPlanNewRef(value as string | null)) {\n const title = value.slice(value.indexOf(':') + 1).trim().toLowerCase()\n const made = context.made.pages.find((page) => page.title.trim().toLowerCase() === title)\n // A page this build was to make and did not is no destination.\n return made && pageIds.has(made.id) ? { screenId: made.id } : null\n }\n const id = value.replace(/^page:/i, '')\n if (pageIds.has(id)) return { screenId: id }\n if (value.startsWith('/') && !value.startsWith('//') && SITE_PATH.test(value)) return { href: value }\n if (/^https:\\/\\/[^\\s/?#]+\\.[^\\s/?#]+(?:[/?#]\\S*)?$/i.test(value)) return { href: value }\n return null\n}\n\n/** The layout an item changes: the one it names, else the one the site's pages render inside. */\nexport function aiNavigationLayoutOf(\n named: string,\n context: { pages: readonly AiNavigationSitePage[]; made: AiBuildMade },\n): string | null {\n if (isAiPlanNewRef(named as string | null)) {\n const name = named.slice(named.indexOf(':') + 1).trim().toLowerCase()\n return context.made.layouts.find((layout) => layout.name.trim().toLowerCase() === name)?.id ?? null\n }\n if (SCREEN_ID.test(named)) return named\n return aiBuildStoreLayoutOf(null, context.pages)\n}\n\n/** Why this member may not change this site's menus now, or `null`. */\nexport async function aiNavigationAccessRefusal(\n input: { host: FirebaseFirestore.DocumentSnapshot; uid: string; org: object | null },\n deps: { lockdown?: typeof getLockdownVerdict } = {},\n): Promise<string | null> {\n const roles = (input.host.get('memberRoles') ?? {}) as Record<string, unknown>\n if (!hostRoleCanWrite(roles[input.uid])) return AI_NAVIGATION_ROLE_COPY\n const locked = await (deps.lockdown ?? getLockdownVerdict)({\n staff: false,\n uid: input.uid,\n org: input.org as never,\n host: input.host.data() as never,\n intent: 'write',\n })\n return locked ? AI_NAVIGATION_LOCKED_COPY : null\n}\n\n/** The new version's output note: what was done, what was not, and that it waits to be published. */\nexport function aiNavigationOutputNote(done: readonly string[], notDone: readonly string[]): string {\n const did = done.length ? `${done.join(' ')} ` : ''\n const not = notDone.length ? ` Not done: ${notDone.join(' ')}` : ''\n return `${did}Saved as a new version of the layout beside the one visitors see: open it to review it, and publish it when it is ready.${not}`.slice(0, 1_000)\n}\n\nexport type AiNavigationWrite =\n | { ok: true; versionId: string; done: string[]; notDone: string[] }\n | { ok: false; error: string }\n\n/**\n * The change written, in one transaction that reads the layout as it is\n * stored NOW: it still exists, its version is still there, the member still\n * holds a write role, and the plan still keeps version history. The change is\n * applied again to that version's nodes, so a save made since is kept.\n */\nexport async function writeAiNavigation(\n firestore: Firestore,\n input: {\n hostId: string\n layoutId: string\n sourceVersionId: string\n versionId: string\n menus: readonly AiMenuName[]\n changes: AiMenuChanges\n uid: string\n aiJobId: string\n versioning: boolean\n now: Date\n },\n): Promise<AiNavigationWrite> {\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n const docRef = hostRef.collection('layouts').doc(input.layoutId)\n const sourceRef = docRef.collection('versions').doc(input.sourceVersionId)\n const newRef = docRef.collection('versions').doc(input.versionId)\n return firestore.runTransaction(async (tx): Promise<AiNavigationWrite> => {\n const [host, doc, source, already] = await Promise.all([tx.get(hostRef), tx.get(docRef), tx.get(sourceRef), tx.get(newRef)])\n if (already.exists) return { ok: true, versionId: input.versionId, done: [], notDone: [] }\n if (!doc.exists || doc.get('deletedAt') != null) return { ok: false, error: AI_EDIT_GONE_COPY }\n if (!source.exists) return { ok: false, error: AI_EDIT_VERSION_GONE_COPY }\n const roles = (host.get('memberRoles') ?? {}) as Record<string, unknown>\n if (!hostRoleCanWrite(roles[input.uid])) return { ok: false, error: AI_NAVIGATION_ROLE_COPY }\n const pointer = text(doc.get('versionId')) || null\n const placement = aiEditPlacement({\n versioning: input.versioning,\n served: true,\n sourceIsPointer: pointer === input.sourceVersionId,\n sourceScheduled: false,\n })\n if (placement === 'refused') return { ok: false, error: AI_EDIT_NEEDS_VERSIONING_COPY }\n const nodes = decodeStoredNodes<Record<string, unknown>>(source.get('nodes'))\n if (!nodes) return { ok: false, error: AI_EDIT_NO_NODES_COPY }\n const applied = aiLayoutWithMenuChanges(nodes, input.menus, input.changes)\n if (!applied.nodes) return { ok: false, error: applied.notDone.join(' ') || AI_NAVIGATION_NOTHING_COPY }\n const packed = encodeStoredNodes(applied.nodes as never)\n if (!packed) return { ok: false, error: AI_EDIT_NO_NODES_COPY }\n // A copy of the version it started from — everything the editor stored —\n // with the changed menus, as the versions route copies one.\n const copy = { ...(source.data() as Record<string, unknown>) }\n delete copy['aiEditJobId']\n tx.create(newRef, {\n ...copy,\n nodes: Buffer.from(packed),\n displayName: AI_NAVIGATION_VERSION_NAME,\n createdAt: input.now,\n updatedAt: input.now,\n createdBy: input.uid,\n aiJobId: input.aiJobId,\n })\n return { ok: true, versionId: input.versionId, done: applied.done, notDone: applied.notDone }\n })\n}\n\nexport interface AiJobNavigationStepDeps {\n readTarget?: typeof readAiEditTarget\n readPages?: typeof readAiNavigationSitePages\n write?: typeof writeAiNavigation\n lockdown?: typeof getLockdownVerdict\n}\n\n/** What the step decided before writing: the layout, its version, the change and what was read away. */\ninterface AiNavigationPlan {\n layoutId: string\n menus: AiMenuName[]\n changes: AiMenuChanges\n problems: string[]\n}\n\nasync function navigationPlanOf(\n firestore: Firestore,\n job: Pick<AiJob, 'hostId' | 'inputs'>,\n readPages: typeof readAiNavigationSitePages,\n): Promise<AiNavigationPlan | string> {\n if (!job.hostId) return AI_NAVIGATION_NO_SITE_COPY\n const inputs = job.inputs ?? {}\n const pages = await readPages(firestore, job.hostId)\n const made = buildMadeOf(inputs)\n const layoutId = aiNavigationLayoutOf(text(inputs['layout']), { pages, made })\n if (!layoutId) return AI_NAVIGATION_NO_LAYOUT_COPY\n const read = aiMenuChangesOf(inputs, (destination) => aiNavigationTarget(destination, { pages, made }))\n if (aiMenuChangesEmpty(read.changes)) return [AI_NAVIGATION_NOTHING_COPY, ...read.problems].join(' ')\n return { layoutId, menus: aiMenusOf(inputs['menu']), changes: read.changes, problems: read.problems }\n}\n\nexport function createAiJobNavigationStep(deps: AiJobNavigationStepDeps = {}): AiJobStepRunner {\n const readTarget = deps.readTarget ?? readAiEditTarget\n const readPages = deps.readPages ?? readAiNavigationSitePages\n const write = deps.write ?? writeAiNavigation\n return async ({ job, now, firestore, org, modelFor }): Promise<AiJobStepOutcome> => {\n // No model is asked: the outcome records the edit step's at no cost.\n const model = modelFor?.('job.edit') ?? aiJobEditModel()\n const unspent = (failure: string): AiJobStepOutcome => ({ ...aiUnspentOutcome(model), failure })\n const hostId = job.hostId\n if (!hostId) return unspent(AI_NAVIGATION_NO_SITE_COPY)\n const plan = await navigationPlanOf(firestore, job, readPages)\n if (typeof plan === 'string') return unspent(plan)\n const versionId = aiEditVersionId(job)\n const read = await readTarget(firestore, {\n orgId: job.orgId,\n hostId,\n inputs: { target: plan.layoutId, targetKind: 'layout', versionId: null },\n jobId: versionId,\n })\n if ('error' in read) return unspent(read.error)\n const output = (done: readonly string[], notDone: readonly string[]): AiJobOutput => ({\n resource: 'layout',\n id: read.id,\n versionId,\n hostId,\n hostSubdomain: read.hostSubdomain,\n label: read.name,\n note: aiNavigationOutputNote(done, notDone),\n })\n if (read.written) return aiUnspentOutcome(model, { outputs: [output([], [])] })\n const orgData = org ?? ((await firestore.collection('orgs').doc(job.orgId).get()).data() ?? null)\n const host = await firestore.collection('hosts').doc(hostId).get()\n const refused = await aiNavigationAccessRefusal({ host, uid: job.createdBy, org: orgData }, { lockdown: deps.lockdown })\n if (refused) return aiUnspentOutcome(model, { review: aiLimitReview(refused) })\n const versioning = checkEntitlement(orgData as never, 'versioning')\n const placement = aiEditPlacement({\n versioning,\n served: true,\n sourceIsPointer: read.sourceVersionId === read.pointerVersionId,\n sourceScheduled: read.scheduledVersionId === read.sourceVersionId,\n })\n if (placement === 'refused') return aiUnspentOutcome(model, { review: aiLimitReview(AI_EDIT_NEEDS_VERSIONING_COPY) })\n const written = await write(firestore, {\n hostId,\n layoutId: read.id,\n sourceVersionId: read.sourceVersionId,\n versionId,\n menus: plan.menus,\n changes: plan.changes,\n uid: job.createdBy,\n aiJobId: aiOriginJobId(job),\n versioning,\n now,\n })\n if (written.ok === false) return unspent(written.error)\n return aiUnspentOutcome(model, { outputs: [output(written.done, [...written.notDone, ...plan.problems])] })\n }\n}\n\nexport const runAiJobNavigationStep = createAiJobNavigationStep()\n\n// ── Admission ─────────────────────────────────────────────────────────────\n\nexport interface AiNavigationJobAdmissionDeps {\n ownerOf?: typeof resolveOrgIdForHost\n readTarget?: typeof readAiEditTarget\n readPages?: typeof readAiNavigationSitePages\n lockdown?: typeof getLockdownVerdict\n}\n\n/**\n * A navigation change is admitted on a site of the job's own org, for a\n * member who may write its content now, where the site has a layout and the\n * change has somewhere to land that no visitor sees on this plan. Refused in\n * those words before anything exists.\n */\nexport function createAiNavigationJobAdmission(deps: AiNavigationJobAdmissionDeps = {}): AiJobAdmission {\n const ownerOf = deps.ownerOf ?? resolveOrgIdForHost\n const readTarget = deps.readTarget ?? readAiEditTarget\n const readPages = deps.readPages ?? readAiNavigationSitePages\n return async (context): Promise<AiJobAdmissionRefusal | null> => {\n if (!context.hostId) return { status: 400, error: AI_NAVIGATION_NO_SITE_COPY }\n const owner = await ownerOf(context.hostId)\n if (!owner || owner !== context.orgId) return { status: 404, error: 'Unknown site' }\n const host = await context.firestore.collection('hosts').doc(context.hostId).get()\n const refused = await aiNavigationAccessRefusal({ host, uid: context.uid ?? '', org: context.org }, { lockdown: deps.lockdown })\n if (refused) return { status: 403, error: refused }\n const plan = await navigationPlanOf(context.firestore as Firestore, { hostId: context.hostId, inputs: context.inputs as Record<string, unknown> }, readPages)\n if (typeof plan === 'string') return { status: 400, error: plan }\n const read = await readTarget(context.firestore, {\n orgId: context.orgId,\n hostId: context.hostId,\n inputs: { target: plan.layoutId, targetKind: 'layout', versionId: null },\n jobId: null,\n })\n if ('error' in read) return read\n const placement = aiEditPlacement({\n versioning: checkEntitlement(context.org as never, 'versioning'),\n served: true,\n sourceIsPointer: read.sourceVersionId === read.pointerVersionId,\n sourceScheduled: read.scheduledVersionId === read.sourceVersionId,\n })\n return placement === 'refused' ? { status: 403, error: AI_EDIT_NEEDS_VERSIONING_COPY } : null\n }\n}\n\nexport const aiNavigationJobAdmission = createAiNavigationJobAdmission()\n\n/** Registers the navigation step and its door (AGL-3728). */\nexport function registerAiNavigationJob(): void {\n registerAiJobStep(AI_NAVIGATION_KIND, runAiJobNavigationStep, { minimumMs: AI_JOB_NAVIGATION_STEP_MINIMUM_MS })\n registerAiJobAdmission(AI_NAVIGATION_KIND, aiNavigationJobAdmission)\n}\n"],"names":["hostRoleCanWrite","checkEntitlement","decodeStoredNodes","encodeStoredNodes","getLockdownVerdict","resolveOrgIdForHost","AI_EDIT_GONE_COPY","AI_EDIT_NEEDS_VERSIONING_COPY","AI_EDIT_NO_NODES_COPY","AI_EDIT_VERSION_GONE_COPY","isAiPlanNewRef","AI_BUILD_MADE_INPUT","aiBuildStoreLayoutOf","aiLayoutWithMenuChanges","aiMenuChangesEmpty","aiMenuChangesOf","aiMenusOf","registerAiJobAdmission","aiOriginJobId","aiEditPlacement","aiEditVersionId","aiJobEditModel","readAiEditTarget","aiLimitReview","aiUnspentOutcome","registerAiJobStep","AI_NAVIGATION_KIND","AI_JOB_NAVIGATION_STEP_MINIMUM_MS","AI_NAVIGATION_NO_SITE_COPY","AI_NAVIGATION_ROLE_COPY","AI_NAVIGATION_LOCKED_COPY","AI_NAVIGATION_NO_LAYOUT_COPY","AI_NAVIGATION_NOTHING_COPY","AI_NAVIGATION_VERSION_NAME","text","value","trim","SITE_PATH","SCREEN_ID","buildMadeOf","inputs","raw","rows","name","Array","isArray","flatMap","entry","row","test","id","pages","layouts","readAiNavigationSitePages","firestore","hostId","snapshot","collection","doc","select","get","docs","kind","layoutId","slug","String","title","template","aiNavigationTarget","destination","context","pageIds","Set","filter","page","map","slice","indexOf","toLowerCase","made","find","has","screenId","replace","startsWith","href","aiNavigationLayoutOf","named","layout","aiNavigationAccessRefusal","input","deps","roles","host","uid","locked","lockdown","staff","org","data","intent","aiNavigationOutputNote","done","notDone","did","length","join","not","writeAiNavigation","hostRef","docRef","sourceRef","sourceVersionId","newRef","versionId","runTransaction","tx","source","already","Promise","all","exists","ok","error","pointer","placement","versioning","served","sourceIsPointer","sourceScheduled","nodes","applied","menus","changes","packed","copy","create","Buffer","from","displayName","createdAt","now","updatedAt","createdBy","aiJobId","navigationPlanOf","job","readPages","read","problems","createAiJobNavigationStep","readTarget","write","modelFor","model","unspent","failure","plan","orgId","target","targetKind","jobId","output","resource","hostSubdomain","label","note","written","outputs","orgData","refused","review","pointerVersionId","scheduledVersionId","runAiJobNavigationStep","createAiNavigationJobAdmission","ownerOf","status","owner","aiNavigationJobAdmission","registerAiNavigationJob","minimumMs"],"mappings":"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"}
@@ -114,7 +114,8 @@ export declare function aiLayoutPageTargets(input: {
114
114
  * (AGL-3616): a records listing is kept only where the site has the dataset —
115
115
  * the tree check admits a repeat over no other, and a card left repeating
116
116
  * over nothing would show its `{{item.*}}` — and one that names no fields
117
- * takes the inventory's, in the dataset's order.
117
+ * takes the inventory's, in the dataset's order. Its cards link the record
118
+ * pages the plan makes, else those the site already serves (AGL-3747).
118
119
  */
119
120
  export declare function aiLayoutListingsWithDatasets(listings: readonly AiLayoutListing[], inventory: Pick<AiSiteInventory, 'datasets'> | null): AiLayoutListing[];
120
121
  /**
@@ -122,7 +123,9 @@ export declare function aiLayoutListingsWithDatasets(listings: readonly AiLayout
122
123
  * binds (AGL-3676): each post's card links its post and shows its cover by
123
124
  * the tokens the page fills per post, which a page's store admits whole only
124
125
  * where a listing places them. A dataset's card shows its record's photo
125
- * the same way (AGL-3616), by `{{item.<photo field>}}`.
126
+ * the same way (AGL-3616), by `{{item.<photo field>}}`. And the page that
127
+ * places the site's form, which a call to action asking a visitor to get in
128
+ * touch links even when it is home (AGL-3729).
126
129
  */
127
130
  export declare function aiLayoutListingContext(context: AiDoctrineTreeContext, targets: AiLayoutTargets): AiDoctrineTreeContext;
128
131
  /** The page's user turn: the page, the brief, the plan, its sections and where its links may go. */
@@ -18,14 +18,14 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  import { isAiPlanNewRef } from "../model/ai-build-plan.js";
19
19
  import { aiHomeScreenIds } from "../model/ai-site-inventory.js";
20
20
  import { AI_LAYOUT_POST_ADDRESS_TOKENS, aiCompileLayoutPage } from "../layout-language/ai-layout-compiler.js";
21
- import { aiLayoutListingAt, aiLayoutListingsOf } from "../layout-language/ai-layout-listings.js";
21
+ import { aiLayoutListingAt, aiLayoutListingsOf, aiLayoutRecordHref } from "../layout-language/ai-layout-listings.js";
22
22
  import { AI_LAYOUT_LANGUAGE_TEXT, AI_LAYOUT_PAGE_TOOL, aiReadLayoutPage } from "../layout-language/ai-layout-language.js";
23
23
  import { aiLayoutCopyCheck } from "../layout-language/ai-layout-gaps.js";
24
24
  import { aiLayoutStoredTree } from "../layout-language/ai-layout-store.js";
25
25
  import { AI_GENERATION_MAX_ATTEMPTS } from "../runtime/ai-generation-bounds.js";
26
26
  import { AI_STEP_TIERS } from "../providers/catalog.js";
27
27
  import { runValidatedGeneration } from "../runtime/ai-doctrine.js";
28
- import { aiSettleMechanicalRules, validateAiDoctrineTree } from "../runtime/ai-doctrine-validators.js";
28
+ import { aiSettleMechanicalRules, aiSettleUnrelatedScreenLinks, validateAiDoctrineTree } from "../runtime/ai-doctrine-validators.js";
29
29
  import { aiSiteKindDesignLines, aiSiteKindOfInputs } from "../model/ai-site-kinds.js";
30
30
  import { aiSiteSeed } from "../model/ai-site-look.js";
31
31
  import { aiOriginJobId } from "./ai-job-draft-ids.js";
@@ -169,10 +169,11 @@ export const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({
169
169
  * (AGL-3616): a records listing is kept only where the site has the dataset —
170
170
  * the tree check admits a repeat over no other, and a card left repeating
171
171
  * over nothing would show its `{{item.*}}` — and one that names no fields
172
- * takes the inventory's, in the dataset's order.
172
+ * takes the inventory's, in the dataset's order. Its cards link the record
173
+ * pages the plan makes, else those the site already serves (AGL-3747).
173
174
  */ export function aiLayoutListingsWithDatasets(listings, inventory) {
174
175
  return listings.flatMap((listing)=>{
175
- var _listing_imageField;
176
+ var _listing_imageField, _listing_addressField, _listing_recordBase;
176
177
  var _listing_fields;
177
178
  if (listing.kind !== 'records') return [
178
179
  listing
@@ -181,6 +182,11 @@ export const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({
181
182
  if (!row) return [];
182
183
  // The photo field is the card's picture, never its words (AGL-3616).
183
184
  const imageField = (_listing_imageField = listing.imageField) != null ? _listing_imageField : row.imageField;
185
+ // The address is the card's link to its record's page, never its words (AGL-3727).
186
+ const addressField = (_listing_addressField = listing.addressField) != null ? _listing_addressField : row.addressField;
187
+ // A record page the site already serves for the dataset (AGL-3747), where
188
+ // the plan makes none of its own.
189
+ const recordBase = (_listing_recordBase = listing.recordBase) != null ? _listing_recordBase : row.recordBase;
184
190
  const fields = (((_listing_fields = listing.fields) == null ? void 0 : _listing_fields.length) ? listing.fields : row.fields.map((name, index)=>{
185
191
  var _row_fieldIds;
186
192
  return {
@@ -188,12 +194,15 @@ export const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({
188
194
  name,
189
195
  type: 'text'
190
196
  };
191
- })).filter((field)=>field.id !== imageField);
197
+ })).filter((field)=>field.id !== imageField && field.id !== addressField);
192
198
  return fields.length ? [
193
199
  _extends({}, listing, {
194
200
  fields
195
201
  }, imageField ? {
196
202
  imageField
203
+ } : {}, recordBase && addressField ? {
204
+ recordBase,
205
+ addressField
197
206
  } : {})
198
207
  ] : [];
199
208
  });
@@ -203,15 +212,29 @@ export const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({
203
212
  * binds (AGL-3676): each post's card links its post and shows its cover by
204
213
  * the tokens the page fills per post, which a page's store admits whole only
205
214
  * where a listing places them. A dataset's card shows its record's photo
206
- * the same way (AGL-3616), by `{{item.<photo field>}}`.
215
+ * the same way (AGL-3616), by `{{item.<photo field>}}`. And the page that
216
+ * places the site's form, which a call to action asking a visitor to get in
217
+ * touch links even when it is home (AGL-3729).
207
218
  */ export function aiLayoutListingContext(context, targets) {
208
219
  var _targets_listings, _context_bindingTokens;
220
+ if (targets.formPageId) context = _extends({}, context, {
221
+ formScreenIds: [
222
+ targets.formPageId
223
+ ]
224
+ });
209
225
  const here = ((_targets_listings = targets.listings) != null ? _targets_listings : []).filter((listing)=>listing.placements.some((placement)=>placement.screenId === targets.pageId));
210
226
  const tokens = [
211
227
  ...here.some((listing)=>listing.kind === 'posts') ? AI_LAYOUT_POST_ADDRESS_TOKENS : [],
212
228
  ...here.flatMap((listing)=>listing.kind === 'records' && listing.imageField ? [
213
229
  `{{item.${listing.imageField}}}`
214
- ] : [])
230
+ ] : []),
231
+ // Each card's link to its record's own page (AGL-3727), admitted whole.
232
+ ...here.flatMap((listing)=>{
233
+ const href = aiLayoutRecordHref(listing);
234
+ return href ? [
235
+ href
236
+ ] : [];
237
+ })
215
238
  ];
216
239
  if (!tokens.length) return context;
217
240
  return _extends({}, context, {
@@ -422,11 +445,16 @@ export const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({
422
445
  ]
423
446
  }, last);
424
447
  const { dropped } = copy;
425
- // An attribute a rule has one answer for is settled, not asked of the model again (AGL-3660).
426
- const nodes = aiSettleMechanicalRules({
448
+ // An attribute a rule has one answer for is settled, not asked of the model again (AGL-3660),
449
+ // and a link sent home for nothing is sent to the form or left out (AGL-3729).
450
+ const nodes = aiSettleUnrelatedScreenLinks(aiSettleMechanicalRules({
427
451
  rootId: CANVAS_ROOT_ELEMENT_ID,
428
452
  nodes: copy.nodes
429
- }, 'page').nodes;
453
+ }, 'page'), 'page', _extends({}, context, {
454
+ ownScreenIds: input.targets.pageId ? [
455
+ input.targets.pageId
456
+ ] : []
457
+ })).nodes;
430
458
  const report = validateAiDoctrineTree({
431
459
  rootId: CANVAS_ROOT_ELEMENT_ID,
432
460
  nodes
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-page-language.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { isAiPlanNewRef, type AiBuildPlanScreen } from '../model/ai-build-plan'\nimport type { AiJob, AiJobPlan } from '../model/ai-jobs.types'\nimport {\n aiHomeScreenIds,\n type AiSiteInventory,\n} from '../model/ai-site-inventory'\nimport { AI_LAYOUT_POST_ADDRESS_TOKENS, aiCompileLayoutPage } from '../layout-language/ai-layout-compiler'\nimport { aiLayoutListingAt, aiLayoutListingsOf, type AiLayoutListing } from '../layout-language/ai-layout-listings'\nimport {\n AI_LAYOUT_LANGUAGE_TEXT,\n AI_LAYOUT_PAGE_TOOL,\n aiReadLayoutPage,\n type AiLayoutSection,\n type AiLayoutSettlement,\n} from '../layout-language/ai-layout-language'\nimport type {\n AiLayoutPage,\n AiLayoutTargets,\n} from '../layout-language/ai-layout-links'\nimport { aiLayoutCopyCheck } from '../layout-language/ai-layout-gaps'\nimport { aiLayoutStoredTree } from '../layout-language/ai-layout-store'\nimport { AI_GENERATION_MAX_ATTEMPTS } from '../runtime/ai-generation-bounds'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport {\n runValidatedGeneration,\n type AiGenerationCheck,\n type AiGenerationSpend,\n type AiValidatedGeneration,\n} from '../runtime/ai-doctrine'\nimport type { AiUsage } from '../providers/contract'\nimport {\n aiSettleMechanicalRules,\n validateAiDoctrineTree,\n type AiDoctrineNode,\n type AiDoctrineTreeContext,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport { aiSiteKindDesignLines, aiSiteKindOfInputs } from '../model/ai-site-kinds'\nimport { aiSiteSeed } from '../model/ai-site-look'\nimport type { AiLayoutDesign } from '../layout-language/ai-layout-design'\nimport { aiOriginJobId } from './ai-job-draft-ids'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { aiGenerationWorstCaseOnTierMs, aiJobStepBudget } from './ai-job-budget'\nimport { aiJobBriefLine, aiPlanReferenceLines } from './ai-job-generation'\nimport { aiLayoutIsHomeSlug, aiLayoutSiteAliases, aiLayoutSitePages } from './ai-job-layout-site-pages'\nimport { aiLayoutInventedContactViolations } from './ai-layout-site-facts'\n\n/**\n * A page built in the compact layout language (AGL-3660): the page step's\n * pass for a site's page units — a guided start's and a build's — in place of\n * the raw-tree section passes.\n *\n * ONE answer a page: every planned section, in the language, through\n * `submit_page`. The compiler writes the tree, the palette validator stores\n * it as it stores any generated tree, and the doctrine checks the whole page.\n * A section the answer left out or left empty is the one thing a re-ask asks\n * for: the sections that read are kept, and the second answer fills only\n * what is still missing. Then the page step's last pass runs as it always\n * has — the whole page held to the doctrine again, its listing written, the\n * draft reported — so the item ledger, the credits and Try again work exactly\n * as they did.\n */\n\n/** The unit input that says a page or a layout is built in the layout language. */\nexport const AI_LAYOUT_LANGUAGE_INPUT = 'layoutLanguage'\n\n/** The unit input naming the page of the site that places its form, where it is another page. */\nexport const AI_LAYOUT_FORM_PAGE_INPUT = 'layoutFormPage'\n\n/** Whether this job builds its page or its layout in the layout language. */\nexport function aiJobUsesLayoutLanguage(job: Pick<AiJob, 'inputs'>): boolean {\n return job.inputs?.[AI_LAYOUT_LANGUAGE_INPUT] === true\n}\n\n/**\n * How the language doors think (AGL-3660): adaptively, at an effort named\n * rather than left to the model's default. Zach, 2026-10-07: quality first —\n * a page and a site's frame are designed, not transcribed, and the caching\n * work pays for the thinking. Medium is where Claude Sonnet 5.5, the balanced\n * default, is tuned to start for multistep design work.\n */\nexport const AI_LAYOUT_LANGUAGE_THINKING = { thinking: 'adaptive', effort: 'medium' } as const\n\n/**\n * Room for the thinking a page's answer does before it writes, inside the\n * same `max_tokens` as the answer: thinking is drawn from the ceiling first,\n * so a ceiling sized for the answer alone would cut a thoughtful answer off.\n *\n * Bounded by TIME, not by money: the page step's answer, its re-ask and the\n * follow-up must fit the least time a beat gives a step\n * (`AI_JOB_STEP_MAX_MINIMUM_MS`), and `aiJobStepBudget` quietly lowers every\n * ceiling to what fits. 2,000 here, with the follow-up's 500, is the most\n * that fits whole on the balanced tier; at 5,000 the budget cut a page's\n * ceiling to 879 tokens, under the answer itself.\n */\nexport const AI_LAYOUT_PAGE_THINKING_TOKENS = 2_000\n\n/** The same room for a site's header and footer, whose step has the time for more. */\nexport const AI_LAYOUT_FRAME_THINKING_TOKENS = 4_000\n\n/**\n * The most one page's answer asks for: the answer and the thinking before it.\n * A section in the language is about 120 to 250 tokens with its copy, so the\n * largest page a plan admits — eight sections — fits in 3,000 with room (the\n * live runs of 2026-10-07 wrote 683 to 1,745); the compiler, not the ceiling,\n * keeps a page small.\n */\nexport const AI_JOB_PAGE_LANGUAGE_TOKENS = 3_000 + AI_LAYOUT_PAGE_THINKING_TOKENS\n\n/**\n * The most one section of a page is written in, its copy included: the top of\n * the 120 to 250 tokens a section runs to. The Free site wall prices each\n * planned section at it (AGL-3660).\n */\nexport const AI_LAYOUT_SECTION_MOST_TOKENS = 250\n\n/**\n * The time a language page pass needs: its lookup rounds, its answer and its\n * re-ask at `AI_JOB_PAGE_LANGUAGE_TOKENS` on the tier the page step is served\n * from, with the step's reads and writes.\n */\n/** The most the follow-up asking only for the sections an answer left out may run to, its thinking included. */\nexport const AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS = 1_500 + 500\n\nexport const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.page'],\n maxTokens: AI_JOB_PAGE_LANGUAGE_TOKENS,\n // The follow-up for sections an answer left out is one more generation.\n ownReadsMs: aiGenerationWorstCaseOnTierMs({\n tier: AI_STEP_TIERS['job.page'],\n maxTokens: AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS,\n }),\n})\n\n/** The doctrine scope the language doors are told: values the compiler checks itself. */\nexport const AI_LAYOUT_PAGE_KIND = 'layout-page'\n\n/** The language, as the page door's cached instructions. */\nexport const AI_JOB_PAGE_LANGUAGE_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n AI_LAYOUT_LANGUAGE_TEXT,\n '',\n 'Answer with submit_page: the whole page, one entry in sections for each planned section, in order.',\n ].join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\n/** The page of a site plan that places its form: the form the plan creates, or one the site has. */\nexport function aiLayoutFormPageOfPlan(\n plan: Pick<AiJobPlan, 'create' | 'screens'>,\n formIds: readonly string[] = [],\n): string | null {\n const created = new Set(\n plan.create\n .filter((entry) => entry.kind === 'form')\n .map((entry) => `new:${entry.name}`.toLowerCase()),\n )\n const forms = new Set(formIds)\n const screen = plan.screens.find((entry) =>\n entry.sections.some((section) =>\n section.uses.some((ref) =>\n isAiPlanNewRef(ref) ? created.has(ref.toLowerCase()) : forms.has(ref),\n ),\n ),\n )\n return typeof screen?.id === 'string' ? screen.id : null\n}\n\n/** Every word the job was given, which a page's contact details are held to. */\nexport function aiLayoutFacts(job: Pick<AiJob, 'brief' | 'inputs'>): string {\n const values = Object.values(job.inputs ?? {}).filter(\n (value): value is string => typeof value === 'string',\n )\n return [job.brief, ...values].join('\\n')\n}\n\n/** What a page's links resolve against: the site's pages, built and planned, its forms and components. */\nexport function aiLayoutPageTargets(input: {\n job: Pick<AiJob, 'brief' | 'inputs'>\n inventory: AiSiteInventory | null\n own: ReadonlyArray<string | null | undefined>\n}): AiLayoutTargets {\n const { job, inventory } = input\n const own = input.own.filter((id): id is string => !!id)\n const planned = aiLayoutSitePages(job.inputs)\n const pages: AiLayoutPage[] = [\n ...planned,\n // Planned pages merged into the blog (AGL-3676): a link to one goes to the blog.\n ...aiLayoutSiteAliases(job.inputs).filter((alias) => !planned.some((page) => page.id === alias.id)),\n ...(inventory?.screens ?? [])\n .filter(\n (row) => !row.template && !planned.some((page) => page.id === row.id),\n )\n .map((row) => ({ id: row.id, label: row.name, slug: row.slug })),\n ]\n const formPage = job.inputs?.[AI_LAYOUT_FORM_PAGE_INPUT]\n return {\n pageId: own[0] ?? null,\n pages: pages.filter((page) => !own.includes(page.id)),\n homeIds: aiHomeScreenIds(inventory),\n forms: (inventory?.forms ?? []).map((form) => ({\n id: form.id,\n name: form.name,\n })),\n formPageId:\n typeof formPage === 'string' && !own.includes(formPage) ? formPage : null,\n components: (inventory?.components ?? []).map((component) => ({\n id: component.id,\n name: component.name,\n props: component.props,\n })),\n facts: aiLayoutFacts(job),\n // The site's catalog and blog, and the sections that list them (AGL-3676),\n // and its datasets (AGL-3616).\n listings: aiLayoutListingsWithDatasets(aiLayoutListingsOf(job.inputs), inventory),\n }\n}\n\n/**\n * A page's listings with each dataset's held to the site's inventory\n * (AGL-3616): a records listing is kept only where the site has the dataset —\n * the tree check admits a repeat over no other, and a card left repeating\n * over nothing would show its `{{item.*}}` — and one that names no fields\n * takes the inventory's, in the dataset's order.\n */\nexport function aiLayoutListingsWithDatasets(\n listings: readonly AiLayoutListing[],\n inventory: Pick<AiSiteInventory, 'datasets'> | null,\n): AiLayoutListing[] {\n return listings.flatMap((listing) => {\n if (listing.kind !== 'records') return [listing]\n const row = inventory?.datasets.find((dataset) => dataset.id === listing.datasetId)\n if (!row) return []\n // The photo field is the card's picture, never its words (AGL-3616).\n const imageField = listing.imageField ?? row.imageField\n const fields = (\n listing.fields?.length\n ? listing.fields\n : row.fields.map((name, index) => ({ id: row.fieldIds?.[index] || name, name, type: 'text' }))\n ).filter((field) => field.id !== imageField)\n return fields.length ? [{ ...listing, fields, ...(imageField ? { imageField } : {}) }] : []\n })\n}\n\n/**\n * The page check's context with what a section listing the blog's posts\n * binds (AGL-3676): each post's card links its post and shows its cover by\n * the tokens the page fills per post, which a page's store admits whole only\n * where a listing places them. A dataset's card shows its record's photo\n * the same way (AGL-3616), by `{{item.<photo field>}}`.\n */\nexport function aiLayoutListingContext(\n context: AiDoctrineTreeContext,\n targets: AiLayoutTargets,\n): AiDoctrineTreeContext {\n const here = (targets.listings ?? []).filter((listing) =>\n listing.placements.some((placement) => placement.screenId === targets.pageId),\n )\n const tokens = [\n ...(here.some((listing) => listing.kind === 'posts') ? AI_LAYOUT_POST_ADDRESS_TOKENS : []),\n ...here.flatMap((listing) => (listing.kind === 'records' && listing.imageField ? [`{{item.${listing.imageField}}}`] : [])),\n ]\n if (!tokens.length) return context\n return { ...context, bindingTokens: [...new Set([...(context.bindingTokens ?? []), ...tokens])] }\n}\n\n/** The page's user turn: the page, the brief, the plan, its sections and where its links may go. */\nexport function aiLayoutPagePrompt(input: {\n job: Pick<AiJob, 'brief' | 'inputs' | '$id'>\n plan: AiJobPlan\n screen: AiBuildPlanScreen\n targets: AiLayoutTargets\n reusableComponents: boolean\n /** The sections a re-ask still needs, by plan index; absent on the first ask. */\n missing?: readonly number[]\n}): string {\n const { job, plan, screen, targets } = input\n const sections = screen.sections.map((section, index) => {\n const places = section.uses.length\n ? `; places ${section.uses.join(', ')}`\n : ''\n // A section the site's records fill (AGL-3676): the platform places them,\n // so the design writes the words around them and no group for them.\n const listed = aiLayoutListingAt(targets.listings ?? [], targets.pageId, index)\n if (listed?.listing.kind === 'tracks') {\n // A music site's player (AGL-3716): placed empty, for the owner's own uploads.\n return `${index + 1}. \"${section.name}\"${places}; the platform places a music player here for the artist's own tracks, which they add themselves: write only its heading and a line inviting visitors to listen, and no cards, list, images or links for songs`\n }\n if (listed?.listing.kind === 'reviews') {\n // A store's own reviews (AGL-3676): shoppers write them; none is ever written here.\n return `${index + 1}. \"${section.name}\"${places}; the platform shows the store's real customer reviews here with their star ratings, once shoppers leave them: write only its heading, and no quotes, names, ratings or cards`\n }\n if (listed?.listing.kind === 'signup') {\n // A store's newsletter field (AGL-3676), placed by the platform.\n return `${index + 1}. \"${section.name}\"${places}; the platform places the newsletter sign-up field here: write its heading and one line on what subscribers hear about, from the brief, and no form, button or offer the brief does not make`\n }\n if (listed?.listing.kind === 'records') {\n // One of the site's datasets (AGL-3616), repeated by the platform over its records.\n const names = listed.listing.records.length ? ` (${listed.listing.records.slice(0, 6).map((name) => `“${name}”`).join(', ')})` : ''\n return `${index + 1}. \"${section.name}\"${places}; the platform lists the records of the dataset “${listed.listing.name}”${names} here itself, one card each: write only its heading and a line about them, and no cards, list or images for them`\n }\n if (listed) {\n return `${index + 1}. \"${section.name}\"${places}; the platform lists ${listed.listing.name}'s ${listed.listing.kind} here itself, with their photos and links: write only its heading and a line about them, and no cards, list or images for them`\n }\n const items = section.items ? `; shows ${section.items} items` : ''\n return `${index + 1}. \"${section.name}\"${places}${items}`\n })\n // A page merged into the blog is not offered: the blog is, by its own entry.\n const pages = targets.pages.filter((page) => !page.standsInFor).map((page) => `${page.label} (page:${page.id})`)\n const components = targets.components.map(\n (component) =>\n `${component.name} (${component.id}; props ${Object.keys(component.props).join(', ') || 'none'})`,\n )\n return [\n `Page: \"${screen.title}\" at ${screen.slug}`,\n aiJobBriefLine(job),\n // The kind of site the person picked sets how its pages are arranged (AGL-3660).\n ...aiSiteKindDesignLines(job.inputs),\n ...aiPlanReferenceLines(plan),\n `Sections to design, in order:`,\n ...sections,\n pages.length\n ? `Other pages of this site: ${pages.join(', ')}.`\n : 'This site has no other page to link.',\n targets.forms.length\n ? `The site's form: ${targets.forms.map((form) => `${form.name} (${form.id})`).join(', ')}.`\n : 'The site has no saved form: place none.',\n ...(components.length\n ? [`Components this page may place: ${components.join('; ')}.`]\n : []),\n input.reusableComponents\n ? 'Where a section repeats an item, place the component its plan line names with cards whose to is that component.'\n : 'This site keeps no reusable components: draw repeated items as cards, steps, stats or a list.',\n // A per-job word, so two identical briefs are designed apart.\n `Design seed: ${job.$id.slice(-6)}.`,\n ].join('\\n')\n}\n\n/**\n * The site a page is designed for (AGL-3660): the kind its job names, the\n * seed its look was drawn with — the same for every page of the site, so the\n * site reads as one design — and whether the page is its home. `null` for a\n * job that names no kind of site, which compiles the compiler's first way.\n */\nexport function aiLayoutDesignOf(job: Pick<AiJob, '$id' | 'inputs'>, slug: string): AiLayoutDesign | null {\n const kind = aiSiteKindOfInputs(job.inputs)\n if (!kind) return null\n const style = job.inputs?.['siteStyle'] as Record<string, unknown> | undefined\n const seed = typeof style?.['seed'] === 'number' && Number.isFinite(style['seed']) ? (style['seed'] as number) >>> 0 : aiSiteSeed(aiOriginJobId(job))\n return { kind: kind.id, seed, home: aiLayoutIsHomeSlug(slug) }\n}\n\n/** A validated language page, stored as the page keeps it. */\nexport interface AiLayoutPageBuilt {\n nodes: NodesMap\n load: AiLoadEstimate | null\n settled: AiLayoutSettlement[]\n /** What the last answer's gaps took out of the page (`ai-layout-gaps.ts`). */\n dropped: string[]\n /** How many repeated items each plan section shows, as built (AGL-3660). */\n items: number[]\n}\n\nexport interface AiLayoutPageCheckInput {\n screen: AiBuildPlanScreen\n sectionIds: readonly string[]\n targets: AiLayoutTargets\n context: AiDoctrineTreeContext\n reusableComponents: boolean\n /**\n * What earlier answers gave, by plan index, shared between the page's\n * generations: a follow-up asking only for missing sections fills the rest.\n */\n kept?: Array<AiLayoutSection | null>\n /** The plan indices this answer's sections fill, in order; absent, every section. */\n only?: readonly number[]\n /** The site the page is drawn for (`aiLayoutDesignOf`); absent, the compiler's first design. */\n design?: AiLayoutDesign | null\n}\n\n/**\n * The follow-up turn for the sections a page's answer left out (AGL-3660):\n * the page as before, told which sections it already has, and asked for only\n * the missing ones, in order — the one part of a page asked for again.\n */\nexport function aiLayoutMissingSectionsPrompt(input: {\n base: string\n screen: AiBuildPlanScreen\n missing: readonly number[]\n}): string {\n const names = input.missing.map((index) => `${index + 1}. \"${input.screen.sections[index].name}\"`)\n return [\n input.base,\n '',\n `The other sections of this page are already designed. Design ONLY these, in this order, one entry in sections each: ${names.join('; ')}. Give each at least one block.`,\n ].join('\\n')\n}\n\n/** The plan indices a page still lacks, after the answers so far. */\nexport function aiLayoutMissingSections(kept: ReadonlyArray<AiLayoutSection | null>): number[] {\n return kept.flatMap((section, index) => (section ? [] : [index]))\n}\n\n/** The violation a planned section the answer gave nothing usable for is named by. */\nexport const AI_LAYOUT_SECTION_MISSING_CODE = 'layout-section-missing'\n\n/** The code a section planned with items that shows none is refused under (AGL-3660). */\nexport const AI_LAYOUT_SECTION_EMPTY_CODE = 'layout-section-no-items'\n\n/** How many of a section's planned items it must show: one for a short list, two for anything longer. */\nconst leastItems = (planned: number) => (planned >= 3 ? 2 : planned >= 1 ? 1 : 0)\n\n/**\n * How many repeated items each section shows once stored (AGL-3660): the\n * elements the compiler drew for them, under the ids they are stored by,\n * that are still in the page after anything later took words out.\n */\nexport function aiLayoutShownItems(\n itemIds: readonly (readonly string[])[],\n storedIds: Readonly<Record<string, string>>,\n nodes: Readonly<Record<string, unknown>>,\n): number[] {\n return itemIds.map((ids) => ids.filter((id) => storedIds[id] !== undefined && storedIds[id] in nodes).length)\n}\n\n/**\n * The sections a page planned with repeated items — cards, steps, a list —\n * that show none, or too few, once the page is built (AGL-3660). A heading\n * and an intro over nothing is a section a visitor reads as broken, so it is\n * asked for again.\n */\nexport function aiLayoutEmptyItemSections(\n screen: Pick<AiBuildPlanScreen, 'sections'>,\n items: readonly number[],\n /** Sections whose only items were customer quotes, which no answer may give (AGL-3676). */\n quotesOnly: readonly number[] = [],\n): AiDoctrineViolation[] {\n return screen.sections.flatMap((section, index): AiDoctrineViolation[] => {\n if (quotesOnly.includes(index)) return []\n const least = leastItems(section.items)\n if (!least) return []\n const shown = items[index] ?? 0\n if (shown >= least) return []\n return [\n {\n rule: null,\n code: AI_LAYOUT_SECTION_EMPTY_CODE,\n message: `Section ${index + 1} (\"${section.name}\") is planned with ${section.items} items and shows ${shown || 'none'}.`,\n detail: `Give section ${index + 1} one cards, steps, stats, list or faq block holding its ${section.items} items, each with a title and its words.`,\n },\n ]\n })\n}\n\n/**\n * The check a language page answer is held to: each planned section read,\n * the page compiled, stored and checked whole. Sections that read are kept\n * across the re-ask, so the second answer is asked only for what was missing,\n * and a section it sends again that already read is not taken twice.\n */\nexport function aiLayoutPageCheck(\n input: AiLayoutPageCheckInput,\n): AiGenerationCheck<AiLayoutPageBuilt> {\n const kept: Array<AiLayoutSection | null> = input.kept ?? input.screen.sections.map(() => null)\n const fills = input.only ?? input.screen.sections.map((_, index) => index)\n const context = aiLayoutListingContext(input.context, input.targets)\n let answers = 0\n return (answer) => {\n // The last answer a generation takes has its gaps taken out rather than asked about again.\n answers += 1\n const last = answers >= AI_GENERATION_MAX_ATTEMPTS\n const reading = aiReadLayoutPage(\n answer,\n fills.length,\n fills.map((index) => input.screen.sections[index]),\n )\n reading.sections.forEach((section, position) => {\n // A section the answer gives is taken as given; one it leaves out keeps\n // what an earlier answer gave, so a re-ask may send only what was missing.\n if (section) kept[fills[position]] = section\n })\n const missing = kept.flatMap((section, index) => (section ? [] : [index]))\n if (missing.length) {\n const names = missing.map(\n (index) => `${index + 1} (\"${input.screen.sections[index].name}\")`,\n )\n return {\n value: null,\n violations: [\n {\n rule: null,\n code: AI_LAYOUT_SECTION_MISSING_CODE,\n message: `The page left out section ${names.join(', ')}.`,\n detail: `Answer again with every section in order; the sections you already designed are kept, so only ${names.join(', ')} ${missing.length === 1 ? 'needs' : 'need'} its design. Give each at least one block.`,\n },\n ],\n }\n }\n const compiled = aiCompileLayoutPage(\n kept as AiLayoutSection[],\n { title: input.screen.title, sections: input.screen.sections },\n input.targets,\n {\n reusableComponents: input.reusableComponents,\n sectionIds: input.sectionIds,\n ...(input.design ? { design: input.design } : {}),\n },\n )\n const stored = aiLayoutStoredTree(\n compiled.tree,\n 'screen',\n context,\n input.sectionIds,\n )\n if (stored.ok === false) {\n // The compiler writes only palette trees, which the property spec holds;\n // a refusal here is the platform's, and is reported as one.\n console.error('ai layout page: a compiled page did not store', {\n error: stored.error,\n })\n return {\n value: null,\n violations: [\n {\n rule: null,\n code: 'layout-compile',\n message: 'This page could not be built. Try again.',\n detail: stored.error,\n },\n ],\n }\n }\n // No gap and no internal reference in the words of a page that is published as built.\n const copy = aiLayoutCopyCheck(\n stored.nodes,\n CANVAS_ROOT_ELEMENT_ID,\n {\n pages: input.targets.pages,\n ids: [\n ...input.targets.pages.map((page) => page.id),\n ...input.targets.forms.map((form) => form.id),\n ...input.targets.components.map((component) => component.id),\n ...input.sectionIds,\n ...(input.targets.pageId ? [input.targets.pageId] : []),\n ],\n },\n last,\n )\n const { dropped } = copy\n // An attribute a rule has one answer for is settled, not asked of the model again (AGL-3660).\n const nodes = aiSettleMechanicalRules(\n { rootId: CANVAS_ROOT_ELEMENT_ID, nodes: copy.nodes as unknown as Record<string, AiDoctrineNode> },\n 'page',\n ).nodes as unknown as typeof copy.nodes\n const report = validateAiDoctrineTree(\n { rootId: CANVAS_ROOT_ELEMENT_ID, nodes },\n 'page',\n {\n ...context,\n scrollTargetIds: input.sectionIds,\n // The layout language draws its own picture cards (AGL-3660).\n repeatsCompiled: true,\n },\n )\n // Each section's items as they are stored, after anything the gaps took out.\n const items = aiLayoutShownItems(compiled.itemIds, stored.storedIds, nodes as unknown as Record<string, unknown>)\n // A street address or opening hours the job was never given is invented\n // (rule 14, AGL-3596), the same check a layout is held to; a phone number\n // or an email the compiler already writes as its gap.\n const violations: AiDoctrineViolation[] = [\n ...report.violations,\n ...copy.violations,\n ...aiLayoutEmptyItemSections(input.screen, items, compiled.quotesOnly),\n ...aiLayoutInventedContactViolations(\n { rootId: CANVAS_ROOT_ELEMENT_ID, nodes: nodes as unknown as Record<string, AiDoctrineNode> },\n input.targets.facts,\n ),\n ]\n return {\n value: violations.length\n ? null\n : {\n nodes,\n load: report.load,\n settled: [...reading.settled, ...compiled.settled],\n dropped,\n items,\n },\n violations,\n }\n }\n}\n\nexport interface AiLayoutPageRunInput {\n job: Pick<AiJob, 'brief' | 'inputs' | '$id'>\n plan: AiJobPlan\n screen: AiBuildPlanScreen\n sectionIds: readonly string[]\n targets: AiLayoutTargets\n context: AiDoctrineTreeContext\n reusableComponents: boolean\n inventory: AiSiteInventory | null\n model: string\n signal?: AbortSignal\n /** Wraps the check, for an eval that records each answer it reads. */\n observe?: (check: AiGenerationCheck<AiLayoutPageBuilt>) => AiGenerationCheck<AiLayoutPageBuilt>\n}\n\n/** Two generations' spend as one: every call is billed. */\nfunction spentTogether(first: AiGenerationSpend, second: AiGenerationSpend): AiGenerationSpend {\n const usage: AiUsage = {\n inputTokens: first.usage.inputTokens + second.usage.inputTokens,\n outputTokens: first.usage.outputTokens + second.usage.outputTokens,\n cacheReadTokens: first.usage.cacheReadTokens + second.usage.cacheReadTokens,\n cacheWriteTokens: first.usage.cacheWriteTokens + second.usage.cacheWriteTokens,\n }\n return {\n attempts: first.attempts + second.attempts,\n usage,\n estCostUsd: Math.round((first.estCostUsd + second.estCostUsd) * 1_000_000) / 1_000_000,\n model: second.model,\n stopReason: second.stopReason,\n effort: second.effort,\n }\n}\n\n/**\n * A whole page in the layout language: one answer for every section, and,\n * where that answer and its re-ask still left a section out, one follow-up\n * asking for only those (AGL-3660). The sections that read are kept between\n * them, so the page is compiled whole once every section is designed.\n */\nexport async function aiRunLayoutPage(input: AiLayoutPageRunInput): Promise<AiValidatedGeneration<AiLayoutPageBuilt>> {\n const kept: Array<AiLayoutSection | null> = input.screen.sections.map(() => null)\n const observe = input.observe ?? ((check) => check)\n const base = aiLayoutPagePrompt({\n job: input.job,\n plan: input.plan,\n screen: input.screen,\n targets: input.targets,\n reusableComponents: input.reusableComponents,\n })\n const ask = (content: string, maxTokens: number, only?: readonly number[]) =>\n runValidatedGeneration<AiLayoutPageBuilt>(AI_LAYOUT_PAGE_KIND, {\n step: 'job.page',\n model: input.model,\n instructions: AI_JOB_PAGE_LANGUAGE_INSTRUCTIONS,\n inventory: input.inventory,\n messages: [{ role: 'user', content }],\n tool: AI_LAYOUT_PAGE_TOOL,\n maxTokens,\n cutOff: { noun: 'page', smaller: 'Write shorter copy, and fewer items in each group.' },\n ...AI_LAYOUT_LANGUAGE_THINKING,\n check: observe(\n aiLayoutPageCheck({\n screen: input.screen,\n sectionIds: input.sectionIds,\n targets: input.targets,\n context: input.context,\n reusableComponents: input.reusableComponents,\n kept,\n design: aiLayoutDesignOf(input.job, input.screen.slug),\n ...(only ? { only } : {}),\n }),\n ),\n ...(input.signal ? { signal: input.signal } : {}),\n })\n const first = await ask(base, AI_JOB_PAGE_LANGUAGE_BUDGET.maxTokens(input.model))\n const missing = aiLayoutMissingSections(kept)\n if (\n first.status !== 'needs_input' ||\n !missing.length ||\n !first.violations.every((violation) => violation.code === AI_LAYOUT_SECTION_MISSING_CODE)\n ) {\n return first\n }\n const second = await ask(\n aiLayoutMissingSectionsPrompt({ base, screen: input.screen, missing }),\n Math.min(AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS, AI_JOB_PAGE_LANGUAGE_BUDGET.maxTokens(input.model)),\n missing,\n )\n return { ...second, ...spentTogether(first, second) }\n}\n"],"names":["CANVAS_ROOT_ELEMENT_ID","isAiPlanNewRef","aiHomeScreenIds","AI_LAYOUT_POST_ADDRESS_TOKENS","aiCompileLayoutPage","aiLayoutListingAt","aiLayoutListingsOf","AI_LAYOUT_LANGUAGE_TEXT","AI_LAYOUT_PAGE_TOOL","aiReadLayoutPage","aiLayoutCopyCheck","aiLayoutStoredTree","AI_GENERATION_MAX_ATTEMPTS","AI_STEP_TIERS","runValidatedGeneration","aiSettleMechanicalRules","validateAiDoctrineTree","aiSiteKindDesignLines","aiSiteKindOfInputs","aiSiteSeed","aiOriginJobId","aiGenerationWorstCaseOnTierMs","aiJobStepBudget","aiJobBriefLine","aiPlanReferenceLines","aiLayoutIsHomeSlug","aiLayoutSiteAliases","aiLayoutSitePages","aiLayoutInventedContactViolations","AI_LAYOUT_LANGUAGE_INPUT","AI_LAYOUT_FORM_PAGE_INPUT","aiJobUsesLayoutLanguage","job","inputs","AI_LAYOUT_LANGUAGE_THINKING","thinking","effort","AI_LAYOUT_PAGE_THINKING_TOKENS","AI_LAYOUT_FRAME_THINKING_TOKENS","AI_JOB_PAGE_LANGUAGE_TOKENS","AI_LAYOUT_SECTION_MOST_TOKENS","AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS","AI_JOB_PAGE_LANGUAGE_BUDGET","tier","maxTokens","ownReadsMs","AI_LAYOUT_PAGE_KIND","AI_JOB_PAGE_LANGUAGE_INSTRUCTIONS","text","join","cacheBreakpoint","aiLayoutFormPageOfPlan","plan","formIds","created","Set","create","filter","entry","kind","map","name","toLowerCase","forms","screen","screens","find","sections","some","section","uses","ref","has","id","aiLayoutFacts","values","Object","value","brief","aiLayoutPageTargets","input","own","inventory","planned","pages","alias","page","row","template","label","slug","formPage","pageId","includes","homeIds","form","formPageId","components","component","props","facts","listings","aiLayoutListingsWithDatasets","flatMap","listing","datasets","dataset","datasetId","imageField","fields","length","index","fieldIds","type","field","aiLayoutListingContext","context","targets","here","placements","placement","screenId","tokens","bindingTokens","aiLayoutPagePrompt","places","listed","names","records","slice","items","standsInFor","keys","title","reusableComponents","$id","aiLayoutDesignOf","style","seed","Number","isFinite","home","aiLayoutMissingSectionsPrompt","missing","base","aiLayoutMissingSections","kept","AI_LAYOUT_SECTION_MISSING_CODE","AI_LAYOUT_SECTION_EMPTY_CODE","leastItems","aiLayoutShownItems","itemIds","storedIds","nodes","ids","undefined","aiLayoutEmptyItemSections","quotesOnly","least","shown","rule","code","message","detail","aiLayoutPageCheck","fills","only","_","answers","answer","last","reading","forEach","position","violations","compiled","sectionIds","design","stored","tree","ok","console","error","copy","dropped","rootId","report","scrollTargetIds","repeatsCompiled","load","settled","spentTogether","first","second","usage","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","attempts","estCostUsd","Math","round","model","stopReason","aiRunLayoutPage","observe","check","ask","content","step","instructions","messages","role","tool","cutOff","noun","smaller","signal","status","every","violation","min"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-page-language.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { isAiPlanNewRef, type AiBuildPlanScreen } from '../model/ai-build-plan'\nimport type { AiJob, AiJobPlan } from '../model/ai-jobs.types'\nimport {\n aiHomeScreenIds,\n type AiSiteInventory,\n} from '../model/ai-site-inventory'\nimport { AI_LAYOUT_POST_ADDRESS_TOKENS, aiCompileLayoutPage } from '../layout-language/ai-layout-compiler'\nimport { aiLayoutListingAt, aiLayoutListingsOf, aiLayoutRecordHref, type AiLayoutListing } from '../layout-language/ai-layout-listings'\nimport {\n AI_LAYOUT_LANGUAGE_TEXT,\n AI_LAYOUT_PAGE_TOOL,\n aiReadLayoutPage,\n type AiLayoutSection,\n type AiLayoutSettlement,\n} from '../layout-language/ai-layout-language'\nimport type {\n AiLayoutPage,\n AiLayoutTargets,\n} from '../layout-language/ai-layout-links'\nimport { aiLayoutCopyCheck } from '../layout-language/ai-layout-gaps'\nimport { aiLayoutStoredTree } from '../layout-language/ai-layout-store'\nimport { AI_GENERATION_MAX_ATTEMPTS } from '../runtime/ai-generation-bounds'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport {\n runValidatedGeneration,\n type AiGenerationCheck,\n type AiGenerationSpend,\n type AiValidatedGeneration,\n} from '../runtime/ai-doctrine'\nimport type { AiUsage } from '../providers/contract'\nimport {\n aiSettleMechanicalRules,\n aiSettleUnrelatedScreenLinks,\n validateAiDoctrineTree,\n type AiDoctrineNode,\n type AiDoctrineTreeContext,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport { aiSiteKindDesignLines, aiSiteKindOfInputs } from '../model/ai-site-kinds'\nimport { aiSiteSeed } from '../model/ai-site-look'\nimport type { AiLayoutDesign } from '../layout-language/ai-layout-design'\nimport { aiOriginJobId } from './ai-job-draft-ids'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { aiGenerationWorstCaseOnTierMs, aiJobStepBudget } from './ai-job-budget'\nimport { aiJobBriefLine, aiPlanReferenceLines } from './ai-job-generation'\nimport { aiLayoutIsHomeSlug, aiLayoutSiteAliases, aiLayoutSitePages } from './ai-job-layout-site-pages'\nimport { aiLayoutInventedContactViolations } from './ai-layout-site-facts'\n\n/**\n * A page built in the compact layout language (AGL-3660): the page step's\n * pass for a site's page units — a guided start's and a build's — in place of\n * the raw-tree section passes.\n *\n * ONE answer a page: every planned section, in the language, through\n * `submit_page`. The compiler writes the tree, the palette validator stores\n * it as it stores any generated tree, and the doctrine checks the whole page.\n * A section the answer left out or left empty is the one thing a re-ask asks\n * for: the sections that read are kept, and the second answer fills only\n * what is still missing. Then the page step's last pass runs as it always\n * has — the whole page held to the doctrine again, its listing written, the\n * draft reported — so the item ledger, the credits and Try again work exactly\n * as they did.\n */\n\n/** The unit input that says a page or a layout is built in the layout language. */\nexport const AI_LAYOUT_LANGUAGE_INPUT = 'layoutLanguage'\n\n/** The unit input naming the page of the site that places its form, where it is another page. */\nexport const AI_LAYOUT_FORM_PAGE_INPUT = 'layoutFormPage'\n\n/** Whether this job builds its page or its layout in the layout language. */\nexport function aiJobUsesLayoutLanguage(job: Pick<AiJob, 'inputs'>): boolean {\n return job.inputs?.[AI_LAYOUT_LANGUAGE_INPUT] === true\n}\n\n/**\n * How the language doors think (AGL-3660): adaptively, at an effort named\n * rather than left to the model's default. Zach, 2026-10-07: quality first —\n * a page and a site's frame are designed, not transcribed, and the caching\n * work pays for the thinking. Medium is where Claude Sonnet 5.5, the balanced\n * default, is tuned to start for multistep design work.\n */\nexport const AI_LAYOUT_LANGUAGE_THINKING = { thinking: 'adaptive', effort: 'medium' } as const\n\n/**\n * Room for the thinking a page's answer does before it writes, inside the\n * same `max_tokens` as the answer: thinking is drawn from the ceiling first,\n * so a ceiling sized for the answer alone would cut a thoughtful answer off.\n *\n * Bounded by TIME, not by money: the page step's answer, its re-ask and the\n * follow-up must fit the least time a beat gives a step\n * (`AI_JOB_STEP_MAX_MINIMUM_MS`), and `aiJobStepBudget` quietly lowers every\n * ceiling to what fits. 2,000 here, with the follow-up's 500, is the most\n * that fits whole on the balanced tier; at 5,000 the budget cut a page's\n * ceiling to 879 tokens, under the answer itself.\n */\nexport const AI_LAYOUT_PAGE_THINKING_TOKENS = 2_000\n\n/** The same room for a site's header and footer, whose step has the time for more. */\nexport const AI_LAYOUT_FRAME_THINKING_TOKENS = 4_000\n\n/**\n * The most one page's answer asks for: the answer and the thinking before it.\n * A section in the language is about 120 to 250 tokens with its copy, so the\n * largest page a plan admits — eight sections — fits in 3,000 with room (the\n * live runs of 2026-10-07 wrote 683 to 1,745); the compiler, not the ceiling,\n * keeps a page small.\n */\nexport const AI_JOB_PAGE_LANGUAGE_TOKENS = 3_000 + AI_LAYOUT_PAGE_THINKING_TOKENS\n\n/**\n * The most one section of a page is written in, its copy included: the top of\n * the 120 to 250 tokens a section runs to. The Free site wall prices each\n * planned section at it (AGL-3660).\n */\nexport const AI_LAYOUT_SECTION_MOST_TOKENS = 250\n\n/**\n * The time a language page pass needs: its lookup rounds, its answer and its\n * re-ask at `AI_JOB_PAGE_LANGUAGE_TOKENS` on the tier the page step is served\n * from, with the step's reads and writes.\n */\n/** The most the follow-up asking only for the sections an answer left out may run to, its thinking included. */\nexport const AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS = 1_500 + 500\n\nexport const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.page'],\n maxTokens: AI_JOB_PAGE_LANGUAGE_TOKENS,\n // The follow-up for sections an answer left out is one more generation.\n ownReadsMs: aiGenerationWorstCaseOnTierMs({\n tier: AI_STEP_TIERS['job.page'],\n maxTokens: AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS,\n }),\n})\n\n/** The doctrine scope the language doors are told: values the compiler checks itself. */\nexport const AI_LAYOUT_PAGE_KIND = 'layout-page'\n\n/** The language, as the page door's cached instructions. */\nexport const AI_JOB_PAGE_LANGUAGE_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n AI_LAYOUT_LANGUAGE_TEXT,\n '',\n 'Answer with submit_page: the whole page, one entry in sections for each planned section, in order.',\n ].join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\n/** The page of a site plan that places its form: the form the plan creates, or one the site has. */\nexport function aiLayoutFormPageOfPlan(\n plan: Pick<AiJobPlan, 'create' | 'screens'>,\n formIds: readonly string[] = [],\n): string | null {\n const created = new Set(\n plan.create\n .filter((entry) => entry.kind === 'form')\n .map((entry) => `new:${entry.name}`.toLowerCase()),\n )\n const forms = new Set(formIds)\n const screen = plan.screens.find((entry) =>\n entry.sections.some((section) =>\n section.uses.some((ref) =>\n isAiPlanNewRef(ref) ? created.has(ref.toLowerCase()) : forms.has(ref),\n ),\n ),\n )\n return typeof screen?.id === 'string' ? screen.id : null\n}\n\n/** Every word the job was given, which a page's contact details are held to. */\nexport function aiLayoutFacts(job: Pick<AiJob, 'brief' | 'inputs'>): string {\n const values = Object.values(job.inputs ?? {}).filter(\n (value): value is string => typeof value === 'string',\n )\n return [job.brief, ...values].join('\\n')\n}\n\n/** What a page's links resolve against: the site's pages, built and planned, its forms and components. */\nexport function aiLayoutPageTargets(input: {\n job: Pick<AiJob, 'brief' | 'inputs'>\n inventory: AiSiteInventory | null\n own: ReadonlyArray<string | null | undefined>\n}): AiLayoutTargets {\n const { job, inventory } = input\n const own = input.own.filter((id): id is string => !!id)\n const planned = aiLayoutSitePages(job.inputs)\n const pages: AiLayoutPage[] = [\n ...planned,\n // Planned pages merged into the blog (AGL-3676): a link to one goes to the blog.\n ...aiLayoutSiteAliases(job.inputs).filter((alias) => !planned.some((page) => page.id === alias.id)),\n ...(inventory?.screens ?? [])\n .filter(\n (row) => !row.template && !planned.some((page) => page.id === row.id),\n )\n .map((row) => ({ id: row.id, label: row.name, slug: row.slug })),\n ]\n const formPage = job.inputs?.[AI_LAYOUT_FORM_PAGE_INPUT]\n return {\n pageId: own[0] ?? null,\n pages: pages.filter((page) => !own.includes(page.id)),\n homeIds: aiHomeScreenIds(inventory),\n forms: (inventory?.forms ?? []).map((form) => ({\n id: form.id,\n name: form.name,\n })),\n formPageId:\n typeof formPage === 'string' && !own.includes(formPage) ? formPage : null,\n components: (inventory?.components ?? []).map((component) => ({\n id: component.id,\n name: component.name,\n props: component.props,\n })),\n facts: aiLayoutFacts(job),\n // The site's catalog and blog, and the sections that list them (AGL-3676),\n // and its datasets (AGL-3616).\n listings: aiLayoutListingsWithDatasets(aiLayoutListingsOf(job.inputs), inventory),\n }\n}\n\n/**\n * A page's listings with each dataset's held to the site's inventory\n * (AGL-3616): a records listing is kept only where the site has the dataset —\n * the tree check admits a repeat over no other, and a card left repeating\n * over nothing would show its `{{item.*}}` — and one that names no fields\n * takes the inventory's, in the dataset's order. Its cards link the record\n * pages the plan makes, else those the site already serves (AGL-3747).\n */\nexport function aiLayoutListingsWithDatasets(\n listings: readonly AiLayoutListing[],\n inventory: Pick<AiSiteInventory, 'datasets'> | null,\n): AiLayoutListing[] {\n return listings.flatMap((listing) => {\n if (listing.kind !== 'records') return [listing]\n const row = inventory?.datasets.find((dataset) => dataset.id === listing.datasetId)\n if (!row) return []\n // The photo field is the card's picture, never its words (AGL-3616).\n const imageField = listing.imageField ?? row.imageField\n // The address is the card's link to its record's page, never its words (AGL-3727).\n const addressField = listing.addressField ?? row.addressField\n // A record page the site already serves for the dataset (AGL-3747), where\n // the plan makes none of its own.\n const recordBase = listing.recordBase ?? row.recordBase\n const fields = (\n listing.fields?.length\n ? listing.fields\n : row.fields.map((name, index) => ({ id: row.fieldIds?.[index] || name, name, type: 'text' }))\n ).filter((field) => field.id !== imageField && field.id !== addressField)\n return fields.length\n ? [\n {\n ...listing,\n fields,\n ...(imageField ? { imageField } : {}),\n ...(recordBase && addressField ? { recordBase, addressField } : {}),\n },\n ]\n : []\n })\n}\n\n/**\n * The page check's context with what a section listing the blog's posts\n * binds (AGL-3676): each post's card links its post and shows its cover by\n * the tokens the page fills per post, which a page's store admits whole only\n * where a listing places them. A dataset's card shows its record's photo\n * the same way (AGL-3616), by `{{item.<photo field>}}`. And the page that\n * places the site's form, which a call to action asking a visitor to get in\n * touch links even when it is home (AGL-3729).\n */\nexport function aiLayoutListingContext(\n context: AiDoctrineTreeContext,\n targets: AiLayoutTargets,\n): AiDoctrineTreeContext {\n if (targets.formPageId) context = { ...context, formScreenIds: [targets.formPageId] }\n const here = (targets.listings ?? []).filter((listing) =>\n listing.placements.some((placement) => placement.screenId === targets.pageId),\n )\n const tokens = [\n ...(here.some((listing) => listing.kind === 'posts') ? AI_LAYOUT_POST_ADDRESS_TOKENS : []),\n ...here.flatMap((listing) => (listing.kind === 'records' && listing.imageField ? [`{{item.${listing.imageField}}}`] : [])),\n // Each card's link to its record's own page (AGL-3727), admitted whole.\n ...here.flatMap((listing) => {\n const href = aiLayoutRecordHref(listing)\n return href ? [href] : []\n }),\n ]\n if (!tokens.length) return context\n return { ...context, bindingTokens: [...new Set([...(context.bindingTokens ?? []), ...tokens])] }\n}\n\n/** The page's user turn: the page, the brief, the plan, its sections and where its links may go. */\nexport function aiLayoutPagePrompt(input: {\n job: Pick<AiJob, 'brief' | 'inputs' | '$id'>\n plan: AiJobPlan\n screen: AiBuildPlanScreen\n targets: AiLayoutTargets\n reusableComponents: boolean\n /** The sections a re-ask still needs, by plan index; absent on the first ask. */\n missing?: readonly number[]\n}): string {\n const { job, plan, screen, targets } = input\n const sections = screen.sections.map((section, index) => {\n const places = section.uses.length\n ? `; places ${section.uses.join(', ')}`\n : ''\n // A section the site's records fill (AGL-3676): the platform places them,\n // so the design writes the words around them and no group for them.\n const listed = aiLayoutListingAt(targets.listings ?? [], targets.pageId, index)\n if (listed?.listing.kind === 'tracks') {\n // A music site's player (AGL-3716): placed empty, for the owner's own uploads.\n return `${index + 1}. \"${section.name}\"${places}; the platform places a music player here for the artist's own tracks, which they add themselves: write only its heading and a line inviting visitors to listen, and no cards, list, images or links for songs`\n }\n if (listed?.listing.kind === 'reviews') {\n // A store's own reviews (AGL-3676): shoppers write them; none is ever written here.\n return `${index + 1}. \"${section.name}\"${places}; the platform shows the store's real customer reviews here with their star ratings, once shoppers leave them: write only its heading, and no quotes, names, ratings or cards`\n }\n if (listed?.listing.kind === 'signup') {\n // A store's newsletter field (AGL-3676), placed by the platform.\n return `${index + 1}. \"${section.name}\"${places}; the platform places the newsletter sign-up field here: write its heading and one line on what subscribers hear about, from the brief, and no form, button or offer the brief does not make`\n }\n if (listed?.listing.kind === 'records') {\n // One of the site's datasets (AGL-3616), repeated by the platform over its records.\n const names = listed.listing.records.length ? ` (${listed.listing.records.slice(0, 6).map((name) => `“${name}”`).join(', ')})` : ''\n return `${index + 1}. \"${section.name}\"${places}; the platform lists the records of the dataset “${listed.listing.name}”${names} here itself, one card each: write only its heading and a line about them, and no cards, list or images for them`\n }\n if (listed) {\n return `${index + 1}. \"${section.name}\"${places}; the platform lists ${listed.listing.name}'s ${listed.listing.kind} here itself, with their photos and links: write only its heading and a line about them, and no cards, list or images for them`\n }\n const items = section.items ? `; shows ${section.items} items` : ''\n return `${index + 1}. \"${section.name}\"${places}${items}`\n })\n // A page merged into the blog is not offered: the blog is, by its own entry.\n const pages = targets.pages.filter((page) => !page.standsInFor).map((page) => `${page.label} (page:${page.id})`)\n const components = targets.components.map(\n (component) =>\n `${component.name} (${component.id}; props ${Object.keys(component.props).join(', ') || 'none'})`,\n )\n return [\n `Page: \"${screen.title}\" at ${screen.slug}`,\n aiJobBriefLine(job),\n // The kind of site the person picked sets how its pages are arranged (AGL-3660).\n ...aiSiteKindDesignLines(job.inputs),\n ...aiPlanReferenceLines(plan),\n `Sections to design, in order:`,\n ...sections,\n pages.length\n ? `Other pages of this site: ${pages.join(', ')}.`\n : 'This site has no other page to link.',\n targets.forms.length\n ? `The site's form: ${targets.forms.map((form) => `${form.name} (${form.id})`).join(', ')}.`\n : 'The site has no saved form: place none.',\n ...(components.length\n ? [`Components this page may place: ${components.join('; ')}.`]\n : []),\n input.reusableComponents\n ? 'Where a section repeats an item, place the component its plan line names with cards whose to is that component.'\n : 'This site keeps no reusable components: draw repeated items as cards, steps, stats or a list.',\n // A per-job word, so two identical briefs are designed apart.\n `Design seed: ${job.$id.slice(-6)}.`,\n ].join('\\n')\n}\n\n/**\n * The site a page is designed for (AGL-3660): the kind its job names, the\n * seed its look was drawn with — the same for every page of the site, so the\n * site reads as one design — and whether the page is its home. `null` for a\n * job that names no kind of site, which compiles the compiler's first way.\n */\nexport function aiLayoutDesignOf(job: Pick<AiJob, '$id' | 'inputs'>, slug: string): AiLayoutDesign | null {\n const kind = aiSiteKindOfInputs(job.inputs)\n if (!kind) return null\n const style = job.inputs?.['siteStyle'] as Record<string, unknown> | undefined\n const seed = typeof style?.['seed'] === 'number' && Number.isFinite(style['seed']) ? (style['seed'] as number) >>> 0 : aiSiteSeed(aiOriginJobId(job))\n return { kind: kind.id, seed, home: aiLayoutIsHomeSlug(slug) }\n}\n\n/** A validated language page, stored as the page keeps it. */\nexport interface AiLayoutPageBuilt {\n nodes: NodesMap\n load: AiLoadEstimate | null\n settled: AiLayoutSettlement[]\n /** What the last answer's gaps took out of the page (`ai-layout-gaps.ts`). */\n dropped: string[]\n /** How many repeated items each plan section shows, as built (AGL-3660). */\n items: number[]\n}\n\nexport interface AiLayoutPageCheckInput {\n screen: AiBuildPlanScreen\n sectionIds: readonly string[]\n targets: AiLayoutTargets\n context: AiDoctrineTreeContext\n reusableComponents: boolean\n /**\n * What earlier answers gave, by plan index, shared between the page's\n * generations: a follow-up asking only for missing sections fills the rest.\n */\n kept?: Array<AiLayoutSection | null>\n /** The plan indices this answer's sections fill, in order; absent, every section. */\n only?: readonly number[]\n /** The site the page is drawn for (`aiLayoutDesignOf`); absent, the compiler's first design. */\n design?: AiLayoutDesign | null\n}\n\n/**\n * The follow-up turn for the sections a page's answer left out (AGL-3660):\n * the page as before, told which sections it already has, and asked for only\n * the missing ones, in order — the one part of a page asked for again.\n */\nexport function aiLayoutMissingSectionsPrompt(input: {\n base: string\n screen: AiBuildPlanScreen\n missing: readonly number[]\n}): string {\n const names = input.missing.map((index) => `${index + 1}. \"${input.screen.sections[index].name}\"`)\n return [\n input.base,\n '',\n `The other sections of this page are already designed. Design ONLY these, in this order, one entry in sections each: ${names.join('; ')}. Give each at least one block.`,\n ].join('\\n')\n}\n\n/** The plan indices a page still lacks, after the answers so far. */\nexport function aiLayoutMissingSections(kept: ReadonlyArray<AiLayoutSection | null>): number[] {\n return kept.flatMap((section, index) => (section ? [] : [index]))\n}\n\n/** The violation a planned section the answer gave nothing usable for is named by. */\nexport const AI_LAYOUT_SECTION_MISSING_CODE = 'layout-section-missing'\n\n/** The code a section planned with items that shows none is refused under (AGL-3660). */\nexport const AI_LAYOUT_SECTION_EMPTY_CODE = 'layout-section-no-items'\n\n/** How many of a section's planned items it must show: one for a short list, two for anything longer. */\nconst leastItems = (planned: number) => (planned >= 3 ? 2 : planned >= 1 ? 1 : 0)\n\n/**\n * How many repeated items each section shows once stored (AGL-3660): the\n * elements the compiler drew for them, under the ids they are stored by,\n * that are still in the page after anything later took words out.\n */\nexport function aiLayoutShownItems(\n itemIds: readonly (readonly string[])[],\n storedIds: Readonly<Record<string, string>>,\n nodes: Readonly<Record<string, unknown>>,\n): number[] {\n return itemIds.map((ids) => ids.filter((id) => storedIds[id] !== undefined && storedIds[id] in nodes).length)\n}\n\n/**\n * The sections a page planned with repeated items — cards, steps, a list —\n * that show none, or too few, once the page is built (AGL-3660). A heading\n * and an intro over nothing is a section a visitor reads as broken, so it is\n * asked for again.\n */\nexport function aiLayoutEmptyItemSections(\n screen: Pick<AiBuildPlanScreen, 'sections'>,\n items: readonly number[],\n /** Sections whose only items were customer quotes, which no answer may give (AGL-3676). */\n quotesOnly: readonly number[] = [],\n): AiDoctrineViolation[] {\n return screen.sections.flatMap((section, index): AiDoctrineViolation[] => {\n if (quotesOnly.includes(index)) return []\n const least = leastItems(section.items)\n if (!least) return []\n const shown = items[index] ?? 0\n if (shown >= least) return []\n return [\n {\n rule: null,\n code: AI_LAYOUT_SECTION_EMPTY_CODE,\n message: `Section ${index + 1} (\"${section.name}\") is planned with ${section.items} items and shows ${shown || 'none'}.`,\n detail: `Give section ${index + 1} one cards, steps, stats, list or faq block holding its ${section.items} items, each with a title and its words.`,\n },\n ]\n })\n}\n\n/**\n * The check a language page answer is held to: each planned section read,\n * the page compiled, stored and checked whole. Sections that read are kept\n * across the re-ask, so the second answer is asked only for what was missing,\n * and a section it sends again that already read is not taken twice.\n */\nexport function aiLayoutPageCheck(\n input: AiLayoutPageCheckInput,\n): AiGenerationCheck<AiLayoutPageBuilt> {\n const kept: Array<AiLayoutSection | null> = input.kept ?? input.screen.sections.map(() => null)\n const fills = input.only ?? input.screen.sections.map((_, index) => index)\n const context = aiLayoutListingContext(input.context, input.targets)\n let answers = 0\n return (answer) => {\n // The last answer a generation takes has its gaps taken out rather than asked about again.\n answers += 1\n const last = answers >= AI_GENERATION_MAX_ATTEMPTS\n const reading = aiReadLayoutPage(\n answer,\n fills.length,\n fills.map((index) => input.screen.sections[index]),\n )\n reading.sections.forEach((section, position) => {\n // A section the answer gives is taken as given; one it leaves out keeps\n // what an earlier answer gave, so a re-ask may send only what was missing.\n if (section) kept[fills[position]] = section\n })\n const missing = kept.flatMap((section, index) => (section ? [] : [index]))\n if (missing.length) {\n const names = missing.map(\n (index) => `${index + 1} (\"${input.screen.sections[index].name}\")`,\n )\n return {\n value: null,\n violations: [\n {\n rule: null,\n code: AI_LAYOUT_SECTION_MISSING_CODE,\n message: `The page left out section ${names.join(', ')}.`,\n detail: `Answer again with every section in order; the sections you already designed are kept, so only ${names.join(', ')} ${missing.length === 1 ? 'needs' : 'need'} its design. Give each at least one block.`,\n },\n ],\n }\n }\n const compiled = aiCompileLayoutPage(\n kept as AiLayoutSection[],\n { title: input.screen.title, sections: input.screen.sections },\n input.targets,\n {\n reusableComponents: input.reusableComponents,\n sectionIds: input.sectionIds,\n ...(input.design ? { design: input.design } : {}),\n },\n )\n const stored = aiLayoutStoredTree(\n compiled.tree,\n 'screen',\n context,\n input.sectionIds,\n )\n if (stored.ok === false) {\n // The compiler writes only palette trees, which the property spec holds;\n // a refusal here is the platform's, and is reported as one.\n console.error('ai layout page: a compiled page did not store', {\n error: stored.error,\n })\n return {\n value: null,\n violations: [\n {\n rule: null,\n code: 'layout-compile',\n message: 'This page could not be built. Try again.',\n detail: stored.error,\n },\n ],\n }\n }\n // No gap and no internal reference in the words of a page that is published as built.\n const copy = aiLayoutCopyCheck(\n stored.nodes,\n CANVAS_ROOT_ELEMENT_ID,\n {\n pages: input.targets.pages,\n ids: [\n ...input.targets.pages.map((page) => page.id),\n ...input.targets.forms.map((form) => form.id),\n ...input.targets.components.map((component) => component.id),\n ...input.sectionIds,\n ...(input.targets.pageId ? [input.targets.pageId] : []),\n ],\n },\n last,\n )\n const { dropped } = copy\n // An attribute a rule has one answer for is settled, not asked of the model again (AGL-3660),\n // and a link sent home for nothing is sent to the form or left out (AGL-3729).\n const nodes = aiSettleUnrelatedScreenLinks(\n aiSettleMechanicalRules(\n { rootId: CANVAS_ROOT_ELEMENT_ID, nodes: copy.nodes as unknown as Record<string, AiDoctrineNode> },\n 'page',\n ),\n 'page',\n { ...context, ownScreenIds: input.targets.pageId ? [input.targets.pageId] : [] },\n ).nodes as unknown as typeof copy.nodes\n const report = validateAiDoctrineTree(\n { rootId: CANVAS_ROOT_ELEMENT_ID, nodes },\n 'page',\n {\n ...context,\n scrollTargetIds: input.sectionIds,\n // The layout language draws its own picture cards (AGL-3660).\n repeatsCompiled: true,\n },\n )\n // Each section's items as they are stored, after anything the gaps took out.\n const items = aiLayoutShownItems(compiled.itemIds, stored.storedIds, nodes as unknown as Record<string, unknown>)\n // A street address or opening hours the job was never given is invented\n // (rule 14, AGL-3596), the same check a layout is held to; a phone number\n // or an email the compiler already writes as its gap.\n const violations: AiDoctrineViolation[] = [\n ...report.violations,\n ...copy.violations,\n ...aiLayoutEmptyItemSections(input.screen, items, compiled.quotesOnly),\n ...aiLayoutInventedContactViolations(\n { rootId: CANVAS_ROOT_ELEMENT_ID, nodes: nodes as unknown as Record<string, AiDoctrineNode> },\n input.targets.facts,\n ),\n ]\n return {\n value: violations.length\n ? null\n : {\n nodes,\n load: report.load,\n settled: [...reading.settled, ...compiled.settled],\n dropped,\n items,\n },\n violations,\n }\n }\n}\n\nexport interface AiLayoutPageRunInput {\n job: Pick<AiJob, 'brief' | 'inputs' | '$id'>\n plan: AiJobPlan\n screen: AiBuildPlanScreen\n sectionIds: readonly string[]\n targets: AiLayoutTargets\n context: AiDoctrineTreeContext\n reusableComponents: boolean\n inventory: AiSiteInventory | null\n model: string\n signal?: AbortSignal\n /** Wraps the check, for an eval that records each answer it reads. */\n observe?: (check: AiGenerationCheck<AiLayoutPageBuilt>) => AiGenerationCheck<AiLayoutPageBuilt>\n}\n\n/** Two generations' spend as one: every call is billed. */\nfunction spentTogether(first: AiGenerationSpend, second: AiGenerationSpend): AiGenerationSpend {\n const usage: AiUsage = {\n inputTokens: first.usage.inputTokens + second.usage.inputTokens,\n outputTokens: first.usage.outputTokens + second.usage.outputTokens,\n cacheReadTokens: first.usage.cacheReadTokens + second.usage.cacheReadTokens,\n cacheWriteTokens: first.usage.cacheWriteTokens + second.usage.cacheWriteTokens,\n }\n return {\n attempts: first.attempts + second.attempts,\n usage,\n estCostUsd: Math.round((first.estCostUsd + second.estCostUsd) * 1_000_000) / 1_000_000,\n model: second.model,\n stopReason: second.stopReason,\n effort: second.effort,\n }\n}\n\n/**\n * A whole page in the layout language: one answer for every section, and,\n * where that answer and its re-ask still left a section out, one follow-up\n * asking for only those (AGL-3660). The sections that read are kept between\n * them, so the page is compiled whole once every section is designed.\n */\nexport async function aiRunLayoutPage(input: AiLayoutPageRunInput): Promise<AiValidatedGeneration<AiLayoutPageBuilt>> {\n const kept: Array<AiLayoutSection | null> = input.screen.sections.map(() => null)\n const observe = input.observe ?? ((check) => check)\n const base = aiLayoutPagePrompt({\n job: input.job,\n plan: input.plan,\n screen: input.screen,\n targets: input.targets,\n reusableComponents: input.reusableComponents,\n })\n const ask = (content: string, maxTokens: number, only?: readonly number[]) =>\n runValidatedGeneration<AiLayoutPageBuilt>(AI_LAYOUT_PAGE_KIND, {\n step: 'job.page',\n model: input.model,\n instructions: AI_JOB_PAGE_LANGUAGE_INSTRUCTIONS,\n inventory: input.inventory,\n messages: [{ role: 'user', content }],\n tool: AI_LAYOUT_PAGE_TOOL,\n maxTokens,\n cutOff: { noun: 'page', smaller: 'Write shorter copy, and fewer items in each group.' },\n ...AI_LAYOUT_LANGUAGE_THINKING,\n check: observe(\n aiLayoutPageCheck({\n screen: input.screen,\n sectionIds: input.sectionIds,\n targets: input.targets,\n context: input.context,\n reusableComponents: input.reusableComponents,\n kept,\n design: aiLayoutDesignOf(input.job, input.screen.slug),\n ...(only ? { only } : {}),\n }),\n ),\n ...(input.signal ? { signal: input.signal } : {}),\n })\n const first = await ask(base, AI_JOB_PAGE_LANGUAGE_BUDGET.maxTokens(input.model))\n const missing = aiLayoutMissingSections(kept)\n if (\n first.status !== 'needs_input' ||\n !missing.length ||\n !first.violations.every((violation) => violation.code === AI_LAYOUT_SECTION_MISSING_CODE)\n ) {\n return first\n }\n const second = await ask(\n aiLayoutMissingSectionsPrompt({ base, screen: input.screen, missing }),\n Math.min(AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS, AI_JOB_PAGE_LANGUAGE_BUDGET.maxTokens(input.model)),\n missing,\n )\n return { ...second, ...spentTogether(first, second) }\n}\n"],"names":["CANVAS_ROOT_ELEMENT_ID","isAiPlanNewRef","aiHomeScreenIds","AI_LAYOUT_POST_ADDRESS_TOKENS","aiCompileLayoutPage","aiLayoutListingAt","aiLayoutListingsOf","aiLayoutRecordHref","AI_LAYOUT_LANGUAGE_TEXT","AI_LAYOUT_PAGE_TOOL","aiReadLayoutPage","aiLayoutCopyCheck","aiLayoutStoredTree","AI_GENERATION_MAX_ATTEMPTS","AI_STEP_TIERS","runValidatedGeneration","aiSettleMechanicalRules","aiSettleUnrelatedScreenLinks","validateAiDoctrineTree","aiSiteKindDesignLines","aiSiteKindOfInputs","aiSiteSeed","aiOriginJobId","aiGenerationWorstCaseOnTierMs","aiJobStepBudget","aiJobBriefLine","aiPlanReferenceLines","aiLayoutIsHomeSlug","aiLayoutSiteAliases","aiLayoutSitePages","aiLayoutInventedContactViolations","AI_LAYOUT_LANGUAGE_INPUT","AI_LAYOUT_FORM_PAGE_INPUT","aiJobUsesLayoutLanguage","job","inputs","AI_LAYOUT_LANGUAGE_THINKING","thinking","effort","AI_LAYOUT_PAGE_THINKING_TOKENS","AI_LAYOUT_FRAME_THINKING_TOKENS","AI_JOB_PAGE_LANGUAGE_TOKENS","AI_LAYOUT_SECTION_MOST_TOKENS","AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS","AI_JOB_PAGE_LANGUAGE_BUDGET","tier","maxTokens","ownReadsMs","AI_LAYOUT_PAGE_KIND","AI_JOB_PAGE_LANGUAGE_INSTRUCTIONS","text","join","cacheBreakpoint","aiLayoutFormPageOfPlan","plan","formIds","created","Set","create","filter","entry","kind","map","name","toLowerCase","forms","screen","screens","find","sections","some","section","uses","ref","has","id","aiLayoutFacts","values","Object","value","brief","aiLayoutPageTargets","input","own","inventory","planned","pages","alias","page","row","template","label","slug","formPage","pageId","includes","homeIds","form","formPageId","components","component","props","facts","listings","aiLayoutListingsWithDatasets","flatMap","listing","datasets","dataset","datasetId","imageField","addressField","recordBase","fields","length","index","fieldIds","type","field","aiLayoutListingContext","context","targets","formScreenIds","here","placements","placement","screenId","tokens","href","bindingTokens","aiLayoutPagePrompt","places","listed","names","records","slice","items","standsInFor","keys","title","reusableComponents","$id","aiLayoutDesignOf","style","seed","Number","isFinite","home","aiLayoutMissingSectionsPrompt","missing","base","aiLayoutMissingSections","kept","AI_LAYOUT_SECTION_MISSING_CODE","AI_LAYOUT_SECTION_EMPTY_CODE","leastItems","aiLayoutShownItems","itemIds","storedIds","nodes","ids","undefined","aiLayoutEmptyItemSections","quotesOnly","least","shown","rule","code","message","detail","aiLayoutPageCheck","fills","only","_","answers","answer","last","reading","forEach","position","violations","compiled","sectionIds","design","stored","tree","ok","console","error","copy","dropped","rootId","ownScreenIds","report","scrollTargetIds","repeatsCompiled","load","settled","spentTogether","first","second","usage","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","attempts","estCostUsd","Math","round","model","stopReason","aiRunLayoutPage","observe","check","ask","content","step","instructions","messages","role","tool","cutOff","noun","smaller","signal","status","every","violation","min"],"mappings":"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"}
@@ -184,7 +184,7 @@ import { aiPlanItemCountViolations } from "./ai-job-plan-conformance.js";
184
184
  * a route, so the job never writes it (rule 13): the member saves it where
185
185
  * every record template is set up.
186
186
  */ export function aiPageRecordNote(template) {
187
- return `This page is drafted as the record template of "${template.name}". To serve a page per record at /${template.base}/…, open Page Properties → Record pages and save its dataset, address and fields.`;
187
+ return `This page is drafted as the record template of "${template.name}". To serve a page per record at /${template.base}/…, open Page Properties → Record pages and save its dataset, address and fields: the cards that list its records link those pages.`;
188
188
  }
189
189
  /**
190
190
  * The pages a section may link by id beyond the ones the site has built, and