@aglyn/plugins-ai 1.0.0-beta.217 → 1.0.0-beta.219
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +17 -17
- package/src/index.d.ts +1 -0
- package/src/index.js.map +1 -1
- package/src/lib/activity/ai-activity-actions.js +10 -2
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-assist-provider.component.js +2 -2
- package/src/lib/components/ai-assist-provider.component.js.map +1 -1
- package/src/lib/components/ai-brief-dialog.component.js +3 -3
- package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-experiment-cards.component.js +1 -1
- package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.d.ts +8 -0
- package/src/lib/components/ai-job-plan.component.js +21 -4
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-permissions-on-host.component.d.ts +12 -2
- package/src/lib/components/ai-permissions-on-host.component.js +12 -3
- package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
- package/src/lib/components/ai-product-copy-card.component.d.ts +1 -1
- package/src/lib/components/ai-product-copy-card.component.js.map +1 -1
- package/src/lib/components/ai-product-import-option.component.d.ts +1 -1
- package/src/lib/components/ai-product-import-option.component.js.map +1 -1
- package/src/lib/components/ai-product-zones.d.ts +124 -0
- package/src/lib/components/ai-product-zones.js +32 -0
- package/src/lib/components/ai-product-zones.js.map +1 -0
- package/src/lib/components/ai-products-hub-card.component.d.ts +1 -1
- package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
- package/src/lib/components/ai-products-proposals.component.d.ts +1 -1
- package/src/lib/components/ai-products-proposals.component.js.map +1 -1
- package/src/lib/components/ai-seo-audit-card.component.js +35 -92
- package/src/lib/components/ai-seo-audit-card.component.js.map +1 -1
- package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
- package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.js +21 -15
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.js +2 -2
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/assist-signals-page.component.js +2 -2
- package/src/lib/components/assist-signals-page.component.js.map +1 -1
- package/src/lib/jobs/ai-email-bindings.d.ts +28 -21
- package/src/lib/jobs/ai-email-bindings.js +75 -42
- package/src/lib/jobs/ai-email-bindings.js.map +1 -1
- package/src/lib/jobs/ai-job-campaign-step.d.ts +0 -2
- package/src/lib/jobs/ai-job-campaign-step.js +5 -10
- package/src/lib/jobs/ai-job-campaign-step.js.map +1 -1
- package/src/lib/jobs/ai-job-component-step.js +9 -3
- package/src/lib/jobs/ai-job-component-step.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.d.ts +9 -1
- package/src/lib/jobs/ai-job-drafts.js +12 -5
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-email-step.d.ts +0 -2
- package/src/lib/jobs/ai-job-email-step.js +1 -3
- package/src/lib/jobs/ai-job-email-step.js.map +1 -1
- package/src/lib/jobs/ai-job-experiment-step.d.ts +1 -1
- package/src/lib/jobs/ai-job-experiment-step.js +1 -1
- package/src/lib/jobs/ai-job-experiment-step.js.map +1 -1
- package/src/lib/jobs/ai-job-generation.d.ts +1 -1
- package/src/lib/jobs/ai-job-generation.js +10 -2
- package/src/lib/jobs/ai-job-generation.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +1 -1
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.d.ts +4 -2
- package/src/lib/jobs/ai-job-page-sections.js +7 -4
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +7 -2
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-conformance.js +33 -0
- package/src/lib/jobs/ai-job-plan-conformance.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +3 -0
- package/src/lib/jobs/ai-job-plan-step.js +29 -17
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plugin-drafts.d.ts +14 -6
- package/src/lib/jobs/ai-job-plugin-drafts.js +12 -4
- package/src/lib/jobs/ai-job-plugin-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-products-step.d.ts +7 -0
- package/src/lib/jobs/ai-job-products-step.js +54 -32
- package/src/lib/jobs/ai-job-products-step.js.map +1 -1
- package/src/lib/jobs/ai-job-seo-step.d.ts +3 -24
- package/src/lib/jobs/ai-job-seo-step.js +35 -108
- package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
- package/src/lib/jobs/ai-job-template-step.js +1 -0
- package/src/lib/jobs/ai-job-template-step.js.map +1 -1
- package/src/lib/jobs/ai-job-theme-step.d.ts +1 -1
- package/src/lib/jobs/ai-job-theme-step.js +1 -1
- package/src/lib/jobs/ai-job-theme-step.js.map +1 -1
- package/src/lib/jobs/ai-job-workflow-step.js +17 -9
- package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
- package/src/lib/jobs/ai-workflow-records.d.ts +19 -11
- package/src/lib/jobs/ai-workflow-records.js +60 -27
- package/src/lib/jobs/ai-workflow-records.js.map +1 -1
- package/src/lib/model/ai-allotments.d.ts +11 -3
- package/src/lib/model/ai-allotments.js +21 -4
- package/src/lib/model/ai-allotments.js.map +1 -1
- package/src/lib/model/ai-automation-outline.d.ts +21 -2
- package/src/lib/model/ai-automation-outline.js.map +1 -1
- package/src/lib/model/ai-build-plan.d.ts +57 -0
- package/src/lib/model/ai-build-plan.js +116 -7
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-seo.d.ts +11 -51
- package/src/lib/model/ai-seo.js +0 -17
- package/src/lib/model/ai-seo.js.map +1 -1
- package/src/lib/model/ai-site-inventory.js +1 -1
- package/src/lib/model/ai-site-inventory.js.map +1 -1
- package/src/lib/model/ai-template-subjects.js +2 -2
- package/src/lib/model/ai-template-subjects.js.map +1 -1
- package/src/lib/model/ai-theme-proposal.d.ts +1 -1
- package/src/lib/model/ai-theme-proposal.js +1 -1
- package/src/lib/model/ai-theme-proposal.js.map +1 -1
- package/src/lib/model/ai-workflow-job.d.ts +1 -3
- package/src/lib/model/ai-workflow-job.js +1 -2
- package/src/lib/model/ai-workflow-job.js.map +1 -1
- package/src/lib/plan-entitlements.d.ts +149 -0
- package/src/lib/plan-entitlements.js +168 -0
- package/src/lib/plan-entitlements.js.map +1 -0
- package/src/lib/plugin.js +1 -1
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +25 -4
- package/src/lib/runtime/ai-doctrine-validators.js +179 -23
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +5 -5
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.js +29 -14
- package/src/lib/runtime/ai-eval-live.js.map +1 -1
- package/src/lib/runtime/ai-eval.d.ts +8 -1
- package/src/lib/runtime/ai-eval.js +29 -2
- package/src/lib/runtime/ai-eval.js.map +1 -1
- package/src/lib/runtime/ai-node-tree.js +3 -3
- package/src/lib/runtime/ai-node-tree.js.map +1 -1
- package/src/lib/runtime/ai-palette.d.ts +6 -0
- package/src/lib/runtime/ai-palette.generated.js +21 -255
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/ai-palette.js +7 -1
- package/src/lib/runtime/ai-palette.js.map +1 -1
- package/src/lib/runtime/seo-audit.d.ts +13 -63
- package/src/lib/runtime/seo-audit.js +22 -207
- package/src/lib/runtime/seo-audit.js.map +1 -1
- package/src/lib/runtime/site-inventory.js +1 -1
- package/src/lib/runtime/site-inventory.js.map +1 -1
- package/src/lib/server/ai-admin-org.js +4 -25
- package/src/lib/server/ai-admin-org.js.map +1 -1
- package/src/lib/server/ai-admin-user.js +2 -19
- package/src/lib/server/ai-admin-user.js.map +1 -1
- package/src/lib/server/ai-assist-prompts.js +3 -3
- package/src/lib/server/ai-assist-prompts.js.map +1 -1
- package/src/lib/server/ai-insight-digest-route.js +8 -1
- package/src/lib/server/ai-insight-digest-route.js.map +1 -1
- package/src/lib/server/assist-chat.js +11 -10
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-view-context.js +23 -23
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/server.d.ts +1 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/stand-in-automation-index.d.ts +20 -0
- package/src/lib/testing/stand-in-automation-index.js +102 -0
- package/src/lib/testing/stand-in-automation-index.js.map +1 -0
- package/src/lib/testing/stand-in-list-send-time.d.ts +4 -0
- package/src/lib/testing/stand-in-list-send-time.js +77 -0
- package/src/lib/testing/stand-in-list-send-time.js.map +1 -0
- package/src/lib/testing/stand-in-pipeline-index.d.ts +32 -0
- package/src/lib/testing/stand-in-pipeline-index.js +59 -0
- package/src/lib/testing/stand-in-pipeline-index.js.map +1 -0
- package/src/lib/testing/stand-in-product-index.d.ts +20 -0
- package/src/lib/testing/stand-in-product-index.js +105 -0
- package/src/lib/testing/stand-in-product-index.js.map +1 -0
- package/src/lib/tools/ai-component-tool.js +2 -2
- package/src/lib/tools/ai-component-tool.js.map +1 -1
- package/src/lib/tools/ai-duplicate-resource-tool.js +1 -1
- package/src/lib/tools/ai-duplicate-resource-tool.js.map +1 -1
- package/src/lib/tools/ai-seo-tool.d.ts +6 -18
- package/src/lib/tools/ai-seo-tool.js +7 -38
- package/src/lib/tools/ai-seo-tool.js.map +1 -1
- package/src/lib/tools/ai-workflow-tool.d.ts +1 -1
- package/src/lib/tools/ai-workflow-tool.js +1 -1
- package/src/lib/tools/ai-workflow-tool.js.map +1 -1
- package/src/lib/usage/ai-allotment-alerts.d.ts +3 -0
- package/src/lib/usage/ai-allotment-alerts.js +20 -8
- package/src/lib/usage/ai-allotment-alerts.js.map +1 -1
- package/src/lib/usage/assist-free-taste.js +2 -1
- package/src/lib/usage/assist-free-taste.js.map +1 -1
- package/src/lib/usage/assist-signal-mining.js +1 -1
- package/src/lib/usage/assist-signal-mining.js.map +1 -1
- package/src/lib/usage-axes.d.ts +39 -0
- package/src/lib/usage-axes.js +70 -0
- package/src/lib/usage-axes.js.map +1 -0
- package/src/lib/runtime/seo-page-facts.d.ts +0 -61
- package/src/lib/runtime/seo-page-facts.js +0 -150
- package/src/lib/runtime/seo-page-facts.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-drafts.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 { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { uniqueDuplicateName } from '@aglyn/aglyn/app-utils/duplicate-resource'\nimport {\n FORM_COMPONENT_ID,\n newFormListFields,\n normalizeFormSlug,\n type FormFieldDecl,\n type FormRouting,\n} from '@aglyn/aglyn/app-utils/forms'\nimport { artifactCreateListKeys } from '@aglyn/aglyn/app-utils/artifact-list-keys'\nimport {\n checkDatasetQuota,\n checkEntitlement,\n checkQuota,\n resolveEffectivePlan,\n resolveOrgEntitlements,\n} from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport {\n billableScreenIds,\n normalizeScreenSlug,\n reservedScreenRouteSegment,\n SCREEN_ROOT_PATH,\n} from '@aglyn/aglyn/app-utils/screen-route'\nimport { decodeStoredNodes, encodeStoredNodes } from '@aglyn/aglyn/app-utils/stored-nodes'\nimport type {\n AglynOrgBilling,\n OrgFeatureFlags,\n} from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport type { ReusableComponentProp } from '@aglyn/aglyn/foundation/definitions/platform.types'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport {\n aiUnrestrictedPlanCapabilities,\n type AiPlanCapabilities,\n type AiPlanCreation,\n} from '../model/ai-plan-capabilities'\nimport type { AiJobAdmissionRefusal } from './ai-job-admission'\n\n/**\n * Where a generation job's layout, page template, form, reusable component\n * or page lands (AGL-2909, AGL-2913, AGL-2908, AGL-2907): a new draft, made\n * as the console's host resources route makes one.\n *\n * ── The document the create route makes ─────────────────────────────────\n *\n * `AI_DRAFT_FIELDS` is that route's allow-list for each kind, and a spec\n * reads the route's source to hold the two together. Nothing outside the\n * list is written except the route's own stamps: `createdAt`, `updatedAt`,\n * `createdBy`, the list keys every artifact create carries\n * (`artifactCreateListKeys`: the name keys, a component's or template's stored\n * kind, a template's library row), and a template's `source`, which is\n * `authored` — a template a member's job generated is theirs, never a starter\n * or a listing. A layout's or a screen's first version carries the keys the\n * versions route seeds one with. A form is the document the Forms page's\n * Create sends: its design canvas-shaped under `rootId` with the form node\n * bound to the form's id and captioned with its name, a slug read from that\n * name, and no version — the form's page mints the first one when someone\n * opens it. Where Create sends an empty `fields`, a generated form sends the\n * declaration read off its own design, so the two agree from the start. A\n * component carries its tree and the props it declares on its own document,\n * as Use template writes one, and gets its first version where every\n * component does: when a member opens it.\n *\n * ── Counted like a create ────────────────────────────────────────────────\n *\n * The band is met INSIDE the transaction that writes (AGL-2231), with the\n * route's arithmetic: every layout document counts against\n * `sharedLayoutsPerHost`; a template counts against `templatesPerHost` when it\n * records a source other than a platform starter — the route's `!=` query,\n * which never matches a document with no `source.type` at all; a screen counts\n * against `screensPerHost` by `billableScreenIds`, over the screens and the\n * host's routing map, which counts an unrouted screen too (AGL-1445); and a\n * form needs the plan to include the route's `entitlement` first, refused in\n * the route's own words, after which every form document counts against\n * `formsPerHost`. A reusable component counts against no allowance at all:\n * the route admits one on a plan with the `reusableComponents` entitlement\n * and refuses it otherwise, and so does the writer.\n *\n * ── Applied to nothing ───────────────────────────────────────────────────\n *\n * A layout renders only around a screen or layout that names it, or as the\n * built-in page layout the host document names; a template is inert until a\n * member uses it; a form renders only where a page places it; a component\n * renders only where an instance places it, and a new one has no instance;\n * and a screen serves nothing until the host's routing map names it, which is\n * what publishing writes (`publishScreenRoute`). So the writer touches the new\n * documents and nothing else — no host document, no routing map, no\n * collection, store setting, other layout or component — and never a\n * `publishedAt` or a publish schedule. What the job hands back is a link to\n * the draft, never a binding.\n *\n * ── One draft per recorded id ────────────────────────────────────────────\n *\n * The caller names the document id: the console resource id the job recorded\n * for the draft before its first write (`ai-job-draft-ids.ts`). A step run\n * again after its draft was written — a process cut off before the machine\n * recorded the step — reads the same id, finds that draft and reports it,\n * rather than writing a second.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport type AiDraftKind = 'layout' | 'template' | 'form' | 'component' | 'screen'\n\n/** The draft kinds that keep their tree in versions, as the besigner opens them. */\nexport type AiVersionedDraftKind = Extract<AiDraftKind, 'layout' | 'screen'>\n\n/** The console host resources route's allow-list for each kind. */\nexport const AI_DRAFT_FIELDS: Readonly<Record<AiDraftKind, readonly string[]>> = {\n layout: ['displayName', 'description', 'versionId'],\n template: [\n 'kind',\n 'displayName',\n 'description',\n 'placeholders',\n 'nodes',\n 'rootId',\n 'props',\n 'slug',\n 'seo',\n // A component template's own kind (AGL-3287); a job never sends one.\n 'componentKind',\n ],\n form: [\n 'displayName',\n 'slug',\n 'fields',\n 'consentFieldName',\n 'routing',\n 'legacyMatch',\n 'rootId',\n 'nodes',\n ],\n // `kind` marks a reusable email block (AGL-3287); a job makes page\n // components and never sends one.\n component: ['displayName', 'description', 'rootId', 'nodes', 'props', 'kind'],\n // `kind` is on the route's list for the email composer; a page has none, and\n // the writer never sends one.\n screen: ['displayName', 'description', 'slug', 'seo', 'kind', 'versionId'],\n}\n\n/** The keys a layout's first version is seeded with, all on the versions route's list. */\nexport const AI_DRAFT_VERSION_FIELDS: readonly string[] = ['layoutId', 'hostId', 'displayName', 'nodes']\n\n/**\n * The keys a screen's first version is seeded with, all on the versions\n * route's list: the screen it belongs to, the site, the label, the tree, and\n * the layout it renders inside when the plan names one the site has.\n */\nexport const AI_DRAFT_SCREEN_VERSION_FIELDS: readonly string[] = [\n 'screenId',\n 'hostId',\n 'displayName',\n 'nodes',\n 'layoutId',\n]\n\n/** The label on a generated draft's first version, as on one Use template makes. */\nexport const AI_DRAFT_VERSION_NAME = 'Initial version'\n\n/** The route's refusal when the site's plan does not include the kind at all. */\nexport const AI_DRAFT_ENTITLEMENT_REFUSAL = 'This feature is not included in your plan — see Billing'\n\nexport interface AiDraftBand {\n collection: 'layouts' | 'templates' | 'forms' | 'components' | 'screens'\n /** The allowance the route counts the kind against, where it counts one. */\n quotaKey?:\n | 'sharedLayoutsPerHost'\n | 'templatesPerHost'\n | 'formsPerHost'\n | 'screensPerHost'\n /** The feature the plan must include before any document of the kind counts. */\n entitlement?: keyof OrgFeatureFlags\n /** The route's plural label, in the refusal it gives. */\n label: string\n}\n\n/** Each kind's collection, counter, feature and label, as the create route declares them. */\nexport const AI_DRAFT_BANDS: Readonly<Record<AiDraftKind, AiDraftBand>> = {\n layout: { collection: 'layouts', quotaKey: 'sharedLayoutsPerHost', label: 'shared layouts' },\n template: { collection: 'templates', quotaKey: 'templatesPerHost', label: 'templates' },\n form: {\n collection: 'forms',\n quotaKey: 'formsPerHost',\n entitlement: 'reusableComponents',\n label: 'forms',\n },\n component: {\n collection: 'components',\n entitlement: 'reusableComponents',\n label: 'reusable components',\n },\n screen: { collection: 'screens', quotaKey: 'screensPerHost', label: 'screens' },\n}\n\ninterface SiblingRow {\n id: string\n name: string\n slug: string\n kind: unknown\n sourceType: unknown\n deletedAt: unknown\n deleted: boolean\n}\n\n/** A host's routing map: screen id → the path it is published at. */\ntype RoutingMap = Record<string, unknown> | null | undefined\n\nfunction draftCollection(firestore: Firestore, hostId: string, kind: AiDraftKind) {\n return firestore.collection('hosts').doc(hostId).collection(AI_DRAFT_BANDS[kind].collection)\n}\n\n/** The fields the band, the name, the address and a deletion are read from. */\nfunction siblingsOf(collection: FirebaseFirestore.CollectionReference) {\n return collection.select('displayName', 'slug', 'kind', 'source.type', 'deletedAt')\n}\n\nfunction siblingRows(snapshot: FirebaseFirestore.QuerySnapshot): SiblingRow[] {\n return snapshot.docs.map((doc) => ({\n id: doc.id,\n name: String(doc.get('displayName') ?? ''),\n slug: String(doc.get('slug') ?? ''),\n kind: doc.get('kind'),\n sourceType: doc.get('source.type'),\n deletedAt: doc.get('deletedAt'),\n deleted: doc.get('deletedAt') != null,\n }))\n}\n\nfunction subdomainOf(host: FirebaseFirestore.DocumentSnapshot): string | null {\n const subdomain = host.get('subdomain')\n return typeof subdomain === 'string' && subdomain ? subdomain : null\n}\n\n/**\n * The route's refusal when the site's plan does not include the kind, or\n * holds no more of it; `null` while it does. The feature is asked first, as\n * the route asks it before it counts.\n */\nexport function aiDraftBandRefusal(\n kind: AiDraftKind,\n rows: ReadonlyArray<Pick<SiblingRow, 'id' | 'kind' | 'sourceType' | 'deletedAt'>>,\n org: Partial<AglynOrgBilling> | null,\n routingMap?: RoutingMap,\n): string | null {\n const band = AI_DRAFT_BANDS[kind]\n if (band.entitlement && !checkEntitlement(org, band.entitlement)) {\n return AI_DRAFT_ENTITLEMENT_REFUSAL\n }\n if (!band.quotaKey) return null\n const used =\n kind === 'template'\n ? rows.filter((row) => row.sourceType !== undefined && row.sourceType !== 'starter').length\n : kind === 'screen'\n ? billableScreenIds(\n rows.map((row) => ({ id: row.id, kind: row.kind, deletedAt: row.deletedAt })),\n (routingMap ?? undefined) as never,\n ).size\n : rows.length\n const quota = checkQuota(org, band.quotaKey, used)\n return quota.allowed\n ? null\n : `Your plan includes ${quota.limit} ${band.label} — upgrade in Billing for more`\n}\n\n/** The draft kinds a plan's creations land as, whose band a plan is told about (AGL-3030). */\nconst AI_PLAN_DRAFT_KINDS = ['component', 'form', 'layout', 'template'] as const\n\ntype AiPlanDraftKind = (typeof AI_PLAN_DRAFT_KINDS)[number]\n\n/** Every draft kind's plural label made singular, for a band of one. */\nfunction bandLabel(label: string, count: number): string {\n return count === 1 ? label.replace(/s$/, '') : label\n}\n\n/**\n * Whether the site may take `used + 1` of a kind, in the create route's\n * arithmetic: the feature first, then the allowance. `null` for a kind whose\n * allowance is unlimited or counts nothing, which needs no rows read.\n */\nfunction planCreation(\n kind: AiPlanDraftKind,\n org: Partial<AglynOrgBilling> | null,\n rows: ReadonlyArray<Pick<SiblingRow, 'id' | 'kind' | 'sourceType' | 'deletedAt'>> | null,\n): AiPlanCreation {\n const band = AI_DRAFT_BANDS[kind]\n if (band.entitlement && !checkEntitlement(org, band.entitlement)) {\n return {\n allowed: false,\n left: 0,\n reason: `this workspace's plan does not include ${kind === 'form' ? 'saved forms' : band.label}`,\n }\n }\n const limit = band.quotaKey ? resolveOrgEntitlements(org)[band.quotaKey] : Number.POSITIVE_INFINITY\n if (!rows || !Number.isFinite(limit)) return { allowed: true, left: null, reason: null }\n // The route's own arithmetic decides; the count beside it is what is left.\n const refusal = aiDraftBandRefusal(kind, rows, org)\n const used =\n kind === 'template'\n ? rows.filter((row) => row.sourceType !== undefined && row.sourceType !== 'starter').length\n : rows.length\n return refusal\n ? {\n allowed: false,\n left: 0,\n reason: `this site already holds the ${limit} ${bandLabel(band.label, limit)} its plan includes`,\n }\n : { allowed: true, left: Math.max(0, limit - used), reason: null }\n}\n\n/** Whether a kind's rows are counted at all on this plan: included, and under a finite allowance. */\nfunction countsRows(kind: AiPlanDraftKind, org: Partial<AglynOrgBilling> | null): boolean {\n const band = AI_DRAFT_BANDS[kind]\n if (!band.quotaKey) return false\n if (band.entitlement && !checkEntitlement(org, band.entitlement)) return false\n return Number.isFinite(resolveOrgEntitlements(org)[band.quotaKey])\n}\n\n/**\n * What a plan may create on this site (AGL-3030), from the workspace's plan\n * and the rows each counted kind already holds — pure, so the specs and the\n * eval harness hold the arithmetic without a Firestore. A kind whose rows\n * are not given is counted as having room, which is what an unlimited\n * allowance means.\n *\n * A theme change is a proposal a member applies, never a draft, so it counts\n * against nothing. A dataset is org data that no job writes; a plan may name\n * one only where the workspace's plan includes datasets at all. An email\n * design is written by the email plugin, whose own gate decides.\n *\n * A workspace on the Free plan spends the Free taste, a wall of monthly\n * credits, so its plan is held to the sections that wall pays for (AGL-3070).\n * The effective plan decides, as it decides the taste's own allowance: a\n * workspace whose subscription died is Free again.\n */\nexport function aiPlanCapabilitiesFrom(\n org: Partial<AglynOrgBilling> | null,\n rows: Partial<Record<AiPlanDraftKind, ReadonlyArray<Pick<SiblingRow, 'id' | 'kind' | 'sourceType' | 'deletedAt'>>>> = {},\n): AiPlanCapabilities {\n const unrestricted = aiUnrestrictedPlanCapabilities()\n const datasets = checkDatasetQuota(org, 0).limit > 0\n return {\n reusableComponents: checkEntitlement(org, 'reusableComponents'),\n create: {\n ...unrestricted.create,\n component: planCreation('component', org, rows.component ?? null),\n form: planCreation('form', org, rows.form ?? null),\n layout: planCreation('layout', org, rows.layout ?? null),\n template: planCreation('template', org, rows.template ?? null),\n dataset: datasets\n ? unrestricted.create.dataset\n : { allowed: false, left: 0, reason: \"this workspace's plan does not include datasets\" },\n },\n ...(resolveEffectivePlan(org) === 'free' ? { freeTaste: true } : {}),\n }\n}\n\n/**\n * What a plan may create on this site, read: the rows of each kind whose\n * allowance the workspace's plan counts, as the draft writer reads them, and\n * nothing for a kind the plan leaves out or leaves unlimited.\n */\nexport async function readAiPlanCapabilities(\n firestore: Firestore,\n input: { hostId: string; org: Partial<AglynOrgBilling> | null },\n): Promise<AiPlanCapabilities> {\n const counted = AI_PLAN_DRAFT_KINDS.filter((kind) => countsRows(kind, input.org))\n const snapshots = await Promise.all(\n counted.map((kind) => siblingsOf(draftCollection(firestore, input.hostId, kind)).get()),\n )\n return aiPlanCapabilitiesFrom(\n input.org,\n Object.fromEntries(counted.map((kind, index) => [kind, siblingRows(snapshots[index])])),\n )\n}\n\n/**\n * Whether the site can take one more draft of the kind: a read, for a door\n * that asks before it spends. The write asks again inside its transaction.\n */\nexport async function aiDraftAllowanceRefusal(\n firestore: Firestore,\n input: { kind: AiDraftKind; hostId: string; org: Partial<AglynOrgBilling> | null },\n): Promise<string | null> {\n const [snapshot, host] = await Promise.all([\n siblingsOf(draftCollection(firestore, input.hostId, input.kind)).get(),\n input.kind === 'screen'\n ? firestore.collection('hosts').doc(input.hostId).get()\n : Promise.resolve(null),\n ])\n return aiDraftBandRefusal(\n input.kind,\n siblingRows(snapshot),\n input.org,\n host?.get('screens') as RoutingMap,\n )\n}\n\n/**\n * A draft screen's one-segment address: the one asked for, else one from its\n * name, never a reserved segment, and never an address a live screen keeps\n * or the routing map already serves — so the member's publish at it later\n * takes nothing from a live page. The root is asked for only while no screen\n * holds it.\n */\nexport function aiDraftScreenSlug(\n requested: string | null | undefined,\n name: string,\n rows: ReadonlyArray<Pick<SiblingRow, 'slug' | 'deleted'>>,\n routingMap: RoutingMap,\n): string {\n const taken = new Set<string>([\n ...rows.filter((row) => !row.deleted && row.slug).map((row) => row.slug),\n ...Object.values(routingMap ?? {}).filter((path): path is string => typeof path === 'string'),\n ])\n const usable = (value: string | undefined) =>\n value && !reservedScreenRouteSegment(value) && !(value === SCREEN_ROOT_PATH && taken.has(value))\n ? value\n : undefined\n const base = usable(normalizeScreenSlug(requested ?? undefined)) ?? usable(normalizeScreenSlug(name)) ?? 'page'\n if (base === SCREEN_ROOT_PATH) return base\n let slug = base\n let attempt = 2\n while (taken.has(slug)) slug = `${base}-${attempt++}`\n return slug\n}\n\nexport interface AiDraftRecord {\n id: string\n /** A layout's or a screen's first version; `null` for every other kind, which the writer gives none. */\n versionId: string | null\n name: string\n hostSubdomain: string | null\n /** A screen's search listing as the draft holds it now; absent on other kinds. */\n seo?: { title?: string; description?: string } | null\n /** Whether a member deleted the draft since it was written. */\n deleted?: boolean\n}\n\nfunction recordOf(\n id: string,\n draft: FirebaseFirestore.DocumentSnapshot,\n host: FirebaseFirestore.DocumentSnapshot,\n): AiDraftRecord {\n const versionId = draft.get('versionId')\n const seo = draft.get('seo')\n return {\n id,\n versionId: typeof versionId === 'string' && versionId ? versionId : null,\n name: String(draft.get('displayName') ?? ''),\n hostSubdomain: subdomainOf(host),\n ...(seo && typeof seo === 'object' ? { seo: seo as AiDraftRecord['seo'] } : {}),\n ...(draft.get('deletedAt') != null ? { deleted: true } : {}),\n }\n}\n\n/** The draft a job already wrote under `id`, when a run of its step got that far. */\nexport async function readAiDraft(\n firestore: Firestore,\n input: { kind: AiDraftKind; hostId: string; id: string },\n): Promise<AiDraftRecord | null> {\n const draft = await draftCollection(firestore, input.hostId, input.kind).doc(input.id).get()\n if (!draft.exists) return null\n return recordOf(input.id, draft, await firestore.collection('hosts').doc(input.hostId).get())\n}\n\n/**\n * The tree a template draft holds now, decoded; `null` when there is none.\n *\n * A template is not a versioned draft — its nodes live on the document\n * itself, which is why `readAiDraftNodes` refuses its kind.\n */\nexport async function readAiTemplateDraftNodes(\n firestore: Firestore,\n input: { hostId: string; id: string },\n): Promise<NodesMap | null> {\n const draft = await draftCollection(firestore, input.hostId, 'template').doc(input.id).get()\n if (!draft.exists) return null\n return decodeStoredNodes<NodesMap>(draft.get('nodes')) ?? null\n}\n\n/**\n * The property names a component draft declares now; `null` when there is no\n * such draft. Read from the draft document rather than its version, because a\n * component's props live beside its tree and a copy carries its source's.\n */\nexport async function readAiDraftComponentProps(\n firestore: Firestore,\n input: { hostId: string; id: string },\n): Promise<string[] | null> {\n const draft = await draftCollection(firestore, input.hostId, 'component').doc(input.id).get()\n if (!draft.exists) return null\n const props = draft.get('props')\n if (!Array.isArray(props)) return []\n return props\n .map((prop) => (prop && typeof prop === 'object' ? (prop as Record<string, unknown>)['name'] : null))\n .filter((name): name is string => typeof name === 'string' && name.length > 0)\n}\n\n/** The tree a versioned draft's first version holds now, decoded; `null` when there is none. */\nexport async function readAiDraftNodes(\n firestore: Firestore,\n input: { kind: AiVersionedDraftKind; hostId: string; id: string },\n): Promise<{ versionId: string; nodes: NodesMap } | null> {\n const draftRef = draftCollection(firestore, input.hostId, input.kind).doc(input.id)\n const draft = await draftRef.get()\n const versionId = draft.exists ? draft.get('versionId') : null\n if (typeof versionId !== 'string' || !versionId) return null\n const version = await draftRef.collection('versions').doc(versionId).get()\n const nodes = version.exists ? decodeStoredNodes<NodesMap>(version.get('nodes')) : null\n return nodes ? { versionId, nodes } : null\n}\n\nexport interface AiDraftInput {\n kind: AiDraftKind\n hostId: string\n /** The draft's document id: the id the job recorded for this draft. */\n id: string\n /** Whose work the draft is: the job's creator, whose brief it built. */\n uid: string\n org: Partial<AglynOrgBilling> | null\n /** The name asked for; a live sibling already called that gets a number. */\n name: string\n nodes: NodesMap\n /** A page template's suggested address, or a screen's one-segment address. */\n slug?: string | null\n /** A form's declaration and the ids its design hangs from; required for a form. */\n form?: AiFormDraftDeclaration\n /** A component's root, which the node map holds; required for a component. */\n rootId?: string\n /** The properties a component declares, which its tree binds. */\n props?: readonly ReusableComponentProp[]\n /** A screen's layout, for its first version to render inside; a layout the site has. */\n layoutId?: string | null\n now: Date\n}\n\n/** What a form draft declares beside its design, read off that design. */\nexport interface AiFormDraftDeclaration {\n /** The canvas root `nodes` is stored under, as the Forms page's Create stores it. */\n rootId: string\n /** The form node, which carries the form's id and is captioned with the draft's name. */\n formNodeId: string\n fields: FormFieldDecl[]\n consentFieldName: string | null\n routing: FormRouting | null\n}\n\nexport type AiDraftWrite =\n | (AiDraftRecord & { ok: true; replayed: boolean })\n | { ok: false; status: 403 | 404; error: string }\n\n/** Keep only the keys the create route admits for the kind. */\nfunction allowListed(kind: AiDraftKind, data: Record<string, unknown>): Record<string, unknown> {\n const allowed = new Set(AI_DRAFT_FIELDS[kind])\n return Object.fromEntries(\n Object.entries(data).filter(([key, value]) => allowed.has(key) && value !== undefined),\n )\n}\n\n/** The node map with its form node captioned with `name`, as Create captions a new form. */\nfunction withFormCaption(nodes: NodesMap, formNodeId: string, name: string): NodesMap {\n const node = nodes[formNodeId]\n if (!node || node.componentId !== FORM_COMPONENT_ID) return nodes\n return { ...nodes, [formNodeId]: { ...node, props: { ...(node.props ?? {}), formName: name } } }\n}\n\n/**\n * Write one draft: the band met, the name made unique among the live\n * siblings, the node map stored as msgpack, all in one transaction. A\n * refusal is returned, never thrown; a Firestore failure propagates, so the\n * machine's retry applies.\n */\nexport async function writeAiDraft(firestore: Firestore, input: AiDraftInput): Promise<AiDraftWrite> {\n const packed = encodeStoredNodes(input.nodes)\n if (!packed) throw new Error('an AI draft needs a node map')\n if (input.kind === 'form' && !input.form) throw new Error('an AI form draft needs its declaration')\n if (input.kind === 'component' && !(input.rootId && input.nodes[input.rootId])) {\n throw new Error('a component draft needs a root its node map holds')\n }\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n const collection = draftCollection(firestore, input.hostId, input.kind)\n const draftRef = collection.doc(input.id)\n return firestore.runTransaction(async (tx): Promise<AiDraftWrite> => {\n // Every read before any write, which Firestore requires.\n const [host, existing, siblings] = await Promise.all([\n tx.get(hostRef),\n tx.get(draftRef),\n tx.get(siblingsOf(collection)),\n ])\n if (!host.exists) return { ok: false, status: 404, error: 'Unknown site' }\n if (existing.exists) {\n return { ok: true, replayed: true, ...recordOf(input.id, existing, host) }\n }\n const rows = siblingRows(siblings)\n // Read, never written: the routing map decides which screens count.\n const routingMap = host.get('screens') as RoutingMap\n const refusal = aiDraftBandRefusal(input.kind, rows, input.org, routingMap)\n if (refusal) return { ok: false, status: 403, error: refusal }\n const name = uniqueDuplicateName(\n input.name,\n rows.filter((row) => !row.deleted).map((row) => row.name),\n )\n const stamps = { createdAt: input.now, updatedAt: input.now, createdBy: input.uid }\n const nodes = Buffer.from(packed)\n let versionId: string | null = null\n if (input.kind === 'layout') {\n versionId = createResourceUid()\n tx.create(draftRef, {\n ...allowListed('layout', { displayName: name, versionId }),\n ...artifactCreateListKeys('layouts', { displayName: name }),\n ...stamps,\n })\n tx.create(draftRef.collection('versions').doc(versionId), {\n layoutId: input.id,\n hostId: input.hostId,\n displayName: AI_DRAFT_VERSION_NAME,\n nodes,\n ...stamps,\n })\n } else if (input.kind === 'screen') {\n versionId = createResourceUid()\n const slug = aiDraftScreenSlug(input.slug, name, rows, routingMap)\n tx.create(draftRef, {\n ...allowListed('screen', { displayName: name, slug, versionId }),\n ...artifactCreateListKeys('screens', { displayName: name }),\n ...stamps,\n })\n tx.create(draftRef.collection('versions').doc(versionId), {\n screenId: input.id,\n hostId: input.hostId,\n displayName: AI_DRAFT_VERSION_NAME,\n nodes,\n ...(input.layoutId ? { layoutId: input.layoutId } : {}),\n ...stamps,\n })\n } else if (input.kind === 'template') {\n tx.create(draftRef, {\n ...allowListed('template', {\n kind: 'page',\n displayName: name,\n nodes,\n slug: input.slug || undefined,\n }),\n source: { type: 'authored' },\n ...artifactCreateListKeys('templates', {\n kind: 'page',\n displayName: name,\n source: { type: 'authored' },\n }),\n ...stamps,\n })\n } else if (input.kind === 'component') {\n tx.create(draftRef, {\n ...allowListed('component', {\n displayName: name,\n rootId: input.rootId,\n nodes,\n props: input.props?.length ? [...input.props] : undefined,\n }),\n ...artifactCreateListKeys('components', { displayName: name }),\n ...stamps,\n })\n } else {\n const form = input.form as AiFormDraftDeclaration\n const captioned = encodeStoredNodes(withFormCaption(input.nodes, form.formNodeId, name)) ?? packed\n const slug = normalizeFormSlug(name) || input.id\n tx.create(draftRef, {\n ...allowListed('form', {\n displayName: name,\n slug,\n fields: form.fields,\n consentFieldName: form.consentFieldName || undefined,\n routing: form.routing ?? undefined,\n rootId: form.rootId,\n nodes: Buffer.from(captioned),\n }),\n // What the Forms list queries (AGL-3330), as a console create writes it.\n ...newFormListFields({ id: input.id, displayName: name, slug, routing: form.routing }),\n ...stamps,\n })\n }\n return {\n ok: true,\n replayed: false,\n id: input.id,\n versionId,\n name,\n hostSubdomain: subdomainOf(host),\n }\n })\n}\n\nexport type AiDraftNodesUpdate =\n | { ok: true; changed: boolean }\n | { ok: false; status: 404; error: string }\n\n/**\n * Change the tree of a versioned draft the job wrote, in one transaction:\n * read the first version as it is stored NOW — a member may have opened and\n * saved it — hand it to `update`, and store what comes back with a fresh\n * `updatedAt`, which is what the besigner's save guard compares. `update`\n * answering `null` changes nothing: a pass run again after its write.\n */\nexport async function updateAiDraftNodes(\n firestore: Firestore,\n input: {\n kind: AiVersionedDraftKind\n hostId: string\n id: string\n now: Date\n update: (nodes: NodesMap) => NodesMap | null\n },\n): Promise<AiDraftNodesUpdate> {\n const draftRef = draftCollection(firestore, input.hostId, input.kind).doc(input.id)\n return firestore.runTransaction(async (tx): Promise<AiDraftNodesUpdate> => {\n const draft = await tx.get(draftRef)\n const versionId = draft.exists && draft.get('deletedAt') == null ? draft.get('versionId') : null\n if (typeof versionId !== 'string' || !versionId) {\n return { ok: false, status: 404, error: 'The draft is no longer on the site' }\n }\n const versionRef = draftRef.collection('versions').doc(versionId)\n const version = await tx.get(versionRef)\n const nodes = version.exists ? decodeStoredNodes<NodesMap>(version.get('nodes')) : null\n if (!nodes) return { ok: false, status: 404, error: 'The draft is no longer on the site' }\n const next = input.update(nodes)\n if (!next) return { ok: true, changed: false }\n const packed = encodeStoredNodes(next)\n if (!packed) throw new Error('an AI draft needs a node map')\n tx.update(versionRef, { nodes: Buffer.from(packed), updatedAt: input.now })\n return { ok: true, changed: true }\n })\n}\n\n/**\n * Put a search title and description on a draft screen the job wrote, each\n * only where the draft has none: a value a member typed since is theirs.\n */\nexport async function writeAiDraftScreenSeo(\n firestore: Firestore,\n input: {\n hostId: string\n id: string\n seo: { title?: string | null; description?: string | null }\n now: Date\n },\n): Promise<void> {\n const ref = draftCollection(firestore, input.hostId, 'screen').doc(input.id)\n await firestore.runTransaction(async (tx) => {\n const screen = await tx.get(ref)\n if (!screen.exists || screen.get('deletedAt') != null) return\n const patch: Record<string, unknown> = {}\n for (const key of ['title', 'description'] as const) {\n const value = String(input.seo[key] ?? '').trim()\n if (value && !String(screen.get(`seo.${key}`) ?? '').trim()) patch[`seo.${key}`] = value\n }\n if (Object.keys(patch).length) tx.update(ref, { ...patch, updatedAt: input.now })\n })\n}\n\n/** The site's subdomain, which a console link names the site by. */\nexport async function aiSiteSubdomain(firestore: Firestore, hostId: string): Promise<string | null> {\n return subdomainOf(await firestore.collection('hosts').doc(hostId).get())\n}\n\n/**\n * What a door says before a job that writes a draft of this kind exists: a\n * site is named, and it is the job's own org's; whatever the kind checks of\n * its own; then the allowance. `null` admits the job.\n */\nexport async function aiDraftAdmissionRefusal(\n firestore: Firestore,\n input: {\n orgId: string\n hostId: string | null\n kind: AiDraftKind\n /** What the refusal calls the job's output; the kind when absent. */\n noun?: string\n org: object | null\n /** The kind's own check, asked once the site is known to be the org's. */\n ownCheck?: (hostId: string) => Promise<AiJobAdmissionRefusal | null>\n },\n): Promise<AiJobAdmissionRefusal | null> {\n if (!input.hostId) {\n return {\n status: 400,\n error: `Open the site the ${input.noun ?? input.kind} is for before starting the job`,\n }\n }\n const owner = await resolveOrgIdForHost(input.hostId)\n if (!owner || owner !== input.orgId) return { status: 404, error: 'Unknown site' }\n const own = await input.ownCheck?.(input.hostId)\n if (own) return own\n const refusal = await aiDraftAllowanceRefusal(firestore, {\n kind: input.kind,\n hostId: input.hostId,\n org: input.org as Partial<AglynOrgBilling> | null,\n })\n return refusal ? { status: 403, error: refusal } : null\n}\n"],"names":["createResourceUid","uniqueDuplicateName","FORM_COMPONENT_ID","newFormListFields","normalizeFormSlug","artifactCreateListKeys","checkDatasetQuota","checkEntitlement","checkQuota","resolveEffectivePlan","resolveOrgEntitlements","billableScreenIds","normalizeScreenSlug","reservedScreenRouteSegment","SCREEN_ROOT_PATH","decodeStoredNodes","encodeStoredNodes","resolveOrgIdForHost","aiUnrestrictedPlanCapabilities","AI_DRAFT_FIELDS","layout","template","form","component","screen","AI_DRAFT_VERSION_FIELDS","AI_DRAFT_SCREEN_VERSION_FIELDS","AI_DRAFT_VERSION_NAME","AI_DRAFT_ENTITLEMENT_REFUSAL","AI_DRAFT_BANDS","collection","quotaKey","label","entitlement","draftCollection","firestore","hostId","kind","doc","siblingsOf","select","siblingRows","snapshot","docs","map","id","name","String","get","slug","sourceType","deletedAt","deleted","subdomainOf","host","subdomain","aiDraftBandRefusal","rows","org","routingMap","band","used","filter","row","undefined","length","size","quota","allowed","limit","AI_PLAN_DRAFT_KINDS","bandLabel","count","replace","planCreation","left","reason","Number","POSITIVE_INFINITY","isFinite","refusal","Math","max","countsRows","aiPlanCapabilitiesFrom","unrestricted","datasets","reusableComponents","create","dataset","freeTaste","readAiPlanCapabilities","input","counted","snapshots","Promise","all","Object","fromEntries","index","aiDraftAllowanceRefusal","resolve","aiDraftScreenSlug","requested","usable","taken","Set","values","path","value","has","base","attempt","recordOf","draft","versionId","seo","hostSubdomain","readAiDraft","exists","readAiTemplateDraftNodes","readAiDraftComponentProps","props","Array","isArray","prop","readAiDraftNodes","draftRef","version","nodes","allowListed","data","entries","key","withFormCaption","formNodeId","node","componentId","formName","writeAiDraft","packed","Error","rootId","hostRef","runTransaction","tx","existing","siblings","ok","status","error","replayed","stamps","createdAt","now","updatedAt","createdBy","uid","Buffer","from","displayName","layoutId","screenId","source","type","captioned","fields","consentFieldName","routing","updateAiDraftNodes","versionRef","next","update","changed","writeAiDraftScreenSeo","ref","patch","trim","keys","aiSiteSubdomain","aiDraftAdmissionRefusal","noun","owner","orgId","own","ownCheck"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-drafts.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 { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { uniqueDuplicateName } from '@aglyn/aglyn/app-utils/duplicate-resource'\nimport {\n FORM_COMPONENT_ID,\n newFormListFields,\n normalizeFormSlug,\n type FormFieldDecl,\n type FormRouting,\n} from '@aglyn/aglyn/app-utils/forms'\nimport { artifactCreateListKeys } from '@aglyn/aglyn/app-utils/artifact-list-keys'\nimport {\n checkDatasetQuota,\n checkEntitlement,\n checkQuota,\n resolveEffectivePlan,\n resolveOrgEntitlements,\n} from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport {\n billableScreenIds,\n normalizeScreenSlug,\n reservedScreenRouteSegment,\n SCREEN_ROOT_PATH,\n} from '@aglyn/aglyn/app-utils/screen-route'\nimport { decodeStoredNodes, encodeStoredNodes } from '@aglyn/aglyn/app-utils/stored-nodes'\nimport type {\n AglynOrgBilling,\n OrgFeatureFlags,\n} from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport type { ReusableComponentProp } from '@aglyn/aglyn/foundation/definitions/platform.types'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport {\n aiUnrestrictedPlanCapabilities,\n type AiPlanCapabilities,\n type AiPlanCreation,\n} from '../model/ai-plan-capabilities'\nimport type { AiJobAdmissionRefusal } from './ai-job-admission'\n\n/**\n * Where a generation job's layout, page template, form, reusable component\n * or page lands (AGL-2909, AGL-2913, AGL-2908, AGL-2907): a new draft, made\n * as the console's host resources route makes one.\n *\n * ── The document the create route makes ─────────────────────────────────\n *\n * `AI_DRAFT_FIELDS` is that route's allow-list for each kind, and a spec\n * reads the route's source to hold the two together. Nothing outside the\n * list is written except the route's own stamps: `createdAt`, `updatedAt`,\n * `createdBy`, the list keys every artifact create carries\n * (`artifactCreateListKeys`: the name keys, a component's or template's stored\n * kind, a template's library row), and a template's `source`, which is\n * `authored` — a template a member's job generated is theirs, never a starter\n * or a listing. A layout's or a screen's first version carries the keys the\n * versions route seeds one with. A form is the document the Forms page's\n * Create sends: its design canvas-shaped under `rootId` with the form node\n * bound to the form's id and captioned with its name, a slug read from that\n * name, and no version — the form's page mints the first one when someone\n * opens it. Where Create sends an empty `fields`, a generated form sends the\n * declaration read off its own design, so the two agree from the start. A\n * component carries its tree and the props it declares on its own document,\n * as Use template writes one, and gets its first version where every\n * component does: when a member opens it.\n *\n * ── Counted like a create ────────────────────────────────────────────────\n *\n * The band is met INSIDE the transaction that writes (AGL-2231), with the\n * route's arithmetic: every layout document counts against\n * `sharedLayoutsPerHost`; a template counts against `templatesPerHost` when it\n * records a source other than a platform starter — the route's `!=` query,\n * which never matches a document with no `source.type` at all; a screen counts\n * against `screensPerHost` by `billableScreenIds`, over the screens and the\n * host's routing map, which counts an unrouted screen too (AGL-1445); and a\n * form needs the plan to include the route's `entitlement` first, refused in\n * the route's own words, after which every form document counts against\n * `formsPerHost`. A reusable component counts against no allowance at all:\n * the route admits one on a plan with the `reusableComponents` entitlement\n * and refuses it otherwise, and so does the writer.\n *\n * ── Applied to nothing ───────────────────────────────────────────────────\n *\n * A layout renders only around a screen or layout that names it, or as the\n * built-in page layout the host document names; a template is inert until a\n * member uses it; a form renders only where a page places it; a component\n * renders only where an instance places it, and a new one has no instance;\n * and a screen serves nothing until the host's routing map names it, which is\n * what publishing writes (`publishScreenRoute`). So the writer touches the new\n * documents and nothing else — no host document, no routing map, no\n * collection, store setting, other layout or component — and never a\n * `publishedAt` or a publish schedule. What the job hands back is a link to\n * the draft, never a binding.\n *\n * ── One draft per recorded id ────────────────────────────────────────────\n *\n * The caller names the document id: the console resource id the job recorded\n * for the draft before its first write (`ai-job-draft-ids.ts`). A step run\n * again after its draft was written — a process cut off before the machine\n * recorded the step — reads the same id, finds that draft and reports it,\n * rather than writing a second.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport type AiDraftKind = 'layout' | 'template' | 'form' | 'component' | 'screen'\n\n/** The draft kinds that keep their tree in versions, as the besigner opens them. */\nexport type AiVersionedDraftKind = Extract<AiDraftKind, 'layout' | 'screen'>\n\n/** The console host resources route's allow-list for each kind. */\nexport const AI_DRAFT_FIELDS: Readonly<Record<AiDraftKind, readonly string[]>> = {\n layout: ['displayName', 'description', 'versionId'],\n template: [\n 'kind',\n 'displayName',\n 'description',\n 'placeholders',\n 'nodes',\n 'rootId',\n 'props',\n 'slug',\n 'seo',\n // A component template's own kind (AGL-3287); a job never sends one.\n 'componentKind',\n ],\n form: [\n 'displayName',\n 'slug',\n 'fields',\n 'consentFieldName',\n 'routing',\n 'legacyMatch',\n 'rootId',\n 'nodes',\n ],\n // `kind` marks a reusable email block (AGL-3287); a job makes page\n // components and never sends one.\n component: ['displayName', 'description', 'rootId', 'nodes', 'props', 'kind'],\n // `kind` is on the route's list for the email composer; a page has none, and\n // the writer never sends one.\n screen: ['displayName', 'description', 'slug', 'seo', 'kind', 'versionId'],\n}\n\n/** The keys a layout's first version is seeded with, all on the versions route's list. */\nexport const AI_DRAFT_VERSION_FIELDS: readonly string[] = ['layoutId', 'hostId', 'displayName', 'nodes']\n\n/**\n * The keys a screen's first version is seeded with, all on the versions\n * route's list: the screen it belongs to, the site, the label, the tree, and\n * the layout it renders inside when the plan names one the site has.\n */\nexport const AI_DRAFT_SCREEN_VERSION_FIELDS: readonly string[] = [\n 'screenId',\n 'hostId',\n 'displayName',\n 'nodes',\n 'layoutId',\n]\n\n/** The label on a generated draft's first version, as on one Use template makes. */\nexport const AI_DRAFT_VERSION_NAME = 'Initial version'\n\n/** The route's refusal when the site's plan does not include the kind at all. */\nexport const AI_DRAFT_ENTITLEMENT_REFUSAL = 'This feature is not included in your plan — see Billing'\n\nexport interface AiDraftBand {\n collection: 'layouts' | 'templates' | 'forms' | 'components' | 'screens'\n /** The allowance the route counts the kind against, where it counts one. */\n quotaKey?:\n | 'sharedLayoutsPerHost'\n | 'templatesPerHost'\n | 'formsPerHost'\n | 'screensPerHost'\n /** The feature the plan must include before any document of the kind counts. */\n entitlement?: keyof OrgFeatureFlags\n /** The route's plural label, in the refusal it gives. */\n label: string\n}\n\n/** Each kind's collection, counter, feature and label, as the create route declares them. */\nexport const AI_DRAFT_BANDS: Readonly<Record<AiDraftKind, AiDraftBand>> = {\n layout: { collection: 'layouts', quotaKey: 'sharedLayoutsPerHost', label: 'shared layouts' },\n template: { collection: 'templates', quotaKey: 'templatesPerHost', label: 'templates' },\n form: {\n collection: 'forms',\n quotaKey: 'formsPerHost',\n entitlement: 'reusableComponents',\n label: 'forms',\n },\n component: {\n collection: 'components',\n entitlement: 'reusableComponents',\n label: 'reusable components',\n },\n screen: { collection: 'screens', quotaKey: 'screensPerHost', label: 'pages' },\n}\n\ninterface SiblingRow {\n id: string\n name: string\n slug: string\n kind: unknown\n sourceType: unknown\n deletedAt: unknown\n deleted: boolean\n}\n\n/** A host's routing map: screen id → the path it is published at. */\ntype RoutingMap = Record<string, unknown> | null | undefined\n\nfunction draftCollection(firestore: Firestore, hostId: string, kind: AiDraftKind) {\n return firestore.collection('hosts').doc(hostId).collection(AI_DRAFT_BANDS[kind].collection)\n}\n\n/** The fields the band, the name, the address and a deletion are read from. */\nfunction siblingsOf(collection: FirebaseFirestore.CollectionReference) {\n return collection.select('displayName', 'slug', 'kind', 'source.type', 'deletedAt')\n}\n\nfunction siblingRows(snapshot: FirebaseFirestore.QuerySnapshot): SiblingRow[] {\n return snapshot.docs.map((doc) => ({\n id: doc.id,\n name: String(doc.get('displayName') ?? ''),\n slug: String(doc.get('slug') ?? ''),\n kind: doc.get('kind'),\n sourceType: doc.get('source.type'),\n deletedAt: doc.get('deletedAt'),\n deleted: doc.get('deletedAt') != null,\n }))\n}\n\nfunction subdomainOf(host: FirebaseFirestore.DocumentSnapshot): string | null {\n const subdomain = host.get('subdomain')\n return typeof subdomain === 'string' && subdomain ? subdomain : null\n}\n\n/**\n * The route's refusal when the site's plan does not include the kind, or\n * holds no more of it; `null` while it does. The feature is asked first, as\n * the route asks it before it counts.\n */\nexport function aiDraftBandRefusal(\n kind: AiDraftKind,\n rows: ReadonlyArray<Pick<SiblingRow, 'id' | 'kind' | 'sourceType' | 'deletedAt'>>,\n org: Partial<AglynOrgBilling> | null,\n routingMap?: RoutingMap,\n): string | null {\n const band = AI_DRAFT_BANDS[kind]\n if (band.entitlement && !checkEntitlement(org, band.entitlement)) {\n return AI_DRAFT_ENTITLEMENT_REFUSAL\n }\n if (!band.quotaKey) return null\n const used =\n kind === 'template'\n ? rows.filter((row) => row.sourceType !== undefined && row.sourceType !== 'starter').length\n : kind === 'screen'\n ? billableScreenIds(\n rows.map((row) => ({ id: row.id, kind: row.kind, deletedAt: row.deletedAt })),\n (routingMap ?? undefined) as never,\n ).size\n : rows.length\n const quota = checkQuota(org, band.quotaKey, used)\n return quota.allowed\n ? null\n : `Your plan includes ${quota.limit} ${band.label} — upgrade in Billing for more`\n}\n\n/** The draft kinds a plan's creations land as, whose band a plan is told about (AGL-3030). */\nconst AI_PLAN_DRAFT_KINDS = ['component', 'form', 'layout', 'template'] as const\n\ntype AiPlanDraftKind = (typeof AI_PLAN_DRAFT_KINDS)[number]\n\n/** Every draft kind's plural label made singular, for a band of one. */\nfunction bandLabel(label: string, count: number): string {\n return count === 1 ? label.replace(/s$/, '') : label\n}\n\n/**\n * Whether the site may take `used + 1` of a kind, in the create route's\n * arithmetic: the feature first, then the allowance. `null` for a kind whose\n * allowance is unlimited or counts nothing, which needs no rows read.\n */\nfunction planCreation(\n kind: AiPlanDraftKind,\n org: Partial<AglynOrgBilling> | null,\n rows: ReadonlyArray<Pick<SiblingRow, 'id' | 'kind' | 'sourceType' | 'deletedAt'>> | null,\n): AiPlanCreation {\n const band = AI_DRAFT_BANDS[kind]\n if (band.entitlement && !checkEntitlement(org, band.entitlement)) {\n return {\n allowed: false,\n left: 0,\n reason: `this workspace's plan does not include ${kind === 'form' ? 'saved forms' : band.label}`,\n }\n }\n const limit = band.quotaKey ? resolveOrgEntitlements(org)[band.quotaKey] : Number.POSITIVE_INFINITY\n if (!rows || !Number.isFinite(limit)) return { allowed: true, left: null, reason: null }\n // The route's own arithmetic decides; the count beside it is what is left.\n const refusal = aiDraftBandRefusal(kind, rows, org)\n const used =\n kind === 'template'\n ? rows.filter((row) => row.sourceType !== undefined && row.sourceType !== 'starter').length\n : rows.length\n return refusal\n ? {\n allowed: false,\n left: 0,\n reason: `this site already holds the ${limit} ${bandLabel(band.label, limit)} its plan includes`,\n }\n : { allowed: true, left: Math.max(0, limit - used), reason: null }\n}\n\n/** Whether a kind's rows are counted at all on this plan: included, and under a finite allowance. */\nfunction countsRows(kind: AiPlanDraftKind, org: Partial<AglynOrgBilling> | null): boolean {\n const band = AI_DRAFT_BANDS[kind]\n if (!band.quotaKey) return false\n if (band.entitlement && !checkEntitlement(org, band.entitlement)) return false\n return Number.isFinite(resolveOrgEntitlements(org)[band.quotaKey])\n}\n\n/**\n * What a plan may create on this site (AGL-3030), from the workspace's plan\n * and the rows each counted kind already holds — pure, so the specs and the\n * eval harness hold the arithmetic without a Firestore. A kind whose rows\n * are not given is counted as having room, which is what an unlimited\n * allowance means.\n *\n * A theme change is a proposal a member applies, never a draft, so it counts\n * against nothing. A dataset is org data that no job writes; a plan may name\n * one only where the workspace's plan includes datasets at all. An email\n * design is written by the email plugin, whose own gate decides.\n *\n * A workspace on the Free plan spends the Free taste, a wall of monthly\n * credits, so its plan is held to the sections that wall pays for (AGL-3070).\n * The effective plan decides, as it decides the taste's own allowance: a\n * workspace whose subscription died is Free again.\n */\nexport function aiPlanCapabilitiesFrom(\n org: Partial<AglynOrgBilling> | null,\n rows: Partial<Record<AiPlanDraftKind, ReadonlyArray<Pick<SiblingRow, 'id' | 'kind' | 'sourceType' | 'deletedAt'>>>> = {},\n): AiPlanCapabilities {\n const unrestricted = aiUnrestrictedPlanCapabilities()\n const datasets = checkDatasetQuota(org, 0).limit > 0\n return {\n reusableComponents: checkEntitlement(org, 'reusableComponents'),\n create: {\n ...unrestricted.create,\n component: planCreation('component', org, rows.component ?? null),\n form: planCreation('form', org, rows.form ?? null),\n layout: planCreation('layout', org, rows.layout ?? null),\n template: planCreation('template', org, rows.template ?? null),\n dataset: datasets\n ? unrestricted.create.dataset\n : { allowed: false, left: 0, reason: \"this workspace's plan does not include datasets\" },\n },\n ...(resolveEffectivePlan(org) === 'free' ? { freeTaste: true } : {}),\n }\n}\n\n/**\n * What a plan may create on this site, read: the rows of each kind whose\n * allowance the workspace's plan counts, as the draft writer reads them, and\n * nothing for a kind the plan leaves out or leaves unlimited.\n */\nexport async function readAiPlanCapabilities(\n firestore: Firestore,\n input: { hostId: string; org: Partial<AglynOrgBilling> | null },\n): Promise<AiPlanCapabilities> {\n const counted = AI_PLAN_DRAFT_KINDS.filter((kind) => countsRows(kind, input.org))\n const snapshots = await Promise.all(\n counted.map((kind) => siblingsOf(draftCollection(firestore, input.hostId, kind)).get()),\n )\n return aiPlanCapabilitiesFrom(\n input.org,\n Object.fromEntries(counted.map((kind, index) => [kind, siblingRows(snapshots[index])])),\n )\n}\n\n/**\n * Whether the site can take one more draft of the kind: a read, for a door\n * that asks before it spends. The write asks again inside its transaction.\n */\nexport async function aiDraftAllowanceRefusal(\n firestore: Firestore,\n input: { kind: AiDraftKind; hostId: string; org: Partial<AglynOrgBilling> | null },\n): Promise<string | null> {\n const [snapshot, host] = await Promise.all([\n siblingsOf(draftCollection(firestore, input.hostId, input.kind)).get(),\n input.kind === 'screen'\n ? firestore.collection('hosts').doc(input.hostId).get()\n : Promise.resolve(null),\n ])\n return aiDraftBandRefusal(\n input.kind,\n siblingRows(snapshot),\n input.org,\n host?.get('screens') as RoutingMap,\n )\n}\n\n/**\n * A draft screen's one-segment address: the one asked for, else one from its\n * name, never a reserved segment, and never an address a live screen keeps\n * or the routing map already serves — so the member's publish at it later\n * takes nothing from a live page. The root is asked for only while no screen\n * holds it.\n *\n * Except the placeholder home page a site is created with (AGL-3408), which\n * holds `/` only until a real home page is published there: the guided start\n * is offered to exactly the sites that still have it, and the home page it\n * drafts has to be able to ask for the root. Publishing it replaces the\n * placeholder in the same write.\n */\nexport function aiDraftScreenSlug(\n requested: string | null | undefined,\n name: string,\n rows: ReadonlyArray<Pick<SiblingRow, 'slug' | 'deleted'> & { id?: string }>,\n routingMap: RoutingMap,\n defaultHomeScreenId?: string | null,\n): string {\n const placeholder = (id: string | undefined, path: unknown) =>\n Boolean(defaultHomeScreenId) && id === defaultHomeScreenId && path === SCREEN_ROOT_PATH\n const taken = new Set<string>([\n ...rows\n .filter((row) => !row.deleted && row.slug && !placeholder(row.id, row.slug))\n .map((row) => row.slug),\n ...Object.entries(routingMap ?? {})\n .filter(([id, path]) => typeof path === 'string' && !placeholder(id, path))\n .map(([, path]) => path as string),\n ])\n const usable = (value: string | undefined) =>\n value && !reservedScreenRouteSegment(value) && !(value === SCREEN_ROOT_PATH && taken.has(value))\n ? value\n : undefined\n const base = usable(normalizeScreenSlug(requested ?? undefined)) ?? usable(normalizeScreenSlug(name)) ?? 'page'\n if (base === SCREEN_ROOT_PATH) return base\n let slug = base\n let attempt = 2\n while (taken.has(slug)) slug = `${base}-${attempt++}`\n return slug\n}\n\nexport interface AiDraftRecord {\n id: string\n /** A layout's or a screen's first version; `null` for every other kind, which the writer gives none. */\n versionId: string | null\n name: string\n hostSubdomain: string | null\n /** A screen's search listing as the draft holds it now; absent on other kinds. */\n seo?: { title?: string; description?: string } | null\n /** Whether a member deleted the draft since it was written. */\n deleted?: boolean\n}\n\nfunction recordOf(\n id: string,\n draft: FirebaseFirestore.DocumentSnapshot,\n host: FirebaseFirestore.DocumentSnapshot,\n): AiDraftRecord {\n const versionId = draft.get('versionId')\n const seo = draft.get('seo')\n return {\n id,\n versionId: typeof versionId === 'string' && versionId ? versionId : null,\n name: String(draft.get('displayName') ?? ''),\n hostSubdomain: subdomainOf(host),\n ...(seo && typeof seo === 'object' ? { seo: seo as AiDraftRecord['seo'] } : {}),\n ...(draft.get('deletedAt') != null ? { deleted: true } : {}),\n }\n}\n\n/** The draft a job already wrote under `id`, when a run of its step got that far. */\nexport async function readAiDraft(\n firestore: Firestore,\n input: { kind: AiDraftKind; hostId: string; id: string },\n): Promise<AiDraftRecord | null> {\n const draft = await draftCollection(firestore, input.hostId, input.kind).doc(input.id).get()\n if (!draft.exists) return null\n return recordOf(input.id, draft, await firestore.collection('hosts').doc(input.hostId).get())\n}\n\n/**\n * The tree a template draft holds now, decoded; `null` when there is none.\n *\n * A template is not a versioned draft — its nodes live on the document\n * itself, which is why `readAiDraftNodes` refuses its kind.\n */\nexport async function readAiTemplateDraftNodes(\n firestore: Firestore,\n input: { hostId: string; id: string },\n): Promise<NodesMap | null> {\n const draft = await draftCollection(firestore, input.hostId, 'template').doc(input.id).get()\n if (!draft.exists) return null\n return decodeStoredNodes<NodesMap>(draft.get('nodes')) ?? null\n}\n\n/**\n * The property names a component draft declares now; `null` when there is no\n * such draft. Read from the draft document rather than its version, because a\n * component's props live beside its tree and a copy carries its source's.\n */\nexport async function readAiDraftComponentProps(\n firestore: Firestore,\n input: { hostId: string; id: string },\n): Promise<string[] | null> {\n const draft = await draftCollection(firestore, input.hostId, 'component').doc(input.id).get()\n if (!draft.exists) return null\n const props = draft.get('props')\n if (!Array.isArray(props)) return []\n return props\n .map((prop) => (prop && typeof prop === 'object' ? (prop as Record<string, unknown>)['name'] : null))\n .filter((name): name is string => typeof name === 'string' && name.length > 0)\n}\n\n/** The tree a versioned draft's first version holds now, decoded; `null` when there is none. */\nexport async function readAiDraftNodes(\n firestore: Firestore,\n input: { kind: AiVersionedDraftKind; hostId: string; id: string },\n): Promise<{ versionId: string; nodes: NodesMap } | null> {\n const draftRef = draftCollection(firestore, input.hostId, input.kind).doc(input.id)\n const draft = await draftRef.get()\n const versionId = draft.exists ? draft.get('versionId') : null\n if (typeof versionId !== 'string' || !versionId) return null\n const version = await draftRef.collection('versions').doc(versionId).get()\n const nodes = version.exists ? decodeStoredNodes<NodesMap>(version.get('nodes')) : null\n return nodes ? { versionId, nodes } : null\n}\n\nexport interface AiDraftInput {\n kind: AiDraftKind\n hostId: string\n /** The draft's document id: the id the job recorded for this draft. */\n id: string\n /** Whose work the draft is: the job's creator, whose brief it built. */\n uid: string\n org: Partial<AglynOrgBilling> | null\n /** The name asked for; a live sibling already called that gets a number. */\n name: string\n nodes: NodesMap\n /** A page template's suggested address, or a screen's one-segment address. */\n slug?: string | null\n /** A form's declaration and the ids its design hangs from; required for a form. */\n form?: AiFormDraftDeclaration\n /** A component's root, which the node map holds; required for a component. */\n rootId?: string\n /** The properties a component declares, which its tree binds. */\n props?: readonly ReusableComponentProp[]\n /** A screen's layout, for its first version to render inside; a layout the site has. */\n layoutId?: string | null\n now: Date\n}\n\n/** What a form draft declares beside its design, read off that design. */\nexport interface AiFormDraftDeclaration {\n /** The canvas root `nodes` is stored under, as the Forms page's Create stores it. */\n rootId: string\n /** The form node, which carries the form's id and is captioned with the draft's name. */\n formNodeId: string\n fields: FormFieldDecl[]\n consentFieldName: string | null\n routing: FormRouting | null\n}\n\nexport type AiDraftWrite =\n | (AiDraftRecord & { ok: true; replayed: boolean })\n | { ok: false; status: 403 | 404; error: string }\n\n/** Keep only the keys the create route admits for the kind. */\nfunction allowListed(kind: AiDraftKind, data: Record<string, unknown>): Record<string, unknown> {\n const allowed = new Set(AI_DRAFT_FIELDS[kind])\n return Object.fromEntries(\n Object.entries(data).filter(([key, value]) => allowed.has(key) && value !== undefined),\n )\n}\n\n/** The node map with its form node captioned with `name`, as Create captions a new form. */\nfunction withFormCaption(nodes: NodesMap, formNodeId: string, name: string): NodesMap {\n const node = nodes[formNodeId]\n if (!node || node.componentId !== FORM_COMPONENT_ID) return nodes\n return { ...nodes, [formNodeId]: { ...node, props: { ...(node.props ?? {}), formName: name } } }\n}\n\n/**\n * Write one draft: the band met, the name made unique among the live\n * siblings, the node map stored as msgpack, all in one transaction. A\n * refusal is returned, never thrown; a Firestore failure propagates, so the\n * machine's retry applies.\n */\nexport async function writeAiDraft(firestore: Firestore, input: AiDraftInput): Promise<AiDraftWrite> {\n const packed = encodeStoredNodes(input.nodes)\n if (!packed) throw new Error('an AI draft needs a node map')\n if (input.kind === 'form' && !input.form) throw new Error('an AI form draft needs its declaration')\n if (input.kind === 'component' && !(input.rootId && input.nodes[input.rootId])) {\n throw new Error('a component draft needs a root its node map holds')\n }\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n const collection = draftCollection(firestore, input.hostId, input.kind)\n const draftRef = collection.doc(input.id)\n return firestore.runTransaction(async (tx): Promise<AiDraftWrite> => {\n // Every read before any write, which Firestore requires.\n const [host, existing, siblings] = await Promise.all([\n tx.get(hostRef),\n tx.get(draftRef),\n tx.get(siblingsOf(collection)),\n ])\n if (!host.exists) return { ok: false, status: 404, error: 'Unknown site' }\n if (existing.exists) {\n return { ok: true, replayed: true, ...recordOf(input.id, existing, host) }\n }\n const rows = siblingRows(siblings)\n // Read, never written: the routing map decides which screens count.\n const routingMap = host.get('screens') as RoutingMap\n const refusal = aiDraftBandRefusal(input.kind, rows, input.org, routingMap)\n if (refusal) return { ok: false, status: 403, error: refusal }\n const name = uniqueDuplicateName(\n input.name,\n rows.filter((row) => !row.deleted).map((row) => row.name),\n )\n const stamps = { createdAt: input.now, updatedAt: input.now, createdBy: input.uid }\n const nodes = Buffer.from(packed)\n let versionId: string | null = null\n if (input.kind === 'layout') {\n versionId = createResourceUid()\n tx.create(draftRef, {\n ...allowListed('layout', { displayName: name, versionId }),\n ...artifactCreateListKeys('layouts', { displayName: name }),\n ...stamps,\n })\n tx.create(draftRef.collection('versions').doc(versionId), {\n layoutId: input.id,\n hostId: input.hostId,\n displayName: AI_DRAFT_VERSION_NAME,\n nodes,\n ...stamps,\n })\n } else if (input.kind === 'screen') {\n versionId = createResourceUid()\n const slug = aiDraftScreenSlug(\n input.slug,\n name,\n rows,\n routingMap,\n host.get('defaultHomeScreenId'),\n )\n tx.create(draftRef, {\n ...allowListed('screen', { displayName: name, slug, versionId }),\n ...artifactCreateListKeys('screens', { displayName: name }),\n ...stamps,\n })\n tx.create(draftRef.collection('versions').doc(versionId), {\n screenId: input.id,\n hostId: input.hostId,\n displayName: AI_DRAFT_VERSION_NAME,\n nodes,\n ...(input.layoutId ? { layoutId: input.layoutId } : {}),\n ...stamps,\n })\n } else if (input.kind === 'template') {\n tx.create(draftRef, {\n ...allowListed('template', {\n kind: 'page',\n displayName: name,\n nodes,\n slug: input.slug || undefined,\n }),\n source: { type: 'authored' },\n ...artifactCreateListKeys('templates', {\n kind: 'page',\n displayName: name,\n source: { type: 'authored' },\n }),\n ...stamps,\n })\n } else if (input.kind === 'component') {\n tx.create(draftRef, {\n ...allowListed('component', {\n displayName: name,\n rootId: input.rootId,\n nodes,\n props: input.props?.length ? [...input.props] : undefined,\n }),\n ...artifactCreateListKeys('components', { displayName: name }),\n ...stamps,\n })\n } else {\n const form = input.form as AiFormDraftDeclaration\n const captioned = encodeStoredNodes(withFormCaption(input.nodes, form.formNodeId, name)) ?? packed\n const slug = normalizeFormSlug(name) || input.id\n tx.create(draftRef, {\n ...allowListed('form', {\n displayName: name,\n slug,\n fields: form.fields,\n consentFieldName: form.consentFieldName || undefined,\n routing: form.routing ?? undefined,\n rootId: form.rootId,\n nodes: Buffer.from(captioned),\n }),\n // What the Forms list queries (AGL-3330), as a console create writes it.\n ...newFormListFields({ id: input.id, displayName: name, slug, routing: form.routing }),\n ...stamps,\n })\n }\n return {\n ok: true,\n replayed: false,\n id: input.id,\n versionId,\n name,\n hostSubdomain: subdomainOf(host),\n }\n })\n}\n\nexport type AiDraftNodesUpdate =\n | { ok: true; changed: boolean }\n | { ok: false; status: 404; error: string }\n\n/**\n * Change the tree of a versioned draft the job wrote, in one transaction:\n * read the first version as it is stored NOW — a member may have opened and\n * saved it — hand it to `update`, and store what comes back with a fresh\n * `updatedAt`, which is what the besigner's save guard compares. `update`\n * answering `null` changes nothing: a pass run again after its write.\n */\nexport async function updateAiDraftNodes(\n firestore: Firestore,\n input: {\n kind: AiVersionedDraftKind\n hostId: string\n id: string\n now: Date\n update: (nodes: NodesMap) => NodesMap | null\n },\n): Promise<AiDraftNodesUpdate> {\n const draftRef = draftCollection(firestore, input.hostId, input.kind).doc(input.id)\n return firestore.runTransaction(async (tx): Promise<AiDraftNodesUpdate> => {\n const draft = await tx.get(draftRef)\n const versionId = draft.exists && draft.get('deletedAt') == null ? draft.get('versionId') : null\n if (typeof versionId !== 'string' || !versionId) {\n return { ok: false, status: 404, error: 'The draft is no longer on the site' }\n }\n const versionRef = draftRef.collection('versions').doc(versionId)\n const version = await tx.get(versionRef)\n const nodes = version.exists ? decodeStoredNodes<NodesMap>(version.get('nodes')) : null\n if (!nodes) return { ok: false, status: 404, error: 'The draft is no longer on the site' }\n const next = input.update(nodes)\n if (!next) return { ok: true, changed: false }\n const packed = encodeStoredNodes(next)\n if (!packed) throw new Error('an AI draft needs a node map')\n tx.update(versionRef, { nodes: Buffer.from(packed), updatedAt: input.now })\n return { ok: true, changed: true }\n })\n}\n\n/**\n * Put a search title and description on a draft screen the job wrote, each\n * only where the draft has none: a value a member typed since is theirs.\n */\nexport async function writeAiDraftScreenSeo(\n firestore: Firestore,\n input: {\n hostId: string\n id: string\n seo: { title?: string | null; description?: string | null }\n now: Date\n },\n): Promise<void> {\n const ref = draftCollection(firestore, input.hostId, 'screen').doc(input.id)\n await firestore.runTransaction(async (tx) => {\n const screen = await tx.get(ref)\n if (!screen.exists || screen.get('deletedAt') != null) return\n const patch: Record<string, unknown> = {}\n for (const key of ['title', 'description'] as const) {\n const value = String(input.seo[key] ?? '').trim()\n if (value && !String(screen.get(`seo.${key}`) ?? '').trim()) patch[`seo.${key}`] = value\n }\n if (Object.keys(patch).length) tx.update(ref, { ...patch, updatedAt: input.now })\n })\n}\n\n/** The site's subdomain, which a console link names the site by. */\nexport async function aiSiteSubdomain(firestore: Firestore, hostId: string): Promise<string | null> {\n return subdomainOf(await firestore.collection('hosts').doc(hostId).get())\n}\n\n/**\n * What a door says before a job that writes a draft of this kind exists: a\n * site is named, and it is the job's own org's; whatever the kind checks of\n * its own; then the allowance. `null` admits the job.\n */\nexport async function aiDraftAdmissionRefusal(\n firestore: Firestore,\n input: {\n orgId: string\n hostId: string | null\n kind: AiDraftKind\n /** What the refusal calls the job's output; the kind when absent. */\n noun?: string\n org: object | null\n /** The kind's own check, asked once the site is known to be the org's. */\n ownCheck?: (hostId: string) => Promise<AiJobAdmissionRefusal | null>\n },\n): Promise<AiJobAdmissionRefusal | null> {\n if (!input.hostId) {\n return {\n status: 400,\n error: `Open the site the ${input.noun ?? input.kind} is for before starting the job`,\n }\n }\n const owner = await resolveOrgIdForHost(input.hostId)\n if (!owner || owner !== input.orgId) return { status: 404, error: 'Unknown site' }\n const own = await input.ownCheck?.(input.hostId)\n if (own) return own\n const refusal = await aiDraftAllowanceRefusal(firestore, {\n kind: input.kind,\n hostId: input.hostId,\n org: input.org as Partial<AglynOrgBilling> | null,\n })\n return refusal ? { status: 403, error: refusal } : null\n}\n"],"names":["createResourceUid","uniqueDuplicateName","FORM_COMPONENT_ID","newFormListFields","normalizeFormSlug","artifactCreateListKeys","checkDatasetQuota","checkEntitlement","checkQuota","resolveEffectivePlan","resolveOrgEntitlements","billableScreenIds","normalizeScreenSlug","reservedScreenRouteSegment","SCREEN_ROOT_PATH","decodeStoredNodes","encodeStoredNodes","resolveOrgIdForHost","aiUnrestrictedPlanCapabilities","AI_DRAFT_FIELDS","layout","template","form","component","screen","AI_DRAFT_VERSION_FIELDS","AI_DRAFT_SCREEN_VERSION_FIELDS","AI_DRAFT_VERSION_NAME","AI_DRAFT_ENTITLEMENT_REFUSAL","AI_DRAFT_BANDS","collection","quotaKey","label","entitlement","draftCollection","firestore","hostId","kind","doc","siblingsOf","select","siblingRows","snapshot","docs","map","id","name","String","get","slug","sourceType","deletedAt","deleted","subdomainOf","host","subdomain","aiDraftBandRefusal","rows","org","routingMap","band","used","filter","row","undefined","length","size","quota","allowed","limit","AI_PLAN_DRAFT_KINDS","bandLabel","count","replace","planCreation","left","reason","Number","POSITIVE_INFINITY","isFinite","refusal","Math","max","countsRows","aiPlanCapabilitiesFrom","unrestricted","datasets","reusableComponents","create","dataset","freeTaste","readAiPlanCapabilities","input","counted","snapshots","Promise","all","Object","fromEntries","index","aiDraftAllowanceRefusal","resolve","aiDraftScreenSlug","requested","defaultHomeScreenId","usable","placeholder","path","Boolean","taken","Set","entries","value","has","base","attempt","recordOf","draft","versionId","seo","hostSubdomain","readAiDraft","exists","readAiTemplateDraftNodes","readAiDraftComponentProps","props","Array","isArray","prop","readAiDraftNodes","draftRef","version","nodes","allowListed","data","key","withFormCaption","formNodeId","node","componentId","formName","writeAiDraft","packed","Error","rootId","hostRef","runTransaction","tx","existing","siblings","ok","status","error","replayed","stamps","createdAt","now","updatedAt","createdBy","uid","Buffer","from","displayName","layoutId","screenId","source","type","captioned","fields","consentFieldName","routing","updateAiDraftNodes","versionRef","next","update","changed","writeAiDraftScreenSeo","ref","patch","trim","keys","aiSiteSubdomain","aiDraftAdmissionRefusal","noun","owner","orgId","own","ownCheck"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,iBAAiB,QAAQ,6CAA4C;AAC9E,SAASC,mBAAmB,QAAQ,4CAA2C;AAC/E,SACEC,iBAAiB,EACjBC,iBAAiB,EACjBC,iBAAiB,QAGZ,+BAA8B;AACrC,SAASC,sBAAsB,QAAQ,4CAA2C;AAClF,SACEC,iBAAiB,EACjBC,gBAAgB,EAChBC,UAAU,EACVC,oBAAoB,EACpBC,sBAAsB,QACjB,2CAA0C;AACjD,SACEC,iBAAiB,EACjBC,mBAAmB,EACnBC,0BAA0B,EAC1BC,gBAAgB,QACX,sCAAqC;AAC5C,SAASC,iBAAiB,EAAEC,iBAAiB,QAAQ,sCAAqC;AAO1F,SAASC,mBAAmB,QAAQ,gDAA+C;AACnF,SACEC,8BAA8B,QAGzB,mCAA+B;AAwEtC,iEAAiE,GACjE,OAAO,MAAMC,kBAAoE;IAC/EC,QAAQ;QAAC;QAAe;QAAe;KAAY;IACnDC,UAAU;QACR;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA,qEAAqE;QACrE;KACD;IACDC,MAAM;QACJ;QACA;QACA;QACA;QACA;QACA;QACA;QACA;KACD;IACD,mEAAmE;IACnE,kCAAkC;IAClCC,WAAW;QAAC;QAAe;QAAe;QAAU;QAAS;QAAS;KAAO;IAC7E,6EAA6E;IAC7E,8BAA8B;IAC9BC,QAAQ;QAAC;QAAe;QAAe;QAAQ;QAAO;QAAQ;KAAY;AAC5E,EAAC;AAED,wFAAwF,GACxF,OAAO,MAAMC,0BAA6C;IAAC;IAAY;IAAU;IAAe;CAAQ,CAAA;AAExG;;;;CAIC,GACD,OAAO,MAAMC,iCAAoD;IAC/D;IACA;IACA;IACA;IACA;CACD,CAAA;AAED,kFAAkF,GAClF,OAAO,MAAMC,wBAAwB,kBAAiB;AAEtD,+EAA+E,GAC/E,OAAO,MAAMC,+BAA+B,0DAAyD;AAgBrG,2FAA2F,GAC3F,OAAO,MAAMC,iBAA6D;IACxET,QAAQ;QAAEU,YAAY;QAAWC,UAAU;QAAwBC,OAAO;IAAiB;IAC3FX,UAAU;QAAES,YAAY;QAAaC,UAAU;QAAoBC,OAAO;IAAY;IACtFV,MAAM;QACJQ,YAAY;QACZC,UAAU;QACVE,aAAa;QACbD,OAAO;IACT;IACAT,WAAW;QACTO,YAAY;QACZG,aAAa;QACbD,OAAO;IACT;IACAR,QAAQ;QAAEM,YAAY;QAAWC,UAAU;QAAkBC,OAAO;IAAQ;AAC9E,EAAC;AAeD,SAASE,gBAAgBC,SAAoB,EAAEC,MAAc,EAAEC,IAAiB;IAC9E,OAAOF,UAAUL,UAAU,CAAC,SAASQ,GAAG,CAACF,QAAQN,UAAU,CAACD,cAAc,CAACQ,KAAK,CAACP,UAAU;AAC7F;AAEA,6EAA6E,GAC7E,SAASS,WAAWT,UAAiD;IACnE,OAAOA,WAAWU,MAAM,CAAC,eAAe,QAAQ,QAAQ,eAAe;AACzE;AAEA,SAASC,YAAYC,QAAyC;IAC5D,OAAOA,SAASC,IAAI,CAACC,GAAG,CAAC,CAACN;YAEXA,UACAA;eAHoB;YACjCO,IAAIP,IAAIO,EAAE;YACVC,MAAMC,QAAOT,WAAAA,IAAIU,GAAG,CAAC,0BAARV,WAA0B;YACvCW,MAAMF,QAAOT,YAAAA,IAAIU,GAAG,CAAC,mBAARV,YAAmB;YAChCD,MAAMC,IAAIU,GAAG,CAAC;YACdE,YAAYZ,IAAIU,GAAG,CAAC;YACpBG,WAAWb,IAAIU,GAAG,CAAC;YACnBI,SAASd,IAAIU,GAAG,CAAC,gBAAgB;QACnC;;AACF;AAEA,SAASK,YAAYC,IAAwC;IAC3D,MAAMC,YAAYD,KAAKN,GAAG,CAAC;IAC3B,OAAO,OAAOO,cAAc,YAAYA,YAAYA,YAAY;AAClE;AAEA;;;;CAIC,GACD,OAAO,SAASC,mBACdnB,IAAiB,EACjBoB,IAAiF,EACjFC,GAAoC,EACpCC,UAAuB;IAEvB,MAAMC,OAAO/B,cAAc,CAACQ,KAAK;IACjC,IAAIuB,KAAK3B,WAAW,IAAI,CAAC1B,iBAAiBmD,KAAKE,KAAK3B,WAAW,GAAG;QAChE,OAAOL;IACT;IACA,IAAI,CAACgC,KAAK7B,QAAQ,EAAE,OAAO;IAC3B,MAAM8B,OACJxB,SAAS,aACLoB,KAAKK,MAAM,CAAC,CAACC,MAAQA,IAAIb,UAAU,KAAKc,aAAaD,IAAIb,UAAU,KAAK,WAAWe,MAAM,GACzF5B,SAAS,WACP1B,kBACE8C,KAAKb,GAAG,CAAC,CAACmB,MAAS,CAAA;YAAElB,IAAIkB,IAAIlB,EAAE;YAAER,MAAM0B,IAAI1B,IAAI;YAAEc,WAAWY,IAAIZ,SAAS;QAAC,CAAA,IACzEQ,qBAAAA,aAAcK,WACfE,IAAI,GACNT,KAAKQ,MAAM;IACnB,MAAME,QAAQ3D,WAAWkD,KAAKE,KAAK7B,QAAQ,EAAE8B;IAC7C,OAAOM,MAAMC,OAAO,GAChB,OACA,CAAC,mBAAmB,EAAED,MAAME,KAAK,CAAC,CAAC,EAAET,KAAK5B,KAAK,CAAC,8BAA8B,CAAC;AACrF;AAEA,4FAA4F,GAC5F,MAAMsC,sBAAsB;IAAC;IAAa;IAAQ;IAAU;CAAW;AAIvE,sEAAsE,GACtE,SAASC,UAAUvC,KAAa,EAAEwC,KAAa;IAC7C,OAAOA,UAAU,IAAIxC,MAAMyC,OAAO,CAAC,MAAM,MAAMzC;AACjD;AAEA;;;;CAIC,GACD,SAAS0C,aACPrC,IAAqB,EACrBqB,GAAoC,EACpCD,IAAwF;IAExF,MAAMG,OAAO/B,cAAc,CAACQ,KAAK;IACjC,IAAIuB,KAAK3B,WAAW,IAAI,CAAC1B,iBAAiBmD,KAAKE,KAAK3B,WAAW,GAAG;QAChE,OAAO;YACLmC,SAAS;YACTO,MAAM;YACNC,QAAQ,CAAC,uCAAuC,EAAEvC,SAAS,SAAS,gBAAgBuB,KAAK5B,KAAK,EAAE;QAClG;IACF;IACA,MAAMqC,QAAQT,KAAK7B,QAAQ,GAAGrB,uBAAuBgD,IAAI,CAACE,KAAK7B,QAAQ,CAAC,GAAG8C,OAAOC,iBAAiB;IACnG,IAAI,CAACrB,QAAQ,CAACoB,OAAOE,QAAQ,CAACV,QAAQ,OAAO;QAAED,SAAS;QAAMO,MAAM;QAAMC,QAAQ;IAAK;IACvF,2EAA2E;IAC3E,MAAMI,UAAUxB,mBAAmBnB,MAAMoB,MAAMC;IAC/C,MAAMG,OACJxB,SAAS,aACLoB,KAAKK,MAAM,CAAC,CAACC,MAAQA,IAAIb,UAAU,KAAKc,aAAaD,IAAIb,UAAU,KAAK,WAAWe,MAAM,GACzFR,KAAKQ,MAAM;IACjB,OAAOe,UACH;QACEZ,SAAS;QACTO,MAAM;QACNC,QAAQ,CAAC,4BAA4B,EAAEP,MAAM,CAAC,EAAEE,UAAUX,KAAK5B,KAAK,EAAEqC,OAAO,kBAAkB,CAAC;IAClG,IACA;QAAED,SAAS;QAAMO,MAAMM,KAAKC,GAAG,CAAC,GAAGb,QAAQR;QAAOe,QAAQ;IAAK;AACrE;AAEA,mGAAmG,GACnG,SAASO,WAAW9C,IAAqB,EAAEqB,GAAoC;IAC7E,MAAME,OAAO/B,cAAc,CAACQ,KAAK;IACjC,IAAI,CAACuB,KAAK7B,QAAQ,EAAE,OAAO;IAC3B,IAAI6B,KAAK3B,WAAW,IAAI,CAAC1B,iBAAiBmD,KAAKE,KAAK3B,WAAW,GAAG,OAAO;IACzE,OAAO4C,OAAOE,QAAQ,CAACrE,uBAAuBgD,IAAI,CAACE,KAAK7B,QAAQ,CAAC;AACnE;AAEA;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASqD,uBACd1B,GAAoC,EACpCD,OAAsH,CAAC,CAAC;QAQ1EA,iBACVA,YACIA,cACIA;IAT5C,MAAM4B,eAAenE;IACrB,MAAMoE,WAAWhF,kBAAkBoD,KAAK,GAAGW,KAAK,GAAG;IACnD,OAAO;QACLkB,oBAAoBhF,iBAAiBmD,KAAK;QAC1C8B,QAAQ,aACHH,aAAaG,MAAM;YACtBjE,WAAWmD,aAAa,aAAahB,MAAKD,kBAAAA,KAAKlC,SAAS,YAAdkC,kBAAkB;YAC5DnC,MAAMoD,aAAa,QAAQhB,MAAKD,aAAAA,KAAKnC,IAAI,YAATmC,aAAa;YAC7CrC,QAAQsD,aAAa,UAAUhB,MAAKD,eAAAA,KAAKrC,MAAM,YAAXqC,eAAe;YACnDpC,UAAUqD,aAAa,YAAYhB,MAAKD,iBAAAA,KAAKpC,QAAQ,YAAboC,iBAAiB;YACzDgC,SAASH,WACLD,aAAaG,MAAM,CAACC,OAAO,GAC3B;gBAAErB,SAAS;gBAAOO,MAAM;gBAAGC,QAAQ;YAAkD;;OAEvFnE,qBAAqBiD,SAAS,SAAS;QAAEgC,WAAW;IAAK,IAAI,CAAC;AAEtE;AAEA;;;;CAIC,GACD,OAAO,eAAeC,uBACpBxD,SAAoB,EACpByD,KAA+D;IAE/D,MAAMC,UAAUvB,oBAAoBR,MAAM,CAAC,CAACzB,OAAS8C,WAAW9C,MAAMuD,MAAMlC,GAAG;IAC/E,MAAMoC,YAAY,MAAMC,QAAQC,GAAG,CACjCH,QAAQjD,GAAG,CAAC,CAACP,OAASE,WAAWL,gBAAgBC,WAAWyD,MAAMxD,MAAM,EAAEC,OAAOW,GAAG;IAEtF,OAAOoC,uBACLQ,MAAMlC,GAAG,EACTuC,OAAOC,WAAW,CAACL,QAAQjD,GAAG,CAAC,CAACP,MAAM8D,QAAU;YAAC9D;YAAMI,YAAYqD,SAAS,CAACK,MAAM;SAAE;AAEzF;AAEA;;;CAGC,GACD,OAAO,eAAeC,wBACpBjE,SAAoB,EACpByD,KAAkF;IAElF,MAAM,CAAClD,UAAUY,KAAK,GAAG,MAAMyC,QAAQC,GAAG,CAAC;QACzCzD,WAAWL,gBAAgBC,WAAWyD,MAAMxD,MAAM,EAAEwD,MAAMvD,IAAI,GAAGW,GAAG;QACpE4C,MAAMvD,IAAI,KAAK,WACXF,UAAUL,UAAU,CAAC,SAASQ,GAAG,CAACsD,MAAMxD,MAAM,EAAEY,GAAG,KACnD+C,QAAQM,OAAO,CAAC;KACrB;IACD,OAAO7C,mBACLoC,MAAMvD,IAAI,EACVI,YAAYC,WACZkD,MAAMlC,GAAG,EACTJ,wBAAAA,KAAMN,GAAG,CAAC;AAEd;AAEA;;;;;;;;;;;;CAYC,GACD,OAAO,SAASsD,kBACdC,SAAoC,EACpCzD,IAAY,EACZW,IAA2E,EAC3EE,UAAsB,EACtB6C,mBAAmC;QAgBtBC,MAAAA;IAdb,MAAMC,cAAc,CAAC7D,IAAwB8D,OAC3CC,QAAQJ,wBAAwB3D,OAAO2D,uBAAuBG,SAAS7F;IACzE,MAAM+F,QAAQ,IAAIC,IAAY;WACzBrD,KACAK,MAAM,CAAC,CAACC,MAAQ,CAACA,IAAIX,OAAO,IAAIW,IAAId,IAAI,IAAI,CAACyD,YAAY3C,IAAIlB,EAAE,EAAEkB,IAAId,IAAI,GACzEL,GAAG,CAAC,CAACmB,MAAQA,IAAId,IAAI;WACrBgD,OAAOc,OAAO,CAACpD,qBAAAA,aAAc,CAAC,GAC9BG,MAAM,CAAC,CAAC,CAACjB,IAAI8D,KAAK,GAAK,OAAOA,SAAS,YAAY,CAACD,YAAY7D,IAAI8D,OACpE/D,GAAG,CAAC,CAAC,GAAG+D,KAAK,GAAKA;KACtB;IACD,MAAMF,SAAS,CAACO,QACdA,SAAS,CAACnG,2BAA2BmG,UAAU,CAAEA,CAAAA,UAAUlG,oBAAoB+F,MAAMI,GAAG,CAACD,MAAK,IAC1FA,QACAhD;IACN,MAAMkD,QAAOT,QAAAA,UAAAA,OAAO7F,oBAAoB2F,oBAAAA,YAAavC,uBAAxCyC,UAAuDA,OAAO7F,oBAAoBkC,kBAAlF2D,OAA4F;IACzG,IAAIS,SAASpG,kBAAkB,OAAOoG;IACtC,IAAIjE,OAAOiE;IACX,IAAIC,UAAU;IACd,MAAON,MAAMI,GAAG,CAAChE,MAAOA,OAAO,GAAGiE,KAAK,CAAC,EAAEC,WAAW;IACrD,OAAOlE;AACT;AAcA,SAASmE,SACPvE,EAAU,EACVwE,KAAyC,EACzC/D,IAAwC;QAOzB+D;IALf,MAAMC,YAAYD,MAAMrE,GAAG,CAAC;IAC5B,MAAMuE,MAAMF,MAAMrE,GAAG,CAAC;IACtB,OAAO;QACLH;QACAyE,WAAW,OAAOA,cAAc,YAAYA,YAAYA,YAAY;QACpExE,MAAMC,QAAOsE,aAAAA,MAAMrE,GAAG,CAAC,0BAAVqE,aAA4B;QACzCG,eAAenE,YAAYC;OACvBiE,OAAO,OAAOA,QAAQ,WAAW;QAAEA,KAAKA;IAA4B,IAAI,CAAC,GACzEF,MAAMrE,GAAG,CAAC,gBAAgB,OAAO;QAAEI,SAAS;IAAK,IAAI,CAAC;AAE9D;AAEA,mFAAmF,GACnF,OAAO,eAAeqE,YACpBtF,SAAoB,EACpByD,KAAwD;IAExD,MAAMyB,QAAQ,MAAMnF,gBAAgBC,WAAWyD,MAAMxD,MAAM,EAAEwD,MAAMvD,IAAI,EAAEC,GAAG,CAACsD,MAAM/C,EAAE,EAAEG,GAAG;IAC1F,IAAI,CAACqE,MAAMK,MAAM,EAAE,OAAO;IAC1B,OAAON,SAASxB,MAAM/C,EAAE,EAAEwE,OAAO,MAAMlF,UAAUL,UAAU,CAAC,SAASQ,GAAG,CAACsD,MAAMxD,MAAM,EAAEY,GAAG;AAC5F;AAEA;;;;;CAKC,GACD,OAAO,eAAe2E,yBACpBxF,SAAoB,EACpByD,KAAqC;QAI9B7E;IAFP,MAAMsG,QAAQ,MAAMnF,gBAAgBC,WAAWyD,MAAMxD,MAAM,EAAE,YAAYE,GAAG,CAACsD,MAAM/C,EAAE,EAAEG,GAAG;IAC1F,IAAI,CAACqE,MAAMK,MAAM,EAAE,OAAO;IAC1B,QAAO3G,qBAAAA,kBAA4BsG,MAAMrE,GAAG,CAAC,qBAAtCjC,qBAAmD;AAC5D;AAEA;;;;CAIC,GACD,OAAO,eAAe6G,0BACpBzF,SAAoB,EACpByD,KAAqC;IAErC,MAAMyB,QAAQ,MAAMnF,gBAAgBC,WAAWyD,MAAMxD,MAAM,EAAE,aAAaE,GAAG,CAACsD,MAAM/C,EAAE,EAAEG,GAAG;IAC3F,IAAI,CAACqE,MAAMK,MAAM,EAAE,OAAO;IAC1B,MAAMG,QAAQR,MAAMrE,GAAG,CAAC;IACxB,IAAI,CAAC8E,MAAMC,OAAO,CAACF,QAAQ,OAAO,EAAE;IACpC,OAAOA,MACJjF,GAAG,CAAC,CAACoF,OAAUA,QAAQ,OAAOA,SAAS,WAAW,AAACA,IAAgC,CAAC,OAAO,GAAG,MAC9FlE,MAAM,CAAC,CAAChB,OAAyB,OAAOA,SAAS,YAAYA,KAAKmB,MAAM,GAAG;AAChF;AAEA,8FAA8F,GAC9F,OAAO,eAAegE,iBACpB9F,SAAoB,EACpByD,KAAiE;IAEjE,MAAMsC,WAAWhG,gBAAgBC,WAAWyD,MAAMxD,MAAM,EAAEwD,MAAMvD,IAAI,EAAEC,GAAG,CAACsD,MAAM/C,EAAE;IAClF,MAAMwE,QAAQ,MAAMa,SAASlF,GAAG;IAChC,MAAMsE,YAAYD,MAAMK,MAAM,GAAGL,MAAMrE,GAAG,CAAC,eAAe;IAC1D,IAAI,OAAOsE,cAAc,YAAY,CAACA,WAAW,OAAO;IACxD,MAAMa,UAAU,MAAMD,SAASpG,UAAU,CAAC,YAAYQ,GAAG,CAACgF,WAAWtE,GAAG;IACxE,MAAMoF,QAAQD,QAAQT,MAAM,GAAG3G,kBAA4BoH,QAAQnF,GAAG,CAAC,YAAY;IACnF,OAAOoF,QAAQ;QAAEd;QAAWc;IAAM,IAAI;AACxC;AAyCA,6DAA6D,GAC7D,SAASC,YAAYhG,IAAiB,EAAEiG,IAA6B;IACnE,MAAMlE,UAAU,IAAI0C,IAAI3F,eAAe,CAACkB,KAAK;IAC7C,OAAO4D,OAAOC,WAAW,CACvBD,OAAOc,OAAO,CAACuB,MAAMxE,MAAM,CAAC,CAAC,CAACyE,KAAKvB,MAAM,GAAK5C,QAAQ6C,GAAG,CAACsB,QAAQvB,UAAUhD;AAEhF;AAEA,0FAA0F,GAC1F,SAASwE,gBAAgBJ,KAAe,EAAEK,UAAkB,EAAE3F,IAAY;QAGf4F;IAFzD,MAAMA,OAAON,KAAK,CAACK,WAAW;IAC9B,IAAI,CAACC,QAAQA,KAAKC,WAAW,KAAKzI,mBAAmB,OAAOkI;IAC5D,OAAO,aAAKA;QAAO,CAACK,WAAW,EAAE,aAAKC;YAAMb,OAAO,cAAMa,cAAAA,KAAKb,KAAK,YAAVa,cAAc,CAAC;gBAAIE,UAAU9F;;;;AACxF;AAEA;;;;;CAKC,GACD,OAAO,eAAe+F,aAAa1G,SAAoB,EAAEyD,KAAmB;IAC1E,MAAMkD,SAAS9H,kBAAkB4E,MAAMwC,KAAK;IAC5C,IAAI,CAACU,QAAQ,MAAM,IAAIC,MAAM;IAC7B,IAAInD,MAAMvD,IAAI,KAAK,UAAU,CAACuD,MAAMtE,IAAI,EAAE,MAAM,IAAIyH,MAAM;IAC1D,IAAInD,MAAMvD,IAAI,KAAK,eAAe,CAAEuD,CAAAA,MAAMoD,MAAM,IAAIpD,MAAMwC,KAAK,CAACxC,MAAMoD,MAAM,CAAC,AAAD,GAAI;QAC9E,MAAM,IAAID,MAAM;IAClB;IACA,MAAME,UAAU9G,UAAUL,UAAU,CAAC,SAASQ,GAAG,CAACsD,MAAMxD,MAAM;IAC9D,MAAMN,aAAaI,gBAAgBC,WAAWyD,MAAMxD,MAAM,EAAEwD,MAAMvD,IAAI;IACtE,MAAM6F,WAAWpG,WAAWQ,GAAG,CAACsD,MAAM/C,EAAE;IACxC,OAAOV,UAAU+G,cAAc,CAAC,OAAOC;QACrC,yDAAyD;QACzD,MAAM,CAAC7F,MAAM8F,UAAUC,SAAS,GAAG,MAAMtD,QAAQC,GAAG,CAAC;YACnDmD,GAAGnG,GAAG,CAACiG;YACPE,GAAGnG,GAAG,CAACkF;YACPiB,GAAGnG,GAAG,CAACT,WAAWT;SACnB;QACD,IAAI,CAACwB,KAAKoE,MAAM,EAAE,OAAO;YAAE4B,IAAI;YAAOC,QAAQ;YAAKC,OAAO;QAAe;QACzE,IAAIJ,SAAS1B,MAAM,EAAE;YACnB,OAAO;gBAAE4B,IAAI;gBAAMG,UAAU;eAASrC,SAASxB,MAAM/C,EAAE,EAAEuG,UAAU9F;QACrE;QACA,MAAMG,OAAOhB,YAAY4G;QACzB,oEAAoE;QACpE,MAAM1F,aAAaL,KAAKN,GAAG,CAAC;QAC5B,MAAMgC,UAAUxB,mBAAmBoC,MAAMvD,IAAI,EAAEoB,MAAMmC,MAAMlC,GAAG,EAAEC;QAChE,IAAIqB,SAAS,OAAO;YAAEsE,IAAI;YAAOC,QAAQ;YAAKC,OAAOxE;QAAQ;QAC7D,MAAMlC,OAAO7C,oBACX2F,MAAM9C,IAAI,EACVW,KAAKK,MAAM,CAAC,CAACC,MAAQ,CAACA,IAAIX,OAAO,EAAER,GAAG,CAAC,CAACmB,MAAQA,IAAIjB,IAAI;QAE1D,MAAM4G,SAAS;YAAEC,WAAW/D,MAAMgE,GAAG;YAAEC,WAAWjE,MAAMgE,GAAG;YAAEE,WAAWlE,MAAMmE,GAAG;QAAC;QAClF,MAAM3B,QAAQ4B,OAAOC,IAAI,CAACnB;QAC1B,IAAIxB,YAA2B;QAC/B,IAAI1B,MAAMvD,IAAI,KAAK,UAAU;YAC3BiF,YAAYtH;YACZmJ,GAAG3D,MAAM,CAAC0C,UAAU,aACfG,YAAY,UAAU;gBAAE6B,aAAapH;gBAAMwE;YAAU,IACrDjH,uBAAuB,WAAW;gBAAE6J,aAAapH;YAAK,IACtD4G;YAELP,GAAG3D,MAAM,CAAC0C,SAASpG,UAAU,CAAC,YAAYQ,GAAG,CAACgF,YAAY;gBACxD6C,UAAUvE,MAAM/C,EAAE;gBAClBT,QAAQwD,MAAMxD,MAAM;gBACpB8H,aAAavI;gBACbyG;eACGsB;QAEP,OAAO,IAAI9D,MAAMvD,IAAI,KAAK,UAAU;YAClCiF,YAAYtH;YACZ,MAAMiD,OAAOqD,kBACXV,MAAM3C,IAAI,EACVH,MACAW,MACAE,YACAL,KAAKN,GAAG,CAAC;YAEXmG,GAAG3D,MAAM,CAAC0C,UAAU,aACfG,YAAY,UAAU;gBAAE6B,aAAapH;gBAAMG;gBAAMqE;YAAU,IAC3DjH,uBAAuB,WAAW;gBAAE6J,aAAapH;YAAK,IACtD4G;YAELP,GAAG3D,MAAM,CAAC0C,SAASpG,UAAU,CAAC,YAAYQ,GAAG,CAACgF,YAAY;gBACxD8C,UAAUxE,MAAM/C,EAAE;gBAClBT,QAAQwD,MAAMxD,MAAM;gBACpB8H,aAAavI;gBACbyG;eACIxC,MAAMuE,QAAQ,GAAG;gBAAEA,UAAUvE,MAAMuE,QAAQ;YAAC,IAAI,CAAC,GAClDT;QAEP,OAAO,IAAI9D,MAAMvD,IAAI,KAAK,YAAY;YACpC8G,GAAG3D,MAAM,CAAC0C,UAAU,aACfG,YAAY,YAAY;gBACzBhG,MAAM;gBACN6H,aAAapH;gBACbsF;gBACAnF,MAAM2C,MAAM3C,IAAI,IAAIe;YACtB;gBACAqG,QAAQ;oBAAEC,MAAM;gBAAW;eACxBjK,uBAAuB,aAAa;gBACrCgC,MAAM;gBACN6H,aAAapH;gBACbuH,QAAQ;oBAAEC,MAAM;gBAAW;YAC7B,IACGZ;QAEP,OAAO,IAAI9D,MAAMvD,IAAI,KAAK,aAAa;gBAM1BuD;YALXuD,GAAG3D,MAAM,CAAC0C,UAAU,aACfG,YAAY,aAAa;gBAC1B6B,aAAapH;gBACbkG,QAAQpD,MAAMoD,MAAM;gBACpBZ;gBACAP,OAAOjC,EAAAA,eAAAA,MAAMiC,KAAK,qBAAXjC,aAAa3B,MAAM,IAAG;uBAAI2B,MAAMiC,KAAK;iBAAC,GAAG7D;YAClD,IACG3D,uBAAuB,cAAc;gBAAE6J,aAAapH;YAAK,IACzD4G;QAEP,OAAO;gBAEa1I,oBAQLM;YATb,MAAMA,OAAOsE,MAAMtE,IAAI;YACvB,MAAMiJ,aAAYvJ,qBAAAA,kBAAkBwH,gBAAgB5C,MAAMwC,KAAK,EAAE9G,KAAKmH,UAAU,EAAE3F,kBAAhE9B,qBAA0E8H;YAC5F,MAAM7F,OAAO7C,kBAAkB0C,SAAS8C,MAAM/C,EAAE;YAChDsG,GAAG3D,MAAM,CAAC0C,UAAU,aACfG,YAAY,QAAQ;gBACrB6B,aAAapH;gBACbG;gBACAuH,QAAQlJ,KAAKkJ,MAAM;gBACnBC,kBAAkBnJ,KAAKmJ,gBAAgB,IAAIzG;gBAC3C0G,OAAO,GAAEpJ,gBAAAA,KAAKoJ,OAAO,YAAZpJ,gBAAgB0C;gBACzBgF,QAAQ1H,KAAK0H,MAAM;gBACnBZ,OAAO4B,OAAOC,IAAI,CAACM;YACrB,IAEGpK,kBAAkB;gBAAE0C,IAAI+C,MAAM/C,EAAE;gBAAEqH,aAAapH;gBAAMG;gBAAMyH,SAASpJ,KAAKoJ,OAAO;YAAC,IACjFhB;QAEP;QACA,OAAO;YACLJ,IAAI;YACJG,UAAU;YACV5G,IAAI+C,MAAM/C,EAAE;YACZyE;YACAxE;YACA0E,eAAenE,YAAYC;QAC7B;IACF;AACF;AAMA;;;;;;CAMC,GACD,OAAO,eAAeqH,mBACpBxI,SAAoB,EACpByD,KAMC;IAED,MAAMsC,WAAWhG,gBAAgBC,WAAWyD,MAAMxD,MAAM,EAAEwD,MAAMvD,IAAI,EAAEC,GAAG,CAACsD,MAAM/C,EAAE;IAClF,OAAOV,UAAU+G,cAAc,CAAC,OAAOC;QACrC,MAAM9B,QAAQ,MAAM8B,GAAGnG,GAAG,CAACkF;QAC3B,MAAMZ,YAAYD,MAAMK,MAAM,IAAIL,MAAMrE,GAAG,CAAC,gBAAgB,OAAOqE,MAAMrE,GAAG,CAAC,eAAe;QAC5F,IAAI,OAAOsE,cAAc,YAAY,CAACA,WAAW;YAC/C,OAAO;gBAAEgC,IAAI;gBAAOC,QAAQ;gBAAKC,OAAO;YAAqC;QAC/E;QACA,MAAMoB,aAAa1C,SAASpG,UAAU,CAAC,YAAYQ,GAAG,CAACgF;QACvD,MAAMa,UAAU,MAAMgB,GAAGnG,GAAG,CAAC4H;QAC7B,MAAMxC,QAAQD,QAAQT,MAAM,GAAG3G,kBAA4BoH,QAAQnF,GAAG,CAAC,YAAY;QACnF,IAAI,CAACoF,OAAO,OAAO;YAAEkB,IAAI;YAAOC,QAAQ;YAAKC,OAAO;QAAqC;QACzF,MAAMqB,OAAOjF,MAAMkF,MAAM,CAAC1C;QAC1B,IAAI,CAACyC,MAAM,OAAO;YAAEvB,IAAI;YAAMyB,SAAS;QAAM;QAC7C,MAAMjC,SAAS9H,kBAAkB6J;QACjC,IAAI,CAAC/B,QAAQ,MAAM,IAAIC,MAAM;QAC7BI,GAAG2B,MAAM,CAACF,YAAY;YAAExC,OAAO4B,OAAOC,IAAI,CAACnB;YAASe,WAAWjE,MAAMgE,GAAG;QAAC;QACzE,OAAO;YAAEN,IAAI;YAAMyB,SAAS;QAAK;IACnC;AACF;AAEA;;;CAGC,GACD,OAAO,eAAeC,sBACpB7I,SAAoB,EACpByD,KAKC;IAED,MAAMqF,MAAM/I,gBAAgBC,WAAWyD,MAAMxD,MAAM,EAAE,UAAUE,GAAG,CAACsD,MAAM/C,EAAE;IAC3E,MAAMV,UAAU+G,cAAc,CAAC,OAAOC;QACpC,MAAM3H,SAAS,MAAM2H,GAAGnG,GAAG,CAACiI;QAC5B,IAAI,CAACzJ,OAAOkG,MAAM,IAAIlG,OAAOwB,GAAG,CAAC,gBAAgB,MAAM;QACvD,MAAMkI,QAAiC,CAAC;QACxC,KAAK,MAAM3C,OAAO;YAAC;YAAS;SAAc,CAAW;gBAC9B3C,gBACApE;YADrB,MAAMwF,QAAQjE,QAAO6C,iBAAAA,MAAM2B,GAAG,CAACgB,IAAI,YAAd3C,iBAAkB,IAAIuF,IAAI;YAC/C,IAAInE,SAAS,CAACjE,QAAOvB,cAAAA,OAAOwB,GAAG,CAAC,CAAC,IAAI,EAAEuF,KAAK,aAAvB/G,cAA4B,IAAI2J,IAAI,IAAID,KAAK,CAAC,CAAC,IAAI,EAAE3C,KAAK,CAAC,GAAGvB;QACrF;QACA,IAAIf,OAAOmF,IAAI,CAACF,OAAOjH,MAAM,EAAEkF,GAAG2B,MAAM,CAACG,KAAK,aAAKC;YAAOrB,WAAWjE,MAAMgE,GAAG;;IAChF;AACF;AAEA,kEAAkE,GAClE,OAAO,eAAeyB,gBAAgBlJ,SAAoB,EAAEC,MAAc;IACxE,OAAOiB,YAAY,MAAMlB,UAAUL,UAAU,CAAC,SAASQ,GAAG,CAACF,QAAQY,GAAG;AACxE;AAEA;;;;CAIC,GACD,OAAO,eAAesI,wBACpBnJ,SAAoB,EACpByD,KASC;IAED,IAAI,CAACA,MAAMxD,MAAM,EAAE;YAGawD;QAF9B,OAAO;YACL2D,QAAQ;YACRC,OAAO,CAAC,kBAAkB,GAAE5D,cAAAA,MAAM2F,IAAI,YAAV3F,cAAcA,MAAMvD,IAAI,CAAC,+BAA+B,CAAC;QACvF;IACF;IACA,MAAMmJ,QAAQ,MAAMvK,oBAAoB2E,MAAMxD,MAAM;IACpD,IAAI,CAACoJ,SAASA,UAAU5F,MAAM6F,KAAK,EAAE,OAAO;QAAElC,QAAQ;QAAKC,OAAO;IAAe;IACjF,MAAMkC,MAAM,OAAM9F,MAAM+F,QAAQ,oBAAd/F,MAAM+F,QAAQ,MAAd/F,OAAiBA,MAAMxD,MAAM;IAC/C,IAAIsJ,KAAK,OAAOA;IAChB,MAAM1G,UAAU,MAAMoB,wBAAwBjE,WAAW;QACvDE,MAAMuD,MAAMvD,IAAI;QAChBD,QAAQwD,MAAMxD,MAAM;QACpBsB,KAAKkC,MAAMlC,GAAG;IAChB;IACA,OAAOsB,UAAU;QAAEuE,QAAQ;QAAKC,OAAOxE;IAAQ,IAAI;AACrD"}
|
|
@@ -68,8 +68,6 @@ import type { AiJobStepRunner } from './ai-job-text-step';
|
|
|
68
68
|
*/
|
|
69
69
|
/** The resource the email plugin writes an email design under. */
|
|
70
70
|
export declare const AI_EMAIL_DESIGN_RESOURCE = "emailDesign";
|
|
71
|
-
/** The id the email plugin is registered under. */
|
|
72
|
-
export declare const AI_EMAIL_PLUGIN_ID = "email";
|
|
73
71
|
/** The longest answer an email may run to; the tree is held to the email budget either way. */
|
|
74
72
|
export declare const AI_JOB_EMAIL_MAX_TOKENS: number;
|
|
75
73
|
/**
|
|
@@ -66,7 +66,6 @@ import { registerAiJobStep } from "./ai-jobs.js";
|
|
|
66
66
|
* answer ceiling than the doctrine's default for an email, lowered on a tier
|
|
67
67
|
* too slow to answer it inside that time.
|
|
68
68
|
*/ /** The resource the email plugin writes an email design under. */ export const AI_EMAIL_DESIGN_RESOURCE = 'emailDesign';
|
|
69
|
-
/** The id the email plugin is registered under. */ export const AI_EMAIL_PLUGIN_ID = 'email';
|
|
70
69
|
/** The longest answer an email may run to; the tree is held to the email budget either way. */ export const AI_JOB_EMAIL_MAX_TOKENS = AI_ROUTING_TABLE['job.email'].maxTokens;
|
|
71
70
|
/**
|
|
72
71
|
* The time an email generation takes (AGL-3035, AGL-3036), for each step kind
|
|
@@ -423,7 +422,6 @@ function visitsOf(tree) {
|
|
|
423
422
|
drafts: [
|
|
424
423
|
{
|
|
425
424
|
resource: AI_EMAIL_DESIGN_RESOURCE,
|
|
426
|
-
pluginId: AI_EMAIL_PLUGIN_ID,
|
|
427
425
|
label: 'Email'
|
|
428
426
|
}
|
|
429
427
|
]
|
|
@@ -465,7 +463,7 @@ export function createAiJobEmailStep(deps = {}) {
|
|
|
465
463
|
firestore
|
|
466
464
|
}),
|
|
467
465
|
firestore.collection('orgs').doc(job.orgId).get(),
|
|
468
|
-
resolveAiEmailProducts(
|
|
466
|
+
resolveAiEmailProducts({
|
|
469
467
|
hostId,
|
|
470
468
|
brief: job.brief,
|
|
471
469
|
inputs: job.inputs
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-email-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n PluginDraftRecord,\n PluginResourceDraftWriter,\n} from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport type { AiJob, AiJobOutput, AiJobPlan } from '../model/ai-jobs.types'\nimport type { AiSiteInventory } from '../model/ai-site-inventory'\nimport { AI_STEP_TIERS, type AiStepKind } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n aiDoctrineTreeTool,\n runValidatedGeneration,\n type AiValidatedGeneration,\n type AiValidatedTree,\n} from '../runtime/ai-doctrine'\nimport {\n walkTree,\n type AiDoctrineNode,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport { AI_TEXT_LIMITS } from '../runtime/ai-palette'\nimport type { AiSystemBlock, AiTool } from '../runtime/ai-runtime'\nimport { readSiteInventory } from '../runtime/site-inventory'\nimport {\n aiEmailProductNote,\n resolveAiEmailProducts,\n type AiEmailProductBinding,\n} from './ai-email-bindings'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport { aiJobStepBudget, type AiJobStepBudget } from './ai-job-budget'\nimport { aiJobDraftId } from './ai-job-draft-ids'\nimport { aiSiteSubdomain } from './ai-job-drafts'\nimport {\n aiConfirmedPlan,\n aiDoctrineReview,\n aiGenerationSpent,\n aiJobBriefLine,\n aiLimitReview,\n aiModelNodeIds,\n aiPlanCreation,\n aiPlanReferenceLines,\n aiUnspentOutcome,\n} from './ai-job-generation'\nimport {\n aiPluginDraftAdmissionRefusal,\n aiPluginDraftWriter,\n type AiPluginDraftWriterLookup,\n} from './ai-job-plugin-drafts'\nimport type { AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The email step (AGL-2912): the generation step of an `email` job, run once a\n * member confirmed the job's plan, and the generation the campaign step\n * drafts its email with.\n *\n * It builds ONE email design from the email palette — sections holding text,\n * buttons, images, dividers, spacers and product blocks, never hand-written\n * HTML, which is not on the email surface — through\n * `runValidatedGeneration('email', …)`, so every building rule holds on the\n * tree, rule 6's brand and rule 17's clip size included. Beside the design the\n * answer carries three subject lines and three preheaders. The door's checks\n * ride `extend`:\n *\n * - every block sits in a section, every button links somewhere, and the\n * copy's only merge tokens are the recipient's own, in text;\n * - exactly as many product blocks as the job binds products;\n * - three different subject lines and preheaders, within their lengths and\n * free of merge tokens, which a subject line does not fill;\n * - and the design RENDERS: the email plugin's writer checks it through the\n * send path's own renderer, reached through the core's resource-drafts\n * seam, and a problem it names is a violation the model is re-asked with.\n *\n * What the model is sent is the brief, the email's name and kind, the\n * confirmed plan and the site inventory, and how many product blocks to place.\n * The products themselves are bound by id after it answers\n * (`ai-email-bindings.ts`); a link to one of the site's pages is completed\n * with the site's address token, which the send fills.\n *\n * The design lands through that same writer: a draft email screen, listed on\n * the Emails page's templates, that no campaign sends until a member picks it.\n * The job never sends, schedules or publishes anything.\n *\n * Like the other generation steps, it runs on the job beat, never at an inline\n * door: it registers the least time its lookup rounds, its answer and its\n * re-ask need (AGL-3035), and runs without extended thinking under a tighter\n * answer ceiling than the doctrine's default for an email, lowered on a tier\n * too slow to answer it inside that time.\n */\n\n/** The resource the email plugin writes an email design under. */\nexport const AI_EMAIL_DESIGN_RESOURCE = 'emailDesign'\n\n/** The id the email plugin is registered under. */\nexport const AI_EMAIL_PLUGIN_ID = 'email'\n\n/** The longest answer an email may run to; the tree is held to the email budget either way. */\nexport const AI_JOB_EMAIL_MAX_TOKENS = AI_ROUTING_TABLE['job.email'].maxTokens\n\n/**\n * The time an email generation takes (AGL-3035, AGL-3036), for each step kind\n * that runs one: its two inventory-lookup rounds, its answer and its re-ask at\n * the kind's routing ceiling on the tier the kind is served from, fitted to\n * what a beat can give a step. The email step registers the first, and the\n * campaign step — whose email is this same generation — the second.\n */\nexport const AI_JOB_EMAIL_STEP_BUDGETS: Readonly<\n Record<AiEmailGenerationInput['step'], AiJobStepBudget>\n> = {\n 'job.email': aiJobStepBudget({\n tier: AI_STEP_TIERS['job.email'],\n maxTokens: AI_ROUTING_TABLE['job.email'].maxTokens,\n }),\n 'job.campaign': aiJobStepBudget({\n tier: AI_STEP_TIERS['job.campaign'],\n maxTokens: AI_ROUTING_TABLE['job.campaign'].maxTokens,\n }),\n}\n\n/** The least time one email step needs before it starts. */\nexport const AI_JOB_EMAIL_STEP_MINIMUM_MS = AI_JOB_EMAIL_STEP_BUDGETS['job.email'].minimumMs\n\n/** How many subject lines, and how many preheaders, an email is written with. */\nexport const AI_EMAIL_VARIANTS = 3\n\n/** A subject line is a headline. */\nexport const AI_EMAIL_SUBJECT_MAX_CHARS = AI_TEXT_LIMITS.headline\n\n/** The longest preheader the campaign send route stores. */\nexport const AI_EMAIL_PREHEADER_MAX_CHARS = 200\n\n/** An email's name when neither the plan nor the job names one. */\nexport const AI_JOB_EMAIL_DEFAULT_NAME = 'New email'\n\n/** A job's failure when the site has no email design writer. */\nexport const AI_EMAIL_UNAVAILABLE_COPY = 'Email is not available on this site.'\n\n/** A job's failure when a design that passed every check could not be stored. */\nexport const AI_EMAIL_SAVE_FAILURE_COPY =\n 'The email was written but could not be saved. Try the job again.'\n\n/**\n * The kinds of email a job may carry, as `inputs.emailType`, and how the\n * prompt says each.\n *\n * A member picks one for an email of their own; a job the platform composes\n * sets one for itself, which is what `reply` is for (AGL-2918, named by the\n * site model's `AI_SITE_EMAIL_TYPE`). A scaffold's email answers one person\n * who wrote in, and none of the five campaign kinds says that: the nearest,\n * `welcome`, names a signup the site has not had, and a kind that\n * misdescribes the email is worse than no kind at all, because this line is\n * what the model reads first.\n *\n * Every label is the step's own words, so none of them may name a list, a\n * recipient or the people an email is sent to — the same rule the step's\n * instructions keep, held over the whole catalog by `ai-job-email-step.spec`.\n */\nexport const AI_EMAIL_TYPES = {\n welcome: 'a welcome email for someone who just signed up',\n newsletter: 'a newsletter',\n launch: 'a launch announcement',\n abandonedCart: 'a reminder about items left in a cart',\n eventReminder: 'a reminder about an upcoming event',\n reply: 'a reply to somebody who got in touch through the site',\n} as const\n\nexport type AiEmailType = keyof typeof AI_EMAIL_TYPES\n\n/** The merge tokens a generated email's copy may carry: the recipient's own, which each send fills. */\nexport const AI_EMAIL_TEXT_MERGE_TOKENS: readonly string[] = [\n 'contact.firstName',\n 'contact.name',\n 'contact.email',\n]\n\n/** The token the send fills with the site's address, set in front of a link to one of its pages. */\nexport const AI_EMAIL_SITE_URL_TOKEN = '{{site.url}}'\n\n/** The email step's own instructions, cached after the doctrine. */\nexport const AI_JOB_EMAIL_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n 'You write one marketing email for the owner of a website: its design as one flat node map, three subject lines and three preheaders. Answer with submit_email.',\n 'The root is the document wrapper (div, id \"_@_\") and holds only Email sections (emailSection). Each section is a band holding the email’s blocks from top to bottom: Email text (emailText), Email buttons (emailButton), Email images (emailImage), Email dividers (emailDivider), Email spacers (emailSpacer) and, only when the job binds products, Email product blocks (emailProduct).',\n 'Open with a heading (emailText with variant heading), keep each paragraph short, and give the email one main call to action as a button. Greet the reader by first name with {{contact.firstName}} where a greeting fits. The only merge tokens are {{contact.firstName}}, {{contact.name}} and {{contact.email}}, and only in text: never in a link, a subject line or a preheader.',\n 'Every button links somewhere: a page of the site by its address from the site inventory, such as /menu, which is completed with the site’s domain when the email is sent, or a full https address the brief gives.',\n 'An image has an empty src for the owner to fill from the media library, alt text describing the picture it should show, and a width of 600 or less.',\n 'Place exactly as many Email product blocks as the job binds products, one for each, in the order the brief names them. Each shows its product’s current name, price and picture when the email is sent, so never type a product’s price.',\n 'Leave out an unsubscribe link and a postal address: the platform adds the unsubscribe link to every campaign email. A fact the brief does not give, such as a date or a place, goes in square brackets.',\n `Subject lines: three that take different approaches, such as direct, curious and benefit-led, each at most ${AI_EMAIL_SUBJECT_MAX_CHARS} characters and best under 60, with no merge token. Preheaders: three, each extending the subject line in the same position, at most ${AI_EMAIL_PREHEADER_MAX_CHARS} characters and best between 40 and 110.`,\n ].join('\\n'),\n },\n]\n\nconst DOCTRINE_EMAIL_TOOL = aiDoctrineTreeTool('email')\n\n/** The doctrine's email tree tool, with the subject lines and preheaders beside the tree. */\nexport const AI_JOB_EMAIL_TOOL: AiTool = {\n name: DOCTRINE_EMAIL_TOOL.name,\n description:\n 'Submit the email: its design as one flat node map, three subject lines and three preheaders.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['tree', 'subjects', 'preheaders'],\n properties: {\n tree: (DOCTRINE_EMAIL_TOOL.inputSchema['properties'] as Record<string, unknown>)['tree'],\n subjects: {\n type: 'array',\n items: { type: 'string' },\n description: 'Three subject lines that take different approaches, the strongest first.',\n },\n preheaders: {\n type: 'array',\n items: { type: 'string' },\n description: 'Three preheaders, each extending the subject line in the same position.',\n },\n },\n },\n}\n\n/** How the prompt names the kind of email a member picked; `null` for none or an unknown one. */\nexport function aiEmailTypeLabel(value: unknown): string | null {\n return typeof value === 'string' && Object.prototype.hasOwnProperty.call(AI_EMAIL_TYPES, value)\n ? AI_EMAIL_TYPES[value as AiEmailType]\n : null\n}\n\n/** A name the member gave the job, on one line and bounded; empty when none. */\nexport function aiJobInputName(job: Pick<AiJob, 'inputs'>): string {\n const raw = job.inputs?.['name']\n return typeof raw === 'string' ? raw.replace(/\\s+/g, ' ').trim().slice(0, 120) : ''\n}\n\n/** The email job as the generation's user turn. */\nexport function aiJobEmailPrompt(\n job: Pick<AiJob, 'brief' | 'inputs'>,\n plan: AiJobPlan | null,\n heading: string | null,\n products: number,\n): string {\n const lines = heading ? [heading] : []\n const type = aiEmailTypeLabel(job.inputs?.['emailType'])\n if (type) lines.push(`Kind of email: ${type}`)\n lines.push(aiJobBriefLine(job), ...aiPlanReferenceLines(plan))\n lines.push(\n products > 0\n ? `Products bound to this email: ${products}. Place exactly ${products} Email product ${\n products === 1 ? 'block' : 'blocks'\n }.`\n : 'No products are bound to this email: place no Email product block.',\n )\n return lines.join('\\n')\n}\n\n/** The subject lines and preheaders an answer carries. */\nexport interface AiEmailCopy {\n subjects: string[]\n preheaders: string[]\n}\n\nfunction oneLine(value: string): string {\n return value.replace(/\\s+/g, ' ').trim()\n}\n\nexport function parseAiEmailCopy(answer: Readonly<Record<string, unknown>>): AiEmailCopy {\n const lines = (value: unknown) =>\n Array.isArray(value)\n ? value.filter((entry): entry is string => typeof entry === 'string').map(oneLine)\n : []\n return { subjects: lines(answer['subjects']), preheaders: lines(answer['preheaders']) }\n}\n\n/** The copy's own rules: three different lines of each, within their lengths, with no merge token. */\nexport function aiEmailCopyViolations(copy: AiEmailCopy): AiDoctrineViolation[] {\n const violations: AiDoctrineViolation[] = []\n const distinct = (lines: string[]) =>\n new Set(lines.filter(Boolean).map((line) => line.toLowerCase())).size\n if (copy.subjects.length !== AI_EMAIL_VARIANTS || distinct(copy.subjects) !== AI_EMAIL_VARIANTS) {\n violations.push({\n rule: null,\n code: 'email-subjects',\n message: `Write exactly ${AI_EMAIL_VARIANTS} different subject lines.`,\n })\n }\n if (copy.preheaders.length !== AI_EMAIL_VARIANTS || distinct(copy.preheaders) !== AI_EMAIL_VARIANTS) {\n violations.push({\n rule: null,\n code: 'email-preheaders',\n message: `Write exactly ${AI_EMAIL_VARIANTS} different preheaders, one for each subject line.`,\n })\n }\n if (copy.subjects.some((line) => line.length > AI_EMAIL_SUBJECT_MAX_CHARS)) {\n violations.push({\n rule: null,\n code: 'email-subject-length',\n message: `A subject line is over ${AI_EMAIL_SUBJECT_MAX_CHARS} characters. Keep each within ${AI_EMAIL_SUBJECT_MAX_CHARS}, and best under 60.`,\n })\n }\n if (copy.preheaders.some((line) => line.length > AI_EMAIL_PREHEADER_MAX_CHARS)) {\n violations.push({\n rule: null,\n code: 'email-preheader-length',\n message: `A preheader is over ${AI_EMAIL_PREHEADER_MAX_CHARS} characters. Keep each within ${AI_EMAIL_PREHEADER_MAX_CHARS}.`,\n })\n }\n if ([...copy.subjects, ...copy.preheaders].some((line) => line.includes('{{'))) {\n violations.push({\n rule: null,\n code: 'email-header-token',\n message:\n 'A subject line or preheader carries a merge token, which is not filled there. Write them without {{…}}.',\n })\n }\n return violations\n}\n\nconst MERGE_TOKEN = /\\{\\{\\s*([^}]*?)\\s*\\}\\}/g\n\nfunction visitsOf(tree: Pick<AiValidatedTree, 'rootId' | 'nodes'>) {\n return walkTree({\n rootId: tree.rootId,\n nodes: tree.nodes as unknown as Record<string, AiDoctrineNode>,\n })\n}\n\n/** The design's own rules, on a tree the doctrine admitted. */\nexport function aiEmailTreeViolations(tree: AiValidatedTree, products: number): AiDoctrineViolation[] {\n const violations: AiDoctrineViolation[] = []\n const loose = (tree.nodes[tree.rootId]?.nodes ?? []).filter(\n (id) => tree.nodes[id]?.componentId !== 'emailSection',\n )\n if (loose.length) {\n violations.push({\n rule: null,\n code: 'email-loose-block',\n message: 'A block sits outside every Email section. Put each block inside a section.',\n nodeIds: aiModelNodeIds(loose, tree.sourceIds),\n })\n }\n const tokens = new Set<string>()\n const tokenNodes: string[] = []\n const unlinked: string[] = []\n let placed = 0\n for (const { id, node } of visitsOf(tree)) {\n for (const value of Object.values(node.props ?? {})) {\n if (typeof value !== 'string') continue\n for (const match of value.matchAll(MERGE_TOKEN)) {\n if (!AI_EMAIL_TEXT_MERGE_TOKENS.includes(match[1])) {\n tokens.add(`{{${match[1]}}}`)\n tokenNodes.push(id)\n }\n }\n }\n if (node.componentId === 'emailButton' && typeof node.props?.['href'] !== 'string') {\n unlinked.push(id)\n }\n if (node.componentId === 'emailProduct') placed += 1\n }\n if (tokens.size) {\n violations.push({\n rule: null,\n code: 'email-merge-token',\n message: `This email writes ${[...tokens].slice(0, 3).join(', ')}, which a generated email may not use. Use only {{contact.firstName}}, {{contact.name}} or {{contact.email}}, and only in text.`,\n nodeIds: aiModelNodeIds([...new Set(tokenNodes)], tree.sourceIds),\n })\n }\n if (unlinked.length) {\n violations.push({\n rule: null,\n code: 'email-button-link',\n message:\n 'An Email button links nowhere. Link each to a page of the site by its address, such as /menu, or to a full https address the brief gives.',\n nodeIds: aiModelNodeIds(unlinked, tree.sourceIds),\n })\n }\n if (placed !== products) {\n violations.push({\n rule: null,\n code: 'email-product-count',\n message: products\n ? `This email binds ${products} ${products === 1 ? 'product' : 'products'}: place exactly ${products} Email product ${\n products === 1 ? 'block' : 'blocks'\n }.`\n : 'No products are bound to this email: remove the Email product blocks.',\n })\n }\n return violations\n}\n\n/**\n * The design as it is stored: a link to one of the site's pages completed with\n * the site's address token, and each product block bound, in document order,\n * to the next product the job binds.\n */\nexport function aiEmailDesignNodes(\n tree: Pick<AiValidatedTree, 'rootId' | 'nodes'>,\n productIds: readonly string[],\n): NodesMap {\n const nodes: NodesMap = { ...tree.nodes }\n let next = 0\n for (const { id, node } of visitsOf(tree)) {\n const props: Record<string, unknown> = { ...(node.props ?? {}) }\n let changed = false\n const href = props['href']\n if (typeof href === 'string' && href.startsWith('/') && !href.startsWith('//')) {\n props['href'] = `${AI_EMAIL_SITE_URL_TOKEN}${href}`\n changed = true\n }\n if (node.componentId === 'emailProduct') {\n const productId = productIds[next]\n next += 1\n if (productId) {\n props['productId'] = productId\n changed = true\n }\n }\n if (changed) nodes[id] = { ...nodes[id], props } as NodesMap[string]\n }\n return nodes\n}\n\n/** The content the email plugin's writer stores a design from. */\nexport function aiEmailDesignContent(nodes: NodesMap, copy: AiEmailCopy): Record<string, unknown> {\n return {\n nodes,\n subject: copy.subjects[0] ?? '',\n preheader: copy.preheaders[0] ?? '',\n subjectVariants: copy.subjects,\n preheaderVariants: copy.preheaders,\n }\n}\n\n/** What the email plugin's renderer says about the design, as violations the model is re-asked with. */\nexport function aiEmailRenderViolations(\n design: PluginResourceDraftWriter,\n hostId: string,\n nodes: NodesMap,\n copy: AiEmailCopy,\n): AiDoctrineViolation[] {\n const checked = design.check(aiEmailDesignContent(nodes, copy), { hostId })\n if (checked.ok === true) return []\n return [...new Set(checked.problems)].map((problem) => ({\n rule: null,\n code: 'email-render',\n message: `The email does not render as written: ${problem}.`,\n }))\n}\n\nexport interface AiEmailGenerationInput {\n job: AiJob\n step: Extract<AiStepKind, 'job.email' | 'job.campaign'>\n model: string\n /** The line naming what is built, such as `Email name: …`; `null` for none. */\n heading: string | null\n plan: AiJobPlan | null\n inventory: AiSiteInventory | null\n hostId: string\n products: AiEmailProductBinding\n /** The email plugin's design writer, whose check the design must pass. */\n design: PluginResourceDraftWriter\n signal?: AbortSignal\n}\n\nexport interface AiEmailGeneration {\n result: AiValidatedGeneration<AiValidatedTree>\n /** The copy of the answer the doctrine kept; `null` unless it kept one. */\n copy: AiEmailCopy | null\n /** The kept design as it is stored; `null` unless the doctrine kept one. */\n nodes: NodesMap | null\n}\n\n/** Generates one email under the doctrine, with the email's own checks riding `extend`. */\nexport async function generateAiEmail(input: AiEmailGenerationInput): Promise<AiEmailGeneration> {\n // The answer `extend` saw last is the one the doctrine kept when it keeps one.\n let last = null as { copy: AiEmailCopy; nodes: NodesMap } | null\n const result = await runValidatedGeneration('email', {\n step: input.step,\n model: input.model,\n instructions: AI_JOB_EMAIL_INSTRUCTIONS,\n inventory: input.inventory,\n messages: [\n {\n role: 'user',\n content: aiJobEmailPrompt(input.job, input.plan, input.heading, input.products.ids.length),\n },\n ],\n tool: AI_JOB_EMAIL_TOOL,\n maxTokens: AI_JOB_EMAIL_STEP_BUDGETS[input.step].maxTokens(input.model),\n thinking: 'off',\n extend: (tree, answer) => {\n const copy = parseAiEmailCopy(answer)\n const nodes = aiEmailDesignNodes(tree, input.products.ids)\n last = { copy, nodes }\n const violations = [\n ...aiEmailTreeViolations(tree, input.products.ids.length),\n ...aiEmailCopyViolations(copy),\n ]\n // Rendered once the design's own rules hold, so a problem is named once.\n return violations.length\n ? violations\n : aiEmailRenderViolations(input.design, input.hostId, nodes, copy)\n },\n ...(input.signal ? { signal: input.signal } : {}),\n })\n const kept = result.status === 'ok' ? last : null\n return { result, copy: kept?.copy ?? null, nodes: kept?.nodes ?? null }\n}\n\n/** An email design as the job's output. */\nexport function aiEmailDesignOutput(\n record: PluginDraftRecord,\n place: { hostId: string; hostSubdomain: string | null },\n note: string | null,\n): AiJobOutput {\n return {\n resource: 'emailScreen',\n id: record.id,\n versionId: record.versionId,\n hostId: place.hostId,\n hostSubdomain: place.hostSubdomain,\n label: record.name,\n ...(note ? { note } : {}),\n }\n}\n\n/** An email job is admitted for a site of its org with email on and room for a design. */\nexport const aiEmailJobAdmission: AiJobAdmission = (context) =>\n aiPluginDraftAdmissionRefusal(context, {\n kind: 'email',\n drafts: [{ resource: AI_EMAIL_DESIGN_RESOURCE, pluginId: AI_EMAIL_PLUGIN_ID, label: 'Email' }],\n })\n\nexport interface AiJobEmailStepDeps {\n /** The inventory reader; specs hand in a fake. */\n readInventory?: typeof readSiteInventory\n /** How a resource's draft writer is found; the core's registry otherwise. */\n writerFor?: AiPluginDraftWriterLookup\n}\n\nexport function createAiJobEmailStep(deps: AiJobEmailStepDeps = {}): AiJobStepRunner {\n const readInventory = deps.readInventory ?? readSiteInventory\n const writerFor = deps.writerFor ?? aiPluginDraftWriter\n return async ({ job, now, signal, firestore, modelFor }): Promise<AiJobStepOutcome> => {\n const hostId = job.hostId\n if (!hostId) throw new Error('an email job names no site, and its admission refuses one')\n // The switch's answer for this job, else the routing table's (AGL-2942).\n const model = modelFor?.('job.email') ?? aiModelForStep('job.email')\n const design = writerFor(AI_EMAIL_DESIGN_RESOURCE)\n if (!design) return { ...aiUnspentOutcome(model), failure: AI_EMAIL_UNAVAILABLE_COPY }\n\n // A run cut off after its design was written reports that design, found by the id the job recorded.\n const draftId = aiJobDraftId(job, 'email')\n const written = await design.read({ hostId, id: draftId })\n if (written) {\n const hostSubdomain = await aiSiteSubdomain(firestore, hostId)\n return aiUnspentOutcome(model, {\n outputs: [aiEmailDesignOutput(written, { hostId, hostSubdomain }, null)],\n })\n }\n\n const [inventory, orgSnapshot, products, hostSubdomain] = await Promise.all([\n readInventory(job.orgId, hostId, { firestore }),\n firestore.collection('orgs').doc(job.orgId).get(),\n resolveAiEmailProducts(firestore, { hostId, brief: job.brief, inputs: job.inputs }),\n aiSiteSubdomain(firestore, hostId),\n ])\n const org = (orgSnapshot.data() ?? null) as Record<string, unknown> | null\n const context = { orgId: job.orgId, hostId, uid: job.createdBy, org, now }\n const refusal = await design.refusal(context)\n if (refusal) {\n return refusal.status === 403\n ? aiUnspentOutcome(model, { review: aiLimitReview(refusal.error) })\n : { ...aiUnspentOutcome(model), failure: refusal.error }\n }\n\n const plan = aiConfirmedPlan(job)\n const name =\n aiPlanCreation(plan, 'email')?.name || aiJobInputName(job) || AI_JOB_EMAIL_DEFAULT_NAME\n const generated = await generateAiEmail({\n job,\n step: 'job.email',\n model,\n heading: `Email name: ${name}`,\n plan,\n inventory,\n hostId,\n products,\n design,\n ...(signal ? { signal } : {}),\n })\n const { result } = generated\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result) }\n if (!generated.copy || !generated.nodes) return { ...spent, failure: AI_EMAIL_SAVE_FAILURE_COPY }\n\n const write = await design.write({\n ...context,\n id: draftId,\n name,\n content: aiEmailDesignContent(generated.nodes, generated.copy),\n })\n if (write.ok === false) {\n return write.status === 403\n ? { ...spent, review: aiLimitReview(write.error) }\n : { ...spent, failure: AI_EMAIL_SAVE_FAILURE_COPY }\n }\n return {\n ...spent,\n outputs: [aiEmailDesignOutput(write, { hostId, hostSubdomain }, aiEmailProductNote(products))],\n }\n }\n}\n\nexport const runAiJobEmailStep = createAiJobEmailStep()\n\n/** Registers the email step and the check a email job passes before it is created or resumed. */\nexport function registerAiEmailJob(): void {\n registerAiJobStep('email', runAiJobEmailStep, { minimumMs: AI_JOB_EMAIL_STEP_MINIMUM_MS })\n registerAiJobAdmission('email', aiEmailJobAdmission)\n}\n"],"names":["AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","aiDoctrineTreeTool","runValidatedGeneration","walkTree","AI_TEXT_LIMITS","readSiteInventory","aiEmailProductNote","resolveAiEmailProducts","registerAiJobAdmission","aiJobStepBudget","aiJobDraftId","aiSiteSubdomain","aiConfirmedPlan","aiDoctrineReview","aiGenerationSpent","aiJobBriefLine","aiLimitReview","aiModelNodeIds","aiPlanCreation","aiPlanReferenceLines","aiUnspentOutcome","aiPluginDraftAdmissionRefusal","aiPluginDraftWriter","registerAiJobStep","AI_EMAIL_DESIGN_RESOURCE","AI_EMAIL_PLUGIN_ID","AI_JOB_EMAIL_MAX_TOKENS","maxTokens","AI_JOB_EMAIL_STEP_BUDGETS","tier","AI_JOB_EMAIL_STEP_MINIMUM_MS","minimumMs","AI_EMAIL_VARIANTS","AI_EMAIL_SUBJECT_MAX_CHARS","headline","AI_EMAIL_PREHEADER_MAX_CHARS","AI_JOB_EMAIL_DEFAULT_NAME","AI_EMAIL_UNAVAILABLE_COPY","AI_EMAIL_SAVE_FAILURE_COPY","AI_EMAIL_TYPES","welcome","newsletter","launch","abandonedCart","eventReminder","reply","AI_EMAIL_TEXT_MERGE_TOKENS","AI_EMAIL_SITE_URL_TOKEN","AI_JOB_EMAIL_INSTRUCTIONS","text","join","DOCTRINE_EMAIL_TOOL","AI_JOB_EMAIL_TOOL","name","description","strict","inputSchema","type","additionalProperties","required","properties","tree","subjects","items","preheaders","aiEmailTypeLabel","value","Object","prototype","hasOwnProperty","call","aiJobInputName","job","raw","inputs","replace","trim","slice","aiJobEmailPrompt","plan","heading","products","lines","push","oneLine","parseAiEmailCopy","answer","Array","isArray","filter","entry","map","aiEmailCopyViolations","copy","violations","distinct","Set","Boolean","line","toLowerCase","size","length","rule","code","message","some","includes","MERGE_TOKEN","visitsOf","rootId","nodes","aiEmailTreeViolations","loose","id","componentId","nodeIds","sourceIds","tokens","tokenNodes","unlinked","placed","node","values","props","match","matchAll","add","aiEmailDesignNodes","productIds","next","changed","href","startsWith","productId","aiEmailDesignContent","subject","preheader","subjectVariants","preheaderVariants","aiEmailRenderViolations","design","hostId","checked","check","ok","problems","problem","generateAiEmail","input","last","result","step","model","instructions","inventory","messages","role","content","ids","tool","thinking","extend","signal","kept","status","aiEmailDesignOutput","record","place","note","resource","versionId","hostSubdomain","label","aiEmailJobAdmission","context","kind","drafts","pluginId","createAiJobEmailStep","deps","readInventory","writerFor","now","firestore","modelFor","orgSnapshot","Error","failure","draftId","written","read","outputs","Promise","all","orgId","collection","doc","get","brief","org","data","uid","createdBy","refusal","review","error","generated","spent","refused","write","runAiJobEmailStep","registerAiEmailJob"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-email-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n PluginDraftRecord,\n PluginResourceDraftWriter,\n} from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport type { AiJob, AiJobOutput, AiJobPlan } from '../model/ai-jobs.types'\nimport type { AiSiteInventory } from '../model/ai-site-inventory'\nimport { AI_STEP_TIERS, type AiStepKind } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n aiDoctrineTreeTool,\n runValidatedGeneration,\n type AiValidatedGeneration,\n type AiValidatedTree,\n} from '../runtime/ai-doctrine'\nimport {\n walkTree,\n type AiDoctrineNode,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport { AI_TEXT_LIMITS } from '../runtime/ai-palette'\nimport type { AiSystemBlock, AiTool } from '../runtime/ai-runtime'\nimport { readSiteInventory } from '../runtime/site-inventory'\nimport {\n aiEmailProductNote,\n resolveAiEmailProducts,\n type AiEmailProductBinding,\n} from './ai-email-bindings'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport { aiJobStepBudget, type AiJobStepBudget } from './ai-job-budget'\nimport { aiJobDraftId } from './ai-job-draft-ids'\nimport { aiSiteSubdomain } from './ai-job-drafts'\nimport {\n aiConfirmedPlan,\n aiDoctrineReview,\n aiGenerationSpent,\n aiJobBriefLine,\n aiLimitReview,\n aiModelNodeIds,\n aiPlanCreation,\n aiPlanReferenceLines,\n aiUnspentOutcome,\n} from './ai-job-generation'\nimport {\n aiPluginDraftAdmissionRefusal,\n aiPluginDraftWriter,\n type AiPluginDraftWriterLookup,\n} from './ai-job-plugin-drafts'\nimport type { AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The email step (AGL-2912): the generation step of an `email` job, run once a\n * member confirmed the job's plan, and the generation the campaign step\n * drafts its email with.\n *\n * It builds ONE email design from the email palette — sections holding text,\n * buttons, images, dividers, spacers and product blocks, never hand-written\n * HTML, which is not on the email surface — through\n * `runValidatedGeneration('email', …)`, so every building rule holds on the\n * tree, rule 6's brand and rule 17's clip size included. Beside the design the\n * answer carries three subject lines and three preheaders. The door's checks\n * ride `extend`:\n *\n * - every block sits in a section, every button links somewhere, and the\n * copy's only merge tokens are the recipient's own, in text;\n * - exactly as many product blocks as the job binds products;\n * - three different subject lines and preheaders, within their lengths and\n * free of merge tokens, which a subject line does not fill;\n * - and the design RENDERS: the email plugin's writer checks it through the\n * send path's own renderer, reached through the core's resource-drafts\n * seam, and a problem it names is a violation the model is re-asked with.\n *\n * What the model is sent is the brief, the email's name and kind, the\n * confirmed plan and the site inventory, and how many product blocks to place.\n * The products themselves are bound by id after it answers\n * (`ai-email-bindings.ts`); a link to one of the site's pages is completed\n * with the site's address token, which the send fills.\n *\n * The design lands through that same writer: a draft email screen, listed on\n * the Emails page's templates, that no campaign sends until a member picks it.\n * The job never sends, schedules or publishes anything.\n *\n * Like the other generation steps, it runs on the job beat, never at an inline\n * door: it registers the least time its lookup rounds, its answer and its\n * re-ask need (AGL-3035), and runs without extended thinking under a tighter\n * answer ceiling than the doctrine's default for an email, lowered on a tier\n * too slow to answer it inside that time.\n */\n\n/** The resource the email plugin writes an email design under. */\nexport const AI_EMAIL_DESIGN_RESOURCE = 'emailDesign'\n\n/** The longest answer an email may run to; the tree is held to the email budget either way. */\nexport const AI_JOB_EMAIL_MAX_TOKENS = AI_ROUTING_TABLE['job.email'].maxTokens\n\n/**\n * The time an email generation takes (AGL-3035, AGL-3036), for each step kind\n * that runs one: its two inventory-lookup rounds, its answer and its re-ask at\n * the kind's routing ceiling on the tier the kind is served from, fitted to\n * what a beat can give a step. The email step registers the first, and the\n * campaign step — whose email is this same generation — the second.\n */\nexport const AI_JOB_EMAIL_STEP_BUDGETS: Readonly<\n Record<AiEmailGenerationInput['step'], AiJobStepBudget>\n> = {\n 'job.email': aiJobStepBudget({\n tier: AI_STEP_TIERS['job.email'],\n maxTokens: AI_ROUTING_TABLE['job.email'].maxTokens,\n }),\n 'job.campaign': aiJobStepBudget({\n tier: AI_STEP_TIERS['job.campaign'],\n maxTokens: AI_ROUTING_TABLE['job.campaign'].maxTokens,\n }),\n}\n\n/** The least time one email step needs before it starts. */\nexport const AI_JOB_EMAIL_STEP_MINIMUM_MS = AI_JOB_EMAIL_STEP_BUDGETS['job.email'].minimumMs\n\n/** How many subject lines, and how many preheaders, an email is written with. */\nexport const AI_EMAIL_VARIANTS = 3\n\n/** A subject line is a headline. */\nexport const AI_EMAIL_SUBJECT_MAX_CHARS = AI_TEXT_LIMITS.headline\n\n/** The longest preheader the campaign send route stores. */\nexport const AI_EMAIL_PREHEADER_MAX_CHARS = 200\n\n/** An email's name when neither the plan nor the job names one. */\nexport const AI_JOB_EMAIL_DEFAULT_NAME = 'New email'\n\n/** A job's failure when the site has no email design writer. */\nexport const AI_EMAIL_UNAVAILABLE_COPY = 'Email is not available on this site.'\n\n/** A job's failure when a design that passed every check could not be stored. */\nexport const AI_EMAIL_SAVE_FAILURE_COPY =\n 'The email was written but could not be saved. Try the job again.'\n\n/**\n * The kinds of email a job may carry, as `inputs.emailType`, and how the\n * prompt says each.\n *\n * A member picks one for an email of their own; a job the platform composes\n * sets one for itself, which is what `reply` is for (AGL-2918, named by the\n * site model's `AI_SITE_EMAIL_TYPE`). A scaffold's email answers one person\n * who wrote in, and none of the five campaign kinds says that: the nearest,\n * `welcome`, names a signup the site has not had, and a kind that\n * misdescribes the email is worse than no kind at all, because this line is\n * what the model reads first.\n *\n * Every label is the step's own words, so none of them may name a list, a\n * recipient or the people an email is sent to — the same rule the step's\n * instructions keep, held over the whole catalog by `ai-job-email-step.spec`.\n */\nexport const AI_EMAIL_TYPES = {\n welcome: 'a welcome email for someone who just signed up',\n newsletter: 'a newsletter',\n launch: 'a launch announcement',\n abandonedCart: 'a reminder about items left in a cart',\n eventReminder: 'a reminder about an upcoming event',\n reply: 'a reply to somebody who got in touch through the site',\n} as const\n\nexport type AiEmailType = keyof typeof AI_EMAIL_TYPES\n\n/** The merge tokens a generated email's copy may carry: the recipient's own, which each send fills. */\nexport const AI_EMAIL_TEXT_MERGE_TOKENS: readonly string[] = [\n 'contact.firstName',\n 'contact.name',\n 'contact.email',\n]\n\n/** The token the send fills with the site's address, set in front of a link to one of its pages. */\nexport const AI_EMAIL_SITE_URL_TOKEN = '{{site.url}}'\n\n/** The email step's own instructions, cached after the doctrine. */\nexport const AI_JOB_EMAIL_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n 'You write one marketing email for the owner of a website: its design as one flat node map, three subject lines and three preheaders. Answer with submit_email.',\n 'The root is the document wrapper (div, id \"_@_\") and holds only Email sections (emailSection). Each section is a band holding the email’s blocks from top to bottom: Email text (emailText), Email buttons (emailButton), Email images (emailImage), Email dividers (emailDivider), Email spacers (emailSpacer) and, only when the job binds products, Email product blocks (emailProduct).',\n 'Open with a heading (emailText with variant heading), keep each paragraph short, and give the email one main call to action as a button. Greet the reader by first name with {{contact.firstName}} where a greeting fits. The only merge tokens are {{contact.firstName}}, {{contact.name}} and {{contact.email}}, and only in text: never in a link, a subject line or a preheader.',\n 'Every button links somewhere: a page of the site by its address from the site inventory, such as /menu, which is completed with the site’s domain when the email is sent, or a full https address the brief gives.',\n 'An image has an empty src for the owner to fill from the media library, alt text describing the picture it should show, and a width of 600 or less.',\n 'Place exactly as many Email product blocks as the job binds products, one for each, in the order the brief names them. Each shows its product’s current name, price and picture when the email is sent, so never type a product’s price.',\n 'Leave out an unsubscribe link and a postal address: the platform adds the unsubscribe link to every campaign email. A fact the brief does not give, such as a date or a place, goes in square brackets.',\n `Subject lines: three that take different approaches, such as direct, curious and benefit-led, each at most ${AI_EMAIL_SUBJECT_MAX_CHARS} characters and best under 60, with no merge token. Preheaders: three, each extending the subject line in the same position, at most ${AI_EMAIL_PREHEADER_MAX_CHARS} characters and best between 40 and 110.`,\n ].join('\\n'),\n },\n]\n\nconst DOCTRINE_EMAIL_TOOL = aiDoctrineTreeTool('email')\n\n/** The doctrine's email tree tool, with the subject lines and preheaders beside the tree. */\nexport const AI_JOB_EMAIL_TOOL: AiTool = {\n name: DOCTRINE_EMAIL_TOOL.name,\n description:\n 'Submit the email: its design as one flat node map, three subject lines and three preheaders.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['tree', 'subjects', 'preheaders'],\n properties: {\n tree: (DOCTRINE_EMAIL_TOOL.inputSchema['properties'] as Record<string, unknown>)['tree'],\n subjects: {\n type: 'array',\n items: { type: 'string' },\n description: 'Three subject lines that take different approaches, the strongest first.',\n },\n preheaders: {\n type: 'array',\n items: { type: 'string' },\n description: 'Three preheaders, each extending the subject line in the same position.',\n },\n },\n },\n}\n\n/** How the prompt names the kind of email a member picked; `null` for none or an unknown one. */\nexport function aiEmailTypeLabel(value: unknown): string | null {\n return typeof value === 'string' && Object.prototype.hasOwnProperty.call(AI_EMAIL_TYPES, value)\n ? AI_EMAIL_TYPES[value as AiEmailType]\n : null\n}\n\n/** A name the member gave the job, on one line and bounded; empty when none. */\nexport function aiJobInputName(job: Pick<AiJob, 'inputs'>): string {\n const raw = job.inputs?.['name']\n return typeof raw === 'string' ? raw.replace(/\\s+/g, ' ').trim().slice(0, 120) : ''\n}\n\n/** The email job as the generation's user turn. */\nexport function aiJobEmailPrompt(\n job: Pick<AiJob, 'brief' | 'inputs'>,\n plan: AiJobPlan | null,\n heading: string | null,\n products: number,\n): string {\n const lines = heading ? [heading] : []\n const type = aiEmailTypeLabel(job.inputs?.['emailType'])\n if (type) lines.push(`Kind of email: ${type}`)\n lines.push(aiJobBriefLine(job), ...aiPlanReferenceLines(plan))\n lines.push(\n products > 0\n ? `Products bound to this email: ${products}. Place exactly ${products} Email product ${\n products === 1 ? 'block' : 'blocks'\n }.`\n : 'No products are bound to this email: place no Email product block.',\n )\n return lines.join('\\n')\n}\n\n/** The subject lines and preheaders an answer carries. */\nexport interface AiEmailCopy {\n subjects: string[]\n preheaders: string[]\n}\n\nfunction oneLine(value: string): string {\n return value.replace(/\\s+/g, ' ').trim()\n}\n\nexport function parseAiEmailCopy(answer: Readonly<Record<string, unknown>>): AiEmailCopy {\n const lines = (value: unknown) =>\n Array.isArray(value)\n ? value.filter((entry): entry is string => typeof entry === 'string').map(oneLine)\n : []\n return { subjects: lines(answer['subjects']), preheaders: lines(answer['preheaders']) }\n}\n\n/** The copy's own rules: three different lines of each, within their lengths, with no merge token. */\nexport function aiEmailCopyViolations(copy: AiEmailCopy): AiDoctrineViolation[] {\n const violations: AiDoctrineViolation[] = []\n const distinct = (lines: string[]) =>\n new Set(lines.filter(Boolean).map((line) => line.toLowerCase())).size\n if (copy.subjects.length !== AI_EMAIL_VARIANTS || distinct(copy.subjects) !== AI_EMAIL_VARIANTS) {\n violations.push({\n rule: null,\n code: 'email-subjects',\n message: `Write exactly ${AI_EMAIL_VARIANTS} different subject lines.`,\n })\n }\n if (copy.preheaders.length !== AI_EMAIL_VARIANTS || distinct(copy.preheaders) !== AI_EMAIL_VARIANTS) {\n violations.push({\n rule: null,\n code: 'email-preheaders',\n message: `Write exactly ${AI_EMAIL_VARIANTS} different preheaders, one for each subject line.`,\n })\n }\n if (copy.subjects.some((line) => line.length > AI_EMAIL_SUBJECT_MAX_CHARS)) {\n violations.push({\n rule: null,\n code: 'email-subject-length',\n message: `A subject line is over ${AI_EMAIL_SUBJECT_MAX_CHARS} characters. Keep each within ${AI_EMAIL_SUBJECT_MAX_CHARS}, and best under 60.`,\n })\n }\n if (copy.preheaders.some((line) => line.length > AI_EMAIL_PREHEADER_MAX_CHARS)) {\n violations.push({\n rule: null,\n code: 'email-preheader-length',\n message: `A preheader is over ${AI_EMAIL_PREHEADER_MAX_CHARS} characters. Keep each within ${AI_EMAIL_PREHEADER_MAX_CHARS}.`,\n })\n }\n if ([...copy.subjects, ...copy.preheaders].some((line) => line.includes('{{'))) {\n violations.push({\n rule: null,\n code: 'email-header-token',\n message:\n 'A subject line or preheader carries a merge token, which is not filled there. Write them without {{…}}.',\n })\n }\n return violations\n}\n\nconst MERGE_TOKEN = /\\{\\{\\s*([^}]*?)\\s*\\}\\}/g\n\nfunction visitsOf(tree: Pick<AiValidatedTree, 'rootId' | 'nodes'>) {\n return walkTree({\n rootId: tree.rootId,\n nodes: tree.nodes as unknown as Record<string, AiDoctrineNode>,\n })\n}\n\n/** The design's own rules, on a tree the doctrine admitted. */\nexport function aiEmailTreeViolations(tree: AiValidatedTree, products: number): AiDoctrineViolation[] {\n const violations: AiDoctrineViolation[] = []\n const loose = (tree.nodes[tree.rootId]?.nodes ?? []).filter(\n (id) => tree.nodes[id]?.componentId !== 'emailSection',\n )\n if (loose.length) {\n violations.push({\n rule: null,\n code: 'email-loose-block',\n message: 'A block sits outside every Email section. Put each block inside a section.',\n nodeIds: aiModelNodeIds(loose, tree.sourceIds),\n })\n }\n const tokens = new Set<string>()\n const tokenNodes: string[] = []\n const unlinked: string[] = []\n let placed = 0\n for (const { id, node } of visitsOf(tree)) {\n for (const value of Object.values(node.props ?? {})) {\n if (typeof value !== 'string') continue\n for (const match of value.matchAll(MERGE_TOKEN)) {\n if (!AI_EMAIL_TEXT_MERGE_TOKENS.includes(match[1])) {\n tokens.add(`{{${match[1]}}}`)\n tokenNodes.push(id)\n }\n }\n }\n if (node.componentId === 'emailButton' && typeof node.props?.['href'] !== 'string') {\n unlinked.push(id)\n }\n if (node.componentId === 'emailProduct') placed += 1\n }\n if (tokens.size) {\n violations.push({\n rule: null,\n code: 'email-merge-token',\n message: `This email writes ${[...tokens].slice(0, 3).join(', ')}, which a generated email may not use. Use only {{contact.firstName}}, {{contact.name}} or {{contact.email}}, and only in text.`,\n nodeIds: aiModelNodeIds([...new Set(tokenNodes)], tree.sourceIds),\n })\n }\n if (unlinked.length) {\n violations.push({\n rule: null,\n code: 'email-button-link',\n message:\n 'An Email button links nowhere. Link each to a page of the site by its address, such as /menu, or to a full https address the brief gives.',\n nodeIds: aiModelNodeIds(unlinked, tree.sourceIds),\n })\n }\n if (placed !== products) {\n violations.push({\n rule: null,\n code: 'email-product-count',\n message: products\n ? `This email binds ${products} ${products === 1 ? 'product' : 'products'}: place exactly ${products} Email product ${\n products === 1 ? 'block' : 'blocks'\n }.`\n : 'No products are bound to this email: remove the Email product blocks.',\n })\n }\n return violations\n}\n\n/**\n * The design as it is stored: a link to one of the site's pages completed with\n * the site's address token, and each product block bound, in document order,\n * to the next product the job binds.\n */\nexport function aiEmailDesignNodes(\n tree: Pick<AiValidatedTree, 'rootId' | 'nodes'>,\n productIds: readonly string[],\n): NodesMap {\n const nodes: NodesMap = { ...tree.nodes }\n let next = 0\n for (const { id, node } of visitsOf(tree)) {\n const props: Record<string, unknown> = { ...(node.props ?? {}) }\n let changed = false\n const href = props['href']\n if (typeof href === 'string' && href.startsWith('/') && !href.startsWith('//')) {\n props['href'] = `${AI_EMAIL_SITE_URL_TOKEN}${href}`\n changed = true\n }\n if (node.componentId === 'emailProduct') {\n const productId = productIds[next]\n next += 1\n if (productId) {\n props['productId'] = productId\n changed = true\n }\n }\n if (changed) nodes[id] = { ...nodes[id], props } as NodesMap[string]\n }\n return nodes\n}\n\n/** The content the email plugin's writer stores a design from. */\nexport function aiEmailDesignContent(nodes: NodesMap, copy: AiEmailCopy): Record<string, unknown> {\n return {\n nodes,\n subject: copy.subjects[0] ?? '',\n preheader: copy.preheaders[0] ?? '',\n subjectVariants: copy.subjects,\n preheaderVariants: copy.preheaders,\n }\n}\n\n/** What the email plugin's renderer says about the design, as violations the model is re-asked with. */\nexport function aiEmailRenderViolations(\n design: PluginResourceDraftWriter,\n hostId: string,\n nodes: NodesMap,\n copy: AiEmailCopy,\n): AiDoctrineViolation[] {\n const checked = design.check(aiEmailDesignContent(nodes, copy), { hostId })\n if (checked.ok === true) return []\n return [...new Set(checked.problems)].map((problem) => ({\n rule: null,\n code: 'email-render',\n message: `The email does not render as written: ${problem}.`,\n }))\n}\n\nexport interface AiEmailGenerationInput {\n job: AiJob\n step: Extract<AiStepKind, 'job.email' | 'job.campaign'>\n model: string\n /** The line naming what is built, such as `Email name: …`; `null` for none. */\n heading: string | null\n plan: AiJobPlan | null\n inventory: AiSiteInventory | null\n hostId: string\n products: AiEmailProductBinding\n /** The email plugin's design writer, whose check the design must pass. */\n design: PluginResourceDraftWriter\n signal?: AbortSignal\n}\n\nexport interface AiEmailGeneration {\n result: AiValidatedGeneration<AiValidatedTree>\n /** The copy of the answer the doctrine kept; `null` unless it kept one. */\n copy: AiEmailCopy | null\n /** The kept design as it is stored; `null` unless the doctrine kept one. */\n nodes: NodesMap | null\n}\n\n/** Generates one email under the doctrine, with the email's own checks riding `extend`. */\nexport async function generateAiEmail(input: AiEmailGenerationInput): Promise<AiEmailGeneration> {\n // The answer `extend` saw last is the one the doctrine kept when it keeps one.\n let last = null as { copy: AiEmailCopy; nodes: NodesMap } | null\n const result = await runValidatedGeneration('email', {\n step: input.step,\n model: input.model,\n instructions: AI_JOB_EMAIL_INSTRUCTIONS,\n inventory: input.inventory,\n messages: [\n {\n role: 'user',\n content: aiJobEmailPrompt(input.job, input.plan, input.heading, input.products.ids.length),\n },\n ],\n tool: AI_JOB_EMAIL_TOOL,\n maxTokens: AI_JOB_EMAIL_STEP_BUDGETS[input.step].maxTokens(input.model),\n thinking: 'off',\n extend: (tree, answer) => {\n const copy = parseAiEmailCopy(answer)\n const nodes = aiEmailDesignNodes(tree, input.products.ids)\n last = { copy, nodes }\n const violations = [\n ...aiEmailTreeViolations(tree, input.products.ids.length),\n ...aiEmailCopyViolations(copy),\n ]\n // Rendered once the design's own rules hold, so a problem is named once.\n return violations.length\n ? violations\n : aiEmailRenderViolations(input.design, input.hostId, nodes, copy)\n },\n ...(input.signal ? { signal: input.signal } : {}),\n })\n const kept = result.status === 'ok' ? last : null\n return { result, copy: kept?.copy ?? null, nodes: kept?.nodes ?? null }\n}\n\n/** An email design as the job's output. */\nexport function aiEmailDesignOutput(\n record: PluginDraftRecord,\n place: { hostId: string; hostSubdomain: string | null },\n note: string | null,\n): AiJobOutput {\n return {\n resource: 'emailScreen',\n id: record.id,\n versionId: record.versionId,\n hostId: place.hostId,\n hostSubdomain: place.hostSubdomain,\n label: record.name,\n ...(note ? { note } : {}),\n }\n}\n\n/** An email job is admitted for a site of its org with email on and room for a design. */\nexport const aiEmailJobAdmission: AiJobAdmission = (context) =>\n aiPluginDraftAdmissionRefusal(context, {\n kind: 'email',\n drafts: [{ resource: AI_EMAIL_DESIGN_RESOURCE, label: 'Email' }],\n })\n\nexport interface AiJobEmailStepDeps {\n /** The inventory reader; specs hand in a fake. */\n readInventory?: typeof readSiteInventory\n /** How a resource's draft writer is found; the core's registry otherwise. */\n writerFor?: AiPluginDraftWriterLookup\n}\n\nexport function createAiJobEmailStep(deps: AiJobEmailStepDeps = {}): AiJobStepRunner {\n const readInventory = deps.readInventory ?? readSiteInventory\n const writerFor = deps.writerFor ?? aiPluginDraftWriter\n return async ({ job, now, signal, firestore, modelFor }): Promise<AiJobStepOutcome> => {\n const hostId = job.hostId\n if (!hostId) throw new Error('an email job names no site, and its admission refuses one')\n // The switch's answer for this job, else the routing table's (AGL-2942).\n const model = modelFor?.('job.email') ?? aiModelForStep('job.email')\n const design = writerFor(AI_EMAIL_DESIGN_RESOURCE)\n if (!design) return { ...aiUnspentOutcome(model), failure: AI_EMAIL_UNAVAILABLE_COPY }\n\n // A run cut off after its design was written reports that design, found by the id the job recorded.\n const draftId = aiJobDraftId(job, 'email')\n const written = await design.read({ hostId, id: draftId })\n if (written) {\n const hostSubdomain = await aiSiteSubdomain(firestore, hostId)\n return aiUnspentOutcome(model, {\n outputs: [aiEmailDesignOutput(written, { hostId, hostSubdomain }, null)],\n })\n }\n\n const [inventory, orgSnapshot, products, hostSubdomain] = await Promise.all([\n readInventory(job.orgId, hostId, { firestore }),\n firestore.collection('orgs').doc(job.orgId).get(),\n resolveAiEmailProducts({ hostId, brief: job.brief, inputs: job.inputs }),\n aiSiteSubdomain(firestore, hostId),\n ])\n const org = (orgSnapshot.data() ?? null) as Record<string, unknown> | null\n const context = { orgId: job.orgId, hostId, uid: job.createdBy, org, now }\n const refusal = await design.refusal(context)\n if (refusal) {\n return refusal.status === 403\n ? aiUnspentOutcome(model, { review: aiLimitReview(refusal.error) })\n : { ...aiUnspentOutcome(model), failure: refusal.error }\n }\n\n const plan = aiConfirmedPlan(job)\n const name =\n aiPlanCreation(plan, 'email')?.name || aiJobInputName(job) || AI_JOB_EMAIL_DEFAULT_NAME\n const generated = await generateAiEmail({\n job,\n step: 'job.email',\n model,\n heading: `Email name: ${name}`,\n plan,\n inventory,\n hostId,\n products,\n design,\n ...(signal ? { signal } : {}),\n })\n const { result } = generated\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result) }\n if (!generated.copy || !generated.nodes) return { ...spent, failure: AI_EMAIL_SAVE_FAILURE_COPY }\n\n const write = await design.write({\n ...context,\n id: draftId,\n name,\n content: aiEmailDesignContent(generated.nodes, generated.copy),\n })\n if (write.ok === false) {\n return write.status === 403\n ? { ...spent, review: aiLimitReview(write.error) }\n : { ...spent, failure: AI_EMAIL_SAVE_FAILURE_COPY }\n }\n return {\n ...spent,\n outputs: [aiEmailDesignOutput(write, { hostId, hostSubdomain }, aiEmailProductNote(products))],\n }\n }\n}\n\nexport const runAiJobEmailStep = createAiJobEmailStep()\n\n/** Registers the email step and the check a email job passes before it is created or resumed. */\nexport function registerAiEmailJob(): void {\n registerAiJobStep('email', runAiJobEmailStep, { minimumMs: AI_JOB_EMAIL_STEP_MINIMUM_MS })\n registerAiJobAdmission('email', aiEmailJobAdmission)\n}\n"],"names":["AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","aiDoctrineTreeTool","runValidatedGeneration","walkTree","AI_TEXT_LIMITS","readSiteInventory","aiEmailProductNote","resolveAiEmailProducts","registerAiJobAdmission","aiJobStepBudget","aiJobDraftId","aiSiteSubdomain","aiConfirmedPlan","aiDoctrineReview","aiGenerationSpent","aiJobBriefLine","aiLimitReview","aiModelNodeIds","aiPlanCreation","aiPlanReferenceLines","aiUnspentOutcome","aiPluginDraftAdmissionRefusal","aiPluginDraftWriter","registerAiJobStep","AI_EMAIL_DESIGN_RESOURCE","AI_JOB_EMAIL_MAX_TOKENS","maxTokens","AI_JOB_EMAIL_STEP_BUDGETS","tier","AI_JOB_EMAIL_STEP_MINIMUM_MS","minimumMs","AI_EMAIL_VARIANTS","AI_EMAIL_SUBJECT_MAX_CHARS","headline","AI_EMAIL_PREHEADER_MAX_CHARS","AI_JOB_EMAIL_DEFAULT_NAME","AI_EMAIL_UNAVAILABLE_COPY","AI_EMAIL_SAVE_FAILURE_COPY","AI_EMAIL_TYPES","welcome","newsletter","launch","abandonedCart","eventReminder","reply","AI_EMAIL_TEXT_MERGE_TOKENS","AI_EMAIL_SITE_URL_TOKEN","AI_JOB_EMAIL_INSTRUCTIONS","text","join","DOCTRINE_EMAIL_TOOL","AI_JOB_EMAIL_TOOL","name","description","strict","inputSchema","type","additionalProperties","required","properties","tree","subjects","items","preheaders","aiEmailTypeLabel","value","Object","prototype","hasOwnProperty","call","aiJobInputName","job","raw","inputs","replace","trim","slice","aiJobEmailPrompt","plan","heading","products","lines","push","oneLine","parseAiEmailCopy","answer","Array","isArray","filter","entry","map","aiEmailCopyViolations","copy","violations","distinct","Set","Boolean","line","toLowerCase","size","length","rule","code","message","some","includes","MERGE_TOKEN","visitsOf","rootId","nodes","aiEmailTreeViolations","loose","id","componentId","nodeIds","sourceIds","tokens","tokenNodes","unlinked","placed","node","values","props","match","matchAll","add","aiEmailDesignNodes","productIds","next","changed","href","startsWith","productId","aiEmailDesignContent","subject","preheader","subjectVariants","preheaderVariants","aiEmailRenderViolations","design","hostId","checked","check","ok","problems","problem","generateAiEmail","input","last","result","step","model","instructions","inventory","messages","role","content","ids","tool","thinking","extend","signal","kept","status","aiEmailDesignOutput","record","place","note","resource","versionId","hostSubdomain","label","aiEmailJobAdmission","context","kind","drafts","createAiJobEmailStep","deps","readInventory","writerFor","now","firestore","modelFor","orgSnapshot","Error","failure","draftId","written","read","outputs","Promise","all","orgId","collection","doc","get","brief","org","data","uid","createdBy","refusal","review","error","generated","spent","refused","write","runAiJobEmailStep","registerAiEmailJob"],"mappings":"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"}
|
|
@@ -55,7 +55,7 @@ export declare const AI_EXPERIMENT_READER_ID = "marketing.experiments";
|
|
|
55
55
|
/** The window a result is read over: a test's figures are totals since it started. */
|
|
56
56
|
export declare const AI_EXPERIMENT_READ_DAYS = 0;
|
|
57
57
|
export declare const AI_EXPERIMENT_NO_TASK_COPY = "Start this from the A/B testing card, which says whether you want variants or an explanation.";
|
|
58
|
-
export declare const AI_EXPERIMENT_NO_TARGET_COPY = "Say whether the test varies a
|
|
58
|
+
export declare const AI_EXPERIMENT_NO_TARGET_COPY = "Say whether the test varies a page, a section or an email.";
|
|
59
59
|
export declare const AI_EXPERIMENT_NO_SITE_COPY = "Open the site whose test this is.";
|
|
60
60
|
export declare const AI_EXPERIMENT_NO_READER_COPY = "A/B testing is not available on this workspace, so there is no test to work on.";
|
|
61
61
|
export declare const AI_EXPERIMENT_NO_SUBJECT_COPY = "Give the copy the test varies, so there is something to write variants of.";
|
|
@@ -58,7 +58,7 @@ import { AI_EXPERIMENT_EXPLAIN_TOOL_NAME, AI_EXPERIMENT_VARIANTS_TOOL_NAME, aiEx
|
|
|
58
58
|
*/ /** The reader whose figures a result is explained from, and the plugin that owns it. */ export const AI_EXPERIMENT_READER_ID = AI_EXPERIMENT_FIGURE_READER;
|
|
59
59
|
/** The window a result is read over: a test's figures are totals since it started. */ export const AI_EXPERIMENT_READ_DAYS = 0;
|
|
60
60
|
export const AI_EXPERIMENT_NO_TASK_COPY = 'Start this from the A/B testing card, which says whether you want variants or an explanation.';
|
|
61
|
-
export const AI_EXPERIMENT_NO_TARGET_COPY = 'Say whether the test varies a
|
|
61
|
+
export const AI_EXPERIMENT_NO_TARGET_COPY = 'Say whether the test varies a page, a section or an email.';
|
|
62
62
|
export const AI_EXPERIMENT_NO_SITE_COPY = 'Open the site whose test this is.';
|
|
63
63
|
export const AI_EXPERIMENT_NO_READER_COPY = 'A/B testing is not available on this workspace, so there is no test to work on.';
|
|
64
64
|
export const AI_EXPERIMENT_NO_SUBJECT_COPY = 'Give the copy the test varies, so there is something to write variants of.';
|