@aglyn/plugins-ai 1.0.0-beta.285 → 1.0.0-beta.288
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/assets/site-styles/nonprofit.webp +0 -0
- package/src/lib/assets/site-styles/professional.webp +0 -0
- package/src/lib/assets/site-styles/store.webp +0 -0
- package/src/lib/assets/site-styles/trades.webp +0 -0
- package/src/lib/components/ai-theme-proposal-card.component.js +12 -3
- package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
- package/src/lib/declarations.js +5 -2
- package/src/lib/declarations.js.map +1 -1
- package/src/lib/declarations.server.js +12 -0
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/jobs/ai-build-capabilities.d.ts +3 -1
- package/src/lib/jobs/ai-build-capabilities.js +27 -2
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
- package/src/lib/jobs/ai-business-profile-prefill.js +6 -3
- package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -1
- package/src/lib/jobs/ai-image-reads.d.ts +59 -0
- package/src/lib/jobs/ai-image-reads.js +138 -0
- package/src/lib/jobs/ai-image-reads.js.map +1 -0
- package/src/lib/jobs/ai-job-build-step.d.ts +29 -0
- package/src/lib/jobs/ai-job-build-step.js +83 -12
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-duplicate-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-duplicate-step.js +6 -6
- package/src/lib/jobs/ai-job-duplicate-step.js.map +1 -1
- package/src/lib/jobs/ai-job-import-step.d.ts +4 -2
- package/src/lib/jobs/ai-job-import-step.js +15 -11
- package/src/lib/jobs/ai-job-import-step.js.map +1 -1
- package/src/lib/jobs/ai-job-insight-step.d.ts +10 -0
- package/src/lib/jobs/ai-job-insight-step.js +23 -5
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.js +2 -1
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-media-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-media-step.js +6 -8
- package/src/lib/jobs/ai-job-media-step.js.map +1 -1
- package/src/lib/jobs/ai-job-navigation-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-navigation-step.js +7 -9
- package/src/lib/jobs/ai-job-navigation-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.js +7 -1
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.js +16 -3
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +9 -1
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +30 -8
- package/src/lib/jobs/ai-job-plan-step.js +78 -13
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-privacy-step.d.ts +4 -2
- package/src/lib/jobs/ai-job-privacy-step.js +14 -15
- package/src/lib/jobs/ai-job-privacy-step.js.map +1 -1
- package/src/lib/jobs/ai-job-products-step.js +5 -3
- package/src/lib/jobs/ai-job-products-step.js.map +1 -1
- package/src/lib/jobs/ai-job-settings-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-settings-step.js +7 -8
- package/src/lib/jobs/ai-job-settings-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +34 -1
- package/src/lib/jobs/ai-job-site-content.js +67 -8
- package/src/lib/jobs/ai-job-site-content.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +53 -3
- package/src/lib/jobs/ai-job-site-datasets.js +300 -30
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.js +6 -3
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-store-pages.js +3 -3
- package/src/lib/jobs/ai-job-site-store-pages.js.map +1 -1
- package/src/lib/jobs/ai-job-text-step.js +2 -1
- package/src/lib/jobs/ai-job-text-step.js.map +1 -1
- package/src/lib/jobs/ai-job-tracking-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-tracking-step.js +7 -8
- package/src/lib/jobs/ai-job-tracking-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs-beat.d.ts +11 -7
- package/src/lib/jobs/ai-jobs-beat.js +18 -8
- package/src/lib/jobs/ai-jobs-beat.js.map +1 -1
- package/src/lib/jobs/ai-jobs-notify.d.ts +66 -1
- package/src/lib/jobs/ai-jobs-notify.js +136 -3
- package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +155 -7
- package/src/lib/jobs/ai-layout-stock-photos.js +1517 -220
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/ai-product-image.d.ts +14 -0
- package/src/lib/jobs/ai-product-image.js +73 -21
- package/src/lib/jobs/ai-product-image.js.map +1 -1
- package/src/lib/jobs/ai-theme-brand-inputs.d.ts +9 -1
- package/src/lib/jobs/ai-theme-brand-inputs.js +28 -2
- package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.d.ts +175 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.js +601 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-pages.d.ts +46 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-pages.js +2862 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-pages.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-plans.d.ts +74 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-plans.js +888 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-plans.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-site-agl4092-datasets.d.ts +120 -0
- package/src/lib/jobs/fixtures/ai-site-agl4092-datasets.js +410 -0
- package/src/lib/jobs/fixtures/ai-site-agl4092-datasets.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.d.ts +29 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.js +81 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.d.ts +87 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.js +225 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-recordings.d.ts +26 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-recordings.js +714 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.d.ts +11 -0
- package/src/lib/layout-language/ai-layout-compiler.js +194 -18
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-copy.d.ts +37 -0
- package/src/lib/layout-language/ai-layout-copy.js +383 -3
- package/src/lib/layout-language/ai-layout-copy.js.map +1 -1
- package/src/lib/layout-language/ai-layout-design.d.ts +19 -0
- package/src/lib/layout-language/ai-layout-design.js +31 -11
- package/src/lib/layout-language/ai-layout-design.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +7 -1
- package/src/lib/layout-language/ai-layout-listings.js +9 -1
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +39 -1
- package/src/lib/layout-language/ai-layout-pictures.js +141 -4
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/layout-language/ai-layout-storefront.d.ts +18 -4
- package/src/lib/layout-language/ai-layout-storefront.js +100 -20
- package/src/lib/layout-language/ai-layout-storefront.js.map +1 -1
- package/src/lib/model/ai-build-job.d.ts +7 -0
- package/src/lib/model/ai-build-job.js +36 -7
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-build-plan.d.ts +28 -4
- package/src/lib/model/ai-build-plan.js +49 -9
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-build-progress.js +3 -1
- package/src/lib/model/ai-build-progress.js.map +1 -1
- package/src/lib/model/ai-job-notice.d.ts +8 -0
- package/src/lib/model/ai-job-notice.js +33 -0
- package/src/lib/model/ai-job-notice.js.map +1 -1
- package/src/lib/model/ai-job-plan-lines.d.ts +2 -1
- package/src/lib/model/ai-job-plan-lines.js +6 -1
- package/src/lib/model/ai-job-plan-lines.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +8 -0
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-permissions.d.ts +13 -0
- package/src/lib/model/ai-permissions.js +29 -1
- package/src/lib/model/ai-permissions.js.map +1 -1
- package/src/lib/model/ai-plan-said-sections.d.ts +12 -3
- package/src/lib/model/ai-plan-said-sections.js +22 -5
- package/src/lib/model/ai-plan-said-sections.js.map +1 -1
- package/src/lib/model/ai-products.d.ts +18 -0
- package/src/lib/model/ai-products.js +32 -0
- package/src/lib/model/ai-products.js.map +1 -1
- package/src/lib/model/ai-site-context.d.ts +6 -2
- package/src/lib/model/ai-site-context.js +8 -2
- package/src/lib/model/ai-site-context.js.map +1 -1
- package/src/lib/model/ai-site-inspiration.d.ts +3 -2
- package/src/lib/model/ai-site-inspiration.js +3 -2
- package/src/lib/model/ai-site-inspiration.js.map +1 -1
- package/src/lib/plugin.js +4 -13
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/anthropic.js +9 -2
- package/src/lib/providers/anthropic.js.map +1 -1
- package/src/lib/providers/catalog.js +14 -5
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/providers/contract.d.ts +24 -1
- package/src/lib/providers/contract.js.map +1 -1
- package/src/lib/providers/routing.js +5 -2
- package/src/lib/providers/routing.js.map +1 -1
- package/src/lib/runtime/ai-dataset-generation.d.ts +120 -1
- package/src/lib/runtime/ai-dataset-generation.js +352 -3
- package/src/lib/runtime/ai-dataset-generation.js.map +1 -1
- package/src/lib/runtime/ai-dev-replay.d.ts +1 -0
- package/src/lib/runtime/ai-dev-replay.js +4 -2
- package/src/lib/runtime/ai-dev-replay.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +47 -3
- package/src/lib/runtime/ai-doctrine-validators.js +345 -28
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.d.ts +5 -4
- package/src/lib/runtime/ai-doctrine.js +10 -3
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-icon-library.d.ts +27 -1
- package/src/lib/runtime/ai-icon-library.js +30 -3
- package/src/lib/runtime/ai-icon-library.js.map +1 -1
- package/src/lib/runtime/ai-products-generation.d.ts +11 -1
- package/src/lib/runtime/ai-products-generation.js +51 -4
- package/src/lib/runtime/ai-products-generation.js.map +1 -1
- package/src/lib/runtime/assist-answer-cache.d.ts +2 -0
- package/src/lib/runtime/assist-answer-cache.js +5 -2
- package/src/lib/runtime/assist-answer-cache.js.map +1 -1
- package/src/lib/server/ai-allotments.js +7 -2
- package/src/lib/server/ai-allotments.js.map +1 -1
- package/src/lib/server/ai-experiment-versions.js +5 -5
- package/src/lib/server/ai-experiment-versions.js.map +1 -1
- package/src/lib/server/ai-host-permissions-roster.d.ts +25 -0
- package/src/lib/server/ai-host-permissions-roster.js +33 -0
- package/src/lib/server/ai-host-permissions-roster.js.map +1 -0
- package/src/lib/server/ai-host-permissions.js +8 -6
- package/src/lib/server/ai-host-permissions.js.map +1 -1
- package/src/lib/server/ai-image-reads-route.d.ts +17 -0
- package/src/lib/server/ai-image-reads-route.js +106 -0
- package/src/lib/server/ai-image-reads-route.js.map +1 -0
- package/src/lib/server/ai-seo-apply.js +5 -5
- package/src/lib/server/ai-seo-apply.js.map +1 -1
- package/src/lib/server/ai-site-import-route.js +7 -4
- package/src/lib/server/ai-site-import-route.js.map +1 -1
- package/src/lib/server/assist-build.d.ts +15 -1
- package/src/lib/server/assist-build.js +30 -3
- package/src/lib/server/assist-build.js.map +1 -1
- package/src/lib/server/assist-chat.d.ts +11 -7
- package/src/lib/server/assist-chat.js +185 -48
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-docs-query.d.ts +51 -0
- package/src/lib/server/assist-docs-query.js +57 -0
- package/src/lib/server/assist-docs-query.js.map +1 -0
- package/src/lib/server/assist-jobs-context.d.ts +102 -0
- package/src/lib/server/assist-jobs-context.js +227 -0
- package/src/lib/server/assist-jobs-context.js.map +1 -0
- package/src/lib/server/assist-view-context.d.ts +8 -4
- package/src/lib/server/assist-view-context.js +12 -7
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/server.js +12 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/tools/ai-dataset-tool.d.ts +11 -1
- package/src/lib/tools/ai-dataset-tool.js +24 -7
- package/src/lib/tools/ai-dataset-tool.js.map +1 -1
- package/src/lib/tools/ai-duplicate-resource-tool.js +5 -6
- package/src/lib/tools/ai-duplicate-resource-tool.js.map +1 -1
- package/src/lib/components/ai-collaborator-permissions-column.component.d.ts +0 -32
- package/src/lib/components/ai-collaborator-permissions-column.component.js +0 -132
- package/src/lib/components/ai-collaborator-permissions-column.component.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-build-capabilities.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 { DUPLICABLE_HOST_RESOURCE_KINDS, DUPLICATE_NAME_MAX } from '@aglyn/aglyn/app-utils/duplicate-resource'\nimport { checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { isHostPluginEnabled } from '@aglyn/aglyn/plugin-manager/enabled-plugins'\nimport {\n pluginAiCapabilities,\n registerPluginAiCapability,\n type PluginAiCapability,\n type ResolvedPluginAiCapability,\n} from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities'\nimport { filterEnabledPluginsByReleaseFlags } from '@aglyn/tenant-data-admin/server/release-flags'\nimport { AI_TEMPLATE_SUBJECTS } from '../model/ai-template-subjects'\nimport { AI_SITE_PASS_CREDITS } from '../model/ai-site-job'\nimport type { AiBuildOps } from '../model/ai-build-job'\nimport { AI_LOGIC_FUNCTION_RESOURCE } from '../model/ai-logic-job'\nimport { AI_AUTOMATION_RESOURCE } from '../model/ai-workflow-job'\nimport { AI_EDIT_TARGET_KINDS } from '../model/ai-edit-job'\nimport { AI_CAMPAIGN_RESOURCE } from './ai-job-campaign-step'\nimport { aiPluginDraftOwner, type AiPluginDraftOwnerLookup } from './ai-job-plugin-drafts'\nimport { aiJobStepRunnerFor } from './ai-jobs'\nimport { AI_SETTINGS_ARGS_SCHEMA, AI_SETTINGS_WHERE } from './ai-job-settings-step'\nimport { AI_MEDIA_ARGS_SCHEMA, AI_MEDIA_LIMITS, AI_MEDIA_WHERE } from './ai-job-media-step'\nimport { AI_TRACKING_ARGS_SCHEMA, AI_TRACKING_WHERE, aiTrackingInputFields, aiTrackingMissingInput } from './ai-job-tracking-step'\nimport { AI_PRIVACY_ARGS_SCHEMA, AI_PRIVACY_WHERE } from './ai-job-privacy-step'\n\n/**\n * What THIS plugin lets a build make (AGL-3616), registered on the core's\n * capability contract exactly as any other plugin registers its own, so the\n * planner, the gates and the estimate read one list and name no plugin.\n *\n * Every one is built by a runner this plugin already has (`runnerKind`):\n * pages, layouts, forms, components and email designs are the plan's\n * creations and screens; templates, campaigns and automations are items with\n * arguments. A campaign and an automation are still WRITTEN by the marketing\n * and workflows plugins, through the writers their runners already call, and\n * a site function by the logic plugin's `function` writer, which the `logic`\n * runner calls when its job is a build's unit. A change to a page or layout\n * the site already has is the `edit` runner's, and a change to its header or\n * footer menu the `navigation` runner's (AGL-3728), a copy of something\n * it has the `duplicate` runner's (AGL-3740), a change to its settings\n * the `settings` runner's (AGL-3748), organizing its media library the\n * `media` runner's (AGL-3752), and a change to its analytics and\n * advertising tags the `tracking` runner's (AGL-3751).\n */\n\n/** This plugin's id, as its registrations name their owner. */\nconst AI_PLUGIN_ID = 'ai'\n\nconst NO_ARGS = { type: 'object', properties: {}, additionalProperties: false } as const\n\nconst passes = (count: number) => () => count * AI_SITE_PASS_CREDITS\n\n/** The operations this plugin owns, as it registers them. */\nexport const AI_OWNED_CAPABILITIES: readonly PluginAiCapability[] = [\n {\n op: 'page',\n noun: 'page',\n where: 'Pages',\n intents: ['new pages: home, about, services, contact, pricing, a landing page'],\n argsSchema: NO_ARGS,\n maxPerPlan: 8,\n freeAllowed: true,\n runnerKind: 'page',\n estimateCredits: passes(6),\n degrade: 'omit',\n },\n {\n op: 'layout',\n noun: 'layout',\n where: 'Layouts',\n intents: ['a header and footer shared by pages'],\n argsSchema: NO_ARGS,\n maxPerPlan: 1,\n freeAllowed: true,\n runnerKind: 'layout',\n estimateCredits: passes(1),\n degrade: 'fallback',\n },\n {\n op: 'form',\n noun: 'form',\n where: 'Forms',\n intents: ['a contact form, a quote request, a sign-up form'],\n argsSchema: NO_ARGS,\n maxPerPlan: 3,\n freeAllowed: true,\n runnerKind: 'form',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n op: 'component',\n noun: 'component',\n where: 'Components',\n intents: ['a reusable card or block placed on several pages'],\n argsSchema: NO_ARGS,\n maxPerPlan: 4,\n freeAllowed: false,\n runnerKind: 'component',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n op: 'email',\n noun: 'email design',\n where: 'Emails → Templates',\n intents: ['an email design: a welcome, a reply, an announcement'],\n argsSchema: NO_ARGS,\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'email',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n op: 'template',\n noun: 'page template',\n where: 'Templates',\n intents: ['a page template for blog posts, products or authors'],\n argsSchema: {\n type: 'object',\n properties: {\n subject: {\n type: 'string',\n description: 'What each page shows: entry (a collection entry), product or author.',\n enum: AI_TEMPLATE_SUBJECTS,\n },\n collectionId: {\n type: 'string',\n description: 'For entry: the collection id from the site inventory.',\n maxLength: 100,\n },\n },\n required: ['subject'],\n additionalProperties: false,\n },\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'template',\n estimateCredits: passes(6),\n degrade: 'omit',\n },\n {\n op: 'campaign',\n noun: 'email campaign',\n where: 'Marketing → Campaigns',\n intents: ['an email campaign drafted and left unsent'],\n argsSchema: {\n type: 'object',\n properties: {\n name: { type: 'string', description: 'What the campaign is called.', maxLength: 120 },\n },\n additionalProperties: false,\n },\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'campaign',\n estimateCredits: passes(2),\n degrade: 'omit',\n },\n {\n op: 'workflow',\n noun: 'automation',\n where: 'Automations',\n intents: ['an automation: when a form is sent, do something'],\n argsSchema: NO_ARGS,\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'workflow',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n // A function needs a model, so it is this plugin's operation; the logic\n // plugin owns the record and writes it (AGL-3616). New functions only: a\n // build never changes one the site already has.\n op: 'function',\n noun: 'site function',\n where: 'Logic → Functions',\n intents: ['a site function that works a value out from what it is given: a price, a total, a quote'],\n argsSchema: NO_ARGS,\n maxPerPlan: 2,\n // Free includes one function (`functionsPerHost: 1`), and Logic no feature.\n freeAllowed: true,\n quota: 'functionsPerHost',\n runnerKind: 'logic',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n // A change to something the site already has (AGL-3616), made by the\n // `edit` runner: the Assist edit rung's protocol against the stored\n // document, saved as a version visitors do not see. Not on the Free\n // taste: without version history only an unpublished page can take a\n // change, which is rarely the page a request means, and its admission\n // would refuse the item after the plan was confirmed.\n op: 'edit',\n noun: 'page change',\n where: 'the page’s or layout’s versions',\n intents: [\n 'a change to a page or layout the site already has — shorter copy, a new section, different wording — saved as a version visitors do not see until it is published',\n ],\n argsSchema: {\n type: 'object',\n properties: {\n target: {\n type: 'string',\n description: 'The id of an existing page or layout from the site inventory.',\n maxLength: 64,\n },\n targetKind: {\n type: 'string',\n description: 'screen when target is a page, layout when it is a layout.',\n enum: AI_EDIT_TARGET_KINDS,\n },\n },\n required: ['target'],\n additionalProperties: false,\n },\n maxPerPlan: 3,\n freeAllowed: false,\n runnerKind: 'edit',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n // The site's header and footer menus (AGL-3728): the Besigner's own Page\n // Links and Dropdown Menus in the site's layout, changed by code from the\n // words the planner wrote, saved as a version beside the live one. No\n // model, so no credits. Not on the Free taste, as `edit`: a layout is\n // always served, so without version history there is nowhere to land.\n op: 'navigation',\n noun: 'menu change',\n where: 'the layout’s versions',\n intents: [\n 'changing the site’s header or footer menu — add, remove, rename, reorder or relink its items, or put items under a dropdown — saved as a layout version visitors do not see until it is published',\n ],\n argsSchema: {\n type: 'object',\n properties: {\n menu: {\n type: 'string',\n description: 'Which menu: header (the default), footer, or both.',\n enum: ['header', 'footer', 'both'],\n },\n layout: {\n type: 'string',\n description: 'The layout id from the site inventory, or new:<name> of a layout this plan makes; leave it out for the layout the site’s pages use.',\n maxLength: 64,\n },\n add: {\n type: 'array',\n description:\n 'Items to add, each \"Label => destination\", or \"Parent > Label => destination\" to put it under the dropdown Parent. A destination is a page id from the site inventory, new:<page title> of a page this plan makes, a path such as /contact, or an https:// address.',\n items: { type: 'string', maxLength: 160 },\n maxItems: 12,\n },\n remove: {\n type: 'array',\n description: 'Labels of items to take out, as \"Label\" or \"Parent > Label\".',\n items: { type: 'string', maxLength: 100 },\n maxItems: 12,\n },\n rename: {\n type: 'array',\n description: 'Items to rename, each \"Old label => New label\".',\n items: { type: 'string', maxLength: 100 },\n maxItems: 12,\n },\n relink: {\n type: 'array',\n description: 'Items to point somewhere else, each \"Label => destination\", destinations as in add.',\n items: { type: 'string', maxLength: 160 },\n maxItems: 12,\n },\n order: {\n type: 'array',\n description: 'Top-level labels in the order wanted; items not named keep their order after them.',\n items: { type: 'string', maxLength: 60 },\n maxItems: 12,\n },\n },\n additionalProperties: false,\n },\n maxPerPlan: 1,\n freeAllowed: false,\n // Offered only where the change has somewhere to land: a plan with version history.\n feature: 'versioning',\n runnerKind: 'navigation',\n // Written by code from the plan's words: no model pass.\n estimateCredits: () => 0,\n degrade: 'omit',\n },\n {\n // A copy of something the site already has (AGL-3740), made by core's\n // `duplicateResource` — the console's own Duplicate… — so \"another page\n // like Services\" is copied rather than generated again. No model, so no\n // credits; the copy meets the plan's own counters as a create does,\n // which is why the Free taste may ask for one.\n op: 'duplicate',\n noun: 'copy',\n where: 'the list the original is on',\n intents: [\n 'a copy of a page, component, layout, template, form, email design or workflow the site already has, as an unpublished draft to change — rather than making it again',\n ],\n argsSchema: {\n type: 'object',\n properties: {\n kind: {\n type: 'string',\n description: 'What to copy: screen (a page), component, layout, template, form, emailDesign or workflow.',\n enum: DUPLICABLE_HOST_RESOURCE_KINDS,\n },\n source: {\n type: 'string',\n description: 'The id of the thing to copy from the site inventory, or its exact name where the inventory does not list it.',\n maxLength: 64,\n },\n name: {\n type: 'string',\n description: 'What the copy is called; leave it out for \"Copy of\" the original’s name.',\n maxLength: DUPLICATE_NAME_MAX,\n },\n },\n required: ['kind', 'source'],\n additionalProperties: false,\n },\n maxPerPlan: 3,\n freeAllowed: true,\n runnerKind: 'duplicate',\n // Copied by code: no model pass.\n estimateCredits: () => 0,\n degrade: 'omit',\n },\n {\n // The site's settings (AGL-3748): the fields the console's settings\n // cards write — name, search defaults, favicon, logo and social image\n // from the media library, language, time zone, contact details and the\n // cookie banner's mode — held to the same limits and saved live when the\n // plan is confirmed, as a settings save is. No model, so no credits; every\n // plan may change its own settings, so the Free taste may too. Domains,\n // members, billing, plugins, indexing and anything with a credential are\n // not arguments, so they cannot be asked for.\n op: 'settings',\n noun: 'settings change',\n where: AI_SETTINGS_WHERE,\n intents: [\n 'changing the site’s settings — its name, search title and description, title pattern, favicon, logo or social image (from images already in the media library), language, time zone, business name, description (tagline), phone, contact email or address, or the cookie banner’s mode — saved to the live site when the plan is confirmed',\n ],\n argsSchema: AI_SETTINGS_ARGS_SCHEMA,\n maxPerPlan: 1,\n freeAllowed: true,\n runnerKind: 'settings',\n // A setting has no draft state: saved, it is what the live site renders.\n live: true,\n // Written by code from the plan's values: no model pass.\n estimateCredits: () => 0,\n degrade: 'omit',\n },\n {\n // Organizing files already in the site's own media library (AGL-3752):\n // the folder each is filed in, its tags, its alt text and its name, as\n // the library's Details drawer and bulk actions write them, saved live\n // when the plan is confirmed. Never an upload, a copy, a replacement or a\n // deletion, and never the organization's shared library. No model, so no\n // credits; every plan has a media library, so the Free taste may too.\n op: 'media',\n noun: 'media library change',\n where: AI_MEDIA_WHERE,\n intents: [\n `organizing files already in the site’s media library — filing them in a folder (made if missing), adding or removing tags, renaming one, or writing alt text for an image the request describes or from the caption the library already holds — up to ${AI_MEDIA_LIMITS.files} files a change, saved live when the plan is confirmed; never uploading, copying or deleting a file`,\n ],\n argsSchema: AI_MEDIA_ARGS_SCHEMA,\n maxPerPlan: 4,\n freeAllowed: true,\n runnerKind: 'media',\n // A library file's details have no draft state: saved, they are what the library shows.\n live: true,\n // Written by code from the plan's values: no model pass.\n estimateCredits: () => 0,\n degrade: 'omit',\n },\n {\n // The site's analytics and advertising tags (AGL-3751): the ids the\n // console's Setup → Tracking card sets — Google Analytics, Tag Manager,\n // and the Meta, Google Ads, LinkedIn, TikTok and Pinterest tags — each\n // held to the card's own pattern, set or removed, saved live on confirm.\n // Only public ids the person states: never a credential (Ad conversions'\n // tokens, Zapier, webhook secrets), and never the consent banner, which\n // still decides where every tag loads. No model, so no credits; the card\n // is on every plan, so the Free taste may ask too.\n op: 'tracking',\n noun: 'tracking change',\n where: AI_TRACKING_WHERE,\n intents: [\n 'connecting or removing the site’s Google Analytics, Google Tag Manager, Meta pixel, Google Ads, LinkedIn Insight, TikTok pixel or Pinterest tag by the ID the person gives (never one it makes up; ask for the ID when the request has none) — saved to the live site when the plan is confirmed, loading only where a visitor’s cookie consent allows',\n ],\n argsSchema: AI_TRACKING_ARGS_SCHEMA,\n maxPerPlan: 1,\n freeAllowed: true,\n runnerKind: 'tracking',\n // A tag has no draft state: saved, it is on every live page.\n live: true,\n // An item with no usable id asks for it instead of being built (AGL-3751).\n missingInput: aiTrackingMissingInput,\n // …and waits for it in `needs_input`, with a field naming the provider (AGL-3978).\n inputFields: aiTrackingInputFields,\n // Written by code from the plan's values: no model pass.\n estimateCredits: () => 0,\n degrade: 'omit',\n },\n {\n // Who can see the site (AGL-4033): maintenance mode, the Discourage search\n // engines switch and a page's visibility, as the console's three controls\n // set them, saved live on confirm. Never a page password, which would put\n // the password in the chat. No model, so no credits; every plan has these\n // controls, so the Free taste may ask too.\n op: 'privacy',\n noun: 'privacy change',\n where: AI_PRIVACY_WHERE,\n intents: [\n 'making the site private or public again — maintenance mode (the whole site offline to every visitor until it is turned off; site admins only), hiding the site from search engines (it stays open to anyone with a link), or setting pages Public, Unlisted or Members only (Business plan and above) — saved to the live site when the plan is confirmed; never a page password, which the person sets in the page’s Page Access card',\n ],\n argsSchema: AI_PRIVACY_ARGS_SCHEMA,\n maxPerPlan: 1,\n freeAllowed: true,\n runnerKind: 'privacy',\n // None of these has a draft state: saved, it is what the live site does.\n live: true,\n // Written by code from the plan's values: no model pass.\n estimateCredits: () => 0,\n degrade: 'omit',\n },\n]\n\n/**\n * The resource another plugin writes for each operation of this plugin's that\n * cannot be built without it. The operation runs where that writer's owner\n * runs — registered, switched on for the workspace and the site, and past its\n * release flag — exactly as an operation that owner registered would.\n */\nexport const AI_OWNED_OP_WRITERS: Readonly<Partial<Record<string, string>>> = {\n function: AI_LOGIC_FUNCTION_RESOURCE,\n // A campaign is written by the marketing plugin's campaign writer, an\n // automation by the workflows plugin's: neither is offered where its\n // writer's plugin is off, rather than planned and then skipped.\n campaign: AI_CAMPAIGN_RESOURCE,\n workflow: AI_AUTOMATION_RESOURCE,\n}\n\n/** Registers this plugin's build operations; the console surface calls it. */\nexport function registerAiBuildCapabilities(): void {\n for (const capability of AI_OWNED_CAPABILITIES) {\n registerPluginAiCapability(capability, { pluginId: AI_PLUGIN_ID })\n }\n}\n\n/** What decides which operations a build may use here. */\nexport interface AiBuildOpsContext {\n orgId: string\n hostId: string | null\n org: Partial<AglynOrgBilling> | null\n /** The site document, for the plugins switched on and off on it. */\n host: Readonly<Record<string, unknown>> | null\n freeTaste: boolean\n}\n\nexport interface AiBuildOpsDeps {\n /** The registered capabilities; the core registry's otherwise. */\n capabilities?: () => ResolvedPluginAiCapability[]\n /** The release-flag read; the platform's otherwise. */\n releasedPlugins?: (pluginIds: string[], orgId: string) => Promise<string[]>\n /** Whether a runner is registered for a kind; the machine's otherwise. */\n hasRunner?: (kind: string) => boolean\n /** Which plugin writes a resource; the core's registry otherwise. */\n writerOwner?: AiPluginDraftOwnerLookup\n}\n\n/**\n * The operations a build may plan on this site, by op (AGL-3616): every\n * registered capability whose owner runs here — switched on for the\n * workspace and the site, past its release flag; for one of this plugin's\n * operations another plugin writes (`AI_OWNED_OP_WRITERS`), that writer's\n * owner, and a writer registered at all — whose entitlement the plan\n * holds, which the Free taste may use where the workspace is on it, and,\n * for an AI-run operation, whose runner this process loaded. One release-flag\n * read for every owner at once. What a member may do and what an allowance\n * has left are the owner's to say when the item runs (`refusal`).\n */\nexport async function aiBuildOps(context: AiBuildOpsContext, deps: AiBuildOpsDeps = {}): Promise<AiBuildOps> {\n const all = (deps.capabilities ?? pluginAiCapabilities)()\n const hasRunner = deps.hasRunner ?? ((kind: string) => aiJobStepRunnerFor(kind as never) !== null)\n const writerOwner = deps.writerOwner ?? aiPluginDraftOwner\n // The plugin each of this plugin's operations depends on, by op; `null` where its writer is not registered.\n const writtenBy = new Map<string, string | null>()\n for (const { pluginId, capability } of all) {\n const resource = pluginId === AI_PLUGIN_ID ? AI_OWNED_OP_WRITERS[capability.op] : undefined\n if (resource) writtenBy.set(capability.op, writerOwner(resource))\n }\n const owners = [\n ...new Set(\n [...all.map((one) => one.pluginId), ...writtenBy.values()].filter(\n (id): id is string => !!id && id !== AI_PLUGIN_ID,\n ),\n ),\n ]\n const released = new Set(\n owners.length\n ? await (deps.releasedPlugins ??\n ((ids, orgId) => filterEnabledPluginsByReleaseFlags(ids, { orgId, authorization: null })))(owners, context.orgId)\n : [],\n )\n const ops = new Map<string, PluginAiCapability>()\n for (const { pluginId, capability } of all) {\n // The plugin that must run here: the owner, or for one of this plugin's\n // operations, the plugin that writes what it makes.\n const runsOn = pluginId === AI_PLUGIN_ID ? writtenBy.get(capability.op) : pluginId\n if (runsOn === null) continue\n if (runsOn !== undefined && runsOn !== AI_PLUGIN_ID) {\n if (!released.has(runsOn)) continue\n if (!isHostPluginEnabled(context.org as { enabledPlugins?: string[] } | null, context.host, runsOn)) continue\n }\n if (capability.feature && !checkEntitlement(context.org, capability.feature)) continue\n if (context.freeTaste && !capability.freeAllowed) continue\n if (capability.runnerKind && !hasRunner(capability.runnerKind)) continue\n ops.set(capability.op, capability)\n }\n return ops\n}\n\n/**\n * The lines the plan step's request lists the item operations in: each op\n * with what it makes, its arguments and their meaning. Per site, so they ride\n * the job's own turn and never a cached system block.\n */\nexport function aiBuildOpLines(ops: AiBuildOps, structural: readonly string[]): string[] {\n const items = [...ops.values()].filter((one) => !structural.includes(one.op))\n if (!items.length) return ['Operations for items: none on this site. Leave items empty.']\n const lines = ['Operations for items (anything else goes in create or screens):']\n for (const one of items) {\n const args = Object.entries(one.argsSchema.properties).map(([name, property]) => {\n const required = one.argsSchema.required?.includes(name) ? '' : ' (optional)'\n // An array's values are named too (AGL-3616): told only \"days: array\",\n // the live canonical plan wrote \"monday\" for the enum's \"mon\".\n const choices = property.enum\n ? ` one of ${property.enum.join('|')}`\n : property.items?.enum\n ? ` of ${property.items.enum.join('|')}`\n : ''\n return `${name}: ${property.type}${choices}${required} — ${property.description}`\n })\n lines.push(\n `- ${one.op}: ${aiNounWithArticle(one.noun)}, at most ${one.maxPerPlan}; for ${one.intents.join('; ')}.${\n one.pageBlocks?.length ? ` A page section that uses it places the ${one.pageBlocks.join(' or ')} block: put new:<name> in that section's uses.` : ''\n } Args: ${args.length ? `{ ${args.join('; ')} }` : '{}'}.`,\n )\n }\n // Measured live on the canonical request (AGL-3616): asked for \"forms for\n // bookings\", the planner made a plain form rather than the operation that\n // takes bookings. An operation does the job; a form that imitates it does not.\n lines.push(\n 'When the request asks for what one of these operations makes, plan that operation as an item, and place it on the page that offers it. Never stand in for it with a form, page or component that only imitates it: a form that collects what the operation would handle does not do its job. A form is for what no operation here makes, such as general inquiries.',\n )\n return lines\n}\n\nfunction aiNounWithArticle(noun: string): string {\n return `${/^[aeiou]/i.test(noun) ? 'an' : 'a'} ${noun}`\n}\n\n/** What a build can make here, in the owner's words, for the chat's instructions. */\nexport function aiBuildIntents(ops: AiBuildOps): string[] {\n return [...ops.values()].flatMap((one) => one.intents)\n}\n"],"names":["DUPLICABLE_HOST_RESOURCE_KINDS","DUPLICATE_NAME_MAX","checkEntitlement","isHostPluginEnabled","pluginAiCapabilities","registerPluginAiCapability","filterEnabledPluginsByReleaseFlags","AI_TEMPLATE_SUBJECTS","AI_SITE_PASS_CREDITS","AI_LOGIC_FUNCTION_RESOURCE","AI_AUTOMATION_RESOURCE","AI_EDIT_TARGET_KINDS","AI_CAMPAIGN_RESOURCE","aiPluginDraftOwner","aiJobStepRunnerFor","AI_SETTINGS_ARGS_SCHEMA","AI_SETTINGS_WHERE","AI_MEDIA_ARGS_SCHEMA","AI_MEDIA_LIMITS","AI_MEDIA_WHERE","AI_TRACKING_ARGS_SCHEMA","AI_TRACKING_WHERE","aiTrackingInputFields","aiTrackingMissingInput","AI_PRIVACY_ARGS_SCHEMA","AI_PRIVACY_WHERE","AI_PLUGIN_ID","NO_ARGS","type","properties","additionalProperties","passes","count","AI_OWNED_CAPABILITIES","op","noun","where","intents","argsSchema","maxPerPlan","freeAllowed","runnerKind","estimateCredits","degrade","subject","description","enum","collectionId","maxLength","required","name","quota","target","targetKind","menu","layout","add","items","maxItems","remove","rename","relink","order","feature","kind","source","live","files","missingInput","inputFields","AI_OWNED_OP_WRITERS","function","campaign","workflow","registerAiBuildCapabilities","capability","pluginId","aiBuildOps","context","deps","all","capabilities","hasRunner","writerOwner","writtenBy","Map","resource","undefined","set","owners","Set","map","one","values","filter","id","released","length","releasedPlugins","ids","orgId","authorization","ops","runsOn","get","has","org","host","freeTaste","aiBuildOpLines","structural","includes","lines","args","Object","entries","property","choices","join","push","aiNounWithArticle","pageBlocks","test","aiBuildIntents","flatMap"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-build-capabilities.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 { DUPLICABLE_HOST_RESOURCE_KINDS, DUPLICATE_NAME_MAX } from '@aglyn/aglyn/app-utils/duplicate-resource'\nimport { checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { isHostPluginEnabled } from '@aglyn/aglyn/plugin-manager/enabled-plugins'\nimport {\n pluginAiCapabilities,\n registerPluginAiCapability,\n type PluginAiCapability,\n type ResolvedPluginAiCapability,\n} from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities'\nimport { filterEnabledPluginsByReleaseFlags } from '@aglyn/tenant-data-admin/server/release-flags'\nimport { AI_TEMPLATE_SUBJECTS } from '../model/ai-template-subjects'\nimport { AI_SITE_PASS_CREDITS } from '../model/ai-site-job'\nimport { AI_BUILD_THEME_OP, type AiBuildOps } from '../model/ai-build-job'\nimport { AI_LOGIC_FUNCTION_RESOURCE } from '../model/ai-logic-job'\nimport { AI_AUTOMATION_RESOURCE } from '../model/ai-workflow-job'\nimport { AI_EDIT_TARGET_KINDS } from '../model/ai-edit-job'\nimport { AI_CAMPAIGN_RESOURCE } from './ai-job-campaign-step'\nimport { aiPluginDraftOwner, type AiPluginDraftOwnerLookup } from './ai-job-plugin-drafts'\nimport { aiJobStepRunnerFor } from './ai-jobs'\nimport { AI_SETTINGS_ARGS_SCHEMA, AI_SETTINGS_WHERE } from './ai-job-settings-step'\nimport { AI_MEDIA_ARGS_SCHEMA, AI_MEDIA_LIMITS, AI_MEDIA_WHERE } from './ai-job-media-step'\nimport { AI_TRACKING_ARGS_SCHEMA, AI_TRACKING_WHERE, aiTrackingInputFields, aiTrackingMissingInput } from './ai-job-tracking-step'\nimport { AI_PRIVACY_ARGS_SCHEMA, AI_PRIVACY_WHERE } from './ai-job-privacy-step'\n\n/**\n * What THIS plugin lets a build make (AGL-3616), registered on the core's\n * capability contract exactly as any other plugin registers its own, so the\n * planner, the gates and the estimate read one list and name no plugin.\n *\n * Every one is built by a runner this plugin already has (`runnerKind`):\n * pages, layouts, forms, components and email designs are the plan's\n * creations and screens; templates, campaigns and automations are items with\n * arguments. A campaign and an automation are still WRITTEN by the marketing\n * and workflows plugins, through the writers their runners already call, and\n * a site function by the logic plugin's `function` writer, which the `logic`\n * runner calls when its job is a build's unit. A change to a page or layout\n * the site already has is the `edit` runner's, and a change to its header or\n * footer menu the `navigation` runner's (AGL-3728), a copy of something\n * it has the `duplicate` runner's (AGL-3740), a change to its settings\n * the `settings` runner's (AGL-3748), organizing its media library the\n * `media` runner's (AGL-3752), a change to its analytics and\n * advertising tags the `tracking` runner's (AGL-3751), and a change to its\n * theme the `theme` runner's proposal (AGL-4077).\n */\n\n/** This plugin's id, as its registrations name their owner. */\nconst AI_PLUGIN_ID = 'ai'\n\nconst NO_ARGS = { type: 'object', properties: {}, additionalProperties: false } as const\n\nconst passes = (count: number) => () => count * AI_SITE_PASS_CREDITS\n\n/** The operations this plugin owns, as it registers them. */\nexport const AI_OWNED_CAPABILITIES: readonly PluginAiCapability[] = [\n {\n op: 'page',\n noun: 'page',\n where: 'Pages',\n intents: ['new pages: home, about, services, contact, pricing, a landing page'],\n argsSchema: NO_ARGS,\n maxPerPlan: 8,\n freeAllowed: true,\n runnerKind: 'page',\n estimateCredits: passes(6),\n degrade: 'omit',\n },\n {\n op: 'layout',\n noun: 'layout',\n where: 'Layouts',\n intents: ['a header and footer shared by pages'],\n argsSchema: NO_ARGS,\n maxPerPlan: 1,\n freeAllowed: true,\n runnerKind: 'layout',\n estimateCredits: passes(1),\n degrade: 'fallback',\n },\n {\n op: 'form',\n noun: 'form',\n where: 'Forms',\n intents: ['a contact form, a quote request, a sign-up form'],\n argsSchema: NO_ARGS,\n maxPerPlan: 3,\n freeAllowed: true,\n runnerKind: 'form',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n op: 'component',\n noun: 'component',\n where: 'Components',\n intents: ['a reusable card or block placed on several pages'],\n argsSchema: NO_ARGS,\n maxPerPlan: 4,\n freeAllowed: false,\n runnerKind: 'component',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n op: 'email',\n noun: 'email design',\n where: 'Emails → Templates',\n intents: ['an email design: a welcome, a reply, an announcement'],\n argsSchema: NO_ARGS,\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'email',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n op: 'template',\n noun: 'page template',\n where: 'Templates',\n intents: ['a page template for blog posts, products or authors'],\n argsSchema: {\n type: 'object',\n properties: {\n subject: {\n type: 'string',\n description: 'What each page shows: entry (a collection entry), product or author.',\n enum: AI_TEMPLATE_SUBJECTS,\n },\n collectionId: {\n type: 'string',\n description: 'For entry: the collection id from the site inventory.',\n maxLength: 100,\n },\n },\n required: ['subject'],\n additionalProperties: false,\n },\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'template',\n estimateCredits: passes(6),\n degrade: 'omit',\n },\n {\n op: 'campaign',\n noun: 'email campaign',\n where: 'Marketing → Campaigns',\n intents: ['an email campaign drafted and left unsent'],\n argsSchema: {\n type: 'object',\n properties: {\n name: { type: 'string', description: 'What the campaign is called.', maxLength: 120 },\n },\n additionalProperties: false,\n },\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'campaign',\n estimateCredits: passes(2),\n degrade: 'omit',\n },\n {\n op: 'workflow',\n noun: 'automation',\n where: 'Automations',\n intents: ['an automation: when a form is sent, do something'],\n argsSchema: NO_ARGS,\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'workflow',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n // A function needs a model, so it is this plugin's operation; the logic\n // plugin owns the record and writes it (AGL-3616). New functions only: a\n // build never changes one the site already has.\n op: 'function',\n noun: 'site function',\n where: 'Logic → Functions',\n intents: ['a site function that works a value out from what it is given: a price, a total, a quote'],\n argsSchema: NO_ARGS,\n maxPerPlan: 2,\n // Free includes one function (`functionsPerHost: 1`), and Logic no feature.\n freeAllowed: true,\n quota: 'functionsPerHost',\n runnerKind: 'logic',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n // A change to something the site already has (AGL-3616), made by the\n // `edit` runner: the Assist edit rung's protocol against the stored\n // document, saved as a version visitors do not see. Not on the Free\n // taste: without version history only an unpublished page can take a\n // change, which is rarely the page a request means, and its admission\n // would refuse the item after the plan was confirmed.\n op: 'edit',\n noun: 'page change',\n where: 'the page’s or layout’s versions',\n intents: [\n 'a change to a page or layout the site already has — shorter copy, a new section, different wording — saved as a version visitors do not see until it is published',\n ],\n argsSchema: {\n type: 'object',\n properties: {\n target: {\n type: 'string',\n description: 'The id of an existing page or layout from the site inventory.',\n maxLength: 64,\n },\n targetKind: {\n type: 'string',\n description: 'screen when target is a page, layout when it is a layout.',\n enum: AI_EDIT_TARGET_KINDS,\n },\n },\n required: ['target'],\n additionalProperties: false,\n },\n maxPerPlan: 3,\n freeAllowed: false,\n runnerKind: 'edit',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n // The site's header and footer menus (AGL-3728): the Besigner's own Page\n // Links and Dropdown Menus in the site's layout, changed by code from the\n // words the planner wrote, saved as a version beside the live one. No\n // model, so no credits. Not on the Free taste, as `edit`: a layout is\n // always served, so without version history there is nowhere to land.\n op: 'navigation',\n noun: 'menu change',\n where: 'the layout’s versions',\n intents: [\n 'changing the site’s header or footer menu — add, remove, rename, reorder or relink its items, or put items under a dropdown — saved as a layout version visitors do not see until it is published',\n ],\n argsSchema: {\n type: 'object',\n properties: {\n menu: {\n type: 'string',\n description: 'Which menu: header (the default), footer, or both.',\n enum: ['header', 'footer', 'both'],\n },\n layout: {\n type: 'string',\n description: 'The layout id from the site inventory, or new:<name> of a layout this plan makes; leave it out for the layout the site’s pages use.',\n maxLength: 64,\n },\n add: {\n type: 'array',\n description:\n 'Items to add, each \"Label => destination\", or \"Parent > Label => destination\" to put it under the dropdown Parent. A destination is a page id from the site inventory, new:<page title> of a page this plan makes, a path such as /contact, or an https:// address.',\n items: { type: 'string', maxLength: 160 },\n maxItems: 12,\n },\n remove: {\n type: 'array',\n description: 'Labels of items to take out, as \"Label\" or \"Parent > Label\".',\n items: { type: 'string', maxLength: 100 },\n maxItems: 12,\n },\n rename: {\n type: 'array',\n description: 'Items to rename, each \"Old label => New label\".',\n items: { type: 'string', maxLength: 100 },\n maxItems: 12,\n },\n relink: {\n type: 'array',\n description: 'Items to point somewhere else, each \"Label => destination\", destinations as in add.',\n items: { type: 'string', maxLength: 160 },\n maxItems: 12,\n },\n order: {\n type: 'array',\n description: 'Top-level labels in the order wanted; items not named keep their order after them.',\n items: { type: 'string', maxLength: 60 },\n maxItems: 12,\n },\n },\n additionalProperties: false,\n },\n maxPerPlan: 1,\n freeAllowed: false,\n // Offered only where the change has somewhere to land: a plan with version history.\n feature: 'versioning',\n runnerKind: 'navigation',\n // Written by code from the plan's words: no model pass.\n estimateCredits: () => 0,\n degrade: 'omit',\n },\n {\n // A copy of something the site already has (AGL-3740), made by core's\n // `duplicateResource` — the console's own Duplicate… — so \"another page\n // like Services\" is copied rather than generated again. No model, so no\n // credits; the copy meets the plan's own counters as a create does,\n // which is why the Free taste may ask for one.\n op: 'duplicate',\n noun: 'copy',\n where: 'the list the original is on',\n intents: [\n 'a copy of a page, component, layout, template, form, email design or workflow the site already has, as an unpublished draft to change — rather than making it again',\n ],\n argsSchema: {\n type: 'object',\n properties: {\n kind: {\n type: 'string',\n description: 'What to copy: screen (a page), component, layout, template, form, emailDesign or workflow.',\n enum: DUPLICABLE_HOST_RESOURCE_KINDS,\n },\n source: {\n type: 'string',\n description: 'The id of the thing to copy from the site inventory, or its exact name where the inventory does not list it.',\n maxLength: 64,\n },\n name: {\n type: 'string',\n description: 'What the copy is called; leave it out for \"Copy of\" the original’s name.',\n maxLength: DUPLICATE_NAME_MAX,\n },\n },\n required: ['kind', 'source'],\n additionalProperties: false,\n },\n maxPerPlan: 3,\n freeAllowed: true,\n runnerKind: 'duplicate',\n // Copied by code: no model pass.\n estimateCredits: () => 0,\n degrade: 'omit',\n },\n {\n // The site's settings (AGL-3748): the fields the console's settings\n // cards write — name, search defaults, favicon, logo and social image\n // from the media library, language, time zone, contact details and the\n // cookie banner's mode — held to the same limits and saved live when the\n // plan is confirmed, as a settings save is. No model, so no credits; every\n // plan may change its own settings, so the Free taste may too. Domains,\n // members, billing, plugins, indexing and anything with a credential are\n // not arguments, so they cannot be asked for.\n op: 'settings',\n noun: 'settings change',\n where: AI_SETTINGS_WHERE,\n intents: [\n 'changing the site’s settings — its name, search title and description, title pattern, favicon, logo or social image (from images already in the media library), language, time zone, business name, description (tagline), phone, contact email or address, or the cookie banner’s mode — saved to the live site when the plan is confirmed',\n ],\n argsSchema: AI_SETTINGS_ARGS_SCHEMA,\n maxPerPlan: 1,\n freeAllowed: true,\n runnerKind: 'settings',\n // A setting has no draft state: saved, it is what the live site renders.\n live: true,\n // Written by code from the plan's values: no model pass.\n estimateCredits: () => 0,\n degrade: 'omit',\n },\n {\n // Organizing files already in the site's own media library (AGL-3752):\n // the folder each is filed in, its tags, its alt text and its name, as\n // the library's Details drawer and bulk actions write them, saved live\n // when the plan is confirmed. Never an upload, a copy, a replacement or a\n // deletion, and never the organization's shared library. No model, so no\n // credits; every plan has a media library, so the Free taste may too.\n op: 'media',\n noun: 'media library change',\n where: AI_MEDIA_WHERE,\n intents: [\n `organizing files already in the site’s media library — filing them in a folder (made if missing), adding or removing tags, renaming one, or writing alt text for an image the request describes or from the caption the library already holds — up to ${AI_MEDIA_LIMITS.files} files a change, saved live when the plan is confirmed; never uploading, copying or deleting a file`,\n ],\n argsSchema: AI_MEDIA_ARGS_SCHEMA,\n maxPerPlan: 4,\n freeAllowed: true,\n runnerKind: 'media',\n // A library file's details have no draft state: saved, they are what the library shows.\n live: true,\n // Written by code from the plan's values: no model pass.\n estimateCredits: () => 0,\n degrade: 'omit',\n },\n {\n // The site's analytics and advertising tags (AGL-3751): the ids the\n // console's Setup → Tracking card sets — Google Analytics, Tag Manager,\n // and the Meta, Google Ads, LinkedIn, TikTok and Pinterest tags — each\n // held to the card's own pattern, set or removed, saved live on confirm.\n // Only public ids the person states: never a credential (Ad conversions'\n // tokens, Zapier, webhook secrets), and never the consent banner, which\n // still decides where every tag loads. No model, so no credits; the card\n // is on every plan, so the Free taste may ask too.\n op: 'tracking',\n noun: 'tracking change',\n where: AI_TRACKING_WHERE,\n intents: [\n 'connecting or removing the site’s Google Analytics, Google Tag Manager, Meta pixel, Google Ads, LinkedIn Insight, TikTok pixel or Pinterest tag by the ID the person gives (never one it makes up; ask for the ID when the request has none) — saved to the live site when the plan is confirmed, loading only where a visitor’s cookie consent allows',\n ],\n argsSchema: AI_TRACKING_ARGS_SCHEMA,\n maxPerPlan: 1,\n freeAllowed: true,\n runnerKind: 'tracking',\n // A tag has no draft state: saved, it is on every live page.\n live: true,\n // An item with no usable id asks for it instead of being built (AGL-3751).\n missingInput: aiTrackingMissingInput,\n // …and waits for it in `needs_input`, with a field naming the provider (AGL-3978).\n inputFields: aiTrackingInputFields,\n // Written by code from the plan's values: no model pass.\n estimateCredits: () => 0,\n degrade: 'omit',\n },\n {\n // Who can see the site (AGL-4033): maintenance mode, the Discourage search\n // engines switch and a page's visibility, as the console's three controls\n // set them, saved live on confirm. Never a page password, which would put\n // the password in the chat. No model, so no credits; every plan has these\n // controls, so the Free taste may ask too.\n op: 'privacy',\n noun: 'privacy change',\n where: AI_PRIVACY_WHERE,\n intents: [\n 'making the site private or public again — maintenance mode (the whole site offline to every visitor until it is turned off; site admins only), hiding the site from search engines (it stays open to anyone with a link), or setting pages Public, Unlisted or Members only (Business plan and above) — saved to the live site when the plan is confirmed; never a page password, which the person sets in the page’s Page Access card',\n ],\n argsSchema: AI_PRIVACY_ARGS_SCHEMA,\n maxPerPlan: 1,\n freeAllowed: true,\n runnerKind: 'privacy',\n // None of these has a draft state: saved, it is what the live site does.\n live: true,\n // Written by code from the plan's values: no model pass.\n estimateCredits: () => 0,\n degrade: 'omit',\n },\n {\n // A change to the site's theme (AGL-4077): the theme runner's proposal —\n // the same one the Theme assistant makes — which the owner previews and\n // applies in Setup → Theme. The build writes nothing to the theme, so it\n // is never live. One a plan, on paid plans only; the Free taste changes\n // its theme in Setup → Theme.\n op: AI_BUILD_THEME_OP,\n noun: 'theme change',\n where: 'Setup → Theme',\n intents: [\n 'a change to the site’s theme — its colors, fonts or corners — proposed for the owner to apply in Setup → Theme; the build never changes the live theme',\n ],\n argsSchema: NO_ARGS,\n maxPerPlan: 1,\n freeAllowed: false,\n runnerKind: 'theme',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n]\n\n/**\n * The resource another plugin writes for each operation of this plugin's that\n * cannot be built without it. The operation runs where that writer's owner\n * runs — registered, switched on for the workspace and the site, and past its\n * release flag — exactly as an operation that owner registered would.\n */\nexport const AI_OWNED_OP_WRITERS: Readonly<Partial<Record<string, string>>> = {\n function: AI_LOGIC_FUNCTION_RESOURCE,\n // A campaign is written by the marketing plugin's campaign writer, an\n // automation by the workflows plugin's: neither is offered where its\n // writer's plugin is off, rather than planned and then skipped.\n campaign: AI_CAMPAIGN_RESOURCE,\n workflow: AI_AUTOMATION_RESOURCE,\n}\n\n/** Registers this plugin's build operations; the console surface calls it. */\nexport function registerAiBuildCapabilities(): void {\n for (const capability of AI_OWNED_CAPABILITIES) {\n registerPluginAiCapability(capability, { pluginId: AI_PLUGIN_ID })\n }\n}\n\n/** What decides which operations a build may use here. */\nexport interface AiBuildOpsContext {\n orgId: string\n hostId: string | null\n org: Partial<AglynOrgBilling> | null\n /** The site document, for the plugins switched on and off on it. */\n host: Readonly<Record<string, unknown>> | null\n freeTaste: boolean\n}\n\nexport interface AiBuildOpsDeps {\n /** The registered capabilities; the core registry's otherwise. */\n capabilities?: () => ResolvedPluginAiCapability[]\n /** The release-flag read; the platform's otherwise. */\n releasedPlugins?: (pluginIds: string[], orgId: string) => Promise<string[]>\n /** Whether a runner is registered for a kind; the machine's otherwise. */\n hasRunner?: (kind: string) => boolean\n /** Which plugin writes a resource; the core's registry otherwise. */\n writerOwner?: AiPluginDraftOwnerLookup\n}\n\n/**\n * The operations a build may plan on this site, by op (AGL-3616): every\n * registered capability whose owner runs here — switched on for the\n * workspace and the site, past its release flag; for one of this plugin's\n * operations another plugin writes (`AI_OWNED_OP_WRITERS`), that writer's\n * owner, and a writer registered at all — whose entitlement the plan\n * holds, which the Free taste may use where the workspace is on it, and,\n * for an AI-run operation, whose runner this process loaded. One release-flag\n * read for every owner at once. What a member may do and what an allowance\n * has left are the owner's to say when the item runs (`refusal`).\n */\nexport async function aiBuildOps(context: AiBuildOpsContext, deps: AiBuildOpsDeps = {}): Promise<AiBuildOps> {\n const all = (deps.capabilities ?? pluginAiCapabilities)()\n const hasRunner = deps.hasRunner ?? ((kind: string) => aiJobStepRunnerFor(kind as never) !== null)\n const writerOwner = deps.writerOwner ?? aiPluginDraftOwner\n // The plugin each of this plugin's operations depends on, by op; `null` where its writer is not registered.\n const writtenBy = new Map<string, string | null>()\n for (const { pluginId, capability } of all) {\n const resource = pluginId === AI_PLUGIN_ID ? AI_OWNED_OP_WRITERS[capability.op] : undefined\n if (resource) writtenBy.set(capability.op, writerOwner(resource))\n }\n const owners = [\n ...new Set(\n [...all.map((one) => one.pluginId), ...writtenBy.values()].filter(\n (id): id is string => !!id && id !== AI_PLUGIN_ID,\n ),\n ),\n ]\n const released = new Set(\n owners.length\n ? await (deps.releasedPlugins ??\n ((ids, orgId) => filterEnabledPluginsByReleaseFlags(ids, { orgId, authorization: null })))(owners, context.orgId)\n : [],\n )\n const ops = new Map<string, PluginAiCapability>()\n for (const { pluginId, capability } of all) {\n // The plugin that must run here: the owner, or for one of this plugin's\n // operations, the plugin that writes what it makes.\n const runsOn = pluginId === AI_PLUGIN_ID ? writtenBy.get(capability.op) : pluginId\n if (runsOn === null) continue\n if (runsOn !== undefined && runsOn !== AI_PLUGIN_ID) {\n if (!released.has(runsOn)) continue\n if (!isHostPluginEnabled(context.org as { enabledPlugins?: string[] } | null, context.host, runsOn)) continue\n }\n if (capability.feature && !checkEntitlement(context.org, capability.feature)) continue\n if (context.freeTaste && !capability.freeAllowed) continue\n if (capability.runnerKind && !hasRunner(capability.runnerKind)) continue\n ops.set(capability.op, capability)\n }\n return ops\n}\n\n/**\n * The lines the plan step's request lists the item operations in: each op\n * with what it makes, its arguments and their meaning. Per site, so they ride\n * the job's own turn and never a cached system block.\n */\nexport function aiBuildOpLines(ops: AiBuildOps, structural: readonly string[]): string[] {\n const items = [...ops.values()].filter((one) => !structural.includes(one.op))\n if (!items.length) return ['Operations for items: none on this site. Leave items empty.']\n const lines = ['Operations for items (anything else goes in create or screens):']\n for (const one of items) {\n const args = Object.entries(one.argsSchema.properties).map(([name, property]) => {\n const required = one.argsSchema.required?.includes(name) ? '' : ' (optional)'\n // An array's values are named too (AGL-3616): told only \"days: array\",\n // the live canonical plan wrote \"monday\" for the enum's \"mon\".\n const choices = property.enum\n ? ` one of ${property.enum.join('|')}`\n : property.items?.enum\n ? ` of ${property.items.enum.join('|')}`\n : ''\n return `${name}: ${property.type}${choices}${required} — ${property.description}`\n })\n lines.push(\n `- ${one.op}: ${aiNounWithArticle(one.noun)}, at most ${one.maxPerPlan}; for ${one.intents.join('; ')}.${\n one.pageBlocks?.length ? ` A page section that uses it places the ${one.pageBlocks.join(' or ')} block: put new:<name> in that section's uses.` : ''\n } Args: ${args.length ? `{ ${args.join('; ')} }` : '{}'}.`,\n )\n }\n // The plan's own vocabulary has a theme change in create, which a build\n // does not build there (AGL-4077): the item is the one way it is made.\n if (ops.has(AI_BUILD_THEME_OP)) lines.push(AI_BUILD_THEME_OP_LINE)\n // Measured live on the canonical request (AGL-3616): asked for \"forms for\n // bookings\", the planner made a plain form rather than the operation that\n // takes bookings. An operation does the job; a form that imitates it does not.\n lines.push(\n 'When the request asks for what one of these operations makes, plan that operation as an item, and place it on the page that offers it. Never stand in for it with a form, page or component that only imitates it: a form that collects what the operation would handle does not do its job. A form is for what no operation here makes, such as general inquiries.',\n )\n return lines\n}\n\n/** What the plan is told where a build may change the theme (AGL-4077). */\nexport const AI_BUILD_THEME_OP_LINE =\n 'A change to the theme is planned as the theme item, never as a theme change in create: at most one, and only when the request asks for a different look.'\n\nfunction aiNounWithArticle(noun: string): string {\n return `${/^[aeiou]/i.test(noun) ? 'an' : 'a'} ${noun}`\n}\n\n/** What a build can make here, in the owner's words, for the chat's instructions. */\nexport function aiBuildIntents(ops: AiBuildOps): string[] {\n return [...ops.values()].flatMap((one) => one.intents)\n}\n"],"names":["DUPLICABLE_HOST_RESOURCE_KINDS","DUPLICATE_NAME_MAX","checkEntitlement","isHostPluginEnabled","pluginAiCapabilities","registerPluginAiCapability","filterEnabledPluginsByReleaseFlags","AI_TEMPLATE_SUBJECTS","AI_SITE_PASS_CREDITS","AI_BUILD_THEME_OP","AI_LOGIC_FUNCTION_RESOURCE","AI_AUTOMATION_RESOURCE","AI_EDIT_TARGET_KINDS","AI_CAMPAIGN_RESOURCE","aiPluginDraftOwner","aiJobStepRunnerFor","AI_SETTINGS_ARGS_SCHEMA","AI_SETTINGS_WHERE","AI_MEDIA_ARGS_SCHEMA","AI_MEDIA_LIMITS","AI_MEDIA_WHERE","AI_TRACKING_ARGS_SCHEMA","AI_TRACKING_WHERE","aiTrackingInputFields","aiTrackingMissingInput","AI_PRIVACY_ARGS_SCHEMA","AI_PRIVACY_WHERE","AI_PLUGIN_ID","NO_ARGS","type","properties","additionalProperties","passes","count","AI_OWNED_CAPABILITIES","op","noun","where","intents","argsSchema","maxPerPlan","freeAllowed","runnerKind","estimateCredits","degrade","subject","description","enum","collectionId","maxLength","required","name","quota","target","targetKind","menu","layout","add","items","maxItems","remove","rename","relink","order","feature","kind","source","live","files","missingInput","inputFields","AI_OWNED_OP_WRITERS","function","campaign","workflow","registerAiBuildCapabilities","capability","pluginId","aiBuildOps","context","deps","all","capabilities","hasRunner","writerOwner","writtenBy","Map","resource","undefined","set","owners","Set","map","one","values","filter","id","released","length","releasedPlugins","ids","orgId","authorization","ops","runsOn","get","has","org","host","freeTaste","aiBuildOpLines","structural","includes","lines","args","Object","entries","property","choices","join","push","aiNounWithArticle","pageBlocks","AI_BUILD_THEME_OP_LINE","test","aiBuildIntents","flatMap"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,8BAA8B,EAAEC,kBAAkB,QAAQ,4CAA2C;AAC9G,SAASC,gBAAgB,QAAQ,2CAA0C;AAE3E,SAASC,mBAAmB,QAAQ,8CAA6C;AACjF,SACEC,oBAAoB,EACpBC,0BAA0B,QAGrB,qDAAoD;AAC3D,SAASC,kCAAkC,QAAQ,gDAA+C;AAClG,SAASC,oBAAoB,QAAQ,mCAA+B;AACpE,SAASC,oBAAoB,QAAQ,0BAAsB;AAC3D,SAASC,iBAAiB,QAAyB,2BAAuB;AAC1E,SAASC,0BAA0B,QAAQ,2BAAuB;AAClE,SAASC,sBAAsB,QAAQ,8BAA0B;AACjE,SAASC,oBAAoB,QAAQ,0BAAsB;AAC3D,SAASC,oBAAoB,QAAQ,4BAAwB;AAC7D,SAASC,kBAAkB,QAAuC,4BAAwB;AAC1F,SAASC,kBAAkB,QAAQ,eAAW;AAC9C,SAASC,uBAAuB,EAAEC,iBAAiB,QAAQ,4BAAwB;AACnF,SAASC,oBAAoB,EAAEC,eAAe,EAAEC,cAAc,QAAQ,yBAAqB;AAC3F,SAASC,uBAAuB,EAAEC,iBAAiB,EAAEC,qBAAqB,EAAEC,sBAAsB,QAAQ,4BAAwB;AAClI,SAASC,sBAAsB,EAAEC,gBAAgB,QAAQ,2BAAuB;AAEhF;;;;;;;;;;;;;;;;;;;CAmBC,GAED,6DAA6D,GAC7D,MAAMC,eAAe;AAErB,MAAMC,UAAU;IAAEC,MAAM;IAAUC,YAAY,CAAC;IAAGC,sBAAsB;AAAM;AAE9E,MAAMC,SAAS,CAACC,QAAkB,IAAMA,QAAQzB;AAEhD,2DAA2D,GAC3D,OAAO,MAAM0B,wBAAuD;IAClE;QACEC,IAAI;QACJC,MAAM;QACNC,OAAO;QACPC,SAAS;YAAC;SAAqE;QAC/EC,YAAYX;QACZY,YAAY;QACZC,aAAa;QACbC,YAAY;QACZC,iBAAiBX,OAAO;QACxBY,SAAS;IACX;IACA;QACET,IAAI;QACJC,MAAM;QACNC,OAAO;QACPC,SAAS;YAAC;SAAsC;QAChDC,YAAYX;QACZY,YAAY;QACZC,aAAa;QACbC,YAAY;QACZC,iBAAiBX,OAAO;QACxBY,SAAS;IACX;IACA;QACET,IAAI;QACJC,MAAM;QACNC,OAAO;QACPC,SAAS;YAAC;SAAkD;QAC5DC,YAAYX;QACZY,YAAY;QACZC,aAAa;QACbC,YAAY;QACZC,iBAAiBX,OAAO;QACxBY,SAAS;IACX;IACA;QACET,IAAI;QACJC,MAAM;QACNC,OAAO;QACPC,SAAS;YAAC;SAAmD;QAC7DC,YAAYX;QACZY,YAAY;QACZC,aAAa;QACbC,YAAY;QACZC,iBAAiBX,OAAO;QACxBY,SAAS;IACX;IACA;QACET,IAAI;QACJC,MAAM;QACNC,OAAO;QACPC,SAAS;YAAC;SAAuD;QACjEC,YAAYX;QACZY,YAAY;QACZC,aAAa;QACbC,YAAY;QACZC,iBAAiBX,OAAO;QACxBY,SAAS;IACX;IACA;QACET,IAAI;QACJC,MAAM;QACNC,OAAO;QACPC,SAAS;YAAC;SAAsD;QAChEC,YAAY;YACVV,MAAM;YACNC,YAAY;gBACVe,SAAS;oBACPhB,MAAM;oBACNiB,aAAa;oBACbC,MAAMxC;gBACR;gBACAyC,cAAc;oBACZnB,MAAM;oBACNiB,aAAa;oBACbG,WAAW;gBACb;YACF;YACAC,UAAU;gBAAC;aAAU;YACrBnB,sBAAsB;QACxB;QACAS,YAAY;QACZC,aAAa;QACbC,YAAY;QACZC,iBAAiBX,OAAO;QACxBY,SAAS;IACX;IACA;QACET,IAAI;QACJC,MAAM;QACNC,OAAO;QACPC,SAAS;YAAC;SAA4C;QACtDC,YAAY;YACVV,MAAM;YACNC,YAAY;gBACVqB,MAAM;oBAAEtB,MAAM;oBAAUiB,aAAa;oBAAgCG,WAAW;gBAAI;YACtF;YACAlB,sBAAsB;QACxB;QACAS,YAAY;QACZC,aAAa;QACbC,YAAY;QACZC,iBAAiBX,OAAO;QACxBY,SAAS;IACX;IACA;QACET,IAAI;QACJC,MAAM;QACNC,OAAO;QACPC,SAAS;YAAC;SAAmD;QAC7DC,YAAYX;QACZY,YAAY;QACZC,aAAa;QACbC,YAAY;QACZC,iBAAiBX,OAAO;QACxBY,SAAS;IACX;IACA;QACE,wEAAwE;QACxE,yEAAyE;QACzE,gDAAgD;QAChDT,IAAI;QACJC,MAAM;QACNC,OAAO;QACPC,SAAS;YAAC;SAA0F;QACpGC,YAAYX;QACZY,YAAY;QACZ,4EAA4E;QAC5EC,aAAa;QACbW,OAAO;QACPV,YAAY;QACZC,iBAAiBX,OAAO;QACxBY,SAAS;IACX;IACA;QACE,qEAAqE;QACrE,oEAAoE;QACpE,oEAAoE;QACpE,qEAAqE;QACrE,sEAAsE;QACtE,sDAAsD;QACtDT,IAAI;QACJC,MAAM;QACNC,OAAO;QACPC,SAAS;YACP;SACD;QACDC,YAAY;YACVV,MAAM;YACNC,YAAY;gBACVuB,QAAQ;oBACNxB,MAAM;oBACNiB,aAAa;oBACbG,WAAW;gBACb;gBACAK,YAAY;oBACVzB,MAAM;oBACNiB,aAAa;oBACbC,MAAMnC;gBACR;YACF;YACAsC,UAAU;gBAAC;aAAS;YACpBnB,sBAAsB;QACxB;QACAS,YAAY;QACZC,aAAa;QACbC,YAAY;QACZC,iBAAiBX,OAAO;QACxBY,SAAS;IACX;IACA;QACE,yEAAyE;QACzE,0EAA0E;QAC1E,sEAAsE;QACtE,sEAAsE;QACtE,sEAAsE;QACtET,IAAI;QACJC,MAAM;QACNC,OAAO;QACPC,SAAS;YACP;SACD;QACDC,YAAY;YACVV,MAAM;YACNC,YAAY;gBACVyB,MAAM;oBACJ1B,MAAM;oBACNiB,aAAa;oBACbC,MAAM;wBAAC;wBAAU;wBAAU;qBAAO;gBACpC;gBACAS,QAAQ;oBACN3B,MAAM;oBACNiB,aAAa;oBACbG,WAAW;gBACb;gBACAQ,KAAK;oBACH5B,MAAM;oBACNiB,aACE;oBACFY,OAAO;wBAAE7B,MAAM;wBAAUoB,WAAW;oBAAI;oBACxCU,UAAU;gBACZ;gBACAC,QAAQ;oBACN/B,MAAM;oBACNiB,aAAa;oBACbY,OAAO;wBAAE7B,MAAM;wBAAUoB,WAAW;oBAAI;oBACxCU,UAAU;gBACZ;gBACAE,QAAQ;oBACNhC,MAAM;oBACNiB,aAAa;oBACbY,OAAO;wBAAE7B,MAAM;wBAAUoB,WAAW;oBAAI;oBACxCU,UAAU;gBACZ;gBACAG,QAAQ;oBACNjC,MAAM;oBACNiB,aAAa;oBACbY,OAAO;wBAAE7B,MAAM;wBAAUoB,WAAW;oBAAI;oBACxCU,UAAU;gBACZ;gBACAI,OAAO;oBACLlC,MAAM;oBACNiB,aAAa;oBACbY,OAAO;wBAAE7B,MAAM;wBAAUoB,WAAW;oBAAG;oBACvCU,UAAU;gBACZ;YACF;YACA5B,sBAAsB;QACxB;QACAS,YAAY;QACZC,aAAa;QACb,oFAAoF;QACpFuB,SAAS;QACTtB,YAAY;QACZ,wDAAwD;QACxDC,iBAAiB,IAAM;QACvBC,SAAS;IACX;IACA;QACE,sEAAsE;QACtE,wEAAwE;QACxE,wEAAwE;QACxE,oEAAoE;QACpE,+CAA+C;QAC/CT,IAAI;QACJC,MAAM;QACNC,OAAO;QACPC,SAAS;YACP;SACD;QACDC,YAAY;YACVV,MAAM;YACNC,YAAY;gBACVmC,MAAM;oBACJpC,MAAM;oBACNiB,aAAa;oBACbC,MAAM/C;gBACR;gBACAkE,QAAQ;oBACNrC,MAAM;oBACNiB,aAAa;oBACbG,WAAW;gBACb;gBACAE,MAAM;oBACJtB,MAAM;oBACNiB,aAAa;oBACbG,WAAWhD;gBACb;YACF;YACAiD,UAAU;gBAAC;gBAAQ;aAAS;YAC5BnB,sBAAsB;QACxB;QACAS,YAAY;QACZC,aAAa;QACbC,YAAY;QACZ,iCAAiC;QACjCC,iBAAiB,IAAM;QACvBC,SAAS;IACX;IACA;QACE,oEAAoE;QACpE,sEAAsE;QACtE,uEAAuE;QACvE,yEAAyE;QACzE,2EAA2E;QAC3E,wEAAwE;QACxE,yEAAyE;QACzE,8CAA8C;QAC9CT,IAAI;QACJC,MAAM;QACNC,OAAOpB;QACPqB,SAAS;YACP;SACD;QACDC,YAAYvB;QACZwB,YAAY;QACZC,aAAa;QACbC,YAAY;QACZ,yEAAyE;QACzEyB,MAAM;QACN,yDAAyD;QACzDxB,iBAAiB,IAAM;QACvBC,SAAS;IACX;IACA;QACE,uEAAuE;QACvE,uEAAuE;QACvE,uEAAuE;QACvE,0EAA0E;QAC1E,yEAAyE;QACzE,sEAAsE;QACtET,IAAI;QACJC,MAAM;QACNC,OAAOjB;QACPkB,SAAS;YACP,CAAC,sPAAsP,EAAEnB,gBAAgBiD,KAAK,CAAC,mGAAmG,CAAC;SACpX;QACD7B,YAAYrB;QACZsB,YAAY;QACZC,aAAa;QACbC,YAAY;QACZ,wFAAwF;QACxFyB,MAAM;QACN,yDAAyD;QACzDxB,iBAAiB,IAAM;QACvBC,SAAS;IACX;IACA;QACE,oEAAoE;QACpE,wEAAwE;QACxE,uEAAuE;QACvE,yEAAyE;QACzE,yEAAyE;QACzE,wEAAwE;QACxE,yEAAyE;QACzE,mDAAmD;QACnDT,IAAI;QACJC,MAAM;QACNC,OAAOf;QACPgB,SAAS;YACP;SACD;QACDC,YAAYlB;QACZmB,YAAY;QACZC,aAAa;QACbC,YAAY;QACZ,6DAA6D;QAC7DyB,MAAM;QACN,2EAA2E;QAC3EE,cAAc7C;QACd,mFAAmF;QACnF8C,aAAa/C;QACb,yDAAyD;QACzDoB,iBAAiB,IAAM;QACvBC,SAAS;IACX;IACA;QACE,2EAA2E;QAC3E,0EAA0E;QAC1E,0EAA0E;QAC1E,0EAA0E;QAC1E,2CAA2C;QAC3CT,IAAI;QACJC,MAAM;QACNC,OAAOX;QACPY,SAAS;YACP;SACD;QACDC,YAAYd;QACZe,YAAY;QACZC,aAAa;QACbC,YAAY;QACZ,yEAAyE;QACzEyB,MAAM;QACN,yDAAyD;QACzDxB,iBAAiB,IAAM;QACvBC,SAAS;IACX;IACA;QACE,yEAAyE;QACzE,wEAAwE;QACxE,yEAAyE;QACzE,wEAAwE;QACxE,8BAA8B;QAC9BT,IAAI1B;QACJ2B,MAAM;QACNC,OAAO;QACPC,SAAS;YACP;SACD;QACDC,YAAYX;QACZY,YAAY;QACZC,aAAa;QACbC,YAAY;QACZC,iBAAiBX,OAAO;QACxBY,SAAS;IACX;CACD,CAAA;AAED;;;;;CAKC,GACD,OAAO,MAAM2B,sBAAiE;IAC5EC,UAAU9D;IACV,sEAAsE;IACtE,qEAAqE;IACrE,gEAAgE;IAChE+D,UAAU5D;IACV6D,UAAU/D;AACZ,EAAC;AAED,4EAA4E,GAC5E,OAAO,SAASgE;IACd,KAAK,MAAMC,cAAc1C,sBAAuB;QAC9C7B,2BAA2BuE,YAAY;YAAEC,UAAUlD;QAAa;IAClE;AACF;AAuBA;;;;;;;;;;CAUC,GACD,OAAO,eAAemD,WAAWC,OAA0B,EAAEC,OAAuB,CAAC,CAAC;QACvEA,oBACKA,iBACEA,mBAgBPA;IAlBb,MAAMC,MAAM,EAACD,qBAAAA,KAAKE,YAAY,YAAjBF,qBAAqB5E;IAClC,MAAM+E,aAAYH,kBAAAA,KAAKG,SAAS,YAAdH,kBAAmB,CAACf,OAAiBlD,mBAAmBkD,UAAmB;IAC7F,MAAMmB,eAAcJ,oBAAAA,KAAKI,WAAW,YAAhBJ,oBAAoBlE;IACxC,4GAA4G;IAC5G,MAAMuE,YAAY,IAAIC;IACtB,KAAK,MAAM,EAAET,QAAQ,EAAED,UAAU,EAAE,IAAIK,IAAK;QAC1C,MAAMM,WAAWV,aAAalD,eAAe4C,mBAAmB,CAACK,WAAWzC,EAAE,CAAC,GAAGqD;QAClF,IAAID,UAAUF,UAAUI,GAAG,CAACb,WAAWzC,EAAE,EAAEiD,YAAYG;IACzD;IACA,MAAMG,SAAS;WACV,IAAIC,IACL;eAAIV,IAAIW,GAAG,CAAC,CAACC,MAAQA,IAAIhB,QAAQ;eAAMQ,UAAUS,MAAM;SAAG,CAACC,MAAM,CAC/D,CAACC,KAAqB,CAAC,CAACA,MAAMA,OAAOrE;KAG1C;IACD,MAAMsE,WAAW,IAAIN,IACnBD,OAAOQ,MAAM,GACT,MAAM,EAAClB,wBAAAA,KAAKmB,eAAe,YAApBnB,wBACJ,CAACoB,KAAKC,QAAU/F,mCAAmC8F,KAAK;YAAEC;YAAOC,eAAe;QAAK,IAAKZ,QAAQX,QAAQsB,KAAK,IAClH,EAAE;IAER,MAAME,MAAM,IAAIjB;IAChB,KAAK,MAAM,EAAET,QAAQ,EAAED,UAAU,EAAE,IAAIK,IAAK;QAC1C,wEAAwE;QACxE,oDAAoD;QACpD,MAAMuB,SAAS3B,aAAalD,eAAe0D,UAAUoB,GAAG,CAAC7B,WAAWzC,EAAE,IAAI0C;QAC1E,IAAI2B,WAAW,MAAM;QACrB,IAAIA,WAAWhB,aAAagB,WAAW7E,cAAc;YACnD,IAAI,CAACsE,SAASS,GAAG,CAACF,SAAS;YAC3B,IAAI,CAACrG,oBAAoB4E,QAAQ4B,GAAG,EAA0C5B,QAAQ6B,IAAI,EAAEJ,SAAS;QACvG;QACA,IAAI5B,WAAWZ,OAAO,IAAI,CAAC9D,iBAAiB6E,QAAQ4B,GAAG,EAAE/B,WAAWZ,OAAO,GAAG;QAC9E,IAAIe,QAAQ8B,SAAS,IAAI,CAACjC,WAAWnC,WAAW,EAAE;QAClD,IAAImC,WAAWlC,UAAU,IAAI,CAACyC,UAAUP,WAAWlC,UAAU,GAAG;QAChE6D,IAAId,GAAG,CAACb,WAAWzC,EAAE,EAAEyC;IACzB;IACA,OAAO2B;AACT;AAEA;;;;CAIC,GACD,OAAO,SAASO,eAAeP,GAAe,EAAEQ,UAA6B;IAC3E,MAAMrD,QAAQ;WAAI6C,IAAIT,MAAM;KAAG,CAACC,MAAM,CAAC,CAACF,MAAQ,CAACkB,WAAWC,QAAQ,CAACnB,IAAI1D,EAAE;IAC3E,IAAI,CAACuB,MAAMwC,MAAM,EAAE,OAAO;QAAC;KAA8D;IACzF,MAAMe,QAAQ;QAAC;KAAkE;IACjF,KAAK,MAAMpB,OAAOnC,MAAO;YAcnBmC;QAbJ,MAAMqB,OAAOC,OAAOC,OAAO,CAACvB,IAAItD,UAAU,CAACT,UAAU,EAAE8D,GAAG,CAAC,CAAC,CAACzC,MAAMkE,SAAS;gBACzDxB,0BAKbwB;YALJ,MAAMnE,WAAW2C,EAAAA,2BAAAA,IAAItD,UAAU,CAACW,QAAQ,qBAAvB2C,yBAAyBmB,QAAQ,CAAC7D,SAAQ,KAAK;YAChE,uEAAuE;YACvE,+DAA+D;YAC/D,MAAMmE,UAAUD,SAAStE,IAAI,GACzB,CAAC,QAAQ,EAAEsE,SAAStE,IAAI,CAACwE,IAAI,CAAC,MAAM,GACpCF,EAAAA,kBAAAA,SAAS3D,KAAK,qBAAd2D,gBAAgBtE,IAAI,IAClB,CAAC,IAAI,EAAEsE,SAAS3D,KAAK,CAACX,IAAI,CAACwE,IAAI,CAAC,MAAM,GACtC;YACN,OAAO,GAAGpE,KAAK,EAAE,EAAEkE,SAASxF,IAAI,GAAGyF,UAAUpE,SAAS,GAAG,EAAEmE,SAASvE,WAAW,EAAE;QACnF;QACAmE,MAAMO,IAAI,CACR,CAAC,EAAE,EAAE3B,IAAI1D,EAAE,CAAC,EAAE,EAAEsF,kBAAkB5B,IAAIzD,IAAI,EAAE,UAAU,EAAEyD,IAAIrD,UAAU,CAAC,MAAM,EAAEqD,IAAIvD,OAAO,CAACiF,IAAI,CAAC,MAAM,CAAC,EACrG1B,EAAAA,kBAAAA,IAAI6B,UAAU,qBAAd7B,gBAAgBK,MAAM,IAAG,CAAC,wCAAwC,EAAEL,IAAI6B,UAAU,CAACH,IAAI,CAAC,QAAQ,8CAA8C,CAAC,GAAG,GACnJ,OAAO,EAAEL,KAAKhB,MAAM,GAAG,CAAC,EAAE,EAAEgB,KAAKK,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC;IAE9D;IACA,wEAAwE;IACxE,uEAAuE;IACvE,IAAIhB,IAAIG,GAAG,CAACjG,oBAAoBwG,MAAMO,IAAI,CAACG;IAC3C,0EAA0E;IAC1E,0EAA0E;IAC1E,+EAA+E;IAC/EV,MAAMO,IAAI,CACR;IAEF,OAAOP;AACT;AAEA,yEAAyE,GACzE,OAAO,MAAMU,yBACX,2JAA0J;AAE5J,SAASF,kBAAkBrF,IAAY;IACrC,OAAO,GAAG,YAAYwF,IAAI,CAACxF,QAAQ,OAAO,IAAI,CAAC,EAAEA,MAAM;AACzD;AAEA,mFAAmF,GACnF,OAAO,SAASyF,eAAetB,GAAe;IAC5C,OAAO;WAAIA,IAAIT,MAAM;KAAG,CAACgC,OAAO,CAAC,CAACjC,MAAQA,IAAIvD,OAAO;AACvD"}
|
|
@@ -23,8 +23,8 @@ import { prefillAiBusinessProfile } from "../runtime/site-context.js";
|
|
|
23
23
|
*
|
|
24
24
|
* Two sources, each recorded as itself:
|
|
25
25
|
* - `start`: the answers the owner gave the guided start — what the business
|
|
26
|
-
* is, who it is for, the areas it serves (else its city)
|
|
27
|
-
* given in another place.
|
|
26
|
+
* is, who it is for, the areas it serves (else its city), the year it was
|
|
27
|
+
* founded (AGL-4092). Their words, given in another place.
|
|
28
28
|
* - `ai`: what the plan wrote for the home page's search description, used
|
|
29
29
|
* only where the start said nothing about what the business does.
|
|
30
30
|
*
|
|
@@ -45,12 +45,15 @@ import { prefillAiBusinessProfile } from "../runtime/site-context.js";
|
|
|
45
45
|
// The areas "More details" names (AGL-3979), as many whole ones as the field holds; else the city.
|
|
46
46
|
const max = BUSINESS_PROFILE_MAX_CHARS.serviceArea;
|
|
47
47
|
let serviceArea = '';
|
|
48
|
-
|
|
48
|
+
const details = aiSiteDetailsOf(inputs);
|
|
49
|
+
for (const area of aiSiteServiceAreas(details.serviceArea)){
|
|
49
50
|
const next = serviceArea ? `${serviceArea}, ${area}` : area;
|
|
50
51
|
if (next.length > max) break;
|
|
51
52
|
serviceArea = next;
|
|
52
53
|
}
|
|
53
54
|
if (serviceArea || city) values.serviceArea = (serviceArea || city).slice(0, max);
|
|
55
|
+
// The year "More details" names (AGL-4092), so a later build reads it from the profile instead of marking a gap for it.
|
|
56
|
+
if (details.founded) values.founded = details.founded.slice(0, BUSINESS_PROFILE_MAX_CHARS.founded);
|
|
54
57
|
return Object.keys(values).length ? values : null;
|
|
55
58
|
}
|
|
56
59
|
/** The plan's home page description, as a line about the business; `null` when it has none. */ export function aiBusinessProfilePlanValues(job) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-business-profile-prefill.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 { BUSINESS_PROFILE_MAX_CHARS, type BusinessProfileValues } from '@aglyn/aglyn/app-utils/business-profile'\nimport type { AiJob } from '../model/ai-jobs.types'\nimport { aiSiteDetailsOf, aiSiteServiceAreas } from '../model/ai-site-details'\nimport { prefillAiBusinessProfile } from '../runtime/site-context'\nimport type { AiJobTransitionListener } from './ai-jobs'\n\n/**\n * A site job fills in the site's business profile (AGL-3661), so the next job\n * — and the owner, on Setup → Business profile — starts from what the guided\n * start was told rather than from nothing.\n *\n * Two sources, each recorded as itself:\n * - `start`: the answers the owner gave the guided start — what the business\n * is, who it is for, the areas it serves (else its city). Their words
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-business-profile-prefill.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 { BUSINESS_PROFILE_MAX_CHARS, type BusinessProfileValues } from '@aglyn/aglyn/app-utils/business-profile'\nimport type { AiJob } from '../model/ai-jobs.types'\nimport { aiSiteDetailsOf, aiSiteServiceAreas } from '../model/ai-site-details'\nimport { prefillAiBusinessProfile } from '../runtime/site-context'\nimport type { AiJobTransitionListener } from './ai-jobs'\n\n/**\n * A site job fills in the site's business profile (AGL-3661), so the next job\n * — and the owner, on Setup → Business profile — starts from what the guided\n * start was told rather than from nothing.\n *\n * Two sources, each recorded as itself:\n * - `start`: the answers the owner gave the guided start — what the business\n * is, who it is for, the areas it serves (else its city), the year it was\n * founded (AGL-4092). Their words, given in another place.\n * - `ai`: what the plan wrote for the home page's search description, used\n * only where the start said nothing about what the business does.\n *\n * Neither ever replaces what the owner typed into the profile; the merge in\n * `@aglyn/aglyn/app-utils/business-profile` decides that, not this file.\n */\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** \"a local dog groomer\" reads as a line once its first letter is a capital. */\nconst sentence = (value: string): string => (value ? value[0].toUpperCase() + value.slice(1) : value)\n\n/** The guided start's answers, as profile values; `null` when it gave none. */\nexport function aiBusinessProfileStartValues(job: Pick<AiJob, 'kind' | 'inputs'>): BusinessProfileValues | null {\n if (job.kind !== 'site') return null\n const inputs = job.inputs ?? {}\n const values: BusinessProfileValues = {}\n const businessType = text(inputs['businessType'])\n const audience = text(inputs['audience'])\n const city = text(inputs['city'])\n if (businessType) values.whatYouDo = sentence(businessType)\n if (audience) values.audience = audience\n // The areas \"More details\" names (AGL-3979), as many whole ones as the field holds; else the city.\n const max = BUSINESS_PROFILE_MAX_CHARS.serviceArea\n let serviceArea = ''\n const details = aiSiteDetailsOf(inputs)\n for (const area of aiSiteServiceAreas(details.serviceArea)) {\n const next = serviceArea ? `${serviceArea}, ${area}` : area\n if (next.length > max) break\n serviceArea = next\n }\n if (serviceArea || city) values.serviceArea = (serviceArea || city).slice(0, max)\n // The year \"More details\" names (AGL-4092), so a later build reads it from the profile instead of marking a gap for it.\n if (details.founded) values.founded = details.founded.slice(0, BUSINESS_PROFILE_MAX_CHARS.founded)\n return Object.keys(values).length ? values : null\n}\n\n/** The plan's home page description, as a line about the business; `null` when it has none. */\nexport function aiBusinessProfilePlanValues(job: Pick<AiJob, 'kind' | 'plan'>): BusinessProfileValues | null {\n if (job.kind !== 'site') return null\n const screens = job.plan?.screens ?? []\n const home = screens.find((screen) => ['', '/', 'home', 'index'].includes(text(screen.slug).replace(/^\\/+|\\/+$/g, '')))\n const description = text(home?.seoDescription)\n return description ? { whatYouDo: description } : null\n}\n\n/**\n * The transition listener's half that prefills (AGL-3661): on a site job's\n * plan and on its finish, the start's answers and then the plan's guess, each\n * a no-op once written. A failure is logged and never fails the job.\n */\nexport function aiBusinessProfilePrefiller(\n firestoreOf: () => FirebaseFirestore.Firestore,\n): AiJobTransitionListener {\n return async ({ job, to }) => {\n if (job.kind !== 'site' || !job.hostId || to === 'failed') return\n const start = aiBusinessProfileStartValues(job)\n const plan = aiBusinessProfilePlanValues(job)\n if (!start && !plan) return\n try {\n const firestore = firestoreOf()\n if (start) await prefillAiBusinessProfile(firestore, job.hostId, start, 'start')\n if (plan) await prefillAiBusinessProfile(firestore, job.hostId, plan, 'ai')\n } catch (error) {\n console.warn('ai business profile prefill failed', { orgId: job.orgId, jobId: job.$id, error })\n }\n }\n}\n"],"names":["BUSINESS_PROFILE_MAX_CHARS","aiSiteDetailsOf","aiSiteServiceAreas","prefillAiBusinessProfile","text","value","trim","sentence","toUpperCase","slice","aiBusinessProfileStartValues","job","kind","inputs","values","businessType","audience","city","whatYouDo","max","serviceArea","details","area","next","length","founded","Object","keys","aiBusinessProfilePlanValues","screens","plan","home","find","screen","includes","slug","replace","description","seoDescription","aiBusinessProfilePrefiller","firestoreOf","to","hostId","start","firestore","error","console","warn","orgId","jobId","$id"],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { AiMediaAssetLocation } from './ai-media-asset';
|
|
18
|
+
/** The images route's registered path, served under `/api/plugin-images/`. */
|
|
19
|
+
export declare const AI_IMAGE_READS_PATH = "ai/image-reads";
|
|
20
|
+
/** The request header that carries the signature: `t=<ms>,v1=<hex>`. */
|
|
21
|
+
export declare const AI_IMAGE_READS_SIGNATURE = "x-aglyn-images-signature";
|
|
22
|
+
/** How far a signed request's moment may be from the function's clock. */
|
|
23
|
+
export declare const AI_IMAGE_READS_WINDOW_MS: number;
|
|
24
|
+
/** The two reads the function makes. */
|
|
25
|
+
export type AiImageReadsOp = 'product-photo' | 'logo-colors';
|
|
26
|
+
/** One request's body: the asset by its media reference, and whose read it is. */
|
|
27
|
+
export interface AiImageReadsRequest {
|
|
28
|
+
op: AiImageReadsOp;
|
|
29
|
+
ref: string;
|
|
30
|
+
hostId: string;
|
|
31
|
+
orgId: string;
|
|
32
|
+
}
|
|
33
|
+
/** Test seam. */
|
|
34
|
+
export declare function setAiImageReadsSecretForTests(secret: (() => string) | null): void;
|
|
35
|
+
/** The header value for a body signed now. Throws on a deploy with no signing secret. */
|
|
36
|
+
export declare function signAiImageReadsRequest(body: string, nowMs?: number): string;
|
|
37
|
+
/**
|
|
38
|
+
* Whether a request's signature is ours, over this body, and recent. False
|
|
39
|
+
* for anything else, a deploy with no signing secret included (fail closed).
|
|
40
|
+
*/
|
|
41
|
+
export declare function verifyAiImageReadsRequest(header: string | null | undefined, body: string, nowMs?: number): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Where to ask for an image read, or `null` to make it in this process. On
|
|
44
|
+
* Vercel only the images function ships libvips, so every other function
|
|
45
|
+
* asks it, at the console's own address; anywhere else this process can
|
|
46
|
+
* decode.
|
|
47
|
+
*/
|
|
48
|
+
export declare function aiImageReadsOrigin(): string | null;
|
|
49
|
+
/**
|
|
50
|
+
* The request for a located asset's read: the asset named by its media
|
|
51
|
+
* reference, which the function locates again. `null` for a location no
|
|
52
|
+
* reference can name, which is never read.
|
|
53
|
+
*/
|
|
54
|
+
export declare function aiImageReadsRequest(op: AiImageReadsOp, location: AiMediaAssetLocation, hostId: string, orgId: string): AiImageReadsRequest | null;
|
|
55
|
+
/**
|
|
56
|
+
* Asks the images function for one read, signed, waiting no longer than the
|
|
57
|
+
* step's own bound for it (`signal`). Throws on anything but an answer.
|
|
58
|
+
*/
|
|
59
|
+
export declare function requestAiImageReads<T>(origin: string, payload: AiImageReadsRequest, signal: AbortSignal): Promise<T>;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { createHash, createHmac, timingSafeEqual } from "node:crypto";
|
|
18
|
+
import { PLUGIN_IMAGES_API_PREFIX } from "@aglyn/aglyn/app-utils/api-plugins";
|
|
19
|
+
import { formatMediaRef } from "@aglyn/aglyn/app-utils/media-ref";
|
|
20
|
+
import { platformConsoleOrigin } from "@aglyn/aglyn/app-utils/platform-brand";
|
|
21
|
+
import { tokenSigningSecret } from "@aglyn/tenant-data-admin/server/media-signing";
|
|
22
|
+
/*==========================================
|
|
23
|
+
* A JOB'S IMAGE READS HAVE THEIR OWN FUNCTION (AGL-4048).
|
|
24
|
+
*
|
|
25
|
+
* Two job steps decode a picture from a site's media library with `sharp`:
|
|
26
|
+
* the products step makes the JPEG of a product's first photo that the
|
|
27
|
+
* model is shown (`ai-product-image.ts`), and the theme step reduces the
|
|
28
|
+
* site logo to its dominant colors (`ai-theme-brand-inputs.ts`). `sharp`
|
|
29
|
+
* takes libvips, a 17 MB native library that on Vercel only the console's
|
|
30
|
+
* plugins' image function ships: neither the plugin dispatcher, where a door
|
|
31
|
+
* runs a job's first step, nor the AI jobs beat, which runs every step after
|
|
32
|
+
* it, carries it. So the read is a route of its own, registered to draw
|
|
33
|
+
* images (`images: true`) and served at `/api/plugin-images/ai/image-reads`
|
|
34
|
+
* (`server/ai-image-reads-route.ts`), and on Vercel a step asks it:
|
|
35
|
+
*
|
|
36
|
+
* product-photo {ref, hostId, orgId} `{read}`: the photo as the step
|
|
37
|
+
* sends it, or why it was not read
|
|
38
|
+
* logo-colors {ref, hostId, orgId} `{colors}`: the logo's dominant
|
|
39
|
+
* colors, or null
|
|
40
|
+
*
|
|
41
|
+
* The function locates the asset again from the reference, the site and
|
|
42
|
+
* the org, under the same rule the step used, and reads it the way the media
|
|
43
|
+
* CDN would serve it, so it reads nothing the step could not have read
|
|
44
|
+
* itself. Each request is signed with the platform's signing secret over the
|
|
45
|
+
* moment and the body, as the card reader start-up screen's are, under this
|
|
46
|
+
* route's own name, so a signature for one route is none for another. Off
|
|
47
|
+
* Vercel one process holds every route, and libvips with it, so the read
|
|
48
|
+
* runs where it is asked for, as before.
|
|
49
|
+
*
|
|
50
|
+
* Any failure is the read's own: the photo is unreadable and the copy is
|
|
51
|
+
* written from the product's words, or the logo's colors are left out with
|
|
52
|
+
* the note that says so.
|
|
53
|
+
*=========================================*/ /** The images route's registered path, served under `/api/plugin-images/`. */ export const AI_IMAGE_READS_PATH = 'ai/image-reads';
|
|
54
|
+
/** The request header that carries the signature: `t=<ms>,v1=<hex>`. */ export const AI_IMAGE_READS_SIGNATURE = 'x-aglyn-images-signature';
|
|
55
|
+
/** How far a signed request's moment may be from the function's clock. */ export const AI_IMAGE_READS_WINDOW_MS = 5 * 60 * 1000;
|
|
56
|
+
let secretOverride = null;
|
|
57
|
+
/** Test seam. */ export function setAiImageReadsSecretForTests(secret) {
|
|
58
|
+
secretOverride = secret;
|
|
59
|
+
}
|
|
60
|
+
function signature(at, body) {
|
|
61
|
+
const digest = createHash('sha256').update(body).digest('hex');
|
|
62
|
+
return createHmac('sha256', (secretOverride != null ? secretOverride : tokenSigningSecret)()).update(`ai-image-reads:${at}.${digest}`).digest('hex');
|
|
63
|
+
}
|
|
64
|
+
/** The header value for a body signed now. Throws on a deploy with no signing secret. */ export function signAiImageReadsRequest(body, nowMs = Date.now()) {
|
|
65
|
+
return `t=${nowMs},v1=${signature(nowMs, body)}`;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Whether a request's signature is ours, over this body, and recent. False
|
|
69
|
+
* for anything else, a deploy with no signing secret included (fail closed).
|
|
70
|
+
*/ export function verifyAiImageReadsRequest(header, body, nowMs = Date.now()) {
|
|
71
|
+
var _parts_v1;
|
|
72
|
+
const parts = Object.fromEntries(String(header != null ? header : '').split(',').map((part)=>part.trim().split('=')).filter((pair)=>pair.length === 2));
|
|
73
|
+
const at = Number(parts['t']);
|
|
74
|
+
const given = String((_parts_v1 = parts['v1']) != null ? _parts_v1 : '');
|
|
75
|
+
if (!Number.isFinite(at) || !/^[a-f0-9]{64}$/.test(given)) return false;
|
|
76
|
+
if (Math.abs(nowMs - at) > AI_IMAGE_READS_WINDOW_MS) return false;
|
|
77
|
+
let expected;
|
|
78
|
+
try {
|
|
79
|
+
expected = signature(at, body);
|
|
80
|
+
} catch (unused) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return timingSafeEqual(Buffer.from(given), Buffer.from(expected));
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Where to ask for an image read, or `null` to make it in this process. On
|
|
87
|
+
* Vercel only the images function ships libvips, so every other function
|
|
88
|
+
* asks it, at the console's own address; anywhere else this process can
|
|
89
|
+
* decode.
|
|
90
|
+
*/ export function aiImageReadsOrigin() {
|
|
91
|
+
if (process.env['VERCEL'] !== '1') return null;
|
|
92
|
+
return platformConsoleOrigin();
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The request for a located asset's read: the asset named by its media
|
|
96
|
+
* reference, which the function locates again. `null` for a location no
|
|
97
|
+
* reference can name, which is never read.
|
|
98
|
+
*/ export function aiImageReadsRequest(op, location, hostId, orgId) {
|
|
99
|
+
const ref = formatMediaRef(location.scopeSegment, location.mediaId);
|
|
100
|
+
return ref ? {
|
|
101
|
+
op,
|
|
102
|
+
ref,
|
|
103
|
+
hostId,
|
|
104
|
+
orgId
|
|
105
|
+
} : null;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The headers that get our own request past our own edge, when configured:
|
|
109
|
+
* the same pair the render monitor sends (AGL-3571).
|
|
110
|
+
*/ function edgeBypassHeaders() {
|
|
111
|
+
var _process_env_AGLYN_PROBE_TOKEN;
|
|
112
|
+
const probe = String((_process_env_AGLYN_PROBE_TOKEN = process.env['AGLYN_PROBE_TOKEN']) != null ? _process_env_AGLYN_PROBE_TOKEN : '').trim();
|
|
113
|
+
const bypass = String(process.env['AGLYN_VERCEL_BYPASS'] || process.env['VERCEL_AUTOMATION_BYPASS_SECRET'] || '').trim();
|
|
114
|
+
return _extends({}, probe ? {
|
|
115
|
+
'x-aglyn-probe': probe
|
|
116
|
+
} : {}, bypass ? {
|
|
117
|
+
'x-vercel-protection-bypass': bypass
|
|
118
|
+
} : {});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Asks the images function for one read, signed, waiting no longer than the
|
|
122
|
+
* step's own bound for it (`signal`). Throws on anything but an answer.
|
|
123
|
+
*/ export async function requestAiImageReads(origin, payload, signal) {
|
|
124
|
+
const body = JSON.stringify(payload);
|
|
125
|
+
const response = await fetch(`${origin}/api/${PLUGIN_IMAGES_API_PREFIX}/${AI_IMAGE_READS_PATH}`, {
|
|
126
|
+
method: 'POST',
|
|
127
|
+
headers: _extends({
|
|
128
|
+
'content-type': 'application/json',
|
|
129
|
+
[AI_IMAGE_READS_SIGNATURE]: signAiImageReadsRequest(body)
|
|
130
|
+
}, edgeBypassHeaders()),
|
|
131
|
+
body,
|
|
132
|
+
signal
|
|
133
|
+
});
|
|
134
|
+
if (!response.ok) throw new Error(`the AI image reads function answered ${response.status}`);
|
|
135
|
+
return await response.json();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
//# sourceMappingURL=ai-image-reads.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-image-reads.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 { createHash, createHmac, timingSafeEqual } from 'node:crypto'\nimport { PLUGIN_IMAGES_API_PREFIX } from '@aglyn/aglyn/app-utils/api-plugins'\nimport { formatMediaRef } from '@aglyn/aglyn/app-utils/media-ref'\nimport { platformConsoleOrigin } from '@aglyn/aglyn/app-utils/platform-brand'\nimport { tokenSigningSecret } from '@aglyn/tenant-data-admin/server/media-signing'\nimport type { AiMediaAssetLocation } from './ai-media-asset'\n\n/*==========================================\n * A JOB'S IMAGE READS HAVE THEIR OWN FUNCTION (AGL-4048).\n *\n * Two job steps decode a picture from a site's media library with `sharp`:\n * the products step makes the JPEG of a product's first photo that the\n * model is shown (`ai-product-image.ts`), and the theme step reduces the\n * site logo to its dominant colors (`ai-theme-brand-inputs.ts`). `sharp`\n * takes libvips, a 17 MB native library that on Vercel only the console's\n * plugins' image function ships: neither the plugin dispatcher, where a door\n * runs a job's first step, nor the AI jobs beat, which runs every step after\n * it, carries it. So the read is a route of its own, registered to draw\n * images (`images: true`) and served at `/api/plugin-images/ai/image-reads`\n * (`server/ai-image-reads-route.ts`), and on Vercel a step asks it:\n *\n * product-photo {ref, hostId, orgId} `{read}`: the photo as the step\n * sends it, or why it was not read\n * logo-colors {ref, hostId, orgId} `{colors}`: the logo's dominant\n * colors, or null\n *\n * The function locates the asset again from the reference, the site and\n * the org, under the same rule the step used, and reads it the way the media\n * CDN would serve it, so it reads nothing the step could not have read\n * itself. Each request is signed with the platform's signing secret over the\n * moment and the body, as the card reader start-up screen's are, under this\n * route's own name, so a signature for one route is none for another. Off\n * Vercel one process holds every route, and libvips with it, so the read\n * runs where it is asked for, as before.\n *\n * Any failure is the read's own: the photo is unreadable and the copy is\n * written from the product's words, or the logo's colors are left out with\n * the note that says so.\n *=========================================*/\n\n/** The images route's registered path, served under `/api/plugin-images/`. */\nexport const AI_IMAGE_READS_PATH = 'ai/image-reads'\n\n/** The request header that carries the signature: `t=<ms>,v1=<hex>`. */\nexport const AI_IMAGE_READS_SIGNATURE = 'x-aglyn-images-signature'\n\n/** How far a signed request's moment may be from the function's clock. */\nexport const AI_IMAGE_READS_WINDOW_MS = 5 * 60 * 1000\n\n/** The two reads the function makes. */\nexport type AiImageReadsOp = 'product-photo' | 'logo-colors'\n\n/** One request's body: the asset by its media reference, and whose read it is. */\nexport interface AiImageReadsRequest {\n op: AiImageReadsOp\n ref: string\n hostId: string\n orgId: string\n}\n\nlet secretOverride: (() => string) | null = null\n\n/** Test seam. */\nexport function setAiImageReadsSecretForTests(secret: (() => string) | null): void {\n secretOverride = secret\n}\n\nfunction signature(at: number, body: string): string {\n const digest = createHash('sha256').update(body).digest('hex')\n return createHmac('sha256', (secretOverride ?? tokenSigningSecret)())\n .update(`ai-image-reads:${at}.${digest}`)\n .digest('hex')\n}\n\n/** The header value for a body signed now. Throws on a deploy with no signing secret. */\nexport function signAiImageReadsRequest(body: string, nowMs: number = Date.now()): string {\n return `t=${nowMs},v1=${signature(nowMs, body)}`\n}\n\n/**\n * Whether a request's signature is ours, over this body, and recent. False\n * for anything else, a deploy with no signing secret included (fail closed).\n */\nexport function verifyAiImageReadsRequest(\n header: string | null | undefined,\n body: string,\n nowMs: number = Date.now(),\n): boolean {\n const parts = Object.fromEntries(\n String(header ?? '')\n .split(',')\n .map((part) => part.trim().split('=') as [string, string])\n .filter((pair) => pair.length === 2),\n )\n const at = Number(parts['t'])\n const given = String(parts['v1'] ?? '')\n if (!Number.isFinite(at) || !/^[a-f0-9]{64}$/.test(given)) return false\n if (Math.abs(nowMs - at) > AI_IMAGE_READS_WINDOW_MS) return false\n let expected: string\n try {\n expected = signature(at, body)\n } catch {\n return false\n }\n return timingSafeEqual(Buffer.from(given), Buffer.from(expected))\n}\n\n/**\n * Where to ask for an image read, or `null` to make it in this process. On\n * Vercel only the images function ships libvips, so every other function\n * asks it, at the console's own address; anywhere else this process can\n * decode.\n */\nexport function aiImageReadsOrigin(): string | null {\n if (process.env['VERCEL'] !== '1') return null\n return platformConsoleOrigin()\n}\n\n/**\n * The request for a located asset's read: the asset named by its media\n * reference, which the function locates again. `null` for a location no\n * reference can name, which is never read.\n */\nexport function aiImageReadsRequest(\n op: AiImageReadsOp,\n location: AiMediaAssetLocation,\n hostId: string,\n orgId: string,\n): AiImageReadsRequest | null {\n const ref = formatMediaRef(location.scopeSegment, location.mediaId)\n return ref ? { op, ref, hostId, orgId } : null\n}\n\n/**\n * The headers that get our own request past our own edge, when configured:\n * the same pair the render monitor sends (AGL-3571).\n */\nfunction edgeBypassHeaders(): Record<string, string> {\n const probe = String(process.env['AGLYN_PROBE_TOKEN'] ?? '').trim()\n const bypass = String(\n process.env['AGLYN_VERCEL_BYPASS'] || process.env['VERCEL_AUTOMATION_BYPASS_SECRET'] || '',\n ).trim()\n return {\n ...(probe ? { 'x-aglyn-probe': probe } : {}),\n ...(bypass ? { 'x-vercel-protection-bypass': bypass } : {}),\n }\n}\n\n/**\n * Asks the images function for one read, signed, waiting no longer than the\n * step's own bound for it (`signal`). Throws on anything but an answer.\n */\nexport async function requestAiImageReads<T>(\n origin: string,\n payload: AiImageReadsRequest,\n signal: AbortSignal,\n): Promise<T> {\n const body = JSON.stringify(payload)\n const response = await fetch(`${origin}/api/${PLUGIN_IMAGES_API_PREFIX}/${AI_IMAGE_READS_PATH}`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n [AI_IMAGE_READS_SIGNATURE]: signAiImageReadsRequest(body),\n ...edgeBypassHeaders(),\n },\n body,\n signal,\n })\n if (!response.ok) throw new Error(`the AI image reads function answered ${response.status}`)\n return (await response.json()) as T\n}\n"],"names":["createHash","createHmac","timingSafeEqual","PLUGIN_IMAGES_API_PREFIX","formatMediaRef","platformConsoleOrigin","tokenSigningSecret","AI_IMAGE_READS_PATH","AI_IMAGE_READS_SIGNATURE","AI_IMAGE_READS_WINDOW_MS","secretOverride","setAiImageReadsSecretForTests","secret","signature","at","body","digest","update","signAiImageReadsRequest","nowMs","Date","now","verifyAiImageReadsRequest","header","parts","Object","fromEntries","String","split","map","part","trim","filter","pair","length","Number","given","isFinite","test","Math","abs","expected","Buffer","from","aiImageReadsOrigin","process","env","aiImageReadsRequest","op","location","hostId","orgId","ref","scopeSegment","mediaId","edgeBypassHeaders","probe","bypass","requestAiImageReads","origin","payload","signal","JSON","stringify","response","fetch","method","headers","ok","Error","status","json"],"mappings":"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"}
|
|
@@ -14,11 +14,13 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
+
import type { ResolvedBusinessProfile } from '@aglyn/aglyn/app-utils/business-profile';
|
|
17
18
|
import { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations';
|
|
18
19
|
import { aiBuildItemOutputNote, type AiBuildOps, type AiBuildUnit } from '../model/ai-build-job';
|
|
19
20
|
import type { AiJob, AiJobItemLedger, AiJobKind, AiJobOutput } from '../model/ai-jobs.types';
|
|
20
21
|
import { type AiCreditRange, type AiCreditsPrompt, type AiCreditsSmaller } from '../model/ai-credit-estimate';
|
|
21
22
|
import { readFreeAiCreditsLeft } from '../usage/free-ai-credits-left';
|
|
23
|
+
import { readAiSiteContext } from '../runtime/site-context';
|
|
22
24
|
import { type AiBuildOpsContext } from './ai-build-capabilities';
|
|
23
25
|
import { aiJobAdmissionRefusal, type AiJobAdmission, type AiJobAdmissionRefusal } from './ai-job-admission';
|
|
24
26
|
import { readAiDraftNodes } from './ai-job-drafts';
|
|
@@ -85,6 +87,29 @@ export { aiBuildItemOutputNote };
|
|
|
85
87
|
* is without, so its own report never shows a `new:` reference.
|
|
86
88
|
*/
|
|
87
89
|
export declare function aiBuildItemArgsWithout(args: Readonly<Record<string, unknown>>, dropped: ReadonlySet<string> | undefined): Record<string, unknown>;
|
|
90
|
+
/**
|
|
91
|
+
* What a build's theme change says under its proposal (AGL-4077): the theme
|
|
92
|
+
* runner proposes and never saves, so the owner applies it, or not, where
|
|
93
|
+
* every theme change is made.
|
|
94
|
+
*/
|
|
95
|
+
export declare const AI_BUILD_THEME_PROPOSAL_NOTE = "A proposal, in Setup \u2192 Theme. Nothing on the site changes until you apply it there and save.";
|
|
96
|
+
/**
|
|
97
|
+
* What a build's units are told about the business (AGL-4077), in the inputs
|
|
98
|
+
* a site start's own units carry: what it does (`businessType`), its name and
|
|
99
|
+
* who it is for. A build started from the chat carries none of them, so its
|
|
100
|
+
* pages searched photos by their section names alone.
|
|
101
|
+
*/
|
|
102
|
+
export interface AiBuildBusinessWords {
|
|
103
|
+
businessType?: string;
|
|
104
|
+
businessName?: string;
|
|
105
|
+
audience?: string;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The site's business profile as those inputs (AGL-4077): what the business
|
|
109
|
+
* does, else the schema.org type its settings name, as words; its name; who
|
|
110
|
+
* it is for. Only what the profile says; nothing for a site with none.
|
|
111
|
+
*/
|
|
112
|
+
export declare function aiBuildBusinessWords(profile: ResolvedBusinessProfile | null | undefined): AiBuildBusinessWords;
|
|
88
113
|
/** The id a unit's job, and so its draft, is named by. */
|
|
89
114
|
export declare function aiBuildUnitJobId(job: Pick<AiJob, '$id'>, unit: AiBuildUnit): string;
|
|
90
115
|
/**
|
|
@@ -104,6 +129,8 @@ export declare function aiBuildUnitJob(job: AiJob, unit: AiBuildUnit, context: {
|
|
|
104
129
|
dropped?: ReadonlySet<string>;
|
|
105
130
|
/** The motion of the site the build adds to, from its stored look (`aiLayoutSiteMotionOf`); absent, none stored. */
|
|
106
131
|
siteMotion?: AiLayoutSiteMotion | null;
|
|
132
|
+
/** What the site's business profile says the business is (`aiBuildBusinessWords`, AGL-4077); absent, nothing read. */
|
|
133
|
+
business?: AiBuildBusinessWords | null;
|
|
107
134
|
}): AiJob;
|
|
108
135
|
export interface AiJobBuildStepDeps {
|
|
109
136
|
/** The runner registry; specs hand in fakes. */
|
|
@@ -127,6 +154,8 @@ export interface AiJobBuildStepDeps {
|
|
|
127
154
|
readSitePages?: typeof readAiStoreSitePages;
|
|
128
155
|
/** The draft version of the site's layout a store's links are written into. */
|
|
129
156
|
writeStoreLinks?: typeof writeAiStoreLinksLayoutDraft;
|
|
157
|
+
/** The site's business profile, read where the build names no business (AGL-4077). */
|
|
158
|
+
readSiteContext?: typeof readAiSiteContext;
|
|
130
159
|
}
|
|
131
160
|
/** The site's pages as a store's own pages are checked against them (AGL-3676): every page that is not deleted. */
|
|
132
161
|
export declare function readAiStoreSitePages(firestore: FirebaseFirestore.Firestore, hostId: string): Promise<AiStoreSitePage[]>;
|