@aglyn/plugins-ai 1.0.0-beta.218 → 1.0.0-beta.220
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +18 -18
- package/src/index.d.ts +1 -0
- package/src/index.js.map +1 -1
- package/src/lib/activity/ai-activity-actions.js +8 -1
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/billing/ai-month-meter.d.ts +51 -0
- package/src/lib/billing/ai-month-meter.js +89 -0
- package/src/lib/billing/ai-month-meter.js.map +1 -0
- package/src/lib/billing/ai-overage-close.d.ts +11 -33
- package/src/lib/billing/ai-overage-close.js +4 -3
- package/src/lib/billing/ai-overage-close.js.map +1 -1
- package/src/lib/billing/ai-overage-cutover.d.ts +28 -12
- package/src/lib/billing/ai-overage-cutover.js +1 -28
- package/src/lib/billing/ai-overage-cutover.js.map +1 -1
- package/src/lib/billing/ai-overage-ledger.js +1 -1
- package/src/lib/billing/ai-overage-ledger.js.map +1 -1
- package/src/lib/billing/ai-overage-standing.js +1 -1
- package/src/lib/billing/ai-overage-standing.js.map +1 -1
- package/src/lib/components/ai-allotment-editor.component.js +1 -1
- package/src/lib/components/ai-allotment-editor.component.js.map +1 -1
- package/src/lib/components/ai-assist-provider.component.js +5 -4
- package/src/lib/components/ai-assist-provider.component.js.map +1 -1
- package/src/lib/components/ai-automation-zones.d.ts +63 -0
- package/src/lib/components/ai-automation-zones.js +31 -0
- package/src/lib/components/ai-automation-zones.js.map +1 -0
- package/src/lib/components/ai-brief-dialog.component.js +3 -3
- package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-describe-automation.component.d.ts +1 -1
- package/src/lib/components/ai-describe-automation.component.js.map +1 -1
- package/src/lib/components/ai-describe-button.component.d.ts +6 -3
- package/src/lib/components/ai-describe-button.component.js +4 -1
- package/src/lib/components/ai-describe-button.component.js.map +1 -1
- package/src/lib/components/ai-experiment-cards.component.js +1 -1
- package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
- package/src/lib/components/ai-explain-automation.component.d.ts +1 -1
- package/src/lib/components/ai-explain-automation.component.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.d.ts +8 -0
- package/src/lib/components/ai-job-plan.component.js +21 -4
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-permissions-on-host.component.d.ts +12 -2
- package/src/lib/components/ai-permissions-on-host.component.js +12 -3
- package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
- package/src/lib/components/ai-product-copy-card.component.d.ts +1 -1
- package/src/lib/components/ai-product-copy-card.component.js.map +1 -1
- package/src/lib/components/ai-product-import-option.component.d.ts +1 -1
- package/src/lib/components/ai-product-import-option.component.js.map +1 -1
- package/src/lib/components/ai-product-zones.d.ts +124 -0
- package/src/lib/components/ai-product-zones.js +32 -0
- package/src/lib/components/ai-product-zones.js.map +1 -0
- package/src/lib/components/ai-products-hub-card.component.d.ts +1 -1
- package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
- package/src/lib/components/ai-products-proposals.component.d.ts +1 -1
- package/src/lib/components/ai-products-proposals.component.js.map +1 -1
- package/src/lib/components/ai-seo-audit-card.component.js +35 -92
- package/src/lib/components/ai-seo-audit-card.component.js.map +1 -1
- package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
- package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.js +21 -15
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.js +2 -2
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/assist-signals-page.component.js +2 -2
- package/src/lib/components/assist-signals-page.component.js.map +1 -1
- package/src/lib/components/billing-ai-allotments.component.js +1 -1
- package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
- package/src/lib/components/billing-assist-overage-card.component.js +1 -1
- package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
- package/src/lib/constants.d.ts +5 -0
- package/src/lib/constants.js +4 -0
- package/src/lib/constants.js.map +1 -1
- package/src/lib/declarations.js +2 -0
- package/src/lib/declarations.js.map +1 -1
- package/src/lib/declarations.server.js +24 -10
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/insights/ai-dataset-catalog.d.ts +49 -0
- package/src/lib/insights/ai-dataset-catalog.js +57 -0
- package/src/lib/insights/ai-dataset-catalog.js.map +1 -0
- package/src/lib/insights/ai-figure-readers.d.ts +6 -39
- package/src/lib/insights/ai-figure-readers.js +2 -391
- package/src/lib/insights/ai-figure-readers.js.map +1 -1
- package/src/lib/insights/ai-insight-digest.d.ts +10 -1
- package/src/lib/insights/ai-insight-digest.js +16 -4
- package/src/lib/insights/ai-insight-digest.js.map +1 -1
- package/src/lib/insights/ai-insight-readers.d.ts +1 -1
- package/src/lib/insights/ai-insight-readers.js.map +1 -1
- package/src/lib/jobs/ai-email-bindings.d.ts +28 -21
- package/src/lib/jobs/ai-email-bindings.js +75 -42
- package/src/lib/jobs/ai-email-bindings.js.map +1 -1
- package/src/lib/jobs/ai-job-campaign-step.d.ts +0 -2
- package/src/lib/jobs/ai-job-campaign-step.js +5 -10
- package/src/lib/jobs/ai-job-campaign-step.js.map +1 -1
- package/src/lib/jobs/ai-job-component-step.d.ts +6 -1
- package/src/lib/jobs/ai-job-component-step.js +32 -9
- package/src/lib/jobs/ai-job-component-step.js.map +1 -1
- package/src/lib/jobs/ai-job-draft-ids.js +11 -0
- package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.js +1 -1
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-email-step.d.ts +5 -3
- package/src/lib/jobs/ai-job-email-step.js +11 -4
- package/src/lib/jobs/ai-job-email-step.js.map +1 -1
- package/src/lib/jobs/ai-job-experiment-step.d.ts +1 -1
- package/src/lib/jobs/ai-job-experiment-step.js +1 -1
- package/src/lib/jobs/ai-job-experiment-step.js.map +1 -1
- package/src/lib/jobs/ai-job-form-step.d.ts +17 -1
- package/src/lib/jobs/ai-job-form-step.js +60 -8
- package/src/lib/jobs/ai-job-form-step.js.map +1 -1
- package/src/lib/jobs/ai-job-generation.d.ts +22 -1
- package/src/lib/jobs/ai-job-generation.js +63 -3
- package/src/lib/jobs/ai-job-generation.js.map +1 -1
- package/src/lib/jobs/ai-job-insight-step.js +1 -1
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.d.ts +15 -4
- package/src/lib/jobs/ai-job-layout-step.js +46 -13
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.d.ts +4 -2
- package/src/lib/jobs/ai-job-page-sections.js +7 -4
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +7 -2
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-conformance.d.ts +5 -0
- package/src/lib/jobs/ai-job-plan-conformance.js +40 -1
- package/src/lib/jobs/ai-job-plan-conformance.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-creations.d.ts +80 -0
- package/src/lib/jobs/ai-job-plan-creations.js +199 -0
- package/src/lib/jobs/ai-job-plan-creations.js.map +1 -0
- package/src/lib/jobs/ai-job-plan-step.d.ts +11 -2
- package/src/lib/jobs/ai-job-plan-step.js +49 -23
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plugin-drafts.d.ts +14 -6
- package/src/lib/jobs/ai-job-plugin-drafts.js +12 -4
- package/src/lib/jobs/ai-job-plugin-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-products-step.d.ts +7 -0
- package/src/lib/jobs/ai-job-products-step.js +54 -32
- package/src/lib/jobs/ai-job-products-step.js.map +1 -1
- package/src/lib/jobs/ai-job-seo-step.d.ts +3 -24
- package/src/lib/jobs/ai-job-seo-step.js +35 -108
- package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +2 -0
- package/src/lib/jobs/ai-job-site-step.js +6 -6
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-template-step.d.ts +9 -6
- package/src/lib/jobs/ai-job-template-step.js +73 -15
- package/src/lib/jobs/ai-job-template-step.js.map +1 -1
- package/src/lib/jobs/ai-job-theme-step.d.ts +1 -1
- package/src/lib/jobs/ai-job-theme-step.js +1 -1
- package/src/lib/jobs/ai-job-theme-step.js.map +1 -1
- package/src/lib/jobs/ai-job-workflow-step.js +17 -9
- package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.js +1 -1
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-workflow-records.d.ts +20 -11
- package/src/lib/jobs/ai-workflow-records.js +80 -43
- package/src/lib/jobs/ai-workflow-records.js.map +1 -1
- package/src/lib/model/ai-allotments.d.ts +27 -6
- package/src/lib/model/ai-allotments.js +54 -14
- package/src/lib/model/ai-allotments.js.map +1 -1
- package/src/lib/model/ai-automation-outline.d.ts +21 -2
- package/src/lib/model/ai-automation-outline.js.map +1 -1
- package/src/lib/model/ai-build-plan.d.ts +57 -0
- package/src/lib/model/ai-build-plan.js +116 -7
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-job-creations.d.ts +55 -0
- package/src/lib/model/ai-job-creations.js +85 -0
- package/src/lib/model/ai-job-creations.js.map +1 -0
- package/src/lib/model/ai-page-job.d.ts +3 -2
- package/src/lib/model/ai-page-job.js +4 -3
- package/src/lib/model/ai-page-job.js.map +1 -1
- package/src/lib/model/ai-seo.d.ts +11 -51
- package/src/lib/model/ai-seo.js +0 -17
- package/src/lib/model/ai-seo.js.map +1 -1
- package/src/lib/model/ai-site-inventory.js +1 -1
- package/src/lib/model/ai-site-inventory.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +4 -2
- package/src/lib/model/ai-site-job.js +8 -4
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-template-subjects.d.ts +10 -0
- package/src/lib/model/ai-template-subjects.js +16 -2
- package/src/lib/model/ai-template-subjects.js.map +1 -1
- package/src/lib/model/ai-theme-proposal.d.ts +1 -1
- package/src/lib/model/ai-theme-proposal.js +1 -1
- package/src/lib/model/ai-theme-proposal.js.map +1 -1
- package/src/lib/model/ai-tokens.js +1 -1
- package/src/lib/model/ai-tokens.js.map +1 -1
- package/src/lib/model/ai-usage-by-user.js +1 -1
- package/src/lib/model/ai-usage-by-user.js.map +1 -1
- package/src/lib/model/ai-workflow-job.d.ts +1 -3
- package/src/lib/model/ai-workflow-job.js +1 -2
- package/src/lib/model/ai-workflow-job.js.map +1 -1
- package/src/lib/plan-entitlements.d.ts +149 -0
- package/src/lib/plan-entitlements.js +168 -0
- package/src/lib/plan-entitlements.js.map +1 -0
- package/src/lib/plugin.js +1 -1
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/catalog.js +1 -1
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/providers/model-choice.js +1 -1
- package/src/lib/providers/model-choice.js.map +1 -1
- package/src/lib/runtime/ai-device-audit.d.ts +1 -1
- package/src/lib/runtime/ai-device-audit.js +1 -1
- package/src/lib/runtime/ai-device-audit.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +25 -4
- package/src/lib/runtime/ai-doctrine-validators.js +178 -21
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +5 -5
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.js +30 -15
- package/src/lib/runtime/ai-eval-live.js.map +1 -1
- package/src/lib/runtime/ai-eval.d.ts +9 -2
- package/src/lib/runtime/ai-eval.js +29 -2
- package/src/lib/runtime/ai-eval.js.map +1 -1
- package/src/lib/runtime/ai-gate.js +1 -1
- package/src/lib/runtime/ai-gate.js.map +1 -1
- package/src/lib/runtime/ai-node-tree.js +3 -3
- package/src/lib/runtime/ai-node-tree.js.map +1 -1
- package/src/lib/runtime/ai-palette.d.ts +7 -1
- package/src/lib/runtime/ai-palette.generated.d.ts +1 -1
- package/src/lib/runtime/ai-palette.generated.js +16 -13
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/ai-palette.js +7 -1
- package/src/lib/runtime/ai-palette.js.map +1 -1
- package/src/lib/runtime/seo-audit.d.ts +13 -63
- package/src/lib/runtime/seo-audit.js +22 -207
- package/src/lib/runtime/seo-audit.js.map +1 -1
- package/src/lib/runtime/site-inventory.js +29 -23
- package/src/lib/runtime/site-inventory.js.map +1 -1
- package/src/lib/server/ai-admin-org.js +1 -1
- package/src/lib/server/ai-admin-org.js.map +1 -1
- package/src/lib/server/ai-admin-orgs-spend.js +1 -1
- package/src/lib/server/ai-admin-orgs-spend.js.map +1 -1
- package/src/lib/server/ai-admin-signals.js +1 -1
- package/src/lib/server/ai-admin-signals.js.map +1 -1
- package/src/lib/server/ai-allotments.js +1 -1
- package/src/lib/server/ai-allotments.js.map +1 -1
- package/src/lib/server/ai-assist-prompts.js +3 -3
- package/src/lib/server/ai-assist-prompts.js.map +1 -1
- package/src/lib/server/ai-assist.js +2 -2
- package/src/lib/server/ai-assist.js.map +1 -1
- package/src/lib/server/assist-chat.d.ts +1 -1
- package/src/lib/server/assist-chat.js +13 -12
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-view-context.js +23 -23
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/server/billing-credits.d.ts +5 -0
- package/src/lib/server/billing-credits.js +8 -2
- package/src/lib/server/billing-credits.js.map +1 -1
- package/src/lib/server/billing-overage.js +2 -2
- package/src/lib/server/billing-overage.js.map +1 -1
- package/src/lib/server.d.ts +1 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/stand-in-automation-index.d.ts +20 -0
- package/src/lib/testing/stand-in-automation-index.js +102 -0
- package/src/lib/testing/stand-in-automation-index.js.map +1 -0
- package/src/lib/testing/stand-in-dataset-index.d.ts +20 -0
- package/src/lib/testing/stand-in-dataset-index.js +80 -0
- package/src/lib/testing/stand-in-dataset-index.js.map +1 -0
- package/src/lib/testing/stand-in-list-send-time.d.ts +4 -0
- package/src/lib/testing/stand-in-list-send-time.js +77 -0
- package/src/lib/testing/stand-in-list-send-time.js.map +1 -0
- package/src/lib/testing/stand-in-pipeline-index.d.ts +32 -0
- package/src/lib/testing/stand-in-pipeline-index.js +59 -0
- package/src/lib/testing/stand-in-pipeline-index.js.map +1 -0
- package/src/lib/testing/stand-in-product-index.d.ts +20 -0
- package/src/lib/testing/stand-in-product-index.js +105 -0
- package/src/lib/testing/stand-in-product-index.js.map +1 -0
- package/src/lib/tools/ai-component-tool.js +2 -2
- package/src/lib/tools/ai-component-tool.js.map +1 -1
- package/src/lib/tools/ai-duplicate-resource-tool.js +1 -1
- package/src/lib/tools/ai-duplicate-resource-tool.js.map +1 -1
- package/src/lib/tools/ai-seo-tool.d.ts +6 -18
- package/src/lib/tools/ai-seo-tool.js +7 -38
- package/src/lib/tools/ai-seo-tool.js.map +1 -1
- package/src/lib/tools/ai-workflow-tool.d.ts +1 -1
- package/src/lib/tools/ai-workflow-tool.js +1 -1
- package/src/lib/tools/ai-workflow-tool.js.map +1 -1
- package/src/lib/usage/ai-allotment-alerts.d.ts +10 -7
- package/src/lib/usage/ai-allotment-alerts.js +39 -15
- package/src/lib/usage/ai-allotment-alerts.js.map +1 -1
- package/src/lib/usage/ai-quota-checks.d.ts +37 -0
- package/src/lib/usage/ai-quota-checks.js +69 -0
- package/src/lib/usage/ai-quota-checks.js.map +1 -0
- package/src/lib/usage/ai-usage-alerts.js +22 -10
- package/src/lib/usage/ai-usage-alerts.js.map +1 -1
- package/src/lib/usage/ai-usage-by-user.js +1 -1
- package/src/lib/usage/ai-usage-by-user.js.map +1 -1
- package/src/lib/usage/assist-ceiling.d.ts +2 -2
- package/src/lib/usage/assist-ceiling.js +4 -4
- package/src/lib/usage/assist-ceiling.js.map +1 -1
- package/src/lib/usage/assist-credits.d.ts +494 -0
- package/src/lib/usage/assist-credits.js +524 -0
- package/src/lib/usage/assist-credits.js.map +1 -0
- package/src/lib/usage/assist-free-taste.d.ts +1 -1
- package/src/lib/usage/assist-free-taste.js +3 -2
- package/src/lib/usage/assist-free-taste.js.map +1 -1
- package/src/lib/usage/assist-signal-mining.js +1 -1
- package/src/lib/usage/assist-signal-mining.js.map +1 -1
- package/src/lib/usage/assist-spend-guards.d.ts +153 -0
- package/src/lib/usage/assist-spend-guards.js +182 -0
- package/src/lib/usage/assist-spend-guards.js.map +1 -0
- package/src/lib/usage/assist-usage.d.ts +1 -1
- package/src/lib/usage/assist-usage.js +1 -1
- package/src/lib/usage/assist-usage.js.map +1 -1
- package/src/lib/usage/staff-org-ai.js +1 -1
- package/src/lib/usage/staff-org-ai.js.map +1 -1
- package/src/lib/usage-axes.d.ts +49 -0
- package/src/lib/usage-axes.js +108 -0
- package/src/lib/usage-axes.js.map +1 -0
- package/src/lib/runtime/seo-page-facts.d.ts +0 -61
- package/src/lib/runtime/seo-page-facts.js +0 -150
- package/src/lib/runtime/seo-page-facts.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-site-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport {\n isAiPlanNewRef,\n type AiBuildPlanCreate,\n type AiBuildPlanCreateKind,\n type AiBuildPlanScreen,\n} from '../model/ai-build-plan'\nimport type {\n AiJob,\n AiJobKind,\n AiJobOutput,\n AiJobOutputResource,\n AiJobPlan,\n} from '../model/ai-jobs.types'\nimport {\n AI_SITE_EMAIL_TYPE,\n AI_SITE_MAX_SECTIONS,\n AI_SITE_PAGES,\n aiSitePlanRefusal,\n aiSiteSubmissions,\n aiSiteWords,\n parseAiSiteJobInputs,\n type AiSiteJobInputs,\n} from '../model/ai-site-job'\nimport {\n AI_SITE_SEO_OUTPUT_ID,\n aiSiteSeoProposalForInputs,\n} from '../model/ai-site-start-seo'\nimport { aiModelForStep } from '../providers/routing'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport { aiRecordedJobDraftId } from './ai-job-draft-ids'\nimport { aiConfirmedPlan, aiUnspentOutcome } from './ai-job-generation'\nimport {\n AI_JOB_BRIEF_MAX_CHARS,\n type AiJobStepContext,\n type AiJobStepOutcome,\n type AiJobStepRunner,\n} from './ai-job-text-step'\nimport { AI_JOB_PAGE_STEP_MINIMUM_MS } from './ai-job-page-budget'\nimport {\n aiJobStepRunMinimumMs,\n aiJobStepRunnerFor,\n registerAiJobStep,\n registerAiJobStepPasses,\n} from './ai-jobs'\n\n/**\n * The site scaffold (AGL-2911): the generation step of a `site` job, run once\n * a member confirmed the plan their brief produced.\n *\n * ── It builds nothing itself ─────────────────────────────────────────────\n *\n * A scaffold is a whole site: a palette suggestion, the layout its pages\n * render inside, the contact form they place, four to eight pages with their\n * search listings, and a welcome email. Every one of those is already a job\n * kind with a step of its own, so this step generates nothing and asks no\n * model anything. It works through the confirmed plan one UNIT at a time and\n * hands each unit to the runner registered for the kind that owns it — the\n * theme step for the palette, the page step for a page — under a job of that\n * kind derived from this one.\n *\n * That is why the scaffold cannot drift from the thing it scaffolds: a page\n * it builds is a page job's page, held to the same doctrine, written by the\n * same draft writer, left unpublished by the same rule. A kind whose step\n * this deployment has not loaded is simply not among the units, so nothing\n * here knows which issues have landed.\n *\n * ── One unit a pass ──────────────────────────────────────────────────────\n *\n * The step runs ONE delegated pass and asks the machine to continue, so every\n * pass is one reservation, one provider exchange and one recorded spend, and\n * the org's monthly ceiling binds a scaffold exactly as it binds a chat turn.\n * Credits running out mid-scaffold is therefore the machine's own park: the\n * job waits as `needs_input` with the meter's words, and the beat resumes it\n * where it stopped once the workspace's standing has changed. A pass needs the\n * time its unit's own step needs (`aiSiteJobRunMinimumMs`), so the beat starts\n * a palette change only with a palette change's time left.\n *\n * Where it stopped is read from the job's OUTPUTS rather than kept anywhere:\n * each unit reports exactly one output when it completes, so the units\n * already represented there are the ones already built. A pass interrupted\n * between its work and its record repeats a unit that then finds its own\n * draft and reports it, which is what every step's draft id already gives.\n *\n * ── Never published ──────────────────────────────────────────────────────\n *\n * Nothing here publishes, routes or sends: each delegated step writes the\n * unpublished draft it already wrote for a job of its own kind, and the\n * palette and navigation travel as proposals a member applies.\n *\n * ── The page job builds its creations the same way (AGL-3031) ────────────\n *\n * A page job whose plan creates a layout, forms or components builds them\n * before its page through this same machinery: the units a creation implies\n * (`aiCreationUnit`), where the job stands read from its outputs\n * (`aiSitePendingUnits`), one delegated pass a unit (`aiRunJobUnit`), and the\n * `new:<name>` references resolved to what was built (`aiSiteBuiltRefs`,\n * `aiSiteUnitJob`). There is one loop, and it is the machine's.\n */\n\n/** What a scaffold's units are, in the order it builds them; a component is a page job's. */\nexport type AiSiteUnitKind = 'theme' | 'layout' | 'form' | 'component' | 'page' | 'email'\n\nexport interface AiSiteUnit {\n kind: AiSiteUnitKind\n /** The job kind whose registered runner builds it. */\n jobKind: AiJobKind\n /** The resource the unit reports when it completes. */\n resource: AiJobOutputResource\n /** Stable within the job: what the unit's derived job and draft are addressed by. */\n slot: string\n /** The plan screen a page unit builds. */\n screen?: AiBuildPlanScreen\n /** The creation a theme, layout or form unit builds. */\n creation?: AiBuildPlanCreate\n /** What the unit is, for the derived job's brief. */\n label: string\n}\n\n/** The job kind and output resource each unit is built through. */\nconst UNIT_KINDS: Record<\n AiSiteUnitKind,\n { jobKind: AiJobKind; resource: AiJobOutputResource }\n> = {\n theme: { jobKind: 'theme', resource: 'theme' },\n layout: { jobKind: 'layout', resource: 'layout' },\n form: { jobKind: 'form', resource: 'form' },\n component: { jobKind: 'component', resource: 'reusableComponent' },\n page: { jobKind: 'page', resource: 'screen' },\n email: { jobKind: 'email', resource: 'emailScreen' },\n}\n\n/** The unit kind a creation is built as, where a unit builds it. */\nconst CREATION_UNIT_KINDS: Partial<Record<AiBuildPlanCreateKind, AiSiteUnitKind>> = {\n 'theme-change': 'theme',\n layout: 'layout',\n form: 'form',\n component: 'component',\n}\n\n/**\n * The unit one creation is built as, under the slot its derived job and draft\n * are addressed by; `null` for a creation no unit builds.\n */\nexport function aiCreationUnit(creation: AiBuildPlanCreate, slot: string): AiSiteUnit | null {\n const kind = CREATION_UNIT_KINDS[creation.kind]\n if (!kind) return null\n return { kind, ...UNIT_KINDS[kind], slot, creation, label: creation.name }\n}\n\n/** The creation kinds a unit is built from, by unit kind. */\nconst CREATION_UNITS: Array<{\n unit: AiSiteUnitKind\n create: AiBuildPlanCreateKind\n}> = [\n { unit: 'theme', create: 'theme-change' },\n { unit: 'layout', create: 'layout' },\n { unit: 'form', create: 'form' },\n]\n\nexport const AI_SITE_NO_PLAN_COPY =\n 'This site job has no confirmed plan to build.'\n\n/** What a scaffold answers where the step that builds a page is not loaded. */\nexport const AI_SITE_NO_PAGE_STEP_COPY =\n 'Building a whole site is not available yet.'\n\n/** A unit that finished without reporting what it built; the scaffold cannot go on. */\nexport const AI_SITE_UNIT_EMPTY_COPY =\n 'Part of this site could not be built. Try the site brief again.'\n\n/**\n * The most passes a scaffold's step may take: the largest plan it admits —\n * eight pages of eight sections, its three creations and a welcome email —\n * with nothing to spare, so a runner that never finishes is still bounded\n * while a real site is not.\n */\nexport const AI_SITE_MAX_PASSES =\n AI_SITE_PAGES.max * (AI_SITE_MAX_SECTIONS + 1) + CREATION_UNITS.length + 1\n\n/**\n * The units a plan implies, in build order: the palette first, because a\n * member reads it while the pages are still building; then the layout every\n * page renders inside and the form they place, because a page binds both by\n * id and so needs them to exist; then the pages; then the welcome email,\n * which is about the site rather than part of it.\n */\nexport function aiSiteJobUnits(\n plan: Pick<AiJobPlan, 'create' | 'screens'>,\n options: { welcomeEmail?: boolean } = {},\n): AiSiteUnit[] {\n const units: AiSiteUnit[] = []\n for (const { unit, create } of CREATION_UNITS) {\n const creation = plan.create.find((entry) => entry.kind === create)\n if (!creation) continue\n units.push({\n kind: unit,\n ...UNIT_KINDS[unit],\n slot: unit[0],\n creation,\n label: creation.name,\n })\n }\n plan.screens.forEach((screen, index) => {\n units.push({\n kind: 'page',\n ...UNIT_KINDS.page,\n slot: `p${index}`,\n screen,\n label: screen.title,\n })\n })\n if (options.welcomeEmail) {\n units.push({\n kind: 'email',\n ...UNIT_KINDS.email,\n slot: 'e',\n label: 'Welcome email',\n })\n }\n return units\n}\n\n/**\n * The units a scaffold still owes after the outputs recorded so far. Each\n * unit reports one output of its own resource when it completes, so the\n * outputs already recorded are consumed against the units in build order.\n */\nexport function aiSitePendingUnits(\n units: readonly AiSiteUnit[],\n outputs: readonly Pick<AiJobOutput, 'resource'>[],\n): AiSiteUnit[] {\n const done = new Map<AiJobOutputResource, number>()\n for (const output of outputs) {\n done.set(output.resource, (done.get(output.resource) ?? 0) + 1)\n }\n return units.filter((unit) => {\n const left = done.get(unit.resource) ?? 0\n if (left <= 0) return true\n done.set(unit.resource, left - 1)\n return false\n })\n}\n\n/**\n * The site's own search listing as the scaffold reports it (AGL-2918), or\n * nothing when the job already reported one or its inputs describe no site.\n *\n * It costs nothing and asks no model: the values are arithmetic on the\n * answers (`aiSiteSeoProposal`), so they ride out on the first pass that\n * builds anything rather than waiting for a pass of their own. Its resource\n * is `seo`, which no unit reports, so it cannot be mistaken for a unit's\n * output by `aiSitePendingUnits` and cannot move where the scaffold thinks\n * it is.\n */\nexport function aiSiteSeoOutputs(job: AiJob): AiJobOutput[] {\n const outputs = job.outputs ?? []\n if (outputs.some((output) => output.resource === 'seo' && output.id === AI_SITE_SEO_OUTPUT_ID)) {\n return []\n }\n const proposal = aiSiteSeoProposalForInputs(job.inputs)\n if (!proposal) return []\n return [\n {\n resource: 'seo',\n id: AI_SITE_SEO_OUTPUT_ID,\n hostId: job.hostId ?? null,\n label: 'The site’s search title and description',\n proposal: proposal as unknown as Record<string, unknown>,\n },\n ]\n}\n\n/** A record an earlier unit created, as a later one references it. */\nexport interface AiSiteBuiltRef {\n id: string\n label: string\n /** Only what becomes a record a page can name; a palette change is not one. */\n kind: 'layout' | 'form' | 'component'\n}\n\n/**\n * What the scaffold has already created, as the plan's `new:<name>`\n * references resolve to: the id of the record each completed creation wrote.\n * A page unit is told about the layout and the form by id, exactly as a page\n * job is told about records the site already had.\n */\nexport function aiSiteBuiltRefs(\n units: readonly AiSiteUnit[],\n outputs: readonly Pick<AiJobOutput, 'resource' | 'id' | 'label'>[],\n): Map<string, AiSiteBuiltRef> {\n const byResource = new Map<\n AiJobOutputResource,\n Array<Pick<AiJobOutput, 'id' | 'label'>>\n >()\n for (const output of outputs) {\n const rows = byResource.get(output.resource) ?? []\n rows.push(output)\n byResource.set(output.resource, rows)\n }\n const built = new Map<string, AiSiteBuiltRef>()\n for (const unit of units) {\n const rows = byResource.get(unit.resource)\n const row = rows?.shift()\n if (!row || !unit.creation) continue\n // A theme change is a proposal with no record to reference; a layout, a\n // form and a component become ids a page can name.\n if (\n unit.creation.kind !== 'layout' &&\n unit.creation.kind !== 'form' &&\n unit.creation.kind !== 'component'\n ) {\n continue\n }\n built.set(unit.creation.name.toLowerCase(), {\n id: row.id,\n label: row.label || unit.creation.name,\n kind: unit.creation.kind,\n })\n }\n return built\n}\n\ntype BuiltRefs = ReturnType<typeof aiSiteBuiltRefs>\n\n/** A plan reference as the unit's own job reads it: an id the site now has, or nothing. */\nfunction resolved(ref: string | null, built: BuiltRefs): string | null {\n if (!ref) return null\n if (!isAiPlanNewRef(ref)) return ref\n return (\n built.get(\n ref\n .slice(ref.indexOf(':') + 1)\n .trim()\n .toLowerCase(),\n )?.id ?? null\n )\n}\n\n/**\n * The site's own words, as every unit's brief carries them.\n *\n * The audience is here rather than left to the brief's prose (AGL-2918). A\n * guided start writes it into the brief itself, but an agency batch's brief\n * is a member's own sentence and may never mention it, and a theme, a layout\n * or a form asked to serve nobody in particular serves nobody in particular.\n */\nexport function aiSiteBriefLines(\n brief: string,\n inputs: AiSiteJobInputs,\n): string[] {\n const lines = [brief.trim()]\n const site = [\n inputs.businessName ? `name: ${inputs.businessName}` : '',\n `business: ${inputs.businessType}`,\n inputs.audience ? `for: ${inputs.audience}` : '',\n inputs.city ? `city: ${inputs.city}` : '',\n inputs.brand ? `brand: ${inputs.brand}` : '',\n ].filter(Boolean)\n lines.push(`Site — ${site.join('; ')}.`)\n return lines\n}\n\n/**\n * What the welcome email is told (AGL-2918), beyond that it is a welcome\n * email.\n *\n * The scaffold's email unit used to carry one sentence, and a yes/no toggle\n * decided whether it ran — so the draft was a welcome email for a business\n * in general, to nobody in particular, about nothing that had happened. The\n * two answers it is missing are who wrote in and what now becomes of what\n * they wrote, and the second of those is the person's own routing answer, so\n * the email and the form it acknowledges cannot say different things.\n *\n * ⛔ A DRAFT either way. Nothing here sends, schedules or enrolls anybody:\n * the email step writes an unpublished email design and stops.\n */\nexport function aiSiteEmailBriefLines(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): string[] {\n const lines = [\n 'Write the welcome email this site sends someone who gets in touch.',\n ]\n const { audience } = aiSiteWords(inputs)\n if (audience) lines.push(`Write it to ${audience}.`)\n const submissions = aiSiteSubmissions(inputs)\n if (submissions === 'lead') {\n lines.push('Their message is a sales lead, so say that somebody will be in touch about it.')\n } else if (submissions === 'inbox') {\n lines.push('Their message has been read, so say it arrived and that a reply is coming.')\n }\n return lines\n}\n\n/**\n * The id a unit's job is named by, and so the id its draft is written under\n * (AGL-3079): the id the plan entry it builds recorded, or for the welcome\n * email the id recorded on this job's step. A unit that writes no draft — a\n * palette change — and a unit whose plan recorded no id are named by this\n * job's id and the unit's slot, the name every draft of such a plan has.\n */\nexport function aiSiteUnitJobId(job: Pick<AiJob, '$id' | 'steps'>, unit: AiSiteUnit): string {\n const recorded =\n unit.creation?.id ?? unit.screen?.id ?? (unit.kind === 'email' ? aiRecordedJobDraftId(job, 'email') : null)\n return recorded || `${job.$id}-${unit.slot}`\n}\n\n/**\n * The job a unit is built under: this job's org, site, creator and model,\n * the kind that owns the unit, and a plan narrowed to the unit alone.\n *\n * The derived job carries NO steps and NO outputs of the scaffold's: a page\n * job's step reads its own draft and its own plan, and what the other units\n * produced is not its business. Its `$id` is the id the unit's draft is\n * written under (`aiSiteUnitJobId`), which is what a step with no id of its\n * own recorded names its draft by, so a unit re-run after its write finds its\n * own draft rather than writing a second.\n *\n * A creation unit reuses what the plan reuses less what the plan's screens\n * place themselves (AGL-3031): a component a page's section places is the\n * page's to place, and a layout or a card told to place it would be refused\n * for leaving it out.\n */\nexport function aiSiteUnitJob(\n job: AiJob,\n unit: AiSiteUnit,\n built: BuiltRefs,\n): AiJob {\n const plan = job.plan as AiJobPlan\n const labels = { ...(plan.labels ?? {}) }\n for (const entry of built.values()) labels[entry.id] = entry.label\n const inputs = parseAiSiteJobInputs(job.inputs)\n const brief =\n typeof inputs === 'string'\n ? [job.brief]\n : aiSiteBriefLines(job.brief, inputs)\n const unitPlan: AiJobPlan = {\n ...plan,\n labels,\n reuse: [...plan.reuse],\n create: [],\n screens: [],\n }\n if (unit.creation) {\n const placedByScreens = new Set(\n plan.screens.flatMap((screen) => screen.sections.flatMap((section) => section.uses)),\n )\n unitPlan.reuse = plan.reuse.filter((entry) => !placedByScreens.has(entry.id))\n unitPlan.create = [\n {\n ...unit.creation,\n duplicateOf: resolved(unit.creation.duplicateOf, built),\n },\n ]\n brief.push(\n `Build the ${unit.creation.kind} “${unit.creation.name}”: ${unit.creation.why}`,\n )\n }\n if (unit.screen) {\n const screen = unit.screen\n // Everything the scaffold has already built is a record the site has, so\n // the page is planned against it the way a page job is planned against\n // what the site always had.\n for (const entry of built.values()) {\n unitPlan.reuse.push({\n kind: entry.kind,\n id: entry.id,\n purpose: `the ${entry.kind} this site’s pages are built on`,\n })\n }\n unitPlan.screens = [\n {\n ...screen,\n layout: resolved(screen.layout, built),\n template: resolved(screen.template, built),\n duplicateOf: resolved(screen.duplicateOf, built),\n sections: screen.sections.map((section) => ({\n ...section,\n uses: section.uses\n .map((ref) => resolved(ref, built))\n .filter((ref): ref is string => !!ref),\n })),\n },\n ]\n brief.push(\n `Build the page “${screen.title}” of this site, at ${screen.slug}.`,\n )\n }\n if (unit.kind === 'email') {\n brief.push(...aiSiteEmailBriefLines(job.inputs))\n }\n return {\n ...job,\n $id: aiSiteUnitJobId(job, unit),\n kind: unit.jobKind,\n steps: [],\n outputs: [],\n plan: unitPlan,\n // A theme change on a site with no theme of its own is a new palette.\n ...(unit.kind === 'theme'\n ? { inputs: { ...job.inputs, mode: 'create' } }\n : {}),\n // The email step reads the kind off the inputs it is handed, so a job the\n // scaffold composed says which kind it is exactly as a member's own does.\n ...(unit.kind === 'email'\n ? { inputs: { ...job.inputs, emailType: AI_SITE_EMAIL_TYPE } }\n : {}),\n brief: brief.join('\\n').slice(0, AI_JOB_BRIEF_MAX_CHARS),\n }\n}\n\n/**\n * A scaffold is admitted with inputs that read, for a site of the job's own\n * org, and only where this deployment has loaded the step that builds a page:\n * a scaffold whose pages nothing can build is not a scaffold.\n */\nexport const aiSiteJobAdmission: AiJobAdmission = async (context) => {\n const inputs = parseAiSiteJobInputs(context.inputs)\n if (typeof inputs === 'string') return { status: 400, error: inputs }\n if (!context.hostId) {\n return {\n status: 400,\n error: 'Open the site the scaffold is for before starting the job',\n }\n }\n const owner = await resolveOrgIdForHost(context.hostId)\n if (!owner || owner !== context.orgId)\n return { status: 404, error: 'Unknown site' }\n if (!aiJobStepRunnerFor('page')) {\n return { status: 400, error: AI_SITE_NO_PAGE_STEP_COPY }\n }\n return null\n}\n\n/** What one delegated pass came to: the unit's spend as the job records it, and whether the unit is built. */\nexport interface AiJobUnitPass {\n outcome: AiJobStepOutcome\n /** The unit reported what it built and asked for no pass of its own. */\n built: boolean\n}\n\n/**\n * One delegated pass (AGL-2911, AGL-3031): the unit handed to the runner of\n * the kind that owns it, under the job derived for it, and its outcome as the\n * delegating job records it. A delegate proposes a plan for its own job,\n * which does not exist, so no plan is carried back. A unit that stopped for a\n * person, refused or failed is the delegating job's stop; a unit that asked\n * for another pass keeps its turn; and a unit that finished without reporting\n * what it built fails with `emptyCopy`, since another pass would ask the same\n * thing again. The tokens it spent are on the bill either way.\n */\nexport async function aiRunJobUnit(\n context: AiJobStepContext,\n input: {\n unit: AiSiteUnit\n units: readonly AiSiteUnit[]\n runner: AiJobStepRunner\n emptyCopy: string\n },\n): Promise<AiJobUnitPass> {\n const { job } = context\n const outputs = job.outputs ?? []\n const outcome = await input.runner({\n ...context,\n job: aiSiteUnitJob(job, input.unit, aiSiteBuiltRefs(input.units, outputs)),\n })\n const spent: AiJobStepOutcome = {\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 ...(outcome.refused ? { refused: true } : {}),\n ...(outcome.failure ? { failure: outcome.failure } : {}),\n ...(outcome.review ? { review: outcome.review } : {}),\n }\n if (spent.review || spent.refused || spent.failure) return { outcome: spent, built: false }\n if (outcome.continue) return { outcome: { ...spent, continue: true }, built: false }\n if (!outcome.outputs.length) return { outcome: { ...spent, failure: input.emptyCopy }, built: false }\n return { outcome: spent, built: true }\n}\n\nexport interface AiJobSiteStepDeps {\n /** The runner registry; specs hand in a fake for a kind they drive. */\n runnerFor?: typeof aiJobStepRunnerFor\n}\n\nexport function createAiJobSiteStep(\n deps: AiJobSiteStepDeps = {},\n): AiJobStepRunner {\n const runnerFor = deps.runnerFor ?? aiJobStepRunnerFor\n return async (context): Promise<AiJobStepOutcome> => {\n const { job } = context\n // The scaffold asks no model of its own. What it names where it spends\n // nothing is the model its plan ran on, which is the one exchange this\n // job has already had of its own.\n const model = context.modelFor?.('job.plan') ?? aiModelForStep('job.plan')\n const plan = aiConfirmedPlan(job)\n if (!plan)\n return { ...aiUnspentOutcome(model), failure: AI_SITE_NO_PLAN_COPY }\n // The resume door refuses such a plan at confirmation once it can read\n // one; a plan confirmed before a site changed still stops here, unspent.\n const refusal = aiSitePlanRefusal(plan)\n if (refusal) return { ...aiUnspentOutcome(model), failure: refusal }\n const inputs = parseAiSiteJobInputs(job.inputs)\n if (typeof inputs === 'string')\n return { ...aiUnspentOutcome(model), failure: inputs }\n\n // A kind this deployment has not loaded is not among the units at all, so\n // a scaffold owes only what something can build.\n const units = aiSiteJobUnits(plan, {\n welcomeEmail: inputs.welcomeEmail,\n }).filter((unit) => runnerFor(unit.jobKind))\n const outputs = job.outputs ?? []\n const pending = aiSitePendingUnits(units, outputs)\n if (!pending.length) return aiUnspentOutcome(model)\n const unit = pending[0]\n const runner = runnerFor(unit.jobKind)\n if (!runner) return aiUnspentOutcome(model)\n\n const pass = await aiRunJobUnit(context, {\n unit,\n units,\n runner,\n emptyCopy: AI_SITE_UNIT_EMPTY_COPY,\n })\n // The site's own listing rides out beside the first unit's output, once\n // (AGL-2918): it is derived from the answers rather than generated, so it\n // is ready before anything is built and costs the pass nothing.\n const outcome: AiJobStepOutcome = {\n ...pass.outcome,\n outputs: [...aiSiteSeoOutputs(job), ...pass.outcome.outputs],\n }\n // A built unit continues the scaffold while units remain after it.\n return pass.built && pending.length > 1 ? { ...outcome, continue: true } : outcome\n }\n}\n\nexport const runAiJobSiteStep = createAiJobSiteStep()\n\n/**\n * The least time a scaffold's next pass needs (AGL-3035). The scaffold asks no\n * model itself: each pass hands one unit to the step registered for its kind,\n * under the job derived for it, so it needs what that step registers for that\n * job — a palette change's or a layout's time for those units, a page pass's\n * for a page. A scaffold with nothing left to build, or with no plan it can\n * build, spends nothing, and a page pass's time covers it.\n */\nexport function aiSiteJobRunMinimumMs(job: AiJob): number {\n const plan = aiConfirmedPlan(job)\n const inputs = parseAiSiteJobInputs(job.inputs)\n if (!plan || typeof inputs === 'string') return AI_JOB_PAGE_STEP_MINIMUM_MS\n const units = aiSiteJobUnits(plan, { welcomeEmail: inputs.welcomeEmail }).filter((unit) =>\n aiJobStepRunnerFor(unit.jobKind),\n )\n const outputs = job.outputs ?? []\n const [unit] = aiSitePendingUnits(units, outputs)\n if (!unit) return AI_JOB_PAGE_STEP_MINIMUM_MS\n return aiJobStepRunMinimumMs(aiSiteUnitJob(job, unit, aiSiteBuiltRefs(units, outputs)))\n}\n\n/**\n * Registers the site scaffold with the least time a pass needs — every\n * scaffold builds pages, and a page pass needs the least of any unit — and the\n * time its next pass needs, the passes a whole site's plan may take, and the\n * check a site job passes before it is created or resumed.\n */\nexport function registerAiSiteJob(): void {\n registerAiJobStep('site', runAiJobSiteStep, {\n minimumMs: AI_JOB_PAGE_STEP_MINIMUM_MS,\n minimumMsFor: aiSiteJobRunMinimumMs,\n })\n registerAiJobStepPasses('site', AI_SITE_MAX_PASSES)\n registerAiJobAdmission('site', aiSiteJobAdmission)\n}\n"],"names":["resolveOrgIdForHost","isAiPlanNewRef","AI_SITE_EMAIL_TYPE","AI_SITE_MAX_SECTIONS","AI_SITE_PAGES","aiSitePlanRefusal","aiSiteSubmissions","aiSiteWords","parseAiSiteJobInputs","AI_SITE_SEO_OUTPUT_ID","aiSiteSeoProposalForInputs","aiModelForStep","registerAiJobAdmission","aiRecordedJobDraftId","aiConfirmedPlan","aiUnspentOutcome","AI_JOB_BRIEF_MAX_CHARS","AI_JOB_PAGE_STEP_MINIMUM_MS","aiJobStepRunMinimumMs","aiJobStepRunnerFor","registerAiJobStep","registerAiJobStepPasses","UNIT_KINDS","theme","jobKind","resource","layout","form","component","page","email","CREATION_UNIT_KINDS","aiCreationUnit","creation","slot","kind","label","name","CREATION_UNITS","unit","create","AI_SITE_NO_PLAN_COPY","AI_SITE_NO_PAGE_STEP_COPY","AI_SITE_UNIT_EMPTY_COPY","AI_SITE_MAX_PASSES","max","length","aiSiteJobUnits","plan","options","units","find","entry","push","screens","forEach","screen","index","title","welcomeEmail","aiSitePendingUnits","outputs","done","Map","output","set","get","filter","left","aiSiteSeoOutputs","job","some","id","proposal","inputs","hostId","aiSiteBuiltRefs","byResource","rows","built","row","shift","toLowerCase","resolved","ref","slice","indexOf","trim","aiSiteBriefLines","brief","lines","site","businessName","businessType","audience","city","brand","Boolean","join","aiSiteEmailBriefLines","submissions","aiSiteUnitJobId","recorded","$id","aiSiteUnitJob","labels","values","unitPlan","reuse","placedByScreens","Set","flatMap","sections","section","uses","has","duplicateOf","why","purpose","template","map","slug","steps","mode","emailType","aiSiteJobAdmission","context","status","error","owner","orgId","aiRunJobUnit","input","outcome","runner","spent","usage","estCostUsd","model","stopReason","effort","refused","failure","review","continue","emptyCopy","createAiJobSiteStep","deps","runnerFor","modelFor","refusal","pending","pass","runAiJobSiteStep","aiSiteJobRunMinimumMs","registerAiSiteJob","minimumMs","minimumMsFor"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-site-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport {\n isAiPlanNewRef,\n type AiBuildPlanCreate,\n type AiBuildPlanCreateKind,\n type AiBuildPlanScreen,\n} from '../model/ai-build-plan'\nimport type {\n AiJob,\n AiJobKind,\n AiJobOutput,\n AiJobOutputResource,\n AiJobPlan,\n} from '../model/ai-jobs.types'\nimport {\n AI_SITE_EMAIL_TYPE,\n AI_SITE_MAX_SECTIONS,\n AI_SITE_PAGES,\n aiSitePlanRefusal,\n aiSiteSubmissions,\n aiSiteWords,\n parseAiSiteJobInputs,\n type AiSiteJobInputs,\n} from '../model/ai-site-job'\nimport {\n AI_SITE_SEO_OUTPUT_ID,\n aiSiteSeoProposalForInputs,\n} from '../model/ai-site-start-seo'\nimport { aiModelForStep } from '../providers/routing'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport { aiRecordedJobDraftId } from './ai-job-draft-ids'\nimport { aiConfirmedPlan, aiUnspentOutcome } from './ai-job-generation'\nimport {\n AI_JOB_BRIEF_MAX_CHARS,\n type AiJobStepContext,\n type AiJobStepOutcome,\n type AiJobStepRunner,\n} from './ai-job-text-step'\nimport { AI_JOB_PAGE_STEP_MINIMUM_MS } from './ai-job-page-budget'\nimport {\n aiJobStepRunMinimumMs,\n aiJobStepRunnerFor,\n registerAiJobStep,\n registerAiJobStepPasses,\n} from './ai-jobs'\n\n/**\n * The site scaffold (AGL-2911): the generation step of a `site` job, run once\n * a member confirmed the plan their brief produced.\n *\n * ── It builds nothing itself ─────────────────────────────────────────────\n *\n * A scaffold is a whole site: a palette suggestion, the layout its pages\n * render inside, the contact form they place, four to eight pages with their\n * search listings, and a welcome email. Every one of those is already a job\n * kind with a step of its own, so this step generates nothing and asks no\n * model anything. It works through the confirmed plan one UNIT at a time and\n * hands each unit to the runner registered for the kind that owns it — the\n * theme step for the palette, the page step for a page — under a job of that\n * kind derived from this one.\n *\n * That is why the scaffold cannot drift from the thing it scaffolds: a page\n * it builds is a page job's page, held to the same doctrine, written by the\n * same draft writer, left unpublished by the same rule. A kind whose step\n * this deployment has not loaded is simply not among the units, so nothing\n * here knows which issues have landed.\n *\n * ── One unit a pass ──────────────────────────────────────────────────────\n *\n * The step runs ONE delegated pass and asks the machine to continue, so every\n * pass is one reservation, one provider exchange and one recorded spend, and\n * the org's monthly ceiling binds a scaffold exactly as it binds a chat turn.\n * Credits running out mid-scaffold is therefore the machine's own park: the\n * job waits as `needs_input` with the meter's words, and the beat resumes it\n * where it stopped once the workspace's standing has changed. A pass needs the\n * time its unit's own step needs (`aiSiteJobRunMinimumMs`), so the beat starts\n * a palette change only with a palette change's time left.\n *\n * Where it stopped is read from the job's OUTPUTS rather than kept anywhere:\n * each unit reports exactly one output when it completes, so the units\n * already represented there are the ones already built. A pass interrupted\n * between its work and its record repeats a unit that then finds its own\n * draft and reports it, which is what every step's draft id already gives.\n *\n * ── Never published ──────────────────────────────────────────────────────\n *\n * Nothing here publishes, routes or sends: each delegated step writes the\n * unpublished draft it already wrote for a job of its own kind, and the\n * palette and navigation travel as proposals a member applies.\n *\n * ── The page job builds its creations the same way (AGL-3031) ────────────\n *\n * A page job whose plan creates a layout, forms or components builds them\n * before its page through this same machinery: the units a creation implies\n * (`aiCreationUnit`), where the job stands read from its outputs\n * (`aiSitePendingUnits`), one delegated pass a unit (`aiRunJobUnit`), and the\n * `new:<name>` references resolved to what was built (`aiSiteBuiltRefs`,\n * `aiSiteUnitJob`). There is one loop, and it is the machine's.\n */\n\n/** What a scaffold's units are, in the order it builds them; a component is a page job's. */\nexport type AiSiteUnitKind = 'theme' | 'layout' | 'form' | 'component' | 'page' | 'email'\n\nexport interface AiSiteUnit {\n kind: AiSiteUnitKind\n /** The job kind whose registered runner builds it. */\n jobKind: AiJobKind\n /** The resource the unit reports when it completes. */\n resource: AiJobOutputResource\n /** Stable within the job: what the unit's derived job and draft are addressed by. */\n slot: string\n /** The plan screen a page unit builds. */\n screen?: AiBuildPlanScreen\n /** The creation a theme, layout or form unit builds. */\n creation?: AiBuildPlanCreate\n /** What the unit is, for the derived job's brief. */\n label: string\n}\n\n/** The job kind and output resource each unit is built through. */\nconst UNIT_KINDS: Record<\n AiSiteUnitKind,\n { jobKind: AiJobKind; resource: AiJobOutputResource }\n> = {\n theme: { jobKind: 'theme', resource: 'theme' },\n layout: { jobKind: 'layout', resource: 'layout' },\n form: { jobKind: 'form', resource: 'form' },\n component: { jobKind: 'component', resource: 'reusableComponent' },\n page: { jobKind: 'page', resource: 'screen' },\n email: { jobKind: 'email', resource: 'emailScreen' },\n}\n\n/** The unit kind a creation is built as, where a unit builds it. */\nconst CREATION_UNIT_KINDS: Partial<Record<AiBuildPlanCreateKind, AiSiteUnitKind>> = {\n 'theme-change': 'theme',\n layout: 'layout',\n form: 'form',\n component: 'component',\n}\n\n/**\n * The unit one creation is built as, under the slot its derived job and draft\n * are addressed by; `null` for a creation no unit builds.\n */\nexport function aiCreationUnit(creation: AiBuildPlanCreate, slot: string): AiSiteUnit | null {\n const kind = CREATION_UNIT_KINDS[creation.kind]\n if (!kind) return null\n return { kind, ...UNIT_KINDS[kind], slot, creation, label: creation.name }\n}\n\n/** The creation kinds a unit is built from, by unit kind. */\nconst CREATION_UNITS: Array<{\n unit: AiSiteUnitKind\n create: AiBuildPlanCreateKind\n}> = [\n { unit: 'theme', create: 'theme-change' },\n { unit: 'layout', create: 'layout' },\n { unit: 'form', create: 'form' },\n]\n\nexport const AI_SITE_NO_PLAN_COPY =\n 'This site job has no confirmed plan to build.'\n\n/** What a scaffold answers where the step that builds a page is not loaded. */\nexport const AI_SITE_NO_PAGE_STEP_COPY =\n 'Building a whole site is not available yet.'\n\n/** A unit that finished without reporting what it built; the scaffold cannot go on. */\nexport const AI_SITE_UNIT_EMPTY_COPY =\n 'Part of this site could not be built. Try the site brief again.'\n\n/**\n * The most passes a scaffold's step may take: the largest plan it admits —\n * eight pages of eight sections, its three creations and a welcome email —\n * with nothing to spare, so a runner that never finishes is still bounded\n * while a real site is not.\n */\nexport const AI_SITE_MAX_PASSES =\n AI_SITE_PAGES.max * (AI_SITE_MAX_SECTIONS + 1) + CREATION_UNITS.length + 1\n\n/**\n * The units a plan implies, in build order: the palette first, because a\n * member reads it while the pages are still building; then the layout every\n * page renders inside and the form they place, because a page binds both by\n * id and so needs them to exist; then the pages; then the welcome email,\n * which is about the site rather than part of it.\n */\nexport function aiSiteJobUnits(\n plan: Pick<AiJobPlan, 'create' | 'screens'>,\n options: { welcomeEmail?: boolean } = {},\n): AiSiteUnit[] {\n const units: AiSiteUnit[] = []\n for (const { unit, create } of CREATION_UNITS) {\n const creation = plan.create.find((entry) => entry.kind === create)\n if (!creation) continue\n units.push({\n kind: unit,\n ...UNIT_KINDS[unit],\n slot: unit[0],\n creation,\n label: creation.name,\n })\n }\n plan.screens.forEach((screen, index) => {\n units.push({\n kind: 'page',\n ...UNIT_KINDS.page,\n slot: `p${index}`,\n screen,\n label: screen.title,\n })\n })\n if (options.welcomeEmail) {\n units.push({\n kind: 'email',\n ...UNIT_KINDS.email,\n slot: 'e',\n label: 'Welcome email',\n })\n }\n return units\n}\n\n/**\n * The units a scaffold still owes after the outputs recorded so far. Each\n * unit reports one output of its own resource when it completes, so the\n * outputs already recorded are consumed against the units in build order.\n */\nexport function aiSitePendingUnits(\n units: readonly AiSiteUnit[],\n outputs: readonly Pick<AiJobOutput, 'resource'>[],\n): AiSiteUnit[] {\n const done = new Map<AiJobOutputResource, number>()\n for (const output of outputs) {\n done.set(output.resource, (done.get(output.resource) ?? 0) + 1)\n }\n return units.filter((unit) => {\n const left = done.get(unit.resource) ?? 0\n if (left <= 0) return true\n done.set(unit.resource, left - 1)\n return false\n })\n}\n\n/**\n * The site's own search listing as the scaffold reports it (AGL-2918), or\n * nothing when the job already reported one or its inputs describe no site.\n *\n * It costs nothing and asks no model: the values are arithmetic on the\n * answers (`aiSiteSeoProposal`), so they ride out on the first pass that\n * builds anything rather than waiting for a pass of their own. Its resource\n * is `seo`, which no unit reports, so it cannot be mistaken for a unit's\n * output by `aiSitePendingUnits` and cannot move where the scaffold thinks\n * it is.\n */\nexport function aiSiteSeoOutputs(job: AiJob): AiJobOutput[] {\n const outputs = job.outputs ?? []\n if (outputs.some((output) => output.resource === 'seo' && output.id === AI_SITE_SEO_OUTPUT_ID)) {\n return []\n }\n const proposal = aiSiteSeoProposalForInputs(job.inputs)\n if (!proposal) return []\n return [\n {\n resource: 'seo',\n id: AI_SITE_SEO_OUTPUT_ID,\n hostId: job.hostId ?? null,\n label: 'The site’s search title and description',\n proposal: proposal as unknown as Record<string, unknown>,\n },\n ]\n}\n\n/** A record an earlier unit created, as a later one references it. */\nexport interface AiSiteBuiltRef {\n id: string\n label: string\n /** Only what becomes a record a page can name; a palette change is not one. */\n kind: 'layout' | 'form' | 'component'\n}\n\n/**\n * What the scaffold has already created, as the plan's `new:<name>`\n * references resolve to: the id of the record each completed creation wrote.\n * A page unit is told about the layout and the form by id, exactly as a page\n * job is told about records the site already had.\n */\nexport function aiSiteBuiltRefs(\n units: readonly AiSiteUnit[],\n outputs: readonly Pick<AiJobOutput, 'resource' | 'id' | 'label'>[],\n): Map<string, AiSiteBuiltRef> {\n const byResource = new Map<\n AiJobOutputResource,\n Array<Pick<AiJobOutput, 'id' | 'label'>>\n >()\n for (const output of outputs) {\n const rows = byResource.get(output.resource) ?? []\n rows.push(output)\n byResource.set(output.resource, rows)\n }\n const built = new Map<string, AiSiteBuiltRef>()\n for (const unit of units) {\n const rows = byResource.get(unit.resource)\n const row = rows?.shift()\n if (!row || !unit.creation) continue\n // A theme change is a proposal with no record to reference; a layout, a\n // form and a component become ids a page can name.\n if (\n unit.creation.kind !== 'layout' &&\n unit.creation.kind !== 'form' &&\n unit.creation.kind !== 'component'\n ) {\n continue\n }\n built.set(unit.creation.name.toLowerCase(), {\n id: row.id,\n label: row.label || unit.creation.name,\n kind: unit.creation.kind,\n })\n }\n return built\n}\n\ntype BuiltRefs = ReturnType<typeof aiSiteBuiltRefs>\n\n/** A plan reference as the unit's own job reads it: an id the site now has, or nothing. */\nexport function aiSiteResolvedRef(ref: string | null, built: BuiltRefs): string | null {\n if (!ref) return null\n if (!isAiPlanNewRef(ref)) return ref\n return (\n built.get(\n ref\n .slice(ref.indexOf(':') + 1)\n .trim()\n .toLowerCase(),\n )?.id ?? null\n )\n}\n\n/**\n * The site's own words, as every unit's brief carries them.\n *\n * The audience is here rather than left to the brief's prose (AGL-2918). A\n * guided start writes it into the brief itself, but an agency batch's brief\n * is a member's own sentence and may never mention it, and a theme, a layout\n * or a form asked to serve nobody in particular serves nobody in particular.\n */\nexport function aiSiteBriefLines(\n brief: string,\n inputs: AiSiteJobInputs,\n): string[] {\n const lines = [brief.trim()]\n const site = [\n inputs.businessName ? `name: ${inputs.businessName}` : '',\n `business: ${inputs.businessType}`,\n inputs.audience ? `for: ${inputs.audience}` : '',\n inputs.city ? `city: ${inputs.city}` : '',\n inputs.brand ? `brand: ${inputs.brand}` : '',\n ].filter(Boolean)\n lines.push(`Site — ${site.join('; ')}.`)\n return lines\n}\n\n/**\n * What the welcome email is told (AGL-2918), beyond that it is a welcome\n * email.\n *\n * The scaffold's email unit used to carry one sentence, and a yes/no toggle\n * decided whether it ran — so the draft was a welcome email for a business\n * in general, to nobody in particular, about nothing that had happened. The\n * two answers it is missing are who wrote in and what now becomes of what\n * they wrote, and the second of those is the person's own routing answer, so\n * the email and the form it acknowledges cannot say different things.\n *\n * ⛔ A DRAFT either way. Nothing here sends, schedules or enrolls anybody:\n * the email step writes an unpublished email design and stops.\n */\nexport function aiSiteEmailBriefLines(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): string[] {\n const lines = [\n 'Write the welcome email this site sends someone who gets in touch.',\n ]\n const { audience } = aiSiteWords(inputs)\n if (audience) lines.push(`Write it to ${audience}.`)\n const submissions = aiSiteSubmissions(inputs)\n if (submissions === 'lead') {\n lines.push('Their message is a sales lead, so say that somebody will be in touch about it.')\n } else if (submissions === 'inbox') {\n lines.push('Their message has been read, so say it arrived and that a reply is coming.')\n }\n return lines\n}\n\n/**\n * The id a unit's job is named by, and so the id its draft is written under\n * (AGL-3079): the id the plan entry it builds recorded, or for the welcome\n * email the id recorded on this job's step. A unit that writes no draft — a\n * palette change — and a unit whose plan recorded no id are named by this\n * job's id and the unit's slot, the name every draft of such a plan has.\n */\nexport function aiSiteUnitJobId(job: Pick<AiJob, '$id' | 'steps'>, unit: AiSiteUnit): string {\n const recorded =\n unit.creation?.id ?? unit.screen?.id ?? (unit.kind === 'email' ? aiRecordedJobDraftId(job, 'email') : null)\n return recorded || `${job.$id}-${unit.slot}`\n}\n\n/**\n * The job a unit is built under: this job's org, site, creator and model,\n * the kind that owns the unit, and a plan narrowed to the unit alone.\n *\n * The derived job carries NO steps and NO outputs of the scaffold's: a page\n * job's step reads its own draft and its own plan, and what the other units\n * produced is not its business. Its `$id` is the id the unit's draft is\n * written under (`aiSiteUnitJobId`), which is what a step with no id of its\n * own recorded names its draft by, so a unit re-run after its write finds its\n * own draft rather than writing a second.\n *\n * A creation unit reuses what the plan reuses less what the plan's screens\n * place themselves (AGL-3031): a component a page's section places is the\n * page's to place, and a layout or a card told to place it would be refused\n * for leaving it out.\n */\nexport function aiSiteUnitJob(\n job: AiJob,\n unit: AiSiteUnit,\n built: BuiltRefs,\n): AiJob {\n const plan = job.plan as AiJobPlan\n const labels = { ...(plan.labels ?? {}) }\n for (const entry of built.values()) labels[entry.id] = entry.label\n const inputs = parseAiSiteJobInputs(job.inputs)\n const brief =\n typeof inputs === 'string'\n ? [job.brief]\n : aiSiteBriefLines(job.brief, inputs)\n const unitPlan: AiJobPlan = {\n ...plan,\n labels,\n reuse: [...plan.reuse],\n create: [],\n screens: [],\n }\n if (unit.creation) {\n const placedByScreens = new Set(\n plan.screens.flatMap((screen) => screen.sections.flatMap((section) => section.uses)),\n )\n unitPlan.reuse = plan.reuse.filter((entry) => !placedByScreens.has(entry.id))\n unitPlan.create = [\n {\n ...unit.creation,\n duplicateOf: aiSiteResolvedRef(unit.creation.duplicateOf, built),\n },\n ]\n brief.push(\n `Build the ${unit.creation.kind} “${unit.creation.name}”: ${unit.creation.why}`,\n )\n }\n if (unit.screen) {\n const screen = unit.screen\n // Everything the scaffold has already built is a record the site has, so\n // the page is planned against it the way a page job is planned against\n // what the site always had.\n for (const entry of built.values()) {\n unitPlan.reuse.push({\n kind: entry.kind,\n id: entry.id,\n purpose: `the ${entry.kind} this site’s pages are built on`,\n })\n }\n unitPlan.screens = [\n {\n ...screen,\n layout: aiSiteResolvedRef(screen.layout, built),\n template: aiSiteResolvedRef(screen.template, built),\n duplicateOf: aiSiteResolvedRef(screen.duplicateOf, built),\n sections: screen.sections.map((section) => ({\n ...section,\n uses: section.uses\n .map((ref) => aiSiteResolvedRef(ref, built))\n .filter((ref): ref is string => !!ref),\n })),\n },\n ]\n brief.push(\n `Build the page “${screen.title}” of this site, at ${screen.slug}.`,\n )\n }\n if (unit.kind === 'email') {\n brief.push(...aiSiteEmailBriefLines(job.inputs))\n }\n return {\n ...job,\n $id: aiSiteUnitJobId(job, unit),\n kind: unit.jobKind,\n steps: [],\n outputs: [],\n plan: unitPlan,\n // A theme change on a site with no theme of its own is a new palette.\n ...(unit.kind === 'theme'\n ? { inputs: { ...job.inputs, mode: 'create' } }\n : {}),\n // The email step reads the kind off the inputs it is handed, so a job the\n // scaffold composed says which kind it is exactly as a member's own does.\n ...(unit.kind === 'email'\n ? { inputs: { ...job.inputs, emailType: AI_SITE_EMAIL_TYPE } }\n : {}),\n brief: brief.join('\\n').slice(0, AI_JOB_BRIEF_MAX_CHARS),\n }\n}\n\n/**\n * A scaffold is admitted with inputs that read, for a site of the job's own\n * org, and only where this deployment has loaded the step that builds a page:\n * a scaffold whose pages nothing can build is not a scaffold.\n */\nexport const aiSiteJobAdmission: AiJobAdmission = async (context) => {\n const inputs = parseAiSiteJobInputs(context.inputs)\n if (typeof inputs === 'string') return { status: 400, error: inputs }\n if (!context.hostId) {\n return {\n status: 400,\n error: 'Open the site the scaffold is for before starting the job',\n }\n }\n const owner = await resolveOrgIdForHost(context.hostId)\n if (!owner || owner !== context.orgId)\n return { status: 404, error: 'Unknown site' }\n if (!aiJobStepRunnerFor('page')) {\n return { status: 400, error: AI_SITE_NO_PAGE_STEP_COPY }\n }\n return null\n}\n\n/** What one delegated pass came to: the unit's spend as the job records it, and whether the unit is built. */\nexport interface AiJobUnitPass {\n outcome: AiJobStepOutcome\n /** The unit reported what it built and asked for no pass of its own. */\n built: boolean\n}\n\n/**\n * One delegated pass (AGL-2911, AGL-3031): the unit handed to the runner of\n * the kind that owns it, under the job derived for it, and its outcome as the\n * delegating job records it. A delegate proposes a plan for its own job,\n * which does not exist, so no plan is carried back. A unit that stopped for a\n * person, refused or failed is the delegating job's stop; a unit that asked\n * for another pass keeps its turn; and a unit that finished without reporting\n * what it built fails with `emptyCopy`, since another pass would ask the same\n * thing again. The tokens it spent are on the bill either way.\n */\nexport async function aiRunJobUnit(\n context: AiJobStepContext,\n input: {\n unit: AiSiteUnit\n units: readonly AiSiteUnit[]\n runner: AiJobStepRunner\n emptyCopy: string\n },\n): Promise<AiJobUnitPass> {\n const { job } = context\n const outputs = job.outputs ?? []\n const outcome = await input.runner({\n ...context,\n job: aiSiteUnitJob(job, input.unit, aiSiteBuiltRefs(input.units, outputs)),\n })\n const spent: AiJobStepOutcome = {\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 ...(outcome.refused ? { refused: true } : {}),\n ...(outcome.failure ? { failure: outcome.failure } : {}),\n ...(outcome.review ? { review: outcome.review } : {}),\n }\n if (spent.review || spent.refused || spent.failure) return { outcome: spent, built: false }\n if (outcome.continue) return { outcome: { ...spent, continue: true }, built: false }\n if (!outcome.outputs.length) return { outcome: { ...spent, failure: input.emptyCopy }, built: false }\n return { outcome: spent, built: true }\n}\n\nexport interface AiJobSiteStepDeps {\n /** The runner registry; specs hand in a fake for a kind they drive. */\n runnerFor?: typeof aiJobStepRunnerFor\n}\n\nexport function createAiJobSiteStep(\n deps: AiJobSiteStepDeps = {},\n): AiJobStepRunner {\n const runnerFor = deps.runnerFor ?? aiJobStepRunnerFor\n return async (context): Promise<AiJobStepOutcome> => {\n const { job } = context\n // The scaffold asks no model of its own. What it names where it spends\n // nothing is the model its plan ran on, which is the one exchange this\n // job has already had of its own.\n const model = context.modelFor?.('job.plan') ?? aiModelForStep('job.plan')\n const plan = aiConfirmedPlan(job)\n if (!plan)\n return { ...aiUnspentOutcome(model), failure: AI_SITE_NO_PLAN_COPY }\n // The resume door refuses such a plan at confirmation once it can read\n // one; a plan confirmed before a site changed still stops here, unspent.\n const refusal = aiSitePlanRefusal(plan)\n if (refusal) return { ...aiUnspentOutcome(model), failure: refusal }\n const inputs = parseAiSiteJobInputs(job.inputs)\n if (typeof inputs === 'string')\n return { ...aiUnspentOutcome(model), failure: inputs }\n\n // A kind this deployment has not loaded is not among the units at all, so\n // a scaffold owes only what something can build.\n const units = aiSiteJobUnits(plan, {\n welcomeEmail: inputs.welcomeEmail,\n }).filter((unit) => runnerFor(unit.jobKind))\n const outputs = job.outputs ?? []\n const pending = aiSitePendingUnits(units, outputs)\n if (!pending.length) return aiUnspentOutcome(model)\n const unit = pending[0]\n const runner = runnerFor(unit.jobKind)\n if (!runner) return aiUnspentOutcome(model)\n\n const pass = await aiRunJobUnit(context, {\n unit,\n units,\n runner,\n emptyCopy: AI_SITE_UNIT_EMPTY_COPY,\n })\n // The site's own listing rides out beside the first unit's output, once\n // (AGL-2918): it is derived from the answers rather than generated, so it\n // is ready before anything is built and costs the pass nothing.\n const outcome: AiJobStepOutcome = {\n ...pass.outcome,\n outputs: [...aiSiteSeoOutputs(job), ...pass.outcome.outputs],\n }\n // A built unit continues the scaffold while units remain after it.\n return pass.built && pending.length > 1 ? { ...outcome, continue: true } : outcome\n }\n}\n\nexport const runAiJobSiteStep = createAiJobSiteStep()\n\n/**\n * The least time a scaffold's next pass needs (AGL-3035). The scaffold asks no\n * model itself: each pass hands one unit to the step registered for its kind,\n * under the job derived for it, so it needs what that step registers for that\n * job — a palette change's or a layout's time for those units, a page pass's\n * for a page. A scaffold with nothing left to build, or with no plan it can\n * build, spends nothing, and a page pass's time covers it.\n */\nexport function aiSiteJobRunMinimumMs(job: AiJob): number {\n const plan = aiConfirmedPlan(job)\n const inputs = parseAiSiteJobInputs(job.inputs)\n if (!plan || typeof inputs === 'string') return AI_JOB_PAGE_STEP_MINIMUM_MS\n const units = aiSiteJobUnits(plan, { welcomeEmail: inputs.welcomeEmail }).filter((unit) =>\n aiJobStepRunnerFor(unit.jobKind),\n )\n const outputs = job.outputs ?? []\n const [unit] = aiSitePendingUnits(units, outputs)\n if (!unit) return AI_JOB_PAGE_STEP_MINIMUM_MS\n return aiJobStepRunMinimumMs(aiSiteUnitJob(job, unit, aiSiteBuiltRefs(units, outputs)))\n}\n\n/**\n * Registers the site scaffold with the least time a pass needs — every\n * scaffold builds pages, and a page pass needs the least of any unit — and the\n * time its next pass needs, the passes a whole site's plan may take, and the\n * check a site job passes before it is created or resumed.\n */\nexport function registerAiSiteJob(): void {\n registerAiJobStep('site', runAiJobSiteStep, {\n minimumMs: AI_JOB_PAGE_STEP_MINIMUM_MS,\n minimumMsFor: aiSiteJobRunMinimumMs,\n })\n registerAiJobStepPasses('site', AI_SITE_MAX_PASSES)\n registerAiJobAdmission('site', aiSiteJobAdmission)\n}\n"],"names":["resolveOrgIdForHost","isAiPlanNewRef","AI_SITE_EMAIL_TYPE","AI_SITE_MAX_SECTIONS","AI_SITE_PAGES","aiSitePlanRefusal","aiSiteSubmissions","aiSiteWords","parseAiSiteJobInputs","AI_SITE_SEO_OUTPUT_ID","aiSiteSeoProposalForInputs","aiModelForStep","registerAiJobAdmission","aiRecordedJobDraftId","aiConfirmedPlan","aiUnspentOutcome","AI_JOB_BRIEF_MAX_CHARS","AI_JOB_PAGE_STEP_MINIMUM_MS","aiJobStepRunMinimumMs","aiJobStepRunnerFor","registerAiJobStep","registerAiJobStepPasses","UNIT_KINDS","theme","jobKind","resource","layout","form","component","page","email","CREATION_UNIT_KINDS","aiCreationUnit","creation","slot","kind","label","name","CREATION_UNITS","unit","create","AI_SITE_NO_PLAN_COPY","AI_SITE_NO_PAGE_STEP_COPY","AI_SITE_UNIT_EMPTY_COPY","AI_SITE_MAX_PASSES","max","length","aiSiteJobUnits","plan","options","units","find","entry","push","screens","forEach","screen","index","title","welcomeEmail","aiSitePendingUnits","outputs","done","Map","output","set","get","filter","left","aiSiteSeoOutputs","job","some","id","proposal","inputs","hostId","aiSiteBuiltRefs","byResource","rows","built","row","shift","toLowerCase","aiSiteResolvedRef","ref","slice","indexOf","trim","aiSiteBriefLines","brief","lines","site","businessName","businessType","audience","city","brand","Boolean","join","aiSiteEmailBriefLines","submissions","aiSiteUnitJobId","recorded","$id","aiSiteUnitJob","labels","values","unitPlan","reuse","placedByScreens","Set","flatMap","sections","section","uses","has","duplicateOf","why","purpose","template","map","slug","steps","mode","emailType","aiSiteJobAdmission","context","status","error","owner","orgId","aiRunJobUnit","input","outcome","runner","spent","usage","estCostUsd","model","stopReason","effort","refused","failure","review","continue","emptyCopy","createAiJobSiteStep","deps","runnerFor","modelFor","refusal","pending","pass","runAiJobSiteStep","aiSiteJobRunMinimumMs","registerAiSiteJob","minimumMs","minimumMsFor"],"mappings":"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"}
|
|
@@ -35,8 +35,9 @@ import type { AiJobStepRunner } from './ai-job-text-step';
|
|
|
35
35
|
* picker offers for that page; a link or media prop may hold one of those
|
|
36
36
|
* tokens whole, which the palette validator admits only because this door
|
|
37
37
|
* names them. The door adds one check of its own through `extend` (rule 8):
|
|
38
|
-
* no token the page does not fill, an h1 bound to the subject's title,
|
|
39
|
-
* block that fills itself only on another subject's page
|
|
38
|
+
* no token the page does not fill, an h1 bound to the subject's title, no
|
|
39
|
+
* block that fills itself only on another subject's page, and no address,
|
|
40
|
+
* slug or timestamp printed in its copy.
|
|
40
41
|
*
|
|
41
42
|
* The model is shown the platform's starter pages as examples, in a cached
|
|
42
43
|
* block (`runtime/ai-template-examples.ts`), and writes against them.
|
|
@@ -72,13 +73,14 @@ export declare function readAiTemplateCollection(firestore: FirebaseFirestore.Fi
|
|
|
72
73
|
export declare function aiTemplateDraftName(definition: AiTemplateSubjectDefinition, collection: AiTemplateCollection | null, planned: string | null | undefined): string;
|
|
73
74
|
/** The address Use template offers for the page it creates. */
|
|
74
75
|
export declare function aiTemplateDraftSlug(definition: AiTemplateSubjectDefinition, collection: AiTemplateCollection | null): string | null;
|
|
75
|
-
/** The template job as the generation's user turn. */
|
|
76
|
-
export declare function aiJobTemplatePrompt(job: Pick<AiJob, 'brief'>, definition: AiTemplateSubjectDefinition, collection: AiTemplateCollection | null, plan: AiJobPlan | null, name: string): string;
|
|
76
|
+
/** The template job as the generation's user turn, with the template a short copy would have started from (AGL-3143 §14). */
|
|
77
|
+
export declare function aiJobTemplatePrompt(job: Pick<AiJob, 'brief'>, definition: AiTemplateSubjectDefinition, collection: AiTemplateCollection | null, plan: AiJobPlan | null, name: string, source?: readonly string[]): string;
|
|
77
78
|
/**
|
|
78
79
|
* The template door's check on a tree the doctrine admitted (rule 8): every
|
|
79
|
-
* token is one the page fills, the h1 binds the subject's title,
|
|
80
|
+
* token is one the page fills, the h1 binds the subject's title, no block
|
|
80
81
|
* that fills itself on another subject's page is placed — except inside a
|
|
81
|
-
* Collection Entries card, where each fills from the card's own entry
|
|
82
|
+
* Collection Entries card, where each fills from the card's own entry — and
|
|
83
|
+
* no copy prints an address, a slug or a timestamp.
|
|
82
84
|
*/
|
|
83
85
|
export declare function aiTemplateBindingCheck(definition: AiTemplateSubjectDefinition): (tree: AiValidatedTree) => AiDoctrineViolation[];
|
|
84
86
|
export interface AiTemplateJobAdmissionDeps {
|
|
@@ -121,6 +123,7 @@ export declare function aiCopiedTemplateReview(firestore: FirebaseFirestore.Fire
|
|
|
121
123
|
definition: AiTemplateSubjectDefinition;
|
|
122
124
|
}): Promise<AiJobReview | null>;
|
|
123
125
|
export declare function createAiJobTemplateStep(deps?: AiJobTemplateStepDeps): AiJobStepRunner;
|
|
126
|
+
/** The template step, building the components and forms its plan creates before the template (AGL-3143 §15). */
|
|
124
127
|
export declare const runAiJobTemplateStep: AiJobStepRunner;
|
|
125
128
|
/** Registers the template step and the check a template job passes before it is created or resumed. */
|
|
126
129
|
export declare function registerAiTemplateJob(): void;
|
|
@@ -18,7 +18,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
18
18
|
import { normalizeScreenSlug } from "@aglyn/aglyn/app-utils/screen-route";
|
|
19
19
|
import { duplicateResource } from "@aglyn/tenant-data-admin/server/duplicate-resource";
|
|
20
20
|
import { CANVAS_ROOT_ELEMENT_ID } from "@aglyn/aglyn/foundation/constants/canvas";
|
|
21
|
-
import { AI_TEMPLATE_SUBJECT_DEFINITIONS, AI_TEMPLATE_SUBJECTS, aiBindingTokensIn, aiTemplateAddressTokens, parseAiTemplateJobInputs } from "../model/ai-template-subjects.js";
|
|
21
|
+
import { AI_TEMPLATE_SUBJECT_DEFINITIONS, AI_TEMPLATE_SUBJECTS, aiBindingTokensIn, aiTemplateAddressTokens, isAiAddressToken, isAiUnreadToken, parseAiTemplateJobInputs } from "../model/ai-template-subjects.js";
|
|
22
22
|
import { AI_STEP_TIERS } from "../providers/catalog.js";
|
|
23
23
|
import { AI_ROUTING_TABLE, aiModelForStep } from "../providers/routing.js";
|
|
24
24
|
import { aiDoctrineTreeTool, runValidatedGeneration } from "../runtime/ai-doctrine.js";
|
|
@@ -29,8 +29,9 @@ import { readSiteInventory } from "../runtime/site-inventory.js";
|
|
|
29
29
|
import { registerAiJobAdmission } from "./ai-job-admission.js";
|
|
30
30
|
import { aiJobDraftId } from "./ai-job-draft-ids.js";
|
|
31
31
|
import { aiDraftAdmissionRefusal, aiDraftAllowanceRefusal, aiSiteSubdomain, readAiDraft, readAiTemplateDraftNodes, writeAiDraft } from "./ai-job-drafts.js";
|
|
32
|
-
import { aiConfirmedPlan, aiDoctrineReview, aiGenerationSpent, aiJobBriefLine, aiLimitReview, aiModelNodeIds, aiPlacedComponentIds, aiPlanCreation, aiPlanReferenceLines, aiPlanReuseViolations, aiUnspentOutcome } from "./ai-job-generation.js";
|
|
32
|
+
import { aiConfirmedPlan, aiCopySourceLines, aiDoctrineReview, aiGenerationSpent, aiJobBriefLine, aiLimitReview, aiModelNodeIds, aiPlacedComponentIds, aiPlanCreation, aiPlanReferenceLines, aiPlanReuseViolations, aiUnspentOutcome } from "./ai-job-generation.js";
|
|
33
33
|
import { aiPlannedTemplateTokens, aiPlanTemplateTokenViolations } from "./ai-job-plan-conformance.js";
|
|
34
|
+
import { aiBuildingPlanCreations, aiJobPlanCreationsRefusal, aiPlanCreationsRunMinimumMs } from "./ai-job-plan-creations.js";
|
|
34
35
|
import { aiJobStepBudget } from "./ai-job-budget.js";
|
|
35
36
|
import { registerAiJobStep } from "./ai-jobs.js";
|
|
36
37
|
/**
|
|
@@ -45,8 +46,9 @@ import { registerAiJobStep } from "./ai-jobs.js";
|
|
|
45
46
|
* picker offers for that page; a link or media prop may hold one of those
|
|
46
47
|
* tokens whole, which the palette validator admits only because this door
|
|
47
48
|
* names them. The door adds one check of its own through `extend` (rule 8):
|
|
48
|
-
* no token the page does not fill, an h1 bound to the subject's title,
|
|
49
|
-
* block that fills itself only on another subject's page
|
|
49
|
+
* no token the page does not fill, an h1 bound to the subject's title, no
|
|
50
|
+
* block that fills itself only on another subject's page, and no address,
|
|
51
|
+
* slug or timestamp printed in its copy.
|
|
50
52
|
*
|
|
51
53
|
* The model is shown the platform's starter pages as examples, in a cached
|
|
52
54
|
* block (`runtime/ai-template-examples.ts`), and writes against them.
|
|
@@ -73,7 +75,8 @@ import { registerAiJobStep } from "./ai-jobs.js";
|
|
|
73
75
|
'You build one page template: the page a website renders once for each record it keeps, such as each entry of a content collection, each product or each author. Answer with submit_template: the whole page as one flat node map.',
|
|
74
76
|
'Everything that differs from one record to the next is a binding token, never typed copy. The request names what the page is for, the tokens that page fills and the blocks that fill themselves there; bind only those tokens.',
|
|
75
77
|
'The page’s one h1 holds the title token the request names. Copy that reads the same on every record, such as a section heading, is written in the site’s voice.',
|
|
76
|
-
'A link or media prop holds exactly one token from the request’s link and media tokens, such as an Image whose src is {{entry.coverImage}}; its alt text may combine words and tokens.',
|
|
78
|
+
'A link or media prop holds exactly one token from the request’s link and media tokens, such as an Image whose src is {{entry.coverImage}}; its alt text may combine words and tokens. Copy never prints a link or media token, a slug or a timestamp.',
|
|
79
|
+
'A template carries no third-party player. It plays a video token only when the confirmed plan lists it, in a Video (video) whose src is that token and whose poster is the cover image token.',
|
|
77
80
|
'The page renders inside the site’s layout, so it carries no header, navigation or footer of its own.'
|
|
78
81
|
].join('\n')
|
|
79
82
|
}
|
|
@@ -99,7 +102,7 @@ import { registerAiJobStep } from "./ai-jobs.js";
|
|
|
99
102
|
var _normalizeScreenSlug;
|
|
100
103
|
return (_normalizeScreenSlug = normalizeScreenSlug(collection ? `${collection.slug || collection.id}-entry-template` : `${definition.subject}-page-template`)) != null ? _normalizeScreenSlug : null;
|
|
101
104
|
}
|
|
102
|
-
/** The template job as the generation's user turn. */ export function aiJobTemplatePrompt(job, definition, collection, plan, name) {
|
|
105
|
+
/** The template job as the generation's user turn, with the template a short copy would have started from (AGL-3143 §14). */ export function aiJobTemplatePrompt(job, definition, collection, plan, name, source = []) {
|
|
103
106
|
const of = collection ? ` of the "${collection.name}" collection${collection.slug ? ` (/${collection.slug})` : ''}` : '';
|
|
104
107
|
return [
|
|
105
108
|
`Template name: ${name}`,
|
|
@@ -109,7 +112,8 @@ import { registerAiJobStep } from "./ai-jobs.js";
|
|
|
109
112
|
`Link and media tokens: ${aiTemplateAddressTokens(definition).join(', ')}`,
|
|
110
113
|
definition.blocks.length ? `Blocks that fill themselves on this page: ${definition.blocks.join(', ')}` : 'No block fills itself on this page: bind the tokens.',
|
|
111
114
|
aiJobBriefLine(job),
|
|
112
|
-
...aiPlanReferenceLines(plan)
|
|
115
|
+
...aiPlanReferenceLines(plan),
|
|
116
|
+
...source
|
|
113
117
|
].join('\n');
|
|
114
118
|
}
|
|
115
119
|
/** Which subject's page each self-filling block belongs to. */ const BLOCK_SUBJECTS = new Map(AI_TEMPLATE_SUBJECTS.flatMap((subject)=>AI_TEMPLATE_SUBJECT_DEFINITIONS[subject].blocks.map((block)=>[
|
|
@@ -131,11 +135,32 @@ import { registerAiJobStep } from "./ai-jobs.js";
|
|
|
131
135
|
const element = typeof ((_node_props = node.props) == null ? void 0 : _node_props['component']) === 'string' ? String(node.props['component']) : '';
|
|
132
136
|
return (element || String((_ref = (_node_props1 = node.props) == null ? void 0 : _node_props1['variant']) != null ? _ref : '')) === 'h1';
|
|
133
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* The tokens a prop prints for a reader that no reader reads: a link or
|
|
140
|
+
* picture token, a slug or a timestamp, in copy (AGL-3143 §16). Copy is a
|
|
141
|
+
* prop the palette gives the text role, read as words where it holds more
|
|
142
|
+
* than one token or is the element's children. A block handed one token
|
|
143
|
+
* whole reads it as a value, as Entry Meta's avatar takes the author's
|
|
144
|
+
* portrait and a Video's publication date takes the timestamp, and a
|
|
145
|
+
* markdown link's target is followed, not read.
|
|
146
|
+
*/ function printedTokens(node) {
|
|
147
|
+
var _ref, _node_props;
|
|
148
|
+
var _AI_PALETTE_node_componentId;
|
|
149
|
+
const roles = (_ref = (_AI_PALETTE_node_componentId = AI_PALETTE[node.componentId]) == null ? void 0 : _AI_PALETTE_node_componentId.propRoles) != null ? _ref : {};
|
|
150
|
+
return Object.entries((_node_props = node.props) != null ? _node_props : {}).flatMap(([key, value])=>{
|
|
151
|
+
if (typeof value !== 'string' || roles[key] !== 'text') return [];
|
|
152
|
+
const tokens = aiBindingTokensIn(value.replace(/\]\([^)]*\)/g, ']'));
|
|
153
|
+
const unread = tokens.filter((token)=>isAiAddressToken(token) || isAiUnreadToken(token));
|
|
154
|
+
const whole = tokens.length === 1 && value.trim() === tokens[0];
|
|
155
|
+
return unread.length && (key === 'children' || !whole) ? unread : [];
|
|
156
|
+
});
|
|
157
|
+
}
|
|
134
158
|
/**
|
|
135
159
|
* The template door's check on a tree the doctrine admitted (rule 8): every
|
|
136
|
-
* token is one the page fills, the h1 binds the subject's title,
|
|
160
|
+
* token is one the page fills, the h1 binds the subject's title, no block
|
|
137
161
|
* that fills itself on another subject's page is placed — except inside a
|
|
138
|
-
* Collection Entries card, where each fills from the card's own entry
|
|
162
|
+
* Collection Entries card, where each fills from the card's own entry — and
|
|
163
|
+
* no copy prints an address, a slug or a timestamp.
|
|
139
164
|
*/ export function aiTemplateBindingCheck(definition) {
|
|
140
165
|
const allowed = new Set(definition.tokens.map((entry)=>entry.token));
|
|
141
166
|
return (tree)=>{
|
|
@@ -144,6 +169,8 @@ import { registerAiJobStep } from "./ai-jobs.js";
|
|
|
144
169
|
const unknownAt = [];
|
|
145
170
|
const foreign = [];
|
|
146
171
|
const titles = [];
|
|
172
|
+
const printed = new Set();
|
|
173
|
+
const printedAt = [];
|
|
147
174
|
let titleBound = false;
|
|
148
175
|
for (const { id, node, ancestors } of walkTree({
|
|
149
176
|
rootId: tree.rootId,
|
|
@@ -155,6 +182,11 @@ import { registerAiJobStep } from "./ai-jobs.js";
|
|
|
155
182
|
for (const token of strays)unknown.add(token);
|
|
156
183
|
unknownAt.push(id);
|
|
157
184
|
}
|
|
185
|
+
const shown = printedTokens(node).filter((token)=>allowed.has(token));
|
|
186
|
+
if (shown.length) {
|
|
187
|
+
for (const token of shown)printed.add(token);
|
|
188
|
+
printedAt.push(id);
|
|
189
|
+
}
|
|
158
190
|
const owner = BLOCK_SUBJECTS.get(node.componentId);
|
|
159
191
|
const inCard = ancestors.some((ancestor)=>{
|
|
160
192
|
var _nodes_ancestor;
|
|
@@ -183,6 +215,16 @@ import { registerAiJobStep } from "./ai-jobs.js";
|
|
|
183
215
|
nodeIds: aiModelNodeIds(unknownAt, tree.sourceIds)
|
|
184
216
|
});
|
|
185
217
|
}
|
|
218
|
+
if (printed.size) {
|
|
219
|
+
violations.push({
|
|
220
|
+
rule: 8,
|
|
221
|
+
code: 'printed-token',
|
|
222
|
+
message: `This prints ${[
|
|
223
|
+
...printed
|
|
224
|
+
].slice(0, 3).join(', ')} in the page's copy, where a reader sees an address, a slug or a timestamp. A link or picture token goes whole in a link or media prop, and a slug or a timestamp stays off the page.`,
|
|
225
|
+
nodeIds: aiModelNodeIds(printedAt, tree.sourceIds)
|
|
226
|
+
});
|
|
227
|
+
}
|
|
186
228
|
// The doctrine already refuses a page with no h1 or with several.
|
|
187
229
|
if (titles.length === 1 && !titleBound) {
|
|
188
230
|
violations.push({
|
|
@@ -227,7 +269,7 @@ import { registerAiJobStep } from "./ai-jobs.js";
|
|
|
227
269
|
ownCheck: async (hostId)=>inputs.collectionId && !await readCollection(context.firestore, hostId, inputs.collectionId) ? {
|
|
228
270
|
status: 404,
|
|
229
271
|
error: 'That content collection is not on this site'
|
|
230
|
-
} :
|
|
272
|
+
} : aiJobPlanCreationsRefusal('template', context, hostId)
|
|
231
273
|
});
|
|
232
274
|
};
|
|
233
275
|
}
|
|
@@ -324,8 +366,23 @@ export function createAiJobTemplateStep(deps = {}) {
|
|
|
324
366
|
const name = aiTemplateDraftName(definition, collection, creation == null ? void 0 : creation.name);
|
|
325
367
|
// The plan starts from a copy of a template the site has (rule 15): the
|
|
326
368
|
// copy is the draft, and nothing is generated. A source gone since the
|
|
327
|
-
// plan was made is built from the brief instead
|
|
328
|
-
|
|
369
|
+
// plan was made is built from the brief instead, and one that cannot keep
|
|
370
|
+
// the plan's tokens is built FROM (AGL-3143 §14): copying it could only end
|
|
371
|
+
// in the review below. A source this step cannot read still copies.
|
|
372
|
+
const source = (creation == null ? void 0 : creation.duplicateOf) ? inventory.templates.find((template)=>template.id === creation.duplicateOf) : undefined;
|
|
373
|
+
const sourceReview = source ? await aiCopiedTemplateReview(firestore, {
|
|
374
|
+
hostId,
|
|
375
|
+
id: source.id,
|
|
376
|
+
name: source.name,
|
|
377
|
+
plan,
|
|
378
|
+
definition
|
|
379
|
+
}) : null;
|
|
380
|
+
const sourceShort = Boolean(sourceReview == null ? void 0 : sourceReview.findings.length);
|
|
381
|
+
const sourceLines = source && sourceShort ? aiCopySourceLines('template', source.name, await readAiTemplateDraftNodes(firestore, {
|
|
382
|
+
hostId,
|
|
383
|
+
id: source.id
|
|
384
|
+
})) : [];
|
|
385
|
+
if ((creation == null ? void 0 : creation.duplicateOf) && source && !sourceShort) {
|
|
329
386
|
const copy = await duplicate('template', {
|
|
330
387
|
orgId: job.orgId,
|
|
331
388
|
hostId,
|
|
@@ -389,7 +446,7 @@ export function createAiJobTemplateStep(deps = {}) {
|
|
|
389
446
|
messages: [
|
|
390
447
|
{
|
|
391
448
|
role: 'user',
|
|
392
|
-
content: aiJobTemplatePrompt(job, definition, collection, plan, name)
|
|
449
|
+
content: aiJobTemplatePrompt(job, definition, collection, plan, name, sourceLines)
|
|
393
450
|
}
|
|
394
451
|
],
|
|
395
452
|
tool: aiDoctrineTreeTool('template'),
|
|
@@ -450,10 +507,11 @@ export function createAiJobTemplateStep(deps = {}) {
|
|
|
450
507
|
});
|
|
451
508
|
};
|
|
452
509
|
}
|
|
453
|
-
export const runAiJobTemplateStep = createAiJobTemplateStep();
|
|
510
|
+
/** The template step, building the components and forms its plan creates before the template (AGL-3143 §15). */ export const runAiJobTemplateStep = aiBuildingPlanCreations('template', 'job.template', createAiJobTemplateStep());
|
|
454
511
|
/** Registers the template step and the check a template job passes before it is created or resumed. */ export function registerAiTemplateJob() {
|
|
455
512
|
registerAiJobStep('template', runAiJobTemplateStep, {
|
|
456
|
-
minimumMs: AI_JOB_TEMPLATE_STEP_MINIMUM_MS
|
|
513
|
+
minimumMs: AI_JOB_TEMPLATE_STEP_MINIMUM_MS,
|
|
514
|
+
minimumMsFor: aiPlanCreationsRunMinimumMs('template')
|
|
457
515
|
});
|
|
458
516
|
registerAiJobAdmission('template', createAiTemplateJobAdmission());
|
|
459
517
|
}
|