@aglyn/plugins-ai 1.0.0-beta.264 → 1.0.0-beta.266

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 (98) hide show
  1. package/package.json +17 -17
  2. package/src/lib/assets/site-styles/beauty.webp +0 -0
  3. package/src/lib/assets/site-styles/blog.webp +0 -0
  4. package/src/lib/assets/site-styles/business.webp +0 -0
  5. package/src/lib/assets/site-styles/coming-soon.webp +0 -0
  6. package/src/lib/assets/site-styles/education.webp +0 -0
  7. package/src/lib/assets/site-styles/events.webp +0 -0
  8. package/src/lib/assets/site-styles/fitness.webp +0 -0
  9. package/src/lib/assets/site-styles/landing.webp +0 -0
  10. package/src/lib/assets/site-styles/music.webp +0 -0
  11. package/src/lib/assets/site-styles/nonprofit.webp +0 -0
  12. package/src/lib/assets/site-styles/personal.webp +0 -0
  13. package/src/lib/assets/site-styles/photography.webp +0 -0
  14. package/src/lib/assets/site-styles/portfolio.webp +0 -0
  15. package/src/lib/assets/site-styles/professional.webp +0 -0
  16. package/src/lib/assets/site-styles/realestate.webp +0 -0
  17. package/src/lib/assets/site-styles/restaurant.webp +0 -0
  18. package/src/lib/assets/site-styles/store.webp +0 -0
  19. package/src/lib/assets/site-styles/studio.webp +0 -0
  20. package/src/lib/assets/site-styles/trades.webp +0 -0
  21. package/src/lib/assets/site-styles/wellness.webp +0 -0
  22. package/src/lib/assets/site-styles/yoga.webp +0 -0
  23. package/src/lib/components/ai-site-start-card.component.d.ts +12 -0
  24. package/src/lib/components/ai-site-start-card.component.js +256 -236
  25. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  26. package/src/lib/components/ai-site-start-steps.component.d.ts +108 -0
  27. package/src/lib/components/ai-site-start-steps.component.js +451 -0
  28. package/src/lib/components/ai-site-start-steps.component.js.map +1 -0
  29. package/src/lib/components/ai-site-start-tracking.d.ts +114 -0
  30. package/src/lib/components/ai-site-start-tracking.js +277 -0
  31. package/src/lib/components/ai-site-start-tracking.js.map +1 -0
  32. package/src/lib/components/ai-site-style-previews.d.ts +26 -0
  33. package/src/lib/components/ai-site-style-previews.js +115 -0
  34. package/src/lib/components/ai-site-style-previews.js.map +1 -0
  35. package/src/lib/insights/ai-insight-digest.d.ts +9 -0
  36. package/src/lib/insights/ai-insight-digest.js +35 -11
  37. package/src/lib/insights/ai-insight-digest.js.map +1 -1
  38. package/src/lib/jobs/ai-job-page-language.d.ts +9 -0
  39. package/src/lib/jobs/ai-job-page-language.js +20 -2
  40. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  41. package/src/lib/jobs/ai-job-plan-step.d.ts +43 -8
  42. package/src/lib/jobs/ai-job-plan-step.js +99 -18
  43. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  44. package/src/lib/jobs/ai-job-site-datasets.d.ts +82 -1
  45. package/src/lib/jobs/ai-job-site-datasets.js +276 -14
  46. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  47. package/src/lib/jobs/ai-layout-stock-photos.d.ts +108 -10
  48. package/src/lib/jobs/ai-layout-stock-photos.js +729 -87
  49. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  50. package/src/lib/jobs/fixtures/ai-site-foreign-datasets-recording.d.ts +58 -0
  51. package/src/lib/jobs/fixtures/ai-site-foreign-datasets-recording.js +678 -0
  52. package/src/lib/jobs/fixtures/ai-site-foreign-datasets-recording.js.map +1 -0
  53. package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-beta265-recordings.d.ts +33 -0
  54. package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-beta265-recordings.js +270 -0
  55. package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-beta265-recordings.js.map +1 -0
  56. package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-people-recordings.d.ts +33 -0
  57. package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-people-recordings.js +277 -0
  58. package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-people-recordings.js.map +1 -0
  59. package/src/lib/layout-language/ai-layout-compiler.js +49 -12
  60. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  61. package/src/lib/layout-language/ai-layout-design.d.ts +6 -3
  62. package/src/lib/layout-language/ai-layout-design.js +14 -13
  63. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  64. package/src/lib/layout-language/ai-layout-listings.d.ts +48 -0
  65. package/src/lib/layout-language/ai-layout-listings.js +91 -5
  66. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  67. package/src/lib/layout-language/ai-layout-pictures.d.ts +15 -0
  68. package/src/lib/layout-language/ai-layout-pictures.js +6 -2
  69. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  70. package/src/lib/model/ai-site-home-arcs.d.ts +75 -0
  71. package/src/lib/model/ai-site-home-arcs.js +741 -0
  72. package/src/lib/model/ai-site-home-arcs.js.map +1 -0
  73. package/src/lib/model/ai-site-inventory.d.ts +18 -0
  74. package/src/lib/model/ai-site-inventory.js.map +1 -1
  75. package/src/lib/model/ai-site-job.d.ts +9 -6
  76. package/src/lib/model/ai-site-job.js +8 -5
  77. package/src/lib/model/ai-site-job.js.map +1 -1
  78. package/src/lib/model/ai-site-kinds.js +24 -24
  79. package/src/lib/model/ai-site-kinds.js.map +1 -1
  80. package/src/lib/model/ai-site-start-steps.d.ts +67 -0
  81. package/src/lib/model/ai-site-start-steps.js +127 -0
  82. package/src/lib/model/ai-site-start-steps.js.map +1 -0
  83. package/src/lib/model/ai-site-start.js +4 -1
  84. package/src/lib/model/ai-site-start.js.map +1 -1
  85. package/src/lib/runtime/ai-doctrine-validators.d.ts +32 -0
  86. package/src/lib/runtime/ai-doctrine-validators.js +119 -2
  87. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  88. package/src/lib/runtime/ai-doctrine.d.ts +6 -1
  89. package/src/lib/runtime/ai-doctrine.js +9 -5
  90. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  91. package/src/lib/runtime/ai-palette.generated.js +5 -2
  92. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  93. package/src/lib/runtime/site-inventory.js +6 -0
  94. package/src/lib/runtime/site-inventory.js.map +1 -1
  95. package/src/lib/server/ai-insight-digest-route.js +2 -2
  96. package/src/lib/server/ai-insight-digest-route.js.map +1 -1
  97. package/src/lib/usage/assist-free-taste.js +3 -0
  98. package/src/lib/usage/assist-free-taste.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/insights/ai-insight-digest.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 { FieldPath } from 'firebase-admin/firestore'\nimport { INSIGHT_DIGESTS_FIELD, insightDigestSubscribed, notificationMuted } from '@aglyn/aglyn/app-utils/notifications'\nimport { hostRoleFor } from '@aglyn/aglyn/app-utils/organizations'\nimport { checkEntitlement, resolveBrandingProfile, resolveEffectivePlan } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { isReleaseFlagOnForOrg, parseOrgReleaseFlagOverrides, type ReleaseFlagValue } from '@aglyn/aglyn/app-utils/release-flags'\nimport type { AglynOrgMember } from '@aglyn/aglyn/foundation/definitions/organization.types'\nimport { weeklySummaryPeriod } from '@aglyn/aglyn/app-utils/weekly-summary'\nimport {\n aiInsightDigestPeriod,\n AI_INSIGHTS_COLLECTION,\n aiInsightIsoWeek,\n type AiInsightRecord,\n} from '../model/ai-insight'\nimport { isAiOnForSite } from '../model/ai-site-switch'\nimport { cancelAiJob, createAiJob, getAiJob } from '../jobs/ai-jobs'\n\n/**\n * THE WEEKLY INSIGHTS (AGL-2915): `POST /api/admin/ai-insights-digest`, twice\n * a day on the console's scheduler.\n *\n * ## Who asked for it\n *\n * A person turns weekly insights on for a workspace beside the answers\n * themselves (`users/{uid}.insightDigests.{orgId}`), and off there or in\n * Notifications. It is opt-in, because each digest is a generation the\n * workspace pays for. At the sweep, every rung is asked again rather than\n * trusted from the switch: the workspace's plan includes `aiGenerative`,\n * `release_ai_generative` is on for it, its AI is not paused, the site has AI\n * on, and the person still holds `ai.generate` on the site.\n *\n * ## Monday: one job per site\n *\n * On a Monday run, for each workspace with someone who asked, the sites those\n * people reach — the busiest `AI_INSIGHT_DIGEST_MAX_SITES` by yesterday's page\n * views — each get one `insight` job with the `digest` surface, created by the\n * first of them who may generate there. The job runs on the AI jobs beat like\n * any other: it reserves, meters and records its credits, and a workspace at\n * its band — a Free one that has used its credits — has its job parked by the\n * reservation like any other. `orgs/{orgId}/aiInsightDigests/{week}` records\n * the jobs; `aiInsightDigestQueue/{week}` lists the workspaces still owed a\n * delivery, so a run that delivers reads one document rather than every\n * workspace.\n *\n * ## Every run: deliver what is written\n *\n * A digest whose job is done and kept at least one insight is delivered once:\n * a `content.insightsDigest` notification (unless the person muted the\n * category) and an email through the platform's own sending path, to each\n * person who asked for the workspace's insights and reaches the site. A job\n * that failed, kept nothing, or parked for credits is SKIPPED SILENTLY: a\n * parked job is canceled rather than left to spend later, and nobody is told,\n * because nobody asked about that week in particular. A job still running\n * past `AI_INSIGHT_DIGEST_DEADLINE_MS` is canceled and skipped the same way.\n *\n * The email says only what the answer says: the insights, each traced to the\n * figures it cites, and a link to the site's Analytics page. It is sent by the\n * platform to a person who asked for it, never to a contact.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport const AI_INSIGHTS_DIGEST_CRON_ID = 'ai-insights-digest'\nexport const AI_INSIGHTS_DIGEST_PATH = 'admin/ai-insights-digest'\n\n/** Orgs one call reads; a chunk that times out never advances the cursor, so the safe size is one that finishes. */\nexport const AI_INSIGHT_DIGEST_ORG_CHUNK = 25\n\n/** Sites one workspace's digest covers a week. */\nexport const AI_INSIGHT_DIGEST_MAX_SITES = 5\n\n/** Sites a workspace's sweep reads before it ranks them. */\nexport const AI_INSIGHT_DIGEST_HOST_READ_LIMIT = 50\n\n/** Workspaces one run delivers to, per week. */\nexport const AI_INSIGHT_DIGEST_DELIVER_LIMIT = 100\n\n/** How long a digest's job may take before its week is given up. */\nexport const AI_INSIGHT_DIGEST_DEADLINE_MS = 48 * 60 * 60_000\n\n/**\n * The time one call works before it stops and leaves the rest to the next:\n * inside the plugin dispatcher's 60 s function ceiling, with room for the\n * reads before the first check and the reply after the last. A call killed\n * mid-send could send one person a site's digest twice, because a send is\n * stamped after it goes.\n */\nexport const AI_INSIGHT_DIGEST_RUN_BUDGET_MS = 45_000\n\nexport const AI_INSIGHT_DIGEST_MARKERS = 'aiInsightDigests'\nexport const AI_INSIGHT_DIGEST_QUEUE = 'aiInsightDigestQueue'\n\nexport type AiInsightDigestSiteState = 'pending' | 'delivered' | 'skipped'\n\nexport interface AiInsightDigestSite {\n jobId: string\n name: string\n state: AiInsightDigestSiteState\n /**\n * The people already sent this site's digest, so a run the send rate\n * stopped never sends one twice. Dropped when the site settles, so a\n * settled marker names no person.\n */\n sent?: Record<string, boolean>\n}\n\nexport interface AiInsightDigestMarker {\n week: string\n createdAtMs: number\n sites: Record<string, AiInsightDigestSite>\n}\n\n/** What a send answered: whether it went, and whether the platform's send rate refused it for now. */\nexport interface AiInsightDigestSendResult {\n sent: boolean\n rateLimited: boolean\n}\n\n/** Everything the sweep reaches beyond Firestore, so a spec can hold each one. */\nexport interface AiInsightDigestDeps {\n firestore: Firestore\n now: Date\n flagValue: ReleaseFlagValue\n listMembers: (orgId: string) => Promise<AglynOrgMember[]>\n mayGenerate: (orgId: string, hostId: string, member: AglynOrgMember) => Promise<boolean>\n paused: (orgId: string) => Promise<boolean>\n notify: (uid: string, payload: { title: string; body: string; link: string; orgId: string; hostId: string }) => Promise<void>\n send: (email: {\n to: string\n subject: string\n text: string\n fromName: string\n /** The site the digest is about, and the org whose brand it wears (AGL-3367). */\n siteName: string\n org: Record<string, unknown>\n }) => Promise<AiInsightDigestSendResult>\n /** The console's absolute origin for an email link, or `''` when none is configured. */\n consoleOrigin: string\n /** The call's working time; `AI_INSIGHT_DIGEST_RUN_BUDGET_MS` unless a spec holds it. */\n budgetMs?: number\n}\n\nexport interface AiInsightDigestReport {\n week: string\n swept: number\n created: number\n delivered: number\n skipped: number\n /** The send rate refused or the call's time ran out; the run stops, and the next one delivers the rest. */\n deferred: boolean\n nextCursor: string | null\n done: boolean\n}\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** The week a Monday digest covers: the week the run falls in. */\nexport function aiInsightDigestWeek(now: Date): string {\n return aiInsightIsoWeek(now)\n}\n\n/** The week before, whose late digests a run still delivers. */\nfunction previousWeek(now: Date): string {\n return aiInsightIsoWeek(new Date(now.getTime() - 7 * 86_400_000))\n}\n\n/**\n * A digest email's words: the insights, the site's Analytics page, and where\n * to turn it off. The digest is an AI job metered against the workspace, so\n * it says what it drew, and it names the workspace a member of several asked\n * for it in.\n */\nexport function aiInsightDigestEmailText(input: {\n siteName: string\n productName: string\n insights: ReadonlyArray<{ text: string }>\n /** Absolute links, or `null` where no console origin is configured to build one from. */\n analyticsUrl: string | null\n settingsUrl: string | null\n /** The workspace the digest was asked for in; `''` falls back to \"this workspace\". */\n workspaceName?: string\n /** The AI credits the digest's job drew; 0 or absent says nothing. */\n creditsSpent?: number\n /** The week the figures cover, \"Oct 5 – Oct 11\"; absent says \"this week\". */\n periodLabel?: string\n}): string {\n const workspace = input.workspaceName?.trim() || ''\n const credits = Math.max(0, Math.round(Number(input.creditsSpent) || 0))\n const drew = credits\n ? ` Writing it used ${credits.toLocaleString('en-US')} of ` +\n `${workspace ? `${workspace}’s` : 'the workspace’s'} AI credits.`\n : ''\n const forWhom = workspace || 'this workspace'\n return [\n input.periodLabel\n ? `Here is what ${input.siteName}'s figures showed last week, ${input.periodLabel}.${drew}`\n : `Here is what ${input.siteName}'s figures showed this week.${drew}`,\n '',\n ...input.insights.map((insight, index) => `${index + 1}. ${insight.text}`),\n '',\n input.analyticsUrl\n ? `Each of these is traced to the figures it cites. See them in ${input.productName}: ${input.analyticsUrl}`\n : `Each of these is traced to the figures it cites. See them on the site's Analytics page in ${input.productName}.`,\n '',\n input.settingsUrl\n ? `You asked for weekly insights for ${forWhom}. Turn them off in Notifications: ${input.settingsUrl}`\n : `You asked for weekly insights for ${forWhom}. Turn them off in your Notifications settings.`,\n ].join('\\n')\n}\n\nasync function readUsers(firestore: Firestore, uids: readonly string[]) {\n if (!uids.length) return new Map<string, FirebaseFirestore.DocumentSnapshot>()\n const docs = await firestore.getAll(...uids.map((uid) => firestore.collection('users').doc(uid)))\n return new Map(docs.map((doc) => [doc.id, doc]))\n}\n\n/** Creates one workspace's digests for the week; answers how many jobs it created. */\nasync function createOrgDigest(\n deps: AiInsightDigestDeps,\n orgDoc: FirebaseFirestore.QueryDocumentSnapshot,\n week: string,\n): Promise<number> {\n const { firestore, now } = deps\n const orgId = orgDoc.id\n const org = (orgDoc.data() ?? {}) as Record<string, unknown>\n if (!checkEntitlement(org as never, 'aiGenerative')) return 0\n const flagOn = isReleaseFlagOnForOrg(\n 'release_ai_generative',\n deps.flagValue,\n orgId,\n parseOrgReleaseFlagOverrides(org['releaseFlags']),\n resolveEffectivePlan(org as never),\n )\n if (!flagOn || (await deps.paused(orgId))) return 0\n const markerRef = orgDoc.ref.collection(AI_INSIGHT_DIGEST_MARKERS).doc(week)\n if ((await markerRef.get()).exists) return 0\n\n const members = (await deps.listMembers(orgId)).filter((member) => member.orgSuspended !== true)\n const users = await readUsers(firestore, members.map((member) => member.$id).sort())\n const subscribers = members\n .filter((member) => insightDigestSubscribed(users.get(member.$id)?.get(INSIGHT_DIGESTS_FIELD), orgId))\n .sort((a, b) => a.$id.localeCompare(b.$id))\n if (!subscribers.length) return 0\n\n const hosts = await firestore\n .collection('hosts')\n .where('orgId', '==', orgId)\n .limit(AI_INSIGHT_DIGEST_HOST_READ_LIMIT)\n .get()\n const yesterday = new Date(now.getTime() - 86_400_000).toISOString().slice(0, 10)\n const candidates: Array<{ hostId: string; name: string; creator: string; views: number }> = []\n for (const host of hosts.docs) {\n const data = host.data() ?? {}\n if (!isAiOnForSite(org, data)) continue\n let creator: string | null = null\n for (const member of subscribers) {\n if (hostRoleFor(member, host.id) !== null && (await deps.mayGenerate(orgId, host.id, member))) {\n creator = member.$id\n break\n }\n }\n if (!creator) continue\n const day = await host.ref.collection('analytics').doc(yesterday).get()\n candidates.push({\n hostId: host.id,\n name: str(data['displayName']) || str(data['name']) || str(data['subdomain']) || host.id,\n creator,\n views: Number(day.get('total') ?? 0) || 0,\n })\n }\n const chosen = candidates\n .sort((a, b) => b.views - a.views || a.hostId.localeCompare(b.hostId))\n .slice(0, AI_INSIGHT_DIGEST_MAX_SITES)\n if (!chosen.length) return 0\n\n const sites: Record<string, AiInsightDigestSite> = {}\n for (const site of chosen) {\n const job = await createAiJob(\n firestore,\n {\n orgId,\n hostId: site.hostId,\n kind: 'insight',\n brief: `Weekly insights for ${site.name}`,\n inputs: { surface: 'digest', week, through: weeklySummaryPeriod(now).to },\n createdBy: site.creator,\n },\n now,\n )\n sites[site.hostId] = { jobId: job.$id, name: site.name, state: 'pending' }\n }\n const marker: AiInsightDigestMarker = { week, createdAtMs: now.getTime(), sites }\n await markerRef.set(marker)\n await firestore\n .collection(AI_INSIGHT_DIGEST_QUEUE)\n .doc(week)\n .set({ week, pending: { [orgId]: true } }, { merge: true })\n return chosen.length\n}\n\n/** Delivers or settles one workspace's digests for a week; `deferred` when the send rate refused. */\nasync function deliverOrgDigest(\n deps: AiInsightDigestDeps,\n orgId: string,\n week: string,\n overBudget: () => boolean,\n): Promise<{ delivered: number; skipped: number; settled: boolean; deferred: boolean }> {\n const { firestore, now } = deps\n const orgRef = firestore.collection('orgs').doc(orgId)\n const markerRef = orgRef.collection(AI_INSIGHT_DIGEST_MARKERS).doc(week)\n const markerSnapshot = await markerRef.get()\n const result = { delivered: 0, skipped: 0, settled: true, deferred: false }\n if (!markerSnapshot.exists) return result\n const marker = markerSnapshot.data() as AiInsightDigestMarker\n const org = ((await orgRef.get()).data() ?? {}) as Record<string, unknown>\n let members: AglynOrgMember[] | null = null\n const branding = resolveBrandingProfile(org as never)\n\n for (const [hostId, site] of Object.entries(marker.sites ?? {})) {\n if (site.state !== 'pending') continue\n const settle = async (state: AiInsightDigestSiteState) => {\n // Replaced whole rather than merged, which drops `sent`.\n const settled: AiInsightDigestSite = { jobId: site.jobId, name: site.name, state }\n await markerRef.update({ [`sites.${hostId}`]: settled })\n if (state === 'delivered') result.delivered += 1\n else result.skipped += 1\n }\n const job = await getAiJob(firestore, orgId, site.jobId)\n if (!job || job.status === 'failed' || job.status === 'canceled') {\n await settle('skipped')\n continue\n }\n if (job.status !== 'done') {\n const late = now.getTime() - Number(marker.createdAtMs ?? 0) > AI_INSIGHT_DIGEST_DEADLINE_MS\n // A job parked for credits — a Free workspace at its band — is given up\n // rather than left to spend on a week that is already over.\n if (job.status === 'needs_input' || late) {\n await cancelAiJob(firestore, orgId, site.jobId, now)\n await settle('skipped')\n } else {\n result.settled = false\n }\n continue\n }\n const answer = await orgRef.collection(AI_INSIGHTS_COLLECTION).doc(site.jobId).get()\n const record = answer.exists ? (answer.data() as AiInsightRecord) : null\n if (!record?.insights?.length) {\n await settle('skipped')\n continue\n }\n\n members ??= (await deps.listMembers(orgId)).filter((member) => member.orgSuspended !== true)\n const reaching = members.filter((member) => hostRoleFor(member, hostId) !== null)\n const users = await readUsers(firestore, reaching.map((member) => member.$id).sort())\n const host = (await firestore.collection('hosts').doc(hostId).get()).data() ?? {}\n const orgSlug = str(org['slug'])\n const subdomain = str(host['subdomain'])\n const origin = deps.consoleOrigin\n const analyticsUrl = origin && orgSlug && subdomain ? `${origin}/${orgSlug}/hosts/${subdomain}/analytics` : null\n for (const member of reaching.sort((a, b) => a.$id.localeCompare(b.$id))) {\n const user = users.get(member.$id)\n if (!insightDigestSubscribed(user?.get(INSIGHT_DIGESTS_FIELD), orgId)) continue\n if (site.sent?.[member.$id]) continue\n if (overBudget()) {\n // Out of time: what was sent is stamped, and the next run sends the rest.\n result.deferred = true\n result.settled = false\n return result\n }\n const address = str(member.email ?? user?.get('email')).toLowerCase()\n if (address.includes('@')) {\n const sent = await deps.send({\n to: address,\n subject: `Your weekly insights for ${site.name}`,\n text: aiInsightDigestEmailText({\n siteName: site.name,\n productName: branding.productName,\n insights: record.insights,\n analyticsUrl,\n settingsUrl: origin ? `${origin}/manage/notifications` : null,\n workspaceName: str(org['name']),\n creditsSpent: job.creditsSpent,\n periodLabel: aiInsightDigestPeriod(job.inputs, new Date(Number(marker.createdAtMs) || now.getTime())).label,\n }),\n fromName: branding.fromName,\n siteName: site.name,\n org,\n })\n if (sent.rateLimited) {\n // Nothing is marked, so the next run delivers this site whole.\n result.deferred = true\n result.settled = false\n return result\n }\n }\n if (!notificationMuted(user?.get('notificationPrefs'), 'content.insightsDigest')) {\n await deps.notify(member.$id, {\n title: `Weekly insights · ${site.name}`,\n body: record.insights[0].text,\n link: `/${hostId}/analytics`,\n orgId,\n hostId,\n })\n }\n await markerRef.set({ sites: { [hostId]: { sent: { [member.$id]: true } } } }, { merge: true })\n }\n await settle('delivered')\n }\n return result\n}\n\n/**\n * One call of the sweep: delivery for this week and the last on the first\n * call, then — on a Monday — one chunk of workspaces' creation, resumed by\n * `cursor`. The response shape is the console crons' (`nextCursor`, `done`),\n * so the scheduler's loop carries a platform of any size across calls.\n */\nexport async function runAiInsightDigestSweep(\n deps: AiInsightDigestDeps,\n options: { cursor: string | null },\n): Promise<AiInsightDigestReport> {\n const { firestore, now } = deps\n const startedAtMs = Date.now()\n const budgetMs = deps.budgetMs ?? AI_INSIGHT_DIGEST_RUN_BUDGET_MS\n const overBudget = () => Date.now() - startedAtMs > budgetMs\n const week = aiInsightDigestWeek(now)\n const report: AiInsightDigestReport = {\n week,\n swept: 0,\n created: 0,\n delivered: 0,\n skipped: 0,\n deferred: false,\n nextCursor: null,\n done: true,\n }\n\n if (!options.cursor) {\n for (const owed of [previousWeek(now), week]) {\n const queueRef = firestore.collection(AI_INSIGHT_DIGEST_QUEUE).doc(owed)\n const pending = Object.entries(((await queueRef.get()).get('pending') ?? {}) as Record<string, unknown>)\n .filter(([, owed]) => owed === true)\n .map(([orgId]) => orgId)\n .sort()\n .slice(0, AI_INSIGHT_DIGEST_DELIVER_LIMIT)\n for (const orgId of pending) {\n if (overBudget()) {\n report.deferred = true\n return report\n }\n try {\n const delivered = await deliverOrgDigest(deps, orgId, owed, overBudget)\n report.delivered += delivered.delivered\n report.skipped += delivered.skipped\n if (delivered.settled) {\n await queueRef.set({ pending: { [orgId]: false } }, { merge: true })\n }\n if (delivered.deferred) {\n report.deferred = true\n return report\n }\n } catch (error) {\n console.error('ai insight digest delivery failed', { orgId, week: owed, error })\n }\n }\n }\n }\n\n // Monday is the week's first day by ISO 8601, and the day a digest is made.\n if (now.getUTCDay() !== 1) return report\n\n let query = firestore.collection('orgs').orderBy(FieldPath.documentId()).limit(AI_INSIGHT_DIGEST_ORG_CHUNK + 1)\n if (options.cursor) query = query.startAfter(firestore.collection('orgs').doc(options.cursor))\n const page = await query.get()\n const orgs = page.docs.slice(0, AI_INSIGHT_DIGEST_ORG_CHUNK)\n for (const [index, orgDoc] of orgs.entries()) {\n if (overBudget()) {\n // Out of time: the next call resumes after the last workspace read. A\n // call that read none has no cursor to hand on, and the next scheduled\n // run starts the week's workspaces again, which the markers make safe.\n report.nextCursor = index ? orgs[index - 1].id : (options.cursor ?? null)\n report.done = report.nextCursor === null\n return report\n }\n report.swept += 1\n try {\n report.created += await createOrgDigest(deps, orgDoc, week)\n } catch (error) {\n console.error('ai insight digest creation failed', { orgId: orgDoc.id, week, error })\n }\n }\n const more = page.docs.length > AI_INSIGHT_DIGEST_ORG_CHUNK\n report.nextCursor = more ? (orgs[orgs.length - 1]?.id ?? null) : null\n report.done = !more\n return report\n}\n"],"names":["FieldPath","INSIGHT_DIGESTS_FIELD","insightDigestSubscribed","notificationMuted","hostRoleFor","checkEntitlement","resolveBrandingProfile","resolveEffectivePlan","isReleaseFlagOnForOrg","parseOrgReleaseFlagOverrides","weeklySummaryPeriod","aiInsightDigestPeriod","AI_INSIGHTS_COLLECTION","aiInsightIsoWeek","isAiOnForSite","cancelAiJob","createAiJob","getAiJob","AI_INSIGHTS_DIGEST_CRON_ID","AI_INSIGHTS_DIGEST_PATH","AI_INSIGHT_DIGEST_ORG_CHUNK","AI_INSIGHT_DIGEST_MAX_SITES","AI_INSIGHT_DIGEST_HOST_READ_LIMIT","AI_INSIGHT_DIGEST_DELIVER_LIMIT","AI_INSIGHT_DIGEST_DEADLINE_MS","AI_INSIGHT_DIGEST_RUN_BUDGET_MS","AI_INSIGHT_DIGEST_MARKERS","AI_INSIGHT_DIGEST_QUEUE","str","value","trim","aiInsightDigestWeek","now","previousWeek","Date","getTime","aiInsightDigestEmailText","input","workspace","workspaceName","credits","Math","max","round","Number","creditsSpent","drew","toLocaleString","forWhom","periodLabel","siteName","insights","map","insight","index","text","analyticsUrl","productName","settingsUrl","join","readUsers","firestore","uids","length","Map","docs","getAll","uid","collection","doc","id","createOrgDigest","deps","orgDoc","week","orgId","org","data","flagOn","flagValue","paused","markerRef","ref","get","exists","members","listMembers","filter","member","orgSuspended","users","$id","sort","subscribers","a","b","localeCompare","hosts","where","limit","yesterday","toISOString","slice","candidates","host","day","creator","mayGenerate","push","hostId","name","views","chosen","sites","site","job","kind","brief","inputs","surface","through","to","createdBy","jobId","state","marker","createdAtMs","set","pending","merge","deliverOrgDigest","overBudget","orgRef","markerSnapshot","result","delivered","skipped","settled","deferred","branding","Object","entries","record","settle","update","status","late","answer","reaching","orgSlug","subdomain","origin","consoleOrigin","user","sent","address","email","toLowerCase","includes","send","subject","label","fromName","rateLimited","notify","title","body","link","runAiInsightDigestSweep","options","orgs","startedAtMs","budgetMs","report","swept","created","nextCursor","done","cursor","owed","queueRef","error","console","getUTCDay","query","orderBy","documentId","startAfter","page","more"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/insights/ai-insight-digest.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 { FieldPath } from 'firebase-admin/firestore'\nimport { INSIGHT_DIGESTS_FIELD, insightDigestSubscribed, notificationMuted } from '@aglyn/aglyn/app-utils/notifications'\nimport { hostRoleFor } from '@aglyn/aglyn/app-utils/organizations'\nimport { checkEntitlement, resolveBrandingProfile, resolveEffectivePlan } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { isReleaseFlagOnForOrg, parseOrgReleaseFlagOverrides, type ReleaseFlagValue } from '@aglyn/aglyn/app-utils/release-flags'\nimport type { AglynOrgMember } from '@aglyn/aglyn/foundation/definitions/organization.types'\nimport { weeklySummaryPeriod } from '@aglyn/aglyn/app-utils/weekly-summary'\nimport {\n aiInsightDigestPeriod,\n AI_INSIGHTS_COLLECTION,\n aiInsightIsoWeek,\n type AiInsightRecord,\n} from '../model/ai-insight'\nimport { isAiOnForSite } from '../model/ai-site-switch'\nimport { cancelAiJob, createAiJob, getAiJob } from '../jobs/ai-jobs'\n\n/**\n * THE WEEKLY INSIGHTS (AGL-2915): `POST /api/admin/ai-insights-digest`, twice\n * a day on the console's scheduler.\n *\n * ## Who asked for it\n *\n * A person turns weekly insights on for a workspace beside the answers\n * themselves (`users/{uid}.insightDigests.{orgId}`), and off there or in\n * Notifications. It is opt-in, because each digest is a generation the\n * workspace pays for. At the sweep, every rung is asked again rather than\n * trusted from the switch: the workspace's plan includes `aiGenerative`,\n * `release_ai_generative` is on for it, its AI is not paused, the site has AI\n * on, and the person still holds `ai.generate` on the site.\n *\n * ## Monday: one job per site\n *\n * On a Monday run, for each workspace with someone who asked, the sites those\n * people reach — the busiest `AI_INSIGHT_DIGEST_MAX_SITES` by yesterday's page\n * views — each get one `insight` job with the `digest` surface, created by the\n * first of them who may generate there. The job runs on the AI jobs beat like\n * any other: it reserves, meters and records its credits, and a workspace at\n * its band — a Free one that has used its credits — has its job parked by the\n * reservation like any other. `orgs/{orgId}/aiInsightDigests/{week}` records\n * the jobs; `aiInsightDigestQueue/{week}` lists the workspaces still owed a\n * delivery, so a run that delivers reads one document rather than every\n * workspace.\n *\n * ## Every run: deliver what is written\n *\n * A digest whose job is done and kept at least one insight is delivered once:\n * a `content.insightsDigest` notification (unless the person muted the\n * category) and an email through the platform's own sending path, to each\n * person who asked for the workspace's insights and reaches the site. A job\n * that failed, kept nothing, or parked for credits is SKIPPED SILENTLY: a\n * parked job is canceled rather than left to spend later, and nobody is told,\n * because nobody asked about that week in particular. A job still running\n * past `AI_INSIGHT_DIGEST_DEADLINE_MS` is canceled and skipped the same way.\n *\n * The email says only what the answer says: the insights, each traced to the\n * figures it cites, and a link to the site's Analytics page. It is sent by the\n * platform to a person who asked for it, never to a contact.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport const AI_INSIGHTS_DIGEST_CRON_ID = 'ai-insights-digest'\nexport const AI_INSIGHTS_DIGEST_PATH = 'admin/ai-insights-digest'\n\n/** Orgs one call reads; a chunk that times out never advances the cursor, so the safe size is one that finishes. */\nexport const AI_INSIGHT_DIGEST_ORG_CHUNK = 25\n\n/** Sites one workspace's digest covers a week. */\nexport const AI_INSIGHT_DIGEST_MAX_SITES = 5\n\n/** Sites a workspace's sweep reads before it ranks them. */\nexport const AI_INSIGHT_DIGEST_HOST_READ_LIMIT = 50\n\n/** Workspaces one run delivers to, per week. */\nexport const AI_INSIGHT_DIGEST_DELIVER_LIMIT = 100\n\n/** How long a digest's job may take before its week is given up. */\nexport const AI_INSIGHT_DIGEST_DEADLINE_MS = 48 * 60 * 60_000\n\n/**\n * The time one call works before it stops and leaves the rest to the next:\n * inside the plugin dispatcher's 60 s function ceiling, with room for the\n * reads before the first check and the reply after the last. A call killed\n * mid-send could send one person a site's digest twice, because a send is\n * stamped after it goes.\n */\nexport const AI_INSIGHT_DIGEST_RUN_BUDGET_MS = 45_000\n\nexport const AI_INSIGHT_DIGEST_MARKERS = 'aiInsightDigests'\nexport const AI_INSIGHT_DIGEST_QUEUE = 'aiInsightDigestQueue'\n\nexport type AiInsightDigestSiteState = 'pending' | 'delivered' | 'skipped'\n\nexport interface AiInsightDigestSite {\n jobId: string\n name: string\n state: AiInsightDigestSiteState\n /**\n * The people already sent this site's digest, so a run the send rate\n * stopped never sends one twice. Dropped when the site settles, so a\n * settled marker names no person.\n */\n sent?: Record<string, boolean>\n}\n\nexport interface AiInsightDigestMarker {\n week: string\n createdAtMs: number\n sites: Record<string, AiInsightDigestSite>\n}\n\n/** What a send answered: whether it went, and whether the platform's send rate refused it for now. */\nexport interface AiInsightDigestSendResult {\n sent: boolean\n rateLimited: boolean\n}\n\n/** Everything the sweep reaches beyond Firestore, so a spec can hold each one. */\nexport interface AiInsightDigestDeps {\n firestore: Firestore\n now: Date\n flagValue: ReleaseFlagValue\n listMembers: (orgId: string) => Promise<AglynOrgMember[]>\n mayGenerate: (orgId: string, hostId: string, member: AglynOrgMember) => Promise<boolean>\n paused: (orgId: string) => Promise<boolean>\n notify: (uid: string, payload: { title: string; body: string; link: string; orgId: string; hostId: string }) => Promise<void>\n send: (email: {\n to: string\n subject: string\n text: string\n fromName: string\n /** The site the digest is about, and the org whose brand it wears (AGL-3367). */\n siteName: string\n org: Record<string, unknown>\n /** The designed email's merge values, from {@link aiInsightDigestEmailParts}. */\n merge?: Record<string, string>\n }) => Promise<AiInsightDigestSendResult>\n /** The console's absolute origin for an email link, or `''` when none is configured. */\n consoleOrigin: string\n /** The call's working time; `AI_INSIGHT_DIGEST_RUN_BUDGET_MS` unless a spec holds it. */\n budgetMs?: number\n}\n\nexport interface AiInsightDigestReport {\n week: string\n swept: number\n created: number\n delivered: number\n skipped: number\n /** The send rate refused or the call's time ran out; the run stops, and the next one delivers the rest. */\n deferred: boolean\n nextCursor: string | null\n done: boolean\n}\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** The week a Monday digest covers: the week the run falls in. */\nexport function aiInsightDigestWeek(now: Date): string {\n return aiInsightIsoWeek(now)\n}\n\n/** The week before, whose late digests a run still delivers. */\nfunction previousWeek(now: Date): string {\n return aiInsightIsoWeek(new Date(now.getTime() - 7 * 86_400_000))\n}\n\n/**\n * A digest email's words: the insights, the site's Analytics page, and where\n * to turn it off. The digest is an AI job metered against the workspace, so\n * it says what it drew, and it names the workspace a member of several asked\n * for it in.\n */\nexport function aiInsightDigestEmailText(input: {\n siteName: string\n productName: string\n insights: ReadonlyArray<{ text: string }>\n /** Absolute links, or `null` where no console origin is configured to build one from. */\n analyticsUrl: string | null\n settingsUrl: string | null\n /** The workspace the digest was asked for in; `''` falls back to \"this workspace\". */\n workspaceName?: string\n /** The AI credits the digest's job drew; 0 or absent says nothing. */\n creditsSpent?: number\n /** The week the figures cover, \"Oct 5 – Oct 11\"; absent says \"this week\". */\n periodLabel?: string\n}): string {\n const workspace = input.workspaceName?.trim() || ''\n const credits = Math.max(0, Math.round(Number(input.creditsSpent) || 0))\n const drew = credits\n ? ` Writing it used ${credits.toLocaleString('en-US')} of ` +\n `${workspace ? `${workspace}’s` : 'the workspace’s'} AI credits.`\n : ''\n const forWhom = workspace || 'this workspace'\n return [\n input.periodLabel\n ? `Here is what ${input.siteName}'s figures showed last week, ${input.periodLabel}.${drew}`\n : `Here is what ${input.siteName}'s figures showed this week.${drew}`,\n '',\n ...input.insights.map((insight, index) => `${index + 1}. ${insight.text}`),\n '',\n input.analyticsUrl\n ? `Each of these is traced to the figures it cites. See them in ${input.productName}: ${input.analyticsUrl}`\n : `Each of these is traced to the figures it cites. See them on the site's Analytics page in ${input.productName}.`,\n '',\n input.settingsUrl\n ? `You asked for weekly insights for ${forWhom}. Turn them off in Notifications: ${input.settingsUrl}`\n : `You asked for weekly insights for ${forWhom}. Turn them off in your Notifications settings.`,\n ].join('\\n')\n}\n\n/**\n * The same digest as {@link aiInsightDigestEmailText}, cut into the parts the\n * `ai-insights-digest` system email draws: the workspace and week above the\n * heading, the insights as a numbered list, a button to the site's\n * Analytics page, and why it came.\n */\nexport function aiInsightDigestEmailParts(\n input: Parameters<typeof aiInsightDigestEmailText>[0],\n): Record<\n 'digest.eyebrow' | 'digest.intro' | 'digest.insights' | 'digest.analyticsUrl' | 'digest.why',\n string\n> {\n const workspace = input.workspaceName?.trim() || ''\n const credits = Math.max(0, Math.round(Number(input.creditsSpent) || 0))\n const forWhom = workspace || 'this workspace'\n return {\n 'digest.eyebrow': [workspace, input.periodLabel?.trim()].filter(Boolean).join(' · '),\n 'digest.intro':\n `What ${input.siteName}'s figures showed ${input.periodLabel ? 'last week' : 'this week'}. ` +\n 'Each one is traced to the figures it cites, so you can check it on the Analytics page.',\n 'digest.insights': input.insights.map((insight, index) => `${index + 1}. ${insight.text}`).join('\\n\\n'),\n 'digest.analyticsUrl': input.analyticsUrl ?? '',\n 'digest.why':\n `You asked for weekly insights for ${forWhom}.` +\n (credits\n ? ` Writing these used ${credits.toLocaleString('en-US')} of ${workspace ? `${workspace}’s` : 'the workspace’s'} AI credits.`\n : '') +\n (input.settingsUrl\n ? ` Turn them off in Notifications: ${input.settingsUrl}`\n : ' Turn them off in your Notifications settings.'),\n }\n}\n\nasync function readUsers(firestore: Firestore, uids: readonly string[]) {\n if (!uids.length) return new Map<string, FirebaseFirestore.DocumentSnapshot>()\n const docs = await firestore.getAll(...uids.map((uid) => firestore.collection('users').doc(uid)))\n return new Map(docs.map((doc) => [doc.id, doc]))\n}\n\n/** Creates one workspace's digests for the week; answers how many jobs it created. */\nasync function createOrgDigest(\n deps: AiInsightDigestDeps,\n orgDoc: FirebaseFirestore.QueryDocumentSnapshot,\n week: string,\n): Promise<number> {\n const { firestore, now } = deps\n const orgId = orgDoc.id\n const org = (orgDoc.data() ?? {}) as Record<string, unknown>\n if (!checkEntitlement(org as never, 'aiGenerative')) return 0\n const flagOn = isReleaseFlagOnForOrg(\n 'release_ai_generative',\n deps.flagValue,\n orgId,\n parseOrgReleaseFlagOverrides(org['releaseFlags']),\n resolveEffectivePlan(org as never),\n )\n if (!flagOn || (await deps.paused(orgId))) return 0\n const markerRef = orgDoc.ref.collection(AI_INSIGHT_DIGEST_MARKERS).doc(week)\n if ((await markerRef.get()).exists) return 0\n\n const members = (await deps.listMembers(orgId)).filter((member) => member.orgSuspended !== true)\n const users = await readUsers(firestore, members.map((member) => member.$id).sort())\n const subscribers = members\n .filter((member) => insightDigestSubscribed(users.get(member.$id)?.get(INSIGHT_DIGESTS_FIELD), orgId))\n .sort((a, b) => a.$id.localeCompare(b.$id))\n if (!subscribers.length) return 0\n\n const hosts = await firestore\n .collection('hosts')\n .where('orgId', '==', orgId)\n .limit(AI_INSIGHT_DIGEST_HOST_READ_LIMIT)\n .get()\n const yesterday = new Date(now.getTime() - 86_400_000).toISOString().slice(0, 10)\n const candidates: Array<{ hostId: string; name: string; creator: string; views: number }> = []\n for (const host of hosts.docs) {\n const data = host.data() ?? {}\n if (!isAiOnForSite(org, data)) continue\n let creator: string | null = null\n for (const member of subscribers) {\n if (hostRoleFor(member, host.id) !== null && (await deps.mayGenerate(orgId, host.id, member))) {\n creator = member.$id\n break\n }\n }\n if (!creator) continue\n const day = await host.ref.collection('analytics').doc(yesterday).get()\n candidates.push({\n hostId: host.id,\n name: str(data['displayName']) || str(data['name']) || str(data['subdomain']) || host.id,\n creator,\n views: Number(day.get('total') ?? 0) || 0,\n })\n }\n const chosen = candidates\n .sort((a, b) => b.views - a.views || a.hostId.localeCompare(b.hostId))\n .slice(0, AI_INSIGHT_DIGEST_MAX_SITES)\n if (!chosen.length) return 0\n\n const sites: Record<string, AiInsightDigestSite> = {}\n for (const site of chosen) {\n const job = await createAiJob(\n firestore,\n {\n orgId,\n hostId: site.hostId,\n kind: 'insight',\n brief: `Weekly insights for ${site.name}`,\n inputs: { surface: 'digest', week, through: weeklySummaryPeriod(now).to },\n createdBy: site.creator,\n },\n now,\n )\n sites[site.hostId] = { jobId: job.$id, name: site.name, state: 'pending' }\n }\n const marker: AiInsightDigestMarker = { week, createdAtMs: now.getTime(), sites }\n await markerRef.set(marker)\n await firestore\n .collection(AI_INSIGHT_DIGEST_QUEUE)\n .doc(week)\n .set({ week, pending: { [orgId]: true } }, { merge: true })\n return chosen.length\n}\n\n/** Delivers or settles one workspace's digests for a week; `deferred` when the send rate refused. */\nasync function deliverOrgDigest(\n deps: AiInsightDigestDeps,\n orgId: string,\n week: string,\n overBudget: () => boolean,\n): Promise<{ delivered: number; skipped: number; settled: boolean; deferred: boolean }> {\n const { firestore, now } = deps\n const orgRef = firestore.collection('orgs').doc(orgId)\n const markerRef = orgRef.collection(AI_INSIGHT_DIGEST_MARKERS).doc(week)\n const markerSnapshot = await markerRef.get()\n const result = { delivered: 0, skipped: 0, settled: true, deferred: false }\n if (!markerSnapshot.exists) return result\n const marker = markerSnapshot.data() as AiInsightDigestMarker\n const org = ((await orgRef.get()).data() ?? {}) as Record<string, unknown>\n let members: AglynOrgMember[] | null = null\n const branding = resolveBrandingProfile(org as never)\n\n for (const [hostId, site] of Object.entries(marker.sites ?? {})) {\n if (site.state !== 'pending') continue\n const settle = async (state: AiInsightDigestSiteState) => {\n // Replaced whole rather than merged, which drops `sent`.\n const settled: AiInsightDigestSite = { jobId: site.jobId, name: site.name, state }\n await markerRef.update({ [`sites.${hostId}`]: settled })\n if (state === 'delivered') result.delivered += 1\n else result.skipped += 1\n }\n const job = await getAiJob(firestore, orgId, site.jobId)\n if (!job || job.status === 'failed' || job.status === 'canceled') {\n await settle('skipped')\n continue\n }\n if (job.status !== 'done') {\n const late = now.getTime() - Number(marker.createdAtMs ?? 0) > AI_INSIGHT_DIGEST_DEADLINE_MS\n // A job parked for credits — a Free workspace at its band — is given up\n // rather than left to spend on a week that is already over.\n if (job.status === 'needs_input' || late) {\n await cancelAiJob(firestore, orgId, site.jobId, now)\n await settle('skipped')\n } else {\n result.settled = false\n }\n continue\n }\n const answer = await orgRef.collection(AI_INSIGHTS_COLLECTION).doc(site.jobId).get()\n const record = answer.exists ? (answer.data() as AiInsightRecord) : null\n if (!record?.insights?.length) {\n await settle('skipped')\n continue\n }\n\n members ??= (await deps.listMembers(orgId)).filter((member) => member.orgSuspended !== true)\n const reaching = members.filter((member) => hostRoleFor(member, hostId) !== null)\n const users = await readUsers(firestore, reaching.map((member) => member.$id).sort())\n const host = (await firestore.collection('hosts').doc(hostId).get()).data() ?? {}\n const orgSlug = str(org['slug'])\n const subdomain = str(host['subdomain'])\n const origin = deps.consoleOrigin\n const analyticsUrl = origin && orgSlug && subdomain ? `${origin}/${orgSlug}/hosts/${subdomain}/analytics` : null\n for (const member of reaching.sort((a, b) => a.$id.localeCompare(b.$id))) {\n const user = users.get(member.$id)\n if (!insightDigestSubscribed(user?.get(INSIGHT_DIGESTS_FIELD), orgId)) continue\n if (site.sent?.[member.$id]) continue\n if (overBudget()) {\n // Out of time: what was sent is stamped, and the next run sends the rest.\n result.deferred = true\n result.settled = false\n return result\n }\n const address = str(member.email ?? user?.get('email')).toLowerCase()\n if (address.includes('@')) {\n const emailInput = {\n siteName: site.name,\n productName: branding.productName,\n insights: record.insights,\n analyticsUrl,\n settingsUrl: origin ? `${origin}/manage/notifications` : null,\n workspaceName: str(org['name']),\n creditsSpent: job.creditsSpent,\n periodLabel: aiInsightDigestPeriod(job.inputs, new Date(Number(marker.createdAtMs) || now.getTime())).label,\n }\n const sent = await deps.send({\n to: address,\n subject: `Your weekly insights for ${site.name}`,\n text: aiInsightDigestEmailText(emailInput),\n fromName: branding.fromName,\n siteName: site.name,\n org,\n merge: aiInsightDigestEmailParts(emailInput),\n })\n if (sent.rateLimited) {\n // Nothing is marked, so the next run delivers this site whole.\n result.deferred = true\n result.settled = false\n return result\n }\n }\n if (!notificationMuted(user?.get('notificationPrefs'), 'content.insightsDigest')) {\n await deps.notify(member.$id, {\n title: `Weekly insights · ${site.name}`,\n body: record.insights[0].text,\n link: `/${hostId}/analytics`,\n orgId,\n hostId,\n })\n }\n await markerRef.set({ sites: { [hostId]: { sent: { [member.$id]: true } } } }, { merge: true })\n }\n await settle('delivered')\n }\n return result\n}\n\n/**\n * One call of the sweep: delivery for this week and the last on the first\n * call, then — on a Monday — one chunk of workspaces' creation, resumed by\n * `cursor`. The response shape is the console crons' (`nextCursor`, `done`),\n * so the scheduler's loop carries a platform of any size across calls.\n */\nexport async function runAiInsightDigestSweep(\n deps: AiInsightDigestDeps,\n options: { cursor: string | null },\n): Promise<AiInsightDigestReport> {\n const { firestore, now } = deps\n const startedAtMs = Date.now()\n const budgetMs = deps.budgetMs ?? AI_INSIGHT_DIGEST_RUN_BUDGET_MS\n const overBudget = () => Date.now() - startedAtMs > budgetMs\n const week = aiInsightDigestWeek(now)\n const report: AiInsightDigestReport = {\n week,\n swept: 0,\n created: 0,\n delivered: 0,\n skipped: 0,\n deferred: false,\n nextCursor: null,\n done: true,\n }\n\n if (!options.cursor) {\n for (const owed of [previousWeek(now), week]) {\n const queueRef = firestore.collection(AI_INSIGHT_DIGEST_QUEUE).doc(owed)\n const pending = Object.entries(((await queueRef.get()).get('pending') ?? {}) as Record<string, unknown>)\n .filter(([, owed]) => owed === true)\n .map(([orgId]) => orgId)\n .sort()\n .slice(0, AI_INSIGHT_DIGEST_DELIVER_LIMIT)\n for (const orgId of pending) {\n if (overBudget()) {\n report.deferred = true\n return report\n }\n try {\n const delivered = await deliverOrgDigest(deps, orgId, owed, overBudget)\n report.delivered += delivered.delivered\n report.skipped += delivered.skipped\n if (delivered.settled) {\n await queueRef.set({ pending: { [orgId]: false } }, { merge: true })\n }\n if (delivered.deferred) {\n report.deferred = true\n return report\n }\n } catch (error) {\n console.error('ai insight digest delivery failed', { orgId, week: owed, error })\n }\n }\n }\n }\n\n // Monday is the week's first day by ISO 8601, and the day a digest is made.\n if (now.getUTCDay() !== 1) return report\n\n let query = firestore.collection('orgs').orderBy(FieldPath.documentId()).limit(AI_INSIGHT_DIGEST_ORG_CHUNK + 1)\n if (options.cursor) query = query.startAfter(firestore.collection('orgs').doc(options.cursor))\n const page = await query.get()\n const orgs = page.docs.slice(0, AI_INSIGHT_DIGEST_ORG_CHUNK)\n for (const [index, orgDoc] of orgs.entries()) {\n if (overBudget()) {\n // Out of time: the next call resumes after the last workspace read. A\n // call that read none has no cursor to hand on, and the next scheduled\n // run starts the week's workspaces again, which the markers make safe.\n report.nextCursor = index ? orgs[index - 1].id : (options.cursor ?? null)\n report.done = report.nextCursor === null\n return report\n }\n report.swept += 1\n try {\n report.created += await createOrgDigest(deps, orgDoc, week)\n } catch (error) {\n console.error('ai insight digest creation failed', { orgId: orgDoc.id, week, error })\n }\n }\n const more = page.docs.length > AI_INSIGHT_DIGEST_ORG_CHUNK\n report.nextCursor = more ? (orgs[orgs.length - 1]?.id ?? null) : null\n report.done = !more\n return report\n}\n"],"names":["FieldPath","INSIGHT_DIGESTS_FIELD","insightDigestSubscribed","notificationMuted","hostRoleFor","checkEntitlement","resolveBrandingProfile","resolveEffectivePlan","isReleaseFlagOnForOrg","parseOrgReleaseFlagOverrides","weeklySummaryPeriod","aiInsightDigestPeriod","AI_INSIGHTS_COLLECTION","aiInsightIsoWeek","isAiOnForSite","cancelAiJob","createAiJob","getAiJob","AI_INSIGHTS_DIGEST_CRON_ID","AI_INSIGHTS_DIGEST_PATH","AI_INSIGHT_DIGEST_ORG_CHUNK","AI_INSIGHT_DIGEST_MAX_SITES","AI_INSIGHT_DIGEST_HOST_READ_LIMIT","AI_INSIGHT_DIGEST_DELIVER_LIMIT","AI_INSIGHT_DIGEST_DEADLINE_MS","AI_INSIGHT_DIGEST_RUN_BUDGET_MS","AI_INSIGHT_DIGEST_MARKERS","AI_INSIGHT_DIGEST_QUEUE","str","value","trim","aiInsightDigestWeek","now","previousWeek","Date","getTime","aiInsightDigestEmailText","input","workspace","workspaceName","credits","Math","max","round","Number","creditsSpent","drew","toLocaleString","forWhom","periodLabel","siteName","insights","map","insight","index","text","analyticsUrl","productName","settingsUrl","join","aiInsightDigestEmailParts","filter","Boolean","readUsers","firestore","uids","length","Map","docs","getAll","uid","collection","doc","id","createOrgDigest","deps","orgDoc","week","orgId","org","data","flagOn","flagValue","paused","markerRef","ref","get","exists","members","listMembers","member","orgSuspended","users","$id","sort","subscribers","a","b","localeCompare","hosts","where","limit","yesterday","toISOString","slice","candidates","host","day","creator","mayGenerate","push","hostId","name","views","chosen","sites","site","job","kind","brief","inputs","surface","through","to","createdBy","jobId","state","marker","createdAtMs","set","pending","merge","deliverOrgDigest","overBudget","orgRef","markerSnapshot","result","delivered","skipped","settled","deferred","branding","Object","entries","record","settle","update","status","late","answer","reaching","orgSlug","subdomain","origin","consoleOrigin","user","sent","address","email","toLowerCase","includes","emailInput","label","send","subject","fromName","rateLimited","notify","title","body","link","runAiInsightDigestSweep","options","orgs","startedAtMs","budgetMs","report","swept","created","nextCursor","done","cursor","owed","queueRef","error","console","getUTCDay","query","orderBy","documentId","startAfter","page","more"],"mappings":"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"}
@@ -161,6 +161,15 @@ export interface AiLayoutStartPage {
161
161
  * the model. Empty when the page has no words to show.
162
162
  */
163
163
  export declare function aiLayoutStartPageLines(start: AiLayoutStartPage | null | undefined): string[];
164
+ /**
165
+ * What a page is told about the site's form (AGL-3899). A page whose plan
166
+ * places the form, or a site whose form has no page of its own, is told the
167
+ * form; any other page is told where the form lives and that its call to
168
+ * action links there. Told "The site's form: …" on every page, two of the
169
+ * newest production homes drew the form on their own, beside the Contact
170
+ * page that held it.
171
+ */
172
+ export declare function aiLayoutFormLine(screen: Pick<AiBuildPlanScreen, 'sections'>, targets: AiLayoutTargets): string;
164
173
  /** The page's user turn: the page, the brief, the plan, its sections and where its links may go. */
165
174
  export declare function aiLayoutPagePrompt(input: {
166
175
  job: Pick<AiJob, 'brief' | 'inputs' | '$id'>;
@@ -286,6 +286,21 @@ export const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({
286
286
  shown
287
287
  ];
288
288
  }
289
+ /**
290
+ * What a page is told about the site's form (AGL-3899). A page whose plan
291
+ * places the form, or a site whose form has no page of its own, is told the
292
+ * form; any other page is told where the form lives and that its call to
293
+ * action links there. Told "The site's form: …" on every page, two of the
294
+ * newest production homes drew the form on their own, beside the Contact
295
+ * page that held it.
296
+ */ export function aiLayoutFormLine(screen, targets) {
297
+ if (!targets.forms.length) return 'The site has no saved form: place none.';
298
+ const ids = new Set(targets.forms.map((form)=>form.id));
299
+ const placed = screen.sections.some((section)=>section.uses.some((ref)=>ids.has(ref)));
300
+ const page = targets.formPageId ? targets.pages.find((entry)=>entry.id === targets.formPageId) : undefined;
301
+ if (placed || !page) return `The site's form: ${targets.forms.map((form)=>`${form.name} (${form.id})`).join(', ')}.`;
302
+ return `The site's form is on the ${page.label} page (page:${page.id}): place no form on this page, and make a call to action that asks visitors to get in touch a button to that page.`;
303
+ }
289
304
  /** The page's user turn: the page, the brief, the plan, its sections and where its links may go. */ export function aiLayoutPagePrompt(input) {
290
305
  const { job, plan, screen, targets } = input;
291
306
  const sections = screen.sections.map((section, index)=>{
@@ -312,8 +327,11 @@ export const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({
312
327
  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`;
313
328
  }
314
329
  if ((listed == null ? void 0 : listed.listing.kind) === 'records') {
330
+ var _listed_filter;
315
331
  // One of the site's datasets (AGL-3616), repeated by the platform over its records.
316
- const names = listed.listing.records.length ? ` (${listed.listing.records.slice(0, 6).map((name)=>`“${name}”`).join(', ')})` : '';
332
+ // A section named for one group lists that group's records alone (AGL-3921).
333
+ const shown = ((_listed_filter = listed.filter) == null ? void 0 : _listed_filter.records.length) ? listed.filter.records : listed.listing.records;
334
+ const names = shown.length ? ` (${shown.slice(0, 6).map((name)=>`“${name}”`).join(', ')})` : '';
317
335
  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`;
318
336
  }
319
337
  if (listed) {
@@ -335,7 +353,7 @@ export const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({
335
353
  `Sections to design, in order:`,
336
354
  ...sections,
337
355
  pages.length ? `Other pages of this site: ${pages.join(', ')}.` : 'This site has no other page to link.',
338
- targets.forms.length ? `The site's form: ${targets.forms.map((form)=>`${form.name} (${form.id})`).join(', ')}.` : 'The site has no saved form: place none.',
356
+ aiLayoutFormLine(screen, targets),
339
357
  ...components.length ? [
340
358
  `Components this page may place: ${components.join('; ')}.`
341
359
  ] : [],
@@ -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 { aiWithSiteMotion } from '../layout-language/ai-layout-motion'\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, aiLayoutWithoutSections } from '../layout-language/ai-layout-gaps'\nimport { aiLayoutPromisedCopyViolations, aiLayoutPromisedSections } from '../layout-language/ai-layout-promised-copy'\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 { aiCacheWriteHourSum } from '../providers/cache-write-hour'\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 — a page and a site's frame are\n * designed, not transcribed.\n *\n * Low since AGL-3781. The live round of 2026-10-14 built the layout-language\n * briefs (`ai-job-layout-language-live.spec.ts`) at medium and at low: low\n * built 39 of 39 pages and 13 of 13 frames, 64 of 65 answers held their rules\n * on the first ask, and none was cut off at its ceiling. The ceilings keep\n * the thinking room sized at medium (`AI_LAYOUT_PAGE_THINKING_TOKENS`,\n * `AI_LAYOUT_FRAME_THINKING_TOKENS`), so a page that thinks longer still fits.\n */\nexport const AI_LAYOUT_LANGUAGE_THINKING = { thinking: 'adaptive', effort: 'low' } 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/**\n * The page of a site plan that places its form: the form the plan creates, or\n * one the site has. Home only when no other page places it (AGL-3805).\n *\n * Every call to action that asks a visitor to get in touch goes here — the\n * header's button on every page, the footer's, a page's own — so a page whose\n * purpose is the form outranks the home page that closes on a copy of it. A\n * plan lists Home first, and a home page that ends \"visit us or order\" places\n * the form too: the first page placing it was Home, and a Spanish bakery's\n * header \"Pide tu pastel\" and footer \"Contáctanos\" (beta.253, job eg4maZMGtN)\n * linked the home page on every page, itself included, while /pasteles and\n * /visitanos held the form. Eight of the 21 newest AI-built sites linked their\n * header's call to action home the same way, six of them with such a page.\n */\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 placing = plan.screens.filter(\n (entry) =>\n typeof entry.id === 'string' &&\n entry.sections.some((section) =>\n section.uses.some((ref) =>\n isAiPlanNewRef(ref) ? created.has(ref.toLowerCase()) : forms.has(ref),\n ),\n ),\n )\n const screen = placing.find((entry) => !aiLayoutIsHomeSlug(entry.slug)) ?? placing[0]\n return 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 most of the text of the page a planned page starts from that its prompt shows: about 1,000 tokens (AGL-3832). */\nexport const AI_LAYOUT_START_PAGE_MAX_CHARS = 4_000\n\n/** The site's page a planned page starts from (its plan's `duplicateOf`), by its name and its words. */\nexport interface AiLayoutStartPage {\n label: string\n text: string\n}\n\n/**\n * The page a planned page starts from, as its prompt shows it (AGL-3832): its\n * words, never its tree. A page in the layout language is written whole, so\n * a copy of the page would be replaced by the answer, and what the copy was\n * for — what the site already says there — reaches the page only through\n * the model. Empty when the page has no words to show.\n */\nexport function aiLayoutStartPageLines(start: AiLayoutStartPage | null | undefined): string[] {\n const text = start?.text.trim() ?? ''\n if (!start || !text) return []\n const shown = text.length > AI_LAYOUT_START_PAGE_MAX_CHARS ? `${text.slice(0, AI_LAYOUT_START_PAGE_MAX_CHARS).trimEnd()} …` : text\n return [\n `This page starts from the site's page \"${start.label}\": keep its facts, names and details wherever a section below covers them, and design every section anew. Its text:`,\n shown,\n ]\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 site's page the plan starts this page from, read off the site (AGL-3832). */\n startsFrom?: AiLayoutStartPage | null\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 === 'events') {\n // The site's own dated events (AGL-3755), listed by the platform with their dates.\n const names = listed.listing.records.length ? ` (${listed.listing.records.map((name) => `\"${name}\"`).join(', ')})` : ''\n return `${index + 1}. \"${section.name}\"${places}; the platform lists the site's upcoming events here${names} with their dates, places and photos: write only its heading and a line inviting visitors to come, and no cards, dates, list or links for events`\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 ...aiLayoutStartPageLines(input.startsFrom),\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/**\n * The site a page's entrance motion is drawn for (AGL-3838): its design where\n * the job names a kind of site, else the job's own seed, which moves in the\n * standard mood — so every page of one site moves alike.\n */\nexport function aiLayoutMotionSiteOf(job: Pick<AiJob, '$id' | 'inputs'>, slug: string): { kind?: string; seed: number } {\n return aiLayoutDesignOf(job, slug) ?? { seed: aiSiteSeed(aiOriginJobId(job)) }\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 /** The site's language, which the compiler's own words are written in (AGL-3760); absent, English. */\n language?: string\n /** The site the page's entrance motion is drawn for (`aiLayoutMotionSiteOf`); absent, its design. */\n motion?: { kind?: string; seed: number }\n /** Whether copy may promise what is coming (a coming-soon site, AGL-3887); absent, it may not. */\n promisesAllowed?: boolean\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/** The plan indices of the sections that show fewer of their planned items than they must (AGL-3660). */\nexport function aiLayoutShortItemSections(\n screen: Pick<AiBuildPlanScreen, 'sections'>,\n items: readonly number[],\n quotesOnly: readonly number[] = [],\n): number[] {\n return screen.sections.flatMap((section, index) => {\n if (quotesOnly.includes(index)) return []\n const least = leastItems(section.items)\n return least && (items[index] ?? 0) < least ? [index] : []\n })\n}\n\n/**\n * What a page built in the layout language says about the planned sections\n * it was published without (AGL-3771): each one the last answer could not\n * fill, or left with only its heading once its gaps were taken out, rather\n * than the page refused. `null` when every section is shown.\n */\nexport function aiLayoutLeftOutNote(\n screen: Pick<AiBuildPlanScreen, 'sections'>,\n sectionIds: readonly string[],\n nodes: Readonly<Record<string, unknown>>,\n): string | null {\n const names = screen.sections.flatMap((section, index) =>\n index > 0 && sectionIds[index] && !(sectionIds[index] in nodes) ? [`“${section.name}”`] : [],\n )\n if (!names.length) return null\n return names.length === 1\n ? `Left out: the section ${names[0]}, which could not be built well enough to publish. Add it in the Besigner when you are ready.`\n : `Left out: the sections ${names.join(', ')}, which could not be built well enough to publish. Add them in the Besigner when you are ready.`\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 /** Sections the page no longer shows (AGL-3771), which ask for nothing. */\n emptied: readonly number[] = [],\n): AiDoctrineViolation[] {\n const short = aiLayoutShortItemSections(screen, items, quotesOnly)\n return screen.sections.flatMap((section, index): AiDoctrineViolation[] => {\n if (!short.includes(index) || emptied.includes(index)) return []\n const shown = items[index] ?? 0\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 ...(input.language ? { language: input.language } : {}),\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.dropped]\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 let 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 // 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 // The last answer's sections that still show none of their planned items\n // are taken out rather than the whole page refused (AGL-3771): a site\n // start's home refused for one such section published a site with\n // nothing at its address. The page's first section heads it, and is\n // never taken out.\n const emptied = last\n ? aiLayoutShortItemSections(input.screen, items, compiled.quotesOnly).filter((index) => index > 0 && !!input.sectionIds[index])\n : []\n if (emptied.length) {\n const without = aiLayoutWithoutSections(\n nodes,\n CANVAS_ROOT_ELEMENT_ID,\n emptied.map((index) => ({\n id: input.sectionIds[index],\n name: input.screen.sections[index].name,\n why: `planned with ${input.screen.sections[index].items} items and showing ${items[index] || 'none'}`,\n })),\n )\n nodes = without.nodes as typeof nodes\n dropped.push(...without.dropped)\n }\n // A section that promises its content later — \"We are building this\n // space\" — is asked for again, and the last answer's is taken out, the\n // page's first section aside (AGL-3887). A coming-soon site's is its point.\n const promised = input.promisesAllowed ? [] : aiLayoutPromisedSections(nodes, input.sectionIds)\n const promisedOut = last ? promised.filter(({ index }) => index > 0) : []\n if (promisedOut.length) {\n const without = aiLayoutWithoutSections(\n nodes,\n CANVAS_ROOT_ELEMENT_ID,\n promisedOut.map(({ index }) => ({\n id: input.sectionIds[index],\n name: input.screen.sections[index].name,\n why: 'which promised content that is not there',\n })),\n )\n nodes = without.nodes as typeof nodes\n dropped.push(...without.dropped)\n }\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 // 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, emptied),\n ...(last ? [] : aiLayoutPromisedCopyViolations(input.screen, promised)),\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 // Entrance motion is the site's, decided here in code and never\n // asked of the model (AGL-3838): written once the page has passed,\n // so no check above reads a prop the palette does not list.\n nodes: aiWithSiteMotion(nodes, input.motion ?? input.design ?? { seed: 0 }, { sectionIds: input.sectionIds }),\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 /** The site's page the plan starts this page from (AGL-3832). */\n startsFrom?: AiLayoutStartPage | null\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 ...aiCacheWriteHourSum(first.usage, second.usage),\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 startsFrom: input.startsFrom ?? null,\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 motion: aiLayoutMotionSiteOf(input.job, input.screen.slug),\n ...(only ? { only } : {}),\n ...(input.plan.language ? { language: input.plan.language } : {}),\n promisesAllowed: aiSiteKindOfInputs(input.job.inputs)?.id === 'coming-soon',\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","aiWithSiteMotion","aiLayoutListingAt","aiLayoutListingsOf","aiLayoutRecordHref","AI_LAYOUT_LANGUAGE_TEXT","AI_LAYOUT_PAGE_TOOL","aiReadLayoutPage","aiLayoutCopyCheck","aiLayoutWithoutSections","aiLayoutPromisedCopyViolations","aiLayoutPromisedSections","aiLayoutStoredTree","AI_GENERATION_MAX_ATTEMPTS","AI_STEP_TIERS","aiCacheWriteHourSum","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","placing","created","Set","create","filter","entry","kind","map","name","toLowerCase","forms","screens","id","sections","some","section","uses","ref","has","screen","find","slug","aiLayoutFacts","values","Object","value","brief","aiLayoutPageTargets","input","own","inventory","planned","pages","alias","page","row","template","label","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","AI_LAYOUT_START_PAGE_MAX_CHARS","aiLayoutStartPageLines","start","trim","shown","slice","trimEnd","aiLayoutPagePrompt","places","listed","names","records","items","standsInFor","keys","title","startsFrom","reusableComponents","$id","aiLayoutDesignOf","style","seed","Number","isFinite","home","aiLayoutMotionSiteOf","aiLayoutMissingSectionsPrompt","missing","base","aiLayoutMissingSections","kept","AI_LAYOUT_SECTION_MISSING_CODE","AI_LAYOUT_SECTION_EMPTY_CODE","leastItems","aiLayoutShownItems","itemIds","storedIds","nodes","ids","undefined","aiLayoutShortItemSections","quotesOnly","least","aiLayoutLeftOutNote","sectionIds","aiLayoutEmptyItemSections","emptied","short","rule","code","message","detail","aiLayoutPageCheck","fills","only","_","answers","answer","last","reading","forEach","position","violations","compiled","design","language","stored","tree","ok","console","error","copy","dropped","rootId","ownScreenIds","without","why","push","promised","promisesAllowed","promisedOut","report","scrollTargetIds","repeatsCompiled","motion","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 { aiWithSiteMotion } from '../layout-language/ai-layout-motion'\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, aiLayoutWithoutSections } from '../layout-language/ai-layout-gaps'\nimport { aiLayoutPromisedCopyViolations, aiLayoutPromisedSections } from '../layout-language/ai-layout-promised-copy'\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 { aiCacheWriteHourSum } from '../providers/cache-write-hour'\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 — a page and a site's frame are\n * designed, not transcribed.\n *\n * Low since AGL-3781. The live round of 2026-10-14 built the layout-language\n * briefs (`ai-job-layout-language-live.spec.ts`) at medium and at low: low\n * built 39 of 39 pages and 13 of 13 frames, 64 of 65 answers held their rules\n * on the first ask, and none was cut off at its ceiling. The ceilings keep\n * the thinking room sized at medium (`AI_LAYOUT_PAGE_THINKING_TOKENS`,\n * `AI_LAYOUT_FRAME_THINKING_TOKENS`), so a page that thinks longer still fits.\n */\nexport const AI_LAYOUT_LANGUAGE_THINKING = { thinking: 'adaptive', effort: 'low' } 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/**\n * The page of a site plan that places its form: the form the plan creates, or\n * one the site has. Home only when no other page places it (AGL-3805).\n *\n * Every call to action that asks a visitor to get in touch goes here — the\n * header's button on every page, the footer's, a page's own — so a page whose\n * purpose is the form outranks the home page that closes on a copy of it. A\n * plan lists Home first, and a home page that ends \"visit us or order\" places\n * the form too: the first page placing it was Home, and a Spanish bakery's\n * header \"Pide tu pastel\" and footer \"Contáctanos\" (beta.253, job eg4maZMGtN)\n * linked the home page on every page, itself included, while /pasteles and\n * /visitanos held the form. Eight of the 21 newest AI-built sites linked their\n * header's call to action home the same way, six of them with such a page.\n */\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 placing = plan.screens.filter(\n (entry) =>\n typeof entry.id === 'string' &&\n entry.sections.some((section) =>\n section.uses.some((ref) =>\n isAiPlanNewRef(ref) ? created.has(ref.toLowerCase()) : forms.has(ref),\n ),\n ),\n )\n const screen = placing.find((entry) => !aiLayoutIsHomeSlug(entry.slug)) ?? placing[0]\n return 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 most of the text of the page a planned page starts from that its prompt shows: about 1,000 tokens (AGL-3832). */\nexport const AI_LAYOUT_START_PAGE_MAX_CHARS = 4_000\n\n/** The site's page a planned page starts from (its plan's `duplicateOf`), by its name and its words. */\nexport interface AiLayoutStartPage {\n label: string\n text: string\n}\n\n/**\n * The page a planned page starts from, as its prompt shows it (AGL-3832): its\n * words, never its tree. A page in the layout language is written whole, so\n * a copy of the page would be replaced by the answer, and what the copy was\n * for — what the site already says there — reaches the page only through\n * the model. Empty when the page has no words to show.\n */\nexport function aiLayoutStartPageLines(start: AiLayoutStartPage | null | undefined): string[] {\n const text = start?.text.trim() ?? ''\n if (!start || !text) return []\n const shown = text.length > AI_LAYOUT_START_PAGE_MAX_CHARS ? `${text.slice(0, AI_LAYOUT_START_PAGE_MAX_CHARS).trimEnd()} …` : text\n return [\n `This page starts from the site's page \"${start.label}\": keep its facts, names and details wherever a section below covers them, and design every section anew. Its text:`,\n shown,\n ]\n}\n\n/**\n * What a page is told about the site's form (AGL-3899). A page whose plan\n * places the form, or a site whose form has no page of its own, is told the\n * form; any other page is told where the form lives and that its call to\n * action links there. Told \"The site's form: …\" on every page, two of the\n * newest production homes drew the form on their own, beside the Contact\n * page that held it.\n */\nexport function aiLayoutFormLine(screen: Pick<AiBuildPlanScreen, 'sections'>, targets: AiLayoutTargets): string {\n if (!targets.forms.length) return 'The site has no saved form: place none.'\n const ids = new Set(targets.forms.map((form) => form.id))\n const placed = screen.sections.some((section) => section.uses.some((ref) => ids.has(ref)))\n const page = targets.formPageId ? targets.pages.find((entry) => entry.id === targets.formPageId) : undefined\n if (placed || !page) return `The site's form: ${targets.forms.map((form) => `${form.name} (${form.id})`).join(', ')}.`\n return `The site's form is on the ${page.label} page (page:${page.id}): place no form on this page, and make a call to action that asks visitors to get in touch a button to that page.`\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 site's page the plan starts this page from, read off the site (AGL-3832). */\n startsFrom?: AiLayoutStartPage | null\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 === 'events') {\n // The site's own dated events (AGL-3755), listed by the platform with their dates.\n const names = listed.listing.records.length ? ` (${listed.listing.records.map((name) => `\"${name}\"`).join(', ')})` : ''\n return `${index + 1}. \"${section.name}\"${places}; the platform lists the site's upcoming events here${names} with their dates, places and photos: write only its heading and a line inviting visitors to come, and no cards, dates, list or links for events`\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 // A section named for one group lists that group's records alone (AGL-3921).\n const shown = listed.filter?.records.length ? listed.filter.records : listed.listing.records\n const names = shown.length ? ` (${shown.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 ...aiLayoutStartPageLines(input.startsFrom),\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 aiLayoutFormLine(screen, targets),\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/**\n * The site a page's entrance motion is drawn for (AGL-3838): its design where\n * the job names a kind of site, else the job's own seed, which moves in the\n * standard mood — so every page of one site moves alike.\n */\nexport function aiLayoutMotionSiteOf(job: Pick<AiJob, '$id' | 'inputs'>, slug: string): { kind?: string; seed: number } {\n return aiLayoutDesignOf(job, slug) ?? { seed: aiSiteSeed(aiOriginJobId(job)) }\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 /** The site's language, which the compiler's own words are written in (AGL-3760); absent, English. */\n language?: string\n /** The site the page's entrance motion is drawn for (`aiLayoutMotionSiteOf`); absent, its design. */\n motion?: { kind?: string; seed: number }\n /** Whether copy may promise what is coming (a coming-soon site, AGL-3887); absent, it may not. */\n promisesAllowed?: boolean\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/** The plan indices of the sections that show fewer of their planned items than they must (AGL-3660). */\nexport function aiLayoutShortItemSections(\n screen: Pick<AiBuildPlanScreen, 'sections'>,\n items: readonly number[],\n quotesOnly: readonly number[] = [],\n): number[] {\n return screen.sections.flatMap((section, index) => {\n if (quotesOnly.includes(index)) return []\n const least = leastItems(section.items)\n return least && (items[index] ?? 0) < least ? [index] : []\n })\n}\n\n/**\n * What a page built in the layout language says about the planned sections\n * it was published without (AGL-3771): each one the last answer could not\n * fill, or left with only its heading once its gaps were taken out, rather\n * than the page refused. `null` when every section is shown.\n */\nexport function aiLayoutLeftOutNote(\n screen: Pick<AiBuildPlanScreen, 'sections'>,\n sectionIds: readonly string[],\n nodes: Readonly<Record<string, unknown>>,\n): string | null {\n const names = screen.sections.flatMap((section, index) =>\n index > 0 && sectionIds[index] && !(sectionIds[index] in nodes) ? [`“${section.name}”`] : [],\n )\n if (!names.length) return null\n return names.length === 1\n ? `Left out: the section ${names[0]}, which could not be built well enough to publish. Add it in the Besigner when you are ready.`\n : `Left out: the sections ${names.join(', ')}, which could not be built well enough to publish. Add them in the Besigner when you are ready.`\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 /** Sections the page no longer shows (AGL-3771), which ask for nothing. */\n emptied: readonly number[] = [],\n): AiDoctrineViolation[] {\n const short = aiLayoutShortItemSections(screen, items, quotesOnly)\n return screen.sections.flatMap((section, index): AiDoctrineViolation[] => {\n if (!short.includes(index) || emptied.includes(index)) return []\n const shown = items[index] ?? 0\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 ...(input.language ? { language: input.language } : {}),\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.dropped]\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 let 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 // 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 // The last answer's sections that still show none of their planned items\n // are taken out rather than the whole page refused (AGL-3771): a site\n // start's home refused for one such section published a site with\n // nothing at its address. The page's first section heads it, and is\n // never taken out.\n const emptied = last\n ? aiLayoutShortItemSections(input.screen, items, compiled.quotesOnly).filter((index) => index > 0 && !!input.sectionIds[index])\n : []\n if (emptied.length) {\n const without = aiLayoutWithoutSections(\n nodes,\n CANVAS_ROOT_ELEMENT_ID,\n emptied.map((index) => ({\n id: input.sectionIds[index],\n name: input.screen.sections[index].name,\n why: `planned with ${input.screen.sections[index].items} items and showing ${items[index] || 'none'}`,\n })),\n )\n nodes = without.nodes as typeof nodes\n dropped.push(...without.dropped)\n }\n // A section that promises its content later — \"We are building this\n // space\" — is asked for again, and the last answer's is taken out, the\n // page's first section aside (AGL-3887). A coming-soon site's is its point.\n const promised = input.promisesAllowed ? [] : aiLayoutPromisedSections(nodes, input.sectionIds)\n const promisedOut = last ? promised.filter(({ index }) => index > 0) : []\n if (promisedOut.length) {\n const without = aiLayoutWithoutSections(\n nodes,\n CANVAS_ROOT_ELEMENT_ID,\n promisedOut.map(({ index }) => ({\n id: input.sectionIds[index],\n name: input.screen.sections[index].name,\n why: 'which promised content that is not there',\n })),\n )\n nodes = without.nodes as typeof nodes\n dropped.push(...without.dropped)\n }\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 // 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, emptied),\n ...(last ? [] : aiLayoutPromisedCopyViolations(input.screen, promised)),\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 // Entrance motion is the site's, decided here in code and never\n // asked of the model (AGL-3838): written once the page has passed,\n // so no check above reads a prop the palette does not list.\n nodes: aiWithSiteMotion(nodes, input.motion ?? input.design ?? { seed: 0 }, { sectionIds: input.sectionIds }),\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 /** The site's page the plan starts this page from (AGL-3832). */\n startsFrom?: AiLayoutStartPage | null\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 ...aiCacheWriteHourSum(first.usage, second.usage),\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 startsFrom: input.startsFrom ?? null,\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 motion: aiLayoutMotionSiteOf(input.job, input.screen.slug),\n ...(only ? { only } : {}),\n ...(input.plan.language ? { language: input.plan.language } : {}),\n promisesAllowed: aiSiteKindOfInputs(input.job.inputs)?.id === 'coming-soon',\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","aiWithSiteMotion","aiLayoutListingAt","aiLayoutListingsOf","aiLayoutRecordHref","AI_LAYOUT_LANGUAGE_TEXT","AI_LAYOUT_PAGE_TOOL","aiReadLayoutPage","aiLayoutCopyCheck","aiLayoutWithoutSections","aiLayoutPromisedCopyViolations","aiLayoutPromisedSections","aiLayoutStoredTree","AI_GENERATION_MAX_ATTEMPTS","AI_STEP_TIERS","aiCacheWriteHourSum","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","placing","created","Set","create","filter","entry","kind","map","name","toLowerCase","forms","screens","id","sections","some","section","uses","ref","has","screen","find","slug","aiLayoutFacts","values","Object","value","brief","aiLayoutPageTargets","input","own","inventory","planned","pages","alias","page","row","template","label","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","AI_LAYOUT_START_PAGE_MAX_CHARS","aiLayoutStartPageLines","start","trim","shown","slice","trimEnd","aiLayoutFormLine","ids","placed","undefined","aiLayoutPagePrompt","places","listed","names","records","items","standsInFor","keys","title","startsFrom","reusableComponents","$id","aiLayoutDesignOf","style","seed","Number","isFinite","home","aiLayoutMotionSiteOf","aiLayoutMissingSectionsPrompt","missing","base","aiLayoutMissingSections","kept","AI_LAYOUT_SECTION_MISSING_CODE","AI_LAYOUT_SECTION_EMPTY_CODE","leastItems","aiLayoutShownItems","itemIds","storedIds","nodes","aiLayoutShortItemSections","quotesOnly","least","aiLayoutLeftOutNote","sectionIds","aiLayoutEmptyItemSections","emptied","short","rule","code","message","detail","aiLayoutPageCheck","fills","only","_","answers","answer","last","reading","forEach","position","violations","compiled","design","language","stored","tree","ok","console","error","copy","dropped","rootId","ownScreenIds","without","why","push","promised","promisesAllowed","promisedOut","report","scrollTargetIds","repeatsCompiled","motion","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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"}