@aglyn/plugins-ai 1.0.0-beta.230 → 1.0.0-beta.232
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/activity/ai-activity-actions.js +8 -4
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-assist-provider-on-host.component.d.ts +66 -0
- package/src/lib/components/ai-assist-provider-on-host.component.js +78 -0
- package/src/lib/components/ai-assist-provider-on-host.component.js.map +1 -0
- package/src/lib/components/ai-assist-provider.component.js +9 -10
- package/src/lib/components/ai-assist-provider.component.js.map +1 -1
- package/src/lib/components/ai-job-follow.component.js +1 -0
- package/src/lib/components/ai-job-follow.component.js.map +1 -1
- package/src/lib/components/ai-job-links.js +13 -0
- package/src/lib/components/ai-job-links.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.js +3 -1
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-jobs-list-page.component.js +1 -0
- package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
- package/src/lib/components/ai-media-create.component.d.ts +20 -7
- package/src/lib/components/ai-media-create.component.js +107 -55
- package/src/lib/components/ai-media-create.component.js.map +1 -1
- package/src/lib/components/ai-permissions-on-host.component.d.ts +3 -46
- package/src/lib/components/ai-permissions-on-host.component.js +5 -53
- package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
- package/src/lib/components/ai-seo-fields-card.component.js +1 -0
- package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +20 -8
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-site-memory-card.component.d.ts +32 -0
- package/src/lib/components/ai-site-memory-card.component.js +187 -0
- package/src/lib/components/ai-site-memory-card.component.js.map +1 -0
- package/src/lib/components/ai-site-start-card.component.js +80 -77
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-theme-proposal-card.component.js +4 -0
- package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
- package/src/lib/components/ai-usage-strip.component.js +1 -0
- package/src/lib/components/ai-usage-strip.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.d.ts +10 -0
- package/src/lib/components/assist-panel.component.js +119 -9
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/assist-signals-page.component.js +2 -0
- package/src/lib/components/assist-signals-page.component.js.map +1 -1
- package/src/lib/components/billing-ai-allotments.component.js +3 -0
- package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
- package/src/lib/components/billing-ai-top-users.component.js +1 -0
- package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
- package/src/lib/components/billing-assist-overage-card.component.js +2 -0
- package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
- package/src/lib/components/member-ai-usage-card.component.js +10 -0
- package/src/lib/components/member-ai-usage-card.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-card.component.js +1 -0
- package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-conversations.component.d.ts +23 -0
- package/src/lib/components/staff-org-ai-conversations.component.js +346 -0
- package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -0
- package/src/lib/jobs/ai-build-capabilities.d.ts +13 -1
- package/src/lib/jobs/ai-build-capabilities.js +101 -7
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
- package/src/lib/jobs/ai-build-unit-outcome.js +4 -2
- package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -1
- package/src/lib/jobs/ai-business-profile-prefill.d.ts +29 -0
- package/src/lib/jobs/ai-business-profile-prefill.js +85 -0
- package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -0
- package/src/lib/jobs/ai-job-auto-confirm.d.ts +8 -0
- package/src/lib/jobs/ai-job-auto-confirm.js +30 -6
- package/src/lib/jobs/ai-job-auto-confirm.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.js +14 -1
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-edit-step.d.ts +225 -0
- package/src/lib/jobs/ai-job-edit-step.js +631 -0
- package/src/lib/jobs/ai-job-edit-step.js.map +1 -0
- package/src/lib/jobs/ai-job-generation.d.ts +8 -0
- package/src/lib/jobs/ai-job-generation.js +13 -2
- package/src/lib/jobs/ai-job-generation.js.map +1 -1
- package/src/lib/jobs/ai-job-insight-step.js +29 -5
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +78 -0
- package/src/lib/jobs/ai-job-layout-language.js +200 -0
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -0
- package/src/lib/jobs/ai-job-layout-site-pages.d.ts +13 -2
- package/src/lib/jobs/ai-job-layout-site-pages.js +18 -2
- package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.d.ts +5 -0
- package/src/lib/jobs/ai-job-layout-step.js +107 -6
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-logic-step.d.ts +20 -0
- package/src/lib/jobs/ai-job-logic-step.js +87 -6
- package/src/lib/jobs/ai-job-logic-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +194 -0
- package/src/lib/jobs/ai-job-page-language.js +418 -0
- package/src/lib/jobs/ai-job-page-language.js.map +1 -0
- package/src/lib/jobs/ai-job-page-sections.d.ts +37 -1
- package/src/lib/jobs/ai-job-page-sections.js +93 -11
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.d.ts +4 -4
- package/src/lib/jobs/ai-job-page-step.js +115 -14
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +20 -6
- package/src/lib/jobs/ai-job-plan-step.js +62 -22
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +163 -0
- package/src/lib/jobs/ai-job-site-content.js +443 -0
- package/src/lib/jobs/ai-job-site-content.js.map +1 -0
- package/src/lib/jobs/ai-job-site-look.d.ts +112 -0
- package/src/lib/jobs/ai-job-site-look.js +322 -0
- package/src/lib/jobs/ai-job-site-look.js.map +1 -0
- package/src/lib/jobs/ai-job-site-step.d.ts +30 -5
- package/src/lib/jobs/ai-job-site-step.js +183 -27
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-workflow-step.js +2 -2
- package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs-notify.d.ts +6 -1
- package/src/lib/jobs/ai-jobs-notify.js +15 -3
- package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +1 -1
- package/src/lib/jobs/ai-jobs.js +17 -8
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-full-height.d.ts +40 -0
- package/src/lib/jobs/ai-layout-full-height.js +82 -0
- package/src/lib/jobs/ai-layout-full-height.js.map +1 -0
- package/src/lib/jobs/ai-layout-site-facts.d.ts +54 -0
- package/src/lib/jobs/ai-layout-site-facts.js +153 -0
- package/src/lib/jobs/ai-layout-site-facts.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.d.ts +154 -0
- package/src/lib/layout-language/ai-layout-compiler.js +1235 -0
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -0
- package/src/lib/layout-language/ai-layout-copy.d.ts +73 -0
- package/src/lib/layout-language/ai-layout-copy.js +137 -0
- package/src/lib/layout-language/ai-layout-copy.js.map +1 -0
- package/src/lib/layout-language/ai-layout-frame.d.ts +52 -0
- package/src/lib/layout-language/ai-layout-frame.js +458 -0
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -0
- package/src/lib/layout-language/ai-layout-gaps.d.ts +63 -0
- package/src/lib/layout-language/ai-layout-gaps.js +345 -0
- package/src/lib/layout-language/ai-layout-gaps.js.map +1 -0
- package/src/lib/layout-language/ai-layout-language.d.ts +168 -0
- package/src/lib/layout-language/ai-layout-language.js +529 -0
- package/src/lib/layout-language/ai-layout-language.js.map +1 -0
- package/src/lib/layout-language/ai-layout-links.d.ts +80 -0
- package/src/lib/layout-language/ai-layout-links.js +109 -0
- package/src/lib/layout-language/ai-layout-links.js.map +1 -0
- package/src/lib/layout-language/ai-layout-pictures.d.ts +144 -0
- package/src/lib/layout-language/ai-layout-pictures.js +214 -0
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -0
- package/src/lib/layout-language/ai-layout-store.d.ts +41 -0
- package/src/lib/layout-language/ai-layout-store.js +75 -0
- package/src/lib/layout-language/ai-layout-store.js.map +1 -0
- package/src/lib/layout-language/ai-layout-tree.d.ts +59 -0
- package/src/lib/layout-language/ai-layout-tree.js +84 -0
- package/src/lib/layout-language/ai-layout-tree.js.map +1 -0
- package/src/lib/lazy-widget.d.ts +45 -0
- package/src/lib/lazy-widget.js +53 -0
- package/src/lib/lazy-widget.js.map +1 -0
- package/src/lib/model/ai-build-job.js +2 -1
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-build-progress.d.ts +20 -3
- package/src/lib/model/ai-build-progress.js +52 -7
- package/src/lib/model/ai-build-progress.js.map +1 -1
- package/src/lib/model/ai-edit-job.d.ts +109 -0
- package/src/lib/model/ai-edit-job.js +157 -0
- package/src/lib/model/ai-edit-job.js.map +1 -0
- package/src/lib/model/ai-host-publish-context.d.ts +51 -0
- package/src/lib/model/ai-host-publish-context.js +104 -0
- package/src/lib/model/ai-host-publish-context.js.map +1 -0
- package/src/lib/model/ai-job-activity.d.ts +22 -0
- package/src/lib/model/ai-job-activity.js +23 -1
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-job-failure-copy.d.ts +38 -1
- package/src/lib/model/ai-job-failure-copy.js +56 -1
- package/src/lib/model/ai-job-failure-copy.js.map +1 -1
- package/src/lib/model/ai-job-notice.d.ts +3 -1
- package/src/lib/model/ai-job-notice.js +31 -9
- package/src/lib/model/ai-job-notice.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +22 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-logic-job.d.ts +18 -1
- package/src/lib/model/ai-logic-job.js +24 -3
- package/src/lib/model/ai-logic-job.js.map +1 -1
- package/src/lib/model/ai-media-credits.d.ts +8 -4
- package/src/lib/model/ai-media-credits.js +11 -4
- package/src/lib/model/ai-media-credits.js.map +1 -1
- package/src/lib/model/ai-media-kinds.d.ts +68 -0
- package/src/lib/model/ai-media-kinds.js +98 -0
- package/src/lib/model/ai-media-kinds.js.map +1 -0
- package/src/lib/model/ai-site-build-progress.d.ts +9 -1
- package/src/lib/model/ai-site-build-progress.js +54 -14
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-site-context.d.ts +111 -0
- package/src/lib/model/ai-site-context.js +222 -0
- package/src/lib/model/ai-site-context.js.map +1 -0
- package/src/lib/model/ai-site-job.d.ts +18 -2
- package/src/lib/model/ai-site-job.js +21 -5
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-kinds.d.ts +164 -0
- package/src/lib/model/ai-site-kinds.js +1942 -0
- package/src/lib/model/ai-site-kinds.js.map +1 -0
- package/src/lib/model/ai-site-look.d.ts +150 -0
- package/src/lib/model/ai-site-look.js +1236 -0
- package/src/lib/model/ai-site-look.js.map +1 -0
- package/src/lib/model/ai-site-memory.d.ts +80 -0
- package/src/lib/model/ai-site-memory.js +171 -0
- package/src/lib/model/ai-site-memory.js.map +1 -0
- package/src/lib/model/ai-site-start-seo.d.ts +1 -1
- package/src/lib/model/ai-site-start.d.ts +15 -3
- package/src/lib/model/ai-site-start.js +21 -4
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/model/assist-follow-up.d.ts +92 -0
- package/src/lib/model/assist-follow-up.js +128 -0
- package/src/lib/model/assist-follow-up.js.map +1 -0
- package/src/lib/plugin-config.js +2 -0
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/plugin.js +99 -48
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/anthropic.d.ts +9 -1
- package/src/lib/providers/anthropic.js +143 -9
- package/src/lib/providers/anthropic.js.map +1 -1
- package/src/lib/providers/catalog.d.ts +38 -8
- package/src/lib/providers/catalog.js +106 -14
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/providers/contract.d.ts +29 -0
- package/src/lib/providers/contract.js.map +1 -1
- package/src/lib/providers/image-contract.d.ts +22 -1
- package/src/lib/providers/image-contract.js +40 -1
- package/src/lib/providers/image-contract.js.map +1 -1
- package/src/lib/providers/image-endpoints.d.ts +24 -0
- package/src/lib/providers/image-endpoints.js +28 -0
- package/src/lib/providers/image-endpoints.js.map +1 -0
- package/src/lib/providers/model-choice.js +23 -2
- package/src/lib/providers/model-choice.js.map +1 -1
- package/src/lib/providers/routing.js +18 -0
- package/src/lib/providers/routing.js.map +1 -1
- package/src/lib/providers/vertex-image-endpoint.d.ts +48 -0
- package/src/lib/providers/vertex-image-endpoint.js +80 -0
- package/src/lib/providers/vertex-image-endpoint.js.map +1 -0
- package/src/lib/providers/vertex-image.d.ts +13 -27
- package/src/lib/providers/vertex-image.js +15 -42
- package/src/lib/providers/vertex-image.js.map +1 -1
- package/src/lib/runtime/ai-blog-post-generation.d.ts +62 -0
- package/src/lib/runtime/ai-blog-post-generation.js +89 -0
- package/src/lib/runtime/ai-blog-post-generation.js.map +1 -0
- package/src/lib/runtime/ai-dev-env.d.ts +53 -0
- package/src/lib/runtime/ai-dev-env.js +72 -0
- package/src/lib/runtime/ai-dev-env.js.map +1 -0
- package/src/lib/runtime/ai-dev-replay.d.ts +116 -0
- package/src/lib/runtime/ai-dev-replay.js +313 -0
- package/src/lib/runtime/ai-dev-replay.js.map +1 -0
- package/src/lib/runtime/ai-doctrine-validators.d.ts +82 -0
- package/src/lib/runtime/ai-doctrine-validators.js +436 -4
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +16 -3
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.js +4 -0
- package/src/lib/runtime/ai-eval-live.js.map +1 -1
- package/src/lib/runtime/ai-eval.d.ts +13 -1
- package/src/lib/runtime/ai-eval.js +84 -1
- package/src/lib/runtime/ai-eval.js.map +1 -1
- package/src/lib/runtime/ai-icon-library.d.ts +257 -0
- package/src/lib/runtime/ai-icon-library.js +254 -0
- package/src/lib/runtime/ai-icon-library.js.map +1 -0
- package/src/lib/runtime/ai-live-batch.d.ts +45 -0
- package/src/lib/runtime/ai-live-batch.js +89 -0
- package/src/lib/runtime/ai-live-batch.js.map +1 -0
- package/src/lib/runtime/ai-node-tree.d.ts +9 -0
- package/src/lib/runtime/ai-node-tree.js +30 -4
- package/src/lib/runtime/ai-node-tree.js.map +1 -1
- package/src/lib/runtime/ai-palette.d.ts +6 -0
- package/src/lib/runtime/ai-palette.generated.js +70 -0
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/ai-palette.js.map +1 -1
- package/src/lib/runtime/ai-runtime.js +46 -2
- package/src/lib/runtime/ai-runtime.js.map +1 -1
- package/src/lib/runtime/site-context.d.ts +58 -0
- package/src/lib/runtime/site-context.js +127 -0
- package/src/lib/runtime/site-context.js.map +1 -0
- package/src/lib/server/ai-admin-conversations.d.ts +22 -0
- package/src/lib/server/ai-admin-conversations.js +197 -0
- package/src/lib/server/ai-admin-conversations.js.map +1 -0
- package/src/lib/server/ai-media-image.d.ts +5 -5
- package/src/lib/server/ai-media-image.js +25 -11
- package/src/lib/server/ai-media-image.js.map +1 -1
- package/src/lib/server/ai-media-raster-prompt.d.ts +34 -0
- package/src/lib/server/ai-media-raster-prompt.js +53 -0
- package/src/lib/server/ai-media-raster-prompt.js.map +1 -0
- package/src/lib/server/assist-chat.d.ts +11 -0
- package/src/lib/server/assist-chat.js +57 -18
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-edit-applied.js +16 -3
- package/src/lib/server/assist-edit-applied.js.map +1 -1
- package/src/lib/server/assist-view-context.d.ts +19 -1
- package/src/lib/server/assist-view-context.js +38 -1
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/server.js +10 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/subprocessors.js +17 -1
- package/src/lib/subprocessors.js.map +1 -1
- package/src/lib/tools/ai-blog-post-tool.d.ts +60 -0
- package/src/lib/tools/ai-blog-post-tool.js +175 -0
- package/src/lib/tools/ai-blog-post-tool.js.map +1 -0
- package/src/lib/tools/ai-workflow-tool.d.ts +6 -2
- package/src/lib/tools/ai-workflow-tool.js +16 -14
- package/src/lib/tools/ai-workflow-tool.js.map +1 -1
- package/src/lib/usage/assist-usage.d.ts +3 -0
- package/src/lib/usage/assist-usage.js +4 -2
- package/src/lib/usage/assist-usage.js.map +1 -1
- package/src/lib/usage/staff-org-ai-conversations.d.ts +103 -0
- package/src/lib/usage/staff-org-ai-conversations.js +152 -0
- package/src/lib/usage/staff-org-ai-conversations.js.map +1 -0
|
@@ -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 { 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 { aiJobStepRunnerFor } from './ai-jobs'\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.\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\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}\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 — 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 owners = [...new Set(all.map((one) => one.pluginId).filter((id) => id !== AI_PLUGIN_ID))]\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 if (pluginId !== AI_PLUGIN_ID) {\n if (!released.has(pluginId)) continue\n if (!isHostPluginEnabled(context.org as { enabledPlugins?: string[] } | null, context.host, pluginId)) 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 const choices = property.enum ? ` one of ${property.enum.join('|')}` : ''\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 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":["checkEntitlement","isHostPluginEnabled","pluginAiCapabilities","registerPluginAiCapability","filterEnabledPluginsByReleaseFlags","AI_TEMPLATE_SUBJECTS","AI_SITE_PASS_CREDITS","aiJobStepRunnerFor","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","registerAiBuildCapabilities","capability","pluginId","aiBuildOps","context","deps","all","capabilities","hasRunner","kind","owners","Set","map","one","filter","id","released","length","releasedPlugins","ids","orgId","authorization","ops","Map","has","org","host","feature","freeTaste","set","aiBuildOpLines","structural","items","values","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 { 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'\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.\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\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 const choices = property.enum ? ` one of ${property.enum.join('|')}` : ''\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 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":["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_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","AI_OWNED_OP_WRITERS","function","campaign","workflow","registerAiBuildCapabilities","capability","pluginId","aiBuildOps","context","deps","all","capabilities","hasRunner","kind","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","feature","freeTaste","aiBuildOpLines","structural","items","includes","lines","args","Object","entries","property","choices","join","push","aiNounWithArticle","pageBlocks","test","aiBuildIntents","flatMap"],"mappings":"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"}
|
|
@@ -61,11 +61,13 @@ import { AiUpstreamError } from "../runtime/ai-runtime.js";
|
|
|
61
61
|
return {
|
|
62
62
|
slot,
|
|
63
63
|
status: 'failed',
|
|
64
|
-
failure: {
|
|
64
|
+
failure: _extends({
|
|
65
65
|
ours,
|
|
66
66
|
reason: ours ? 'doctrine-refused' : 'review',
|
|
67
67
|
message: outcome.review.message || AI_BUILD_UNIT_EMPTY_COPY
|
|
68
|
-
},
|
|
68
|
+
}, outcome.review.detail ? {
|
|
69
|
+
detail: outcome.review.detail
|
|
70
|
+
} : {}),
|
|
69
71
|
outputs
|
|
70
72
|
};
|
|
71
73
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-build-unit-outcome.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 { aiBuildItemDelivered, type AiBuildUnit } from '../model/ai-build-job'\nimport type { AiJobItemLedger, AiJobOutput } from '../model/ai-jobs.types'\nimport { AiUpstreamError } from '../runtime/ai-runtime'\nimport type { AiSiteBuiltRef } from './ai-job-site-step'\nimport type { AiJobItemOutcome, AiJobStepOutcome } from './ai-job-text-step'\n\n/**\n * One delegated unit's pass as an ITEM of a job settled item by item\n * (AGL-3616) — a `build`, and a site scaffold, which is the build's preset.\n * The runner's spend is the pass's; what the runner came to is the item's\n * row: still running, built (or built without something that failed),\n * failed on our side, or failed on the model's or the workspace's.\n *\n * Shared by both steps, and importing neither, so neither imports the other.\n */\n\n/** What a unit says when it finished without reporting anything. */\nexport const AI_BUILD_UNIT_EMPTY_COPY = 'It could not be built this time.'\n\n/** What a unit's model declining the request says. */\nexport const AI_BUILD_UNIT_REFUSED_COPY = 'The AI declined to build this one.'\n\n/** A runner's outcome narrowed to its spend: no plan, no review, nothing the delegating job keeps. */\nexport function aiUnitSpend(outcome: AiJobStepOutcome): AiJobStepOutcome {\n return {\n outputs: outcome.outputs,\n usage: outcome.usage,\n estCostUsd: outcome.estCostUsd,\n model: outcome.model,\n stopReason: outcome.stopReason,\n ...(outcome.effort ? { effort: outcome.effort } : {}),\n }\n}\n\n/**\n * What a runner's outcome comes to for its item; `null` when the unit\n * reported what it built and the caller decides whether that counts.\n */\nexport function aiUnitFailure(slot: string, outcome: AiJobStepOutcome): AiJobItemOutcome | null {\n const outputs = outcome.outputs.map((output) => output.id)\n if (outcome.refused) {\n return { slot, status: 'failed', failure: { ours: false, reason: 'refused', message: AI_BUILD_UNIT_REFUSED_COPY }, outputs }\n }\n if (outcome.review) {\n // A building rule still broken after its re-ask is ours (AGL-3596); a\n // site at an allowance is the workspace's to change.\n const ours = outcome.review.reason === 'doctrine'\n return {\n slot,\n status: 'failed',\n failure: {
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-build-unit-outcome.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 { aiBuildItemDelivered, type AiBuildUnit } from '../model/ai-build-job'\nimport type { AiJobItemLedger, AiJobOutput } from '../model/ai-jobs.types'\nimport { AiUpstreamError } from '../runtime/ai-runtime'\nimport type { AiSiteBuiltRef } from './ai-job-site-step'\nimport type { AiJobItemOutcome, AiJobStepOutcome } from './ai-job-text-step'\n\n/**\n * One delegated unit's pass as an ITEM of a job settled item by item\n * (AGL-3616) — a `build`, and a site scaffold, which is the build's preset.\n * The runner's spend is the pass's; what the runner came to is the item's\n * row: still running, built (or built without something that failed),\n * failed on our side, or failed on the model's or the workspace's.\n *\n * Shared by both steps, and importing neither, so neither imports the other.\n */\n\n/** What a unit says when it finished without reporting anything. */\nexport const AI_BUILD_UNIT_EMPTY_COPY = 'It could not be built this time.'\n\n/** What a unit's model declining the request says. */\nexport const AI_BUILD_UNIT_REFUSED_COPY = 'The AI declined to build this one.'\n\n/** A runner's outcome narrowed to its spend: no plan, no review, nothing the delegating job keeps. */\nexport function aiUnitSpend(outcome: AiJobStepOutcome): AiJobStepOutcome {\n return {\n outputs: outcome.outputs,\n usage: outcome.usage,\n estCostUsd: outcome.estCostUsd,\n model: outcome.model,\n stopReason: outcome.stopReason,\n ...(outcome.effort ? { effort: outcome.effort } : {}),\n }\n}\n\n/**\n * What a runner's outcome comes to for its item; `null` when the unit\n * reported what it built and the caller decides whether that counts.\n */\nexport function aiUnitFailure(slot: string, outcome: AiJobStepOutcome): AiJobItemOutcome | null {\n const outputs = outcome.outputs.map((output) => output.id)\n if (outcome.refused) {\n return { slot, status: 'failed', failure: { ours: false, reason: 'refused', message: AI_BUILD_UNIT_REFUSED_COPY }, outputs }\n }\n if (outcome.review) {\n // A building rule still broken after its re-ask is ours (AGL-3596); a\n // site at an allowance is the workspace's to change.\n const ours = outcome.review.reason === 'doctrine'\n return {\n slot,\n status: 'failed',\n failure: {\n ours,\n reason: ours ? 'doctrine-refused' : 'review',\n message: outcome.review.message || AI_BUILD_UNIT_EMPTY_COPY,\n ...(outcome.review.detail ? { detail: outcome.review.detail } : {}),\n },\n outputs,\n }\n }\n if (outcome.failure) {\n return { slot, status: 'failed', failure: { ours: true, reason: 'step-failure', message: outcome.failure }, outputs }\n }\n if (outcome.continue) return { slot, status: 'running', outputs }\n if (!outcome.outputs.length) {\n return { slot, status: 'failed', failure: { ours: true, reason: 'step-failure', message: AI_BUILD_UNIT_EMPTY_COPY } }\n }\n return null\n}\n\n/**\n * Whether an error a unit's runner threw is the machine's to retry — a\n * provider that will answer later, or a budget that ended — rather than this\n * item's failure.\n */\nexport function aiUnitErrorRetryable(error: unknown): boolean {\n if (error instanceof AiUpstreamError && error.retryable) return true\n const name = (error as { name?: string } | null)?.name\n return name === 'AbortError' || name === 'TimeoutError'\n}\n\n/**\n * What the build has created that a later unit can name: each delivered\n * creation's record, by its plan name. A creation's first output is the\n * record it wrote.\n */\nexport function aiBuildBuiltRefs(\n units: readonly AiBuildUnit[],\n ledger: readonly AiJobItemLedger[],\n outputs: readonly Pick<AiJobOutput, 'id' | 'label'>[],\n): Map<string, AiSiteBuiltRef> {\n const rows = new Map(ledger.map((row) => [row.slot, row]))\n const built = new Map<string, AiSiteBuiltRef>()\n for (const unit of units) {\n const creation = unit.creation\n const row = rows.get(unit.slot)\n if (!creation || !row || !aiBuildItemDelivered(row) || !row.outputs.length) continue\n if (creation.kind !== 'layout' && creation.kind !== 'form' && creation.kind !== 'component') continue\n const id = row.outputs[0]\n const label = outputs.find((output) => output.id === id)?.label || creation.name\n built.set(creation.name.toLowerCase(), { id, label, kind: creation.kind })\n }\n return built\n}\n"],"names":["aiBuildItemDelivered","AiUpstreamError","AI_BUILD_UNIT_EMPTY_COPY","AI_BUILD_UNIT_REFUSED_COPY","aiUnitSpend","outcome","outputs","usage","estCostUsd","model","stopReason","effort","aiUnitFailure","slot","map","output","id","refused","status","failure","ours","reason","message","review","detail","continue","length","aiUnitErrorRetryable","error","retryable","name","aiBuildBuiltRefs","units","ledger","rows","Map","row","built","unit","creation","get","kind","label","find","set","toLowerCase"],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
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 { BusinessProfileValues } from '@aglyn/aglyn/app-utils/business-profile';
|
|
18
|
+
import type { AiJob } from '../model/ai-jobs.types';
|
|
19
|
+
import type { AiJobTransitionListener } from './ai-jobs';
|
|
20
|
+
/** The guided start's answers, as profile values; `null` when it gave none. */
|
|
21
|
+
export declare function aiBusinessProfileStartValues(job: Pick<AiJob, 'kind' | 'inputs'>): BusinessProfileValues | null;
|
|
22
|
+
/** The plan's home page description, as a line about the business; `null` when it has none. */
|
|
23
|
+
export declare function aiBusinessProfilePlanValues(job: Pick<AiJob, 'kind' | 'plan'>): BusinessProfileValues | null;
|
|
24
|
+
/**
|
|
25
|
+
* The transition listener's half that prefills (AGL-3661): on a site job's
|
|
26
|
+
* plan and on its finish, the start's answers and then the plan's guess, each
|
|
27
|
+
* a no-op once written. A failure is logged and never fails the job.
|
|
28
|
+
*/
|
|
29
|
+
export declare function aiBusinessProfilePrefiller(firestoreOf: () => FirebaseFirestore.Firestore): AiJobTransitionListener;
|
|
@@ -0,0 +1,85 @@
|
|
|
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
|
+
*/ import { prefillAiBusinessProfile } from "../runtime/site-context.js";
|
|
17
|
+
/**
|
|
18
|
+
* A site job fills in the site's business profile (AGL-3661), so the next job
|
|
19
|
+
* — and the owner, on Setup → Business profile — starts from what the guided
|
|
20
|
+
* start was told rather than from nothing.
|
|
21
|
+
*
|
|
22
|
+
* Two sources, each recorded as itself:
|
|
23
|
+
* - `start`: the answers the owner gave the guided start — what the business
|
|
24
|
+
* is, who it is for, its city. Their words, given in another place.
|
|
25
|
+
* - `ai`: what the plan wrote for the home page's search description, used
|
|
26
|
+
* only where the start said nothing about what the business does.
|
|
27
|
+
*
|
|
28
|
+
* Neither ever replaces what the owner typed into the profile; the merge in
|
|
29
|
+
* `@aglyn/aglyn/app-utils/business-profile` decides that, not this file.
|
|
30
|
+
*/ const text = (value)=>typeof value === 'string' ? value.trim() : '';
|
|
31
|
+
/** "a local dog groomer" reads as a line once its first letter is a capital. */ const sentence = (value)=>value ? value[0].toUpperCase() + value.slice(1) : value;
|
|
32
|
+
/** The guided start's answers, as profile values; `null` when it gave none. */ export function aiBusinessProfileStartValues(job) {
|
|
33
|
+
var _job_inputs;
|
|
34
|
+
if (job.kind !== 'site') return null;
|
|
35
|
+
const inputs = (_job_inputs = job.inputs) != null ? _job_inputs : {};
|
|
36
|
+
const values = {};
|
|
37
|
+
const businessType = text(inputs['businessType']);
|
|
38
|
+
const audience = text(inputs['audience']);
|
|
39
|
+
const city = text(inputs['city']);
|
|
40
|
+
if (businessType) values.whatYouDo = sentence(businessType);
|
|
41
|
+
if (audience) values.audience = audience;
|
|
42
|
+
if (city) values.serviceArea = city;
|
|
43
|
+
return Object.keys(values).length ? values : null;
|
|
44
|
+
}
|
|
45
|
+
/** The plan's home page description, as a line about the business; `null` when it has none. */ export function aiBusinessProfilePlanValues(job) {
|
|
46
|
+
var _ref;
|
|
47
|
+
var _job_plan;
|
|
48
|
+
if (job.kind !== 'site') return null;
|
|
49
|
+
const screens = (_ref = (_job_plan = job.plan) == null ? void 0 : _job_plan.screens) != null ? _ref : [];
|
|
50
|
+
const home = screens.find((screen)=>[
|
|
51
|
+
'',
|
|
52
|
+
'/',
|
|
53
|
+
'home',
|
|
54
|
+
'index'
|
|
55
|
+
].includes(text(screen.slug).replace(/^\/+|\/+$/g, '')));
|
|
56
|
+
const description = text(home == null ? void 0 : home.seoDescription);
|
|
57
|
+
return description ? {
|
|
58
|
+
whatYouDo: description
|
|
59
|
+
} : null;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The transition listener's half that prefills (AGL-3661): on a site job's
|
|
63
|
+
* plan and on its finish, the start's answers and then the plan's guess, each
|
|
64
|
+
* a no-op once written. A failure is logged and never fails the job.
|
|
65
|
+
*/ export function aiBusinessProfilePrefiller(firestoreOf) {
|
|
66
|
+
return async ({ job, to })=>{
|
|
67
|
+
if (job.kind !== 'site' || !job.hostId || to === 'failed') return;
|
|
68
|
+
const start = aiBusinessProfileStartValues(job);
|
|
69
|
+
const plan = aiBusinessProfilePlanValues(job);
|
|
70
|
+
if (!start && !plan) return;
|
|
71
|
+
try {
|
|
72
|
+
const firestore = firestoreOf();
|
|
73
|
+
if (start) await prefillAiBusinessProfile(firestore, job.hostId, start, 'start');
|
|
74
|
+
if (plan) await prefillAiBusinessProfile(firestore, job.hostId, plan, 'ai');
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.warn('ai business profile prefill failed', {
|
|
77
|
+
orgId: job.orgId,
|
|
78
|
+
jobId: job.$id,
|
|
79
|
+
error
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
//# sourceMappingURL=ai-business-profile-prefill.js.map
|
|
@@ -0,0 +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 type { BusinessProfileValues } from '@aglyn/aglyn/app-utils/business-profile'\nimport type { AiJob } from '../model/ai-jobs.types'\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, its city. 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 if (city) values.serviceArea = city\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":["prefillAiBusinessProfile","text","value","trim","sentence","toUpperCase","slice","aiBusinessProfileStartValues","job","kind","inputs","values","businessType","audience","city","whatYouDo","serviceArea","Object","keys","length","aiBusinessProfilePlanValues","screens","plan","home","find","screen","includes","slug","replace","description","seoDescription","aiBusinessProfilePrefiller","firestoreOf","to","hostId","start","firestore","error","console","warn","orgId","jobId","$id"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAID,SAASA,wBAAwB,QAAQ,6BAAyB;AAGlE;;;;;;;;;;;;;CAaC,GAED,MAAMC,OAAO,CAACC,QAA4B,OAAOA,UAAU,WAAWA,MAAMC,IAAI,KAAK;AAErF,8EAA8E,GAC9E,MAAMC,WAAW,CAACF,QAA2BA,QAAQA,KAAK,CAAC,EAAE,CAACG,WAAW,KAAKH,MAAMI,KAAK,CAAC,KAAKJ;AAE/F,6EAA6E,GAC7E,OAAO,SAASK,6BAA6BC,GAAmC;QAE/DA;IADf,IAAIA,IAAIC,IAAI,KAAK,QAAQ,OAAO;IAChC,MAAMC,UAASF,cAAAA,IAAIE,MAAM,YAAVF,cAAc,CAAC;IAC9B,MAAMG,SAAgC,CAAC;IACvC,MAAMC,eAAeX,KAAKS,MAAM,CAAC,eAAe;IAChD,MAAMG,WAAWZ,KAAKS,MAAM,CAAC,WAAW;IACxC,MAAMI,OAAOb,KAAKS,MAAM,CAAC,OAAO;IAChC,IAAIE,cAAcD,OAAOI,SAAS,GAAGX,SAASQ;IAC9C,IAAIC,UAAUF,OAAOE,QAAQ,GAAGA;IAChC,IAAIC,MAAMH,OAAOK,WAAW,GAAGF;IAC/B,OAAOG,OAAOC,IAAI,CAACP,QAAQQ,MAAM,GAAGR,SAAS;AAC/C;AAEA,6FAA6F,GAC7F,OAAO,SAASS,4BAA4BZ,GAAiC;;QAE3DA;IADhB,IAAIA,IAAIC,IAAI,KAAK,QAAQ,OAAO;IAChC,MAAMY,mBAAUb,YAAAA,IAAIc,IAAI,qBAARd,UAAUa,OAAO,mBAAI,EAAE;IACvC,MAAME,OAAOF,QAAQG,IAAI,CAAC,CAACC,SAAW;YAAC;YAAI;YAAK;YAAQ;SAAQ,CAACC,QAAQ,CAACzB,KAAKwB,OAAOE,IAAI,EAAEC,OAAO,CAAC,cAAc;IAClH,MAAMC,cAAc5B,KAAKsB,wBAAAA,KAAMO,cAAc;IAC7C,OAAOD,cAAc;QAAEd,WAAWc;IAAY,IAAI;AACpD;AAEA;;;;CAIC,GACD,OAAO,SAASE,2BACdC,WAA8C;IAE9C,OAAO,OAAO,EAAExB,GAAG,EAAEyB,EAAE,EAAE;QACvB,IAAIzB,IAAIC,IAAI,KAAK,UAAU,CAACD,IAAI0B,MAAM,IAAID,OAAO,UAAU;QAC3D,MAAME,QAAQ5B,6BAA6BC;QAC3C,MAAMc,OAAOF,4BAA4BZ;QACzC,IAAI,CAAC2B,SAAS,CAACb,MAAM;QACrB,IAAI;YACF,MAAMc,YAAYJ;YAClB,IAAIG,OAAO,MAAMnC,yBAAyBoC,WAAW5B,IAAI0B,MAAM,EAAEC,OAAO;YACxE,IAAIb,MAAM,MAAMtB,yBAAyBoC,WAAW5B,IAAI0B,MAAM,EAAEZ,MAAM;QACxE,EAAE,OAAOe,OAAO;YACdC,QAAQC,IAAI,CAAC,sCAAsC;gBAAEC,OAAOhC,IAAIgC,KAAK;gBAAEC,OAAOjC,IAAIkC,GAAG;gBAAEL;YAAM;QAC/F;IACF;AACF"}
|
|
@@ -44,6 +44,14 @@ export declare function aiJobAutoConfirms(job: Pick<AiJob, 'kind' | 'inputs'>):
|
|
|
44
44
|
* A job's inputs as the create door stores them: `autoConfirm` kept only on a
|
|
45
45
|
* `site` job for a site that publishes nothing of the owner's yet, and dropped
|
|
46
46
|
* from every other request, which then waits for a person as it always has.
|
|
47
|
+
*
|
|
48
|
+
* A `site` job that names no business is given its site's own name
|
|
49
|
+
* (AGL-3596). The guided start asks what the site is for, never what it is
|
|
50
|
+
* called — the person named it when they created it — and a plan, a header
|
|
51
|
+
* and a footer told no name invented one. Filled here, on the click, from the
|
|
52
|
+
* one read of the site the confirmation already makes, so every door that
|
|
53
|
+
* creates a site job carries it and nothing is read while the guided start
|
|
54
|
+
* renders. A name the request gave — an agency batch's — is kept.
|
|
47
55
|
*/
|
|
48
56
|
export declare function aiJobAdmittedInputs(firestore: FirebaseFirestore.Firestore, request: {
|
|
49
57
|
kind: string;
|
|
@@ -17,7 +17,7 @@ import { _ as _to_property_key } from "@swc/helpers/_/_to_property_key";
|
|
|
17
17
|
* See the License for the specific language governing permissions and
|
|
18
18
|
* limitations under the License.
|
|
19
19
|
*/ import { isFirstPublishedRoute } from "@aglyn/aglyn/app-utils/analytics-events";
|
|
20
|
-
import { AI_JOB_AUTO_CONFIRM_INPUT } from "../model/ai-site-job.js";
|
|
20
|
+
import { AI_JOB_AUTO_CONFIRM_INPUT, AI_SITE_INPUT_MAX_CHARS } from "../model/ai-site-job.js";
|
|
21
21
|
/**
|
|
22
22
|
* A guided site start confirms its own plan (AGL-3594).
|
|
23
23
|
*
|
|
@@ -46,17 +46,41 @@ import { AI_JOB_AUTO_CONFIRM_INPUT } from "../model/ai-site-job.js";
|
|
|
46
46
|
* A job's inputs as the create door stores them: `autoConfirm` kept only on a
|
|
47
47
|
* `site` job for a site that publishes nothing of the owner's yet, and dropped
|
|
48
48
|
* from every other request, which then waits for a person as it always has.
|
|
49
|
+
*
|
|
50
|
+
* A `site` job that names no business is given its site's own name
|
|
51
|
+
* (AGL-3596). The guided start asks what the site is for, never what it is
|
|
52
|
+
* called — the person named it when they created it — and a plan, a header
|
|
53
|
+
* and a footer told no name invented one. Filled here, on the click, from the
|
|
54
|
+
* one read of the site the confirmation already makes, so every door that
|
|
55
|
+
* creates a site job carries it and nothing is read while the guided start
|
|
56
|
+
* renders. A name the request gave — an agency batch's — is kept.
|
|
49
57
|
*/ export async function aiJobAdmittedInputs(firestore, request) {
|
|
50
|
-
|
|
58
|
+
const askedToConfirm = AI_JOB_AUTO_CONFIRM_INPUT in request.inputs;
|
|
59
|
+
const unnamedSite = request.kind === 'site' && !!request.hostId && !aiSiteJobNamed(request.inputs);
|
|
60
|
+
if (!askedToConfirm && !unnamedSite) return request.inputs;
|
|
51
61
|
const _request_inputs = request.inputs, { [AI_JOB_AUTO_CONFIRM_INPUT]: asked } = _request_inputs, rest = _object_without_properties_loose(_request_inputs, [
|
|
52
62
|
_to_property_key(AI_JOB_AUTO_CONFIRM_INPUT)
|
|
53
63
|
]);
|
|
54
|
-
|
|
64
|
+
const mayConfirm = request.kind === 'site' && asked === true && !!request.hostId;
|
|
65
|
+
if (!mayConfirm && !unnamedSite) return rest;
|
|
55
66
|
const host = await firestore.collection('hosts').doc(request.hostId).get();
|
|
56
|
-
const blank = host.exists && isFirstPublishedRoute(host.get('screens'), host.get('defaultHomeScreenId'));
|
|
57
|
-
|
|
67
|
+
const blank = mayConfirm && host.exists && isFirstPublishedRoute(host.get('screens'), host.get('defaultHomeScreenId'));
|
|
68
|
+
const name = unnamedSite && host.exists ? aiSiteJobNameOf(host.get('displayName')) : '';
|
|
69
|
+
// A request that asked nothing of this door, for a site with no name to give, is passed through untouched.
|
|
70
|
+
if (!askedToConfirm && !name) return request.inputs;
|
|
71
|
+
return _extends({}, rest, name ? {
|
|
72
|
+
businessName: name
|
|
73
|
+
} : {}, blank ? {
|
|
58
74
|
[AI_JOB_AUTO_CONFIRM_INPUT]: true
|
|
59
|
-
}
|
|
75
|
+
} : {});
|
|
76
|
+
}
|
|
77
|
+
/** Whether a site job's inputs already name the business. */ function aiSiteJobNamed(inputs) {
|
|
78
|
+
const name = inputs['businessName'];
|
|
79
|
+
return typeof name === 'string' && name.trim() !== '';
|
|
80
|
+
}
|
|
81
|
+
/** A site's display name as a site job's `businessName`: one line, within what the job admits. */ function aiSiteJobNameOf(value) {
|
|
82
|
+
if (typeof value !== 'string') return '';
|
|
83
|
+
return value.replace(/\s+/g, ' ').trim().slice(0, AI_SITE_INPUT_MAX_CHARS).trim();
|
|
60
84
|
}
|
|
61
85
|
|
|
62
86
|
//# sourceMappingURL=ai-job-auto-confirm.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-auto-confirm.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 { isFirstPublishedRoute } from '@aglyn/aglyn/app-utils/analytics-events'\nimport type { AiJob } from '../model/ai-jobs.types'\nimport { AI_JOB_AUTO_CONFIRM_INPUT } from '../model/ai-site-job'\n\n/**\n * A guided site start confirms its own plan (AGL-3594).\n *\n * Every planned job stops after its plan for a person to read and confirm it.\n * For the guided start on a new site that stop read as a step nobody knew they\n * had to take: a person who had just answered \"what is your site?\" was shown a\n * plan to approve, and a site nobody approved was a site never built. So the\n * guided start asks for its plan to be confirmed for it, with this input, and\n * the machine confirms it the moment the plan step keeps a plan — the same\n * confirmation the resume door makes, by the job's creator, with the same\n * hold on what the plan is estimated to cost.\n *\n * Honored only where it is safe to skip the read: a `site` job, on a site\n * that has published nothing of its own yet — the site the guided start is\n * offered on. Everywhere else the create door drops it, so a page, a form or\n * a site job on an established site still waits for a person. What bounds the\n * spend is unchanged: the plan step's own checks (the Free page cap and the\n * Free wall among them), the plan's admission, and the reservation each step\n * takes.\n */\n\n/** The job input the guided start sets. */\nexport { AI_JOB_AUTO_CONFIRM_INPUT }\n\n/** Whether a job's plan is confirmed for it when the plan step keeps one. */\nexport function aiJobAutoConfirms(job: Pick<AiJob, 'kind' | 'inputs'>): boolean {\n return job.kind === 'site' && (job.inputs ?? {})[AI_JOB_AUTO_CONFIRM_INPUT] === true\n}\n\n/**\n * A job's inputs as the create door stores them: `autoConfirm` kept only on a\n * `site` job for a site that publishes nothing of the owner's yet, and dropped\n * from every other request, which then waits for a person as it always has.\n */\nexport async function aiJobAdmittedInputs(\n firestore: FirebaseFirestore.Firestore,\n request: { kind: string; hostId: string | null; inputs: Record<string, unknown> },\n): Promise<Record<string, unknown>> {\n
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-auto-confirm.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 { isFirstPublishedRoute } from '@aglyn/aglyn/app-utils/analytics-events'\nimport type { AiJob } from '../model/ai-jobs.types'\nimport { AI_JOB_AUTO_CONFIRM_INPUT, AI_SITE_INPUT_MAX_CHARS } from '../model/ai-site-job'\n\n/**\n * A guided site start confirms its own plan (AGL-3594).\n *\n * Every planned job stops after its plan for a person to read and confirm it.\n * For the guided start on a new site that stop read as a step nobody knew they\n * had to take: a person who had just answered \"what is your site?\" was shown a\n * plan to approve, and a site nobody approved was a site never built. So the\n * guided start asks for its plan to be confirmed for it, with this input, and\n * the machine confirms it the moment the plan step keeps a plan — the same\n * confirmation the resume door makes, by the job's creator, with the same\n * hold on what the plan is estimated to cost.\n *\n * Honored only where it is safe to skip the read: a `site` job, on a site\n * that has published nothing of its own yet — the site the guided start is\n * offered on. Everywhere else the create door drops it, so a page, a form or\n * a site job on an established site still waits for a person. What bounds the\n * spend is unchanged: the plan step's own checks (the Free page cap and the\n * Free wall among them), the plan's admission, and the reservation each step\n * takes.\n */\n\n/** The job input the guided start sets. */\nexport { AI_JOB_AUTO_CONFIRM_INPUT }\n\n/** Whether a job's plan is confirmed for it when the plan step keeps one. */\nexport function aiJobAutoConfirms(job: Pick<AiJob, 'kind' | 'inputs'>): boolean {\n return job.kind === 'site' && (job.inputs ?? {})[AI_JOB_AUTO_CONFIRM_INPUT] === true\n}\n\n/**\n * A job's inputs as the create door stores them: `autoConfirm` kept only on a\n * `site` job for a site that publishes nothing of the owner's yet, and dropped\n * from every other request, which then waits for a person as it always has.\n *\n * A `site` job that names no business is given its site's own name\n * (AGL-3596). The guided start asks what the site is for, never what it is\n * called — the person named it when they created it — and a plan, a header\n * and a footer told no name invented one. Filled here, on the click, from the\n * one read of the site the confirmation already makes, so every door that\n * creates a site job carries it and nothing is read while the guided start\n * renders. A name the request gave — an agency batch's — is kept.\n */\nexport async function aiJobAdmittedInputs(\n firestore: FirebaseFirestore.Firestore,\n request: { kind: string; hostId: string | null; inputs: Record<string, unknown> },\n): Promise<Record<string, unknown>> {\n const askedToConfirm = AI_JOB_AUTO_CONFIRM_INPUT in request.inputs\n const unnamedSite = request.kind === 'site' && !!request.hostId && !aiSiteJobNamed(request.inputs)\n if (!askedToConfirm && !unnamedSite) return request.inputs\n const { [AI_JOB_AUTO_CONFIRM_INPUT]: asked, ...rest } = request.inputs\n const mayConfirm = request.kind === 'site' && asked === true && !!request.hostId\n if (!mayConfirm && !unnamedSite) return rest\n const host = await firestore.collection('hosts').doc(request.hostId as string).get()\n const blank =\n mayConfirm &&\n host.exists &&\n isFirstPublishedRoute(\n host.get('screens') as Record<string, unknown> | undefined,\n host.get('defaultHomeScreenId') as string | undefined,\n )\n const name = unnamedSite && host.exists ? aiSiteJobNameOf(host.get('displayName')) : ''\n // A request that asked nothing of this door, for a site with no name to give, is passed through untouched.\n if (!askedToConfirm && !name) return request.inputs\n return {\n ...rest,\n ...(name ? { businessName: name } : {}),\n ...(blank ? { [AI_JOB_AUTO_CONFIRM_INPUT]: true } : {}),\n }\n}\n\n/** Whether a site job's inputs already name the business. */\nfunction aiSiteJobNamed(inputs: Readonly<Record<string, unknown>>): boolean {\n const name = inputs['businessName']\n return typeof name === 'string' && name.trim() !== ''\n}\n\n/** A site's display name as a site job's `businessName`: one line, within what the job admits. */\nfunction aiSiteJobNameOf(value: unknown): string {\n if (typeof value !== 'string') return ''\n return value.replace(/\\s+/g, ' ').trim().slice(0, AI_SITE_INPUT_MAX_CHARS).trim()\n}\n"],"names":["isFirstPublishedRoute","AI_JOB_AUTO_CONFIRM_INPUT","AI_SITE_INPUT_MAX_CHARS","aiJobAutoConfirms","job","kind","inputs","aiJobAdmittedInputs","firestore","request","askedToConfirm","unnamedSite","hostId","aiSiteJobNamed","asked","rest","mayConfirm","host","collection","doc","get","blank","exists","name","aiSiteJobNameOf","businessName","trim","value","replace","slice"],"mappings":";;;AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,qBAAqB,QAAQ,0CAAyC;AAE/E,SAASC,yBAAyB,EAAEC,uBAAuB,QAAQ,0BAAsB;AAEzF;;;;;;;;;;;;;;;;;;;CAmBC,GAED,yCAAyC,GACzC,SAASD,yBAAyB,GAAE;AAEpC,2EAA2E,GAC3E,OAAO,SAASE,kBAAkBC,GAAmC;QACpCA;IAA/B,OAAOA,IAAIC,IAAI,KAAK,UAAU,EAACD,cAAAA,IAAIE,MAAM,YAAVF,cAAc,CAAC,EAAE,CAACH,0BAA0B,KAAK;AAClF;AAEA;;;;;;;;;;;;CAYC,GACD,OAAO,eAAeM,oBACpBC,SAAsC,EACtCC,OAAiF;IAEjF,MAAMC,iBAAiBT,6BAA6BQ,QAAQH,MAAM;IAClE,MAAMK,cAAcF,QAAQJ,IAAI,KAAK,UAAU,CAAC,CAACI,QAAQG,MAAM,IAAI,CAACC,eAAeJ,QAAQH,MAAM;IACjG,IAAI,CAACI,kBAAkB,CAACC,aAAa,OAAOF,QAAQH,MAAM;IAC1D,MAAwDG,kBAAAA,QAAQH,MAAM,EAAhE,EAAE,CAACL,0BAA0B,EAAEa,KAAK,EAAW,GAAGL,iBAATM,wCAASN;yBAA/CR;;IACT,MAAMe,aAAaP,QAAQJ,IAAI,KAAK,UAAUS,UAAU,QAAQ,CAAC,CAACL,QAAQG,MAAM;IAChF,IAAI,CAACI,cAAc,CAACL,aAAa,OAAOI;IACxC,MAAME,OAAO,MAAMT,UAAUU,UAAU,CAAC,SAASC,GAAG,CAACV,QAAQG,MAAM,EAAYQ,GAAG;IAClF,MAAMC,QACJL,cACAC,KAAKK,MAAM,IACXtB,sBACEiB,KAAKG,GAAG,CAAC,YACTH,KAAKG,GAAG,CAAC;IAEb,MAAMG,OAAOZ,eAAeM,KAAKK,MAAM,GAAGE,gBAAgBP,KAAKG,GAAG,CAAC,kBAAkB;IACrF,2GAA2G;IAC3G,IAAI,CAACV,kBAAkB,CAACa,MAAM,OAAOd,QAAQH,MAAM;IACnD,OAAO,aACFS,MACCQ,OAAO;QAAEE,cAAcF;IAAK,IAAI,CAAC,GACjCF,QAAQ;QAAE,CAACpB,0BAA0B,EAAE;IAAK,IAAI,CAAC;AAEzD;AAEA,2DAA2D,GAC3D,SAASY,eAAeP,MAAyC;IAC/D,MAAMiB,OAAOjB,MAAM,CAAC,eAAe;IACnC,OAAO,OAAOiB,SAAS,YAAYA,KAAKG,IAAI,OAAO;AACrD;AAEA,gGAAgG,GAChG,SAASF,gBAAgBG,KAAc;IACrC,IAAI,OAAOA,UAAU,UAAU,OAAO;IACtC,OAAOA,MAAMC,OAAO,CAAC,QAAQ,KAAKF,IAAI,GAAGG,KAAK,CAAC,GAAG3B,yBAAyBwB,IAAI;AACjF"}
|
|
@@ -33,6 +33,8 @@ import { aiConfirmedPlan, aiUnspentOutcome } from "./ai-job-generation.js";
|
|
|
33
33
|
import { AI_JOB_PAGE_STEP_MINIMUM_MS } from "./ai-job-page-budget.js";
|
|
34
34
|
import { aiPluginDraftAdmissionRefusal, aiPluginDraftWriter } from "./ai-job-plugin-drafts.js";
|
|
35
35
|
import { aiSitePageWritten, aiSiteResolvedRef } from "./ai-job-site-step.js";
|
|
36
|
+
import { AI_LAYOUT_SITE_PAGES_INPUT, aiLayoutSitePagesOfPlan } from "./ai-job-layout-site-pages.js";
|
|
37
|
+
import { AI_LAYOUT_FORM_PAGE_INPUT, AI_LAYOUT_LANGUAGE_INPUT, aiLayoutFormPageOfPlan } from "./ai-job-page-language.js";
|
|
36
38
|
import { AI_JOB_BRIEF_MAX_CHARS } from "./ai-job-text-step.js";
|
|
37
39
|
import { aiJobPublishesBuild, aiPublishGuidedSite } from "./ai-site-publish.js";
|
|
38
40
|
import { AI_PLANNED_JOB_KINDS, aiJobStepRunMinimumMs, aiJobStepRunnerFor, registerAiJobStep, registerAiJobStepPasses } from "./ai-jobs.js";
|
|
@@ -185,6 +187,15 @@ import { AI_PLANNED_JOB_KINDS, aiJobStepRunMinimumMs, aiJobStepRunnerFor, regist
|
|
|
185
187
|
originJobId: aiOriginJobId(job)
|
|
186
188
|
});
|
|
187
189
|
delete inputs[AI_BUILD_PUBLISH_INPUT];
|
|
190
|
+
// A build's pages and layouts are designed in the layout language and
|
|
191
|
+
// compiled (AGL-3660), told the pages the build plans and where its form is.
|
|
192
|
+
if (context.kind === 'page' || context.kind === 'layout') {
|
|
193
|
+
inputs[AI_LAYOUT_LANGUAGE_INPUT] = true;
|
|
194
|
+
const pages = aiLayoutSitePagesOfPlan(plan.screens);
|
|
195
|
+
if (pages.length) inputs[AI_LAYOUT_SITE_PAGES_INPUT] = pages;
|
|
196
|
+
const formPage = aiLayoutFormPageOfPlan(plan);
|
|
197
|
+
if (formPage) inputs[AI_LAYOUT_FORM_PAGE_INPUT] = formPage;
|
|
198
|
+
}
|
|
188
199
|
return _extends({}, job, {
|
|
189
200
|
$id: aiBuildUnitJobId(job, unit),
|
|
190
201
|
kind: context.kind,
|
|
@@ -556,7 +567,9 @@ export const runAiJobBuildStep = createAiJobBuildStep();
|
|
|
556
567
|
/** The runner kinds this plugin's own item operations use, for the time a pass needs without the registry. */ const AI_BUILD_ITEM_RUNNER_KINDS = {
|
|
557
568
|
template: 'template',
|
|
558
569
|
campaign: 'campaign',
|
|
559
|
-
workflow: 'workflow'
|
|
570
|
+
workflow: 'workflow',
|
|
571
|
+
function: 'logic',
|
|
572
|
+
edit: 'edit'
|
|
560
573
|
};
|
|
561
574
|
/**
|
|
562
575
|
* Why a Free build cannot be confirmed: its estimate is more than the owner's
|