@aglyn/plugins-ai 1.0.0-beta.229 → 1.0.0-beta.231
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 +3 -3
- package/package.json +17 -17
- package/src/lib/activity/ai-activity-actions.js +7 -1
- 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-automation-zones.d.ts +38 -0
- package/src/lib/components/ai-automation-zones.js +5 -1
- package/src/lib/components/ai-automation-zones.js.map +1 -1
- package/src/lib/components/ai-brief-dialog.component.d.ts +20 -2
- package/src/lib/components/ai-brief-dialog.component.js +87 -8
- package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-campaign-create.component.d.ts +54 -0
- package/src/lib/components/ai-campaign-create.component.js +270 -0
- package/src/lib/components/ai-campaign-create.component.js.map +1 -0
- package/src/lib/components/ai-describe-automation.component.d.ts +8 -3
- package/src/lib/components/ai-describe-automation.component.js +15 -6
- package/src/lib/components/ai-describe-automation.component.js.map +1 -1
- package/src/lib/components/ai-describe-button.component.d.ts +20 -8
- package/src/lib/components/ai-describe-button.component.js +19 -36
- package/src/lib/components/ai-describe-button.component.js.map +1 -1
- package/src/lib/components/ai-describe-email.component.d.ts +31 -0
- package/src/lib/components/ai-describe-email.component.js +22 -0
- package/src/lib/components/ai-describe-email.component.js.map +1 -0
- package/src/lib/components/ai-describe-org-automation.component.d.ts +36 -0
- package/src/lib/components/ai-describe-org-automation.component.js +238 -0
- package/src/lib/components/ai-describe-org-automation.component.js.map +1 -0
- package/src/lib/components/ai-experiment-cards.component.d.ts +9 -1
- package/src/lib/components/ai-experiment-cards.component.js +104 -18
- package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
- package/src/lib/components/ai-explain-automation.component.js +1 -5
- package/src/lib/components/ai-explain-automation.component.js.map +1 -1
- package/src/lib/components/ai-funnel-zones.component.d.ts +61 -0
- package/src/lib/components/ai-funnel-zones.component.js +178 -0
- package/src/lib/components/ai-funnel-zones.component.js.map +1 -0
- package/src/lib/components/ai-insight-card.component.d.ts +42 -0
- package/src/lib/components/ai-insight-card.component.js +137 -0
- package/src/lib/components/ai-insight-card.component.js.map +1 -0
- package/src/lib/components/ai-insight-dialog.component.d.ts +6 -0
- package/src/lib/components/ai-insight-dialog.component.js +9 -3
- package/src/lib/components/ai-insight-dialog.component.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.d.ts +7 -2
- package/src/lib/components/ai-job-plan.component.js +38 -6
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-job-requests.d.ts +17 -1
- package/src/lib/components/ai-job-requests.js +39 -3
- package/src/lib/components/ai-job-requests.js.map +1 -1
- package/src/lib/components/ai-logic-zones.d.ts +64 -0
- package/src/lib/components/ai-logic-zones.js +18 -0
- package/src/lib/components/ai-logic-zones.js.map +1 -0
- package/src/lib/components/ai-logic.component.d.ts +77 -0
- package/src/lib/components/ai-logic.component.js +553 -0
- package/src/lib/components/ai-logic.component.js.map +1 -0
- package/src/lib/components/ai-marketing-insight.component.d.ts +30 -0
- package/src/lib/components/ai-marketing-insight.component.js +80 -0
- package/src/lib/components/ai-marketing-insight.component.js.map +1 -0
- package/src/lib/components/ai-media-create.component.d.ts +55 -0
- package/src/lib/components/ai-media-create.component.js +433 -0
- package/src/lib/components/ai-media-create.component.js.map +1 -0
- package/src/lib/components/ai-overlay-cards.component.d.ts +83 -0
- package/src/lib/components/ai-overlay-cards.component.js +505 -0
- package/src/lib/components/ai-overlay-cards.component.js.map +1 -0
- 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-product-zones.d.ts +5 -0
- package/src/lib/components/ai-product-zones.js +1 -1
- package/src/lib/components/ai-product-zones.js.map +1 -1
- package/src/lib/components/ai-products-create-button.component.d.ts +42 -0
- package/src/lib/components/ai-products-create-button.component.js +67 -0
- package/src/lib/components/ai-products-create-button.component.js.map +1 -0
- package/src/lib/components/ai-products-hub-card.component.js +100 -81
- package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
- package/src/lib/components/ai-revise-automation.component.d.ts +16 -0
- package/src/lib/components/ai-revise-automation.component.js +207 -0
- package/src/lib/components/ai-revise-automation.component.js.map +1 -0
- package/src/lib/components/ai-site-build-page.component.js +160 -24
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-upsell-dialog.component.d.ts +53 -0
- package/src/lib/components/ai-upsell-dialog.component.js +177 -0
- package/src/lib/components/ai-upsell-dialog.component.js.map +1 -0
- package/src/lib/components/assist-build-card.component.d.ts +42 -0
- package/src/lib/components/assist-build-card.component.js +183 -0
- package/src/lib/components/assist-build-card.component.js.map +1 -0
- package/src/lib/components/assist-edit-canvas.d.ts +2 -0
- package/src/lib/components/assist-edit-canvas.js +56 -1
- package/src/lib/components/assist-edit-canvas.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.js +48 -3
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.js +51 -2
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/use-ai-job-run.d.ts +3 -14
- package/src/lib/components/use-ai-job-run.js +0 -50
- package/src/lib/components/use-ai-job-run.js.map +1 -1
- package/src/lib/jobs/ai-build-capabilities.d.ts +58 -0
- package/src/lib/jobs/ai-build-capabilities.js +256 -0
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -0
- package/src/lib/jobs/ai-build-unit-outcome.d.ts +52 -0
- package/src/lib/jobs/ai-build-unit-outcome.js +140 -0
- package/src/lib/jobs/ai-build-unit-outcome.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.d.ts +144 -0
- package/src/lib/jobs/ai-job-build-step.js +668 -0
- package/src/lib/jobs/ai-job-build-step.js.map +1 -0
- package/src/lib/jobs/ai-job-draft-ids.js +9 -1
- package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.d.ts +14 -4
- package/src/lib/jobs/ai-job-drafts.js +25 -4
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- 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 +8 -1
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +73 -0
- package/src/lib/jobs/ai-job-layout-language.js +194 -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 +105 -5
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-logic-step.d.ts +107 -0
- package/src/lib/jobs/ai-job-logic-step.js +395 -0
- package/src/lib/jobs/ai-job-logic-step.js.map +1 -0
- package/src/lib/jobs/ai-job-overlay-copy.d.ts +70 -0
- package/src/lib/jobs/ai-job-overlay-copy.js +178 -0
- package/src/lib/jobs/ai-job-overlay-copy.js.map +1 -0
- package/src/lib/jobs/ai-job-page-language.d.ts +149 -0
- package/src/lib/jobs/ai-job-page-language.js +355 -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 +111 -20
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +27 -2
- package/src/lib/jobs/ai-job-plan-step.js +87 -13
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +19 -1
- package/src/lib/jobs/ai-job-site-step.js +220 -61
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-text-step.d.ts +22 -1
- package/src/lib/jobs/ai-job-text-step.js +12 -0
- package/src/lib/jobs/ai-job-text-step.js.map +1 -1
- package/src/lib/jobs/ai-job-workflow-step.d.ts +54 -4
- package/src/lib/jobs/ai-job-workflow-step.js +320 -15
- package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +80 -2
- package/src/lib/jobs/ai-jobs.js +391 -18
- 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/jobs/ai-logic-records.d.ts +40 -0
- package/src/lib/jobs/ai-logic-records.js +55 -0
- package/src/lib/jobs/ai-logic-records.js.map +1 -0
- package/src/lib/jobs/ai-site-publish.d.ts +7 -0
- package/src/lib/jobs/ai-site-publish.js +9 -0
- package/src/lib/jobs/ai-site-publish.js.map +1 -1
- package/src/lib/jobs/ai-workflow-records.d.ts +4 -0
- package/src/lib/jobs/ai-workflow-records.js +40 -1
- package/src/lib/jobs/ai-workflow-records.js.map +1 -1
- package/src/lib/layout-language/ai-layout-compiler.d.ts +145 -0
- package/src/lib/layout-language/ai-layout-compiler.js +1148 -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 +457 -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 +161 -0
- package/src/lib/layout-language/ai-layout-language.js +446 -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-store.d.ts +39 -0
- package/src/lib/layout-language/ai-layout-store.js +69 -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-automation-draft.d.ts +18 -0
- package/src/lib/model/ai-automation-draft.js +86 -0
- package/src/lib/model/ai-automation-draft.js.map +1 -1
- package/src/lib/model/ai-build-job.d.ts +201 -0
- package/src/lib/model/ai-build-job.js +472 -0
- package/src/lib/model/ai-build-job.js.map +1 -0
- package/src/lib/model/ai-build-plan.d.ts +61 -0
- package/src/lib/model/ai-build-plan.js +154 -9
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-build-progress.d.ts +73 -0
- package/src/lib/model/ai-build-progress.js +170 -0
- package/src/lib/model/ai-build-progress.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-insight.d.ts +20 -10
- package/src/lib/model/ai-insight.js +72 -9
- package/src/lib/model/ai-insight.js.map +1 -1
- package/src/lib/model/ai-job-activity.d.ts +22 -0
- package/src/lib/model/ai-job-activity.js +25 -2
- 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 +99 -2
- package/src/lib/model/ai-jobs.types.js +3 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-logic-job.d.ts +108 -0
- package/src/lib/model/ai-logic-job.js +327 -0
- package/src/lib/model/ai-logic-job.js.map +1 -0
- package/src/lib/model/ai-media-credits.d.ts +52 -0
- package/src/lib/model/ai-media-credits.js +61 -0
- package/src/lib/model/ai-media-credits.js.map +1 -0
- package/src/lib/model/ai-overlay-copy.d.ts +114 -0
- package/src/lib/model/ai-overlay-copy.js +175 -0
- package/src/lib/model/ai-overlay-copy.js.map +1 -0
- package/src/lib/model/ai-site-build-progress.d.ts +25 -4
- package/src/lib/model/ai-site-build-progress.js +194 -11
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +8 -0
- package/src/lib/model/ai-site-job.js +9 -0
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-start-seo.d.ts +1 -1
- package/src/lib/model/ai-site-start.d.ts +5 -2
- package/src/lib/model/ai-site-start.js +5 -2
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/model/ai-svg.d.ts +69 -0
- package/src/lib/model/ai-svg.js +265 -0
- package/src/lib/model/ai-svg.js.map +1 -0
- package/src/lib/model/ai-usage-by-user.d.ts +3 -2
- package/src/lib/model/ai-usage-by-user.js.map +1 -1
- package/src/lib/model/ai-workflow-job.d.ts +92 -4
- package/src/lib/model/ai-workflow-job.js +124 -7
- package/src/lib/model/ai-workflow-job.js.map +1 -1
- package/src/lib/model/assist-build.d.ts +58 -0
- package/src/lib/model/assist-build.js +52 -0
- package/src/lib/model/assist-build.js.map +1 -0
- package/src/lib/model/assist-edit.d.ts +48 -3
- package/src/lib/model/assist-edit.js +48 -4
- package/src/lib/model/assist-edit.js.map +1 -1
- package/src/lib/plugin-config.js +2 -0
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/plugin.js +308 -47
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/catalog.d.ts +82 -5
- package/src/lib/providers/catalog.js +83 -12
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/providers/image-contract.d.ts +116 -0
- package/src/lib/providers/image-contract.js +74 -0
- package/src/lib/providers/image-contract.js.map +1 -0
- package/src/lib/providers/model-choice.js +11 -0
- 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.d.ts +121 -0
- package/src/lib/providers/vertex-image.js +312 -0
- package/src/lib/providers/vertex-image.js.map +1 -0
- package/src/lib/runtime/ai-doctrine-validators.d.ts +101 -0
- package/src/lib/runtime/ai-doctrine-validators.js +525 -4
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +21 -6
- 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 +15 -1
- package/src/lib/runtime/ai-eval.js +52 -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-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/experiment-variant-copy.d.ts +34 -0
- package/src/lib/runtime/experiment-variant-copy.js +102 -0
- package/src/lib/runtime/experiment-variant-copy.js.map +1 -0
- package/src/lib/server/ai-experiment-versions.d.ts +51 -0
- package/src/lib/server/ai-experiment-versions.js +270 -0
- package/src/lib/server/ai-experiment-versions.js.map +1 -0
- package/src/lib/server/ai-jobs-resume.js +74 -4
- package/src/lib/server/ai-jobs-resume.js.map +1 -1
- package/src/lib/server/ai-media-image.d.ts +66 -0
- package/src/lib/server/ai-media-image.js +521 -0
- package/src/lib/server/ai-media-image.js.map +1 -0
- package/src/lib/server/ai-media-svg.d.ts +91 -0
- package/src/lib/server/ai-media-svg.js +186 -0
- package/src/lib/server/ai-media-svg.js.map +1 -0
- package/src/lib/server/assist-build.d.ts +54 -0
- package/src/lib/server/assist-build.js +121 -0
- package/src/lib/server/assist-build.js.map +1 -0
- package/src/lib/server/assist-chat.js +108 -12
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-edit.js +213 -3
- package/src/lib/server/assist-edit.js.map +1 -1
- package/src/lib/server/assist-view-context.js +242 -4
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/server.js +26 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/tools/ai-logic-tool.d.ts +27 -0
- package/src/lib/tools/ai-logic-tool.js +207 -0
- package/src/lib/tools/ai-logic-tool.js.map +1 -0
- package/src/lib/tools/ai-overlay-tool.d.ts +35 -0
- package/src/lib/tools/ai-overlay-tool.js +107 -0
- package/src/lib/tools/ai-overlay-tool.js.map +1 -0
- package/src/lib/tools/ai-workflow-tool.d.ts +17 -2
- package/src/lib/tools/ai-workflow-tool.js +47 -14
- package/src/lib/tools/ai-workflow-tool.js.map +1 -1
- package/src/lib/usage/assist-job-refund.d.ts +28 -1
- package/src/lib/usage/assist-job-refund.js +44 -9
- package/src/lib/usage/assist-job-refund.js.map +1 -1
- package/src/lib/usage/assist-usage.d.ts +5 -0
- package/src/lib/usage/assist-usage.js +2 -0
- package/src/lib/usage/assist-usage.js.map +1 -1
|
@@ -17,6 +17,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
17
17
|
*/ import { REUSABLE_INSTANCE_COMPONENT_ID, REUSABLE_INSTANCE_PROP_VALUES_KEY } from "@aglyn/aglyn/app-utils/reusable-component-keys";
|
|
18
18
|
import { CANVAS_ROOT_ELEMENT_ID } from "@aglyn/aglyn/foundation/constants/canvas";
|
|
19
19
|
import { AI_BUILD_PLAN_EMBED_HOST_NAMES, AI_PLAN_NEW_REF_PREFIX, isAiPlanNewRef } from "../model/ai-build-plan.js";
|
|
20
|
+
import { AI_DOCTRINE_REFUSED_COPY, AI_DOCTRINE_REFUSED_PAGE_COPY, aiIsDoctrineRuleText } from "../model/ai-job-failure-copy.js";
|
|
20
21
|
import { isAiUnreadToken } from "../model/ai-template-subjects.js";
|
|
21
22
|
import { aiAnswerTree } from "../runtime/ai-doctrine.js";
|
|
22
23
|
import { aiBracketedFacts, aiTreeCopy, walkTree } from "../runtime/ai-doctrine-validators.js";
|
|
@@ -327,11 +328,21 @@ function elementOf(node) {
|
|
|
327
328
|
* entries it names, and an outline of those parts of the answer it was found
|
|
328
329
|
* in (AGL-3078), so why a job stopped can be read back from the job once the
|
|
329
330
|
* answer itself is gone.
|
|
330
|
-
|
|
331
|
+
*
|
|
332
|
+
* The doctrine's own sentence ("Rule 12 (Responsive by the theme's
|
|
333
|
+
* breakpoints): ...") is never what the customer reads (AGL-3596): it rides
|
|
334
|
+
* as `detail`, for staff, and `message` is the plain refusal, a page's when
|
|
335
|
+
* `page` says the refused thing is one, followed by `then` when given.
|
|
336
|
+
*/ export function aiDoctrineReview(result, options = {}) {
|
|
331
337
|
const outline = aiDoctrineReviewOutline(result.answer, result.violations);
|
|
338
|
+
const ruleText = aiIsDoctrineRuleText(result.message);
|
|
339
|
+
const plain = ruleText ? options.page ? AI_DOCTRINE_REFUSED_PAGE_COPY : AI_DOCTRINE_REFUSED_COPY : result.message;
|
|
332
340
|
return _extends({
|
|
333
341
|
reason: 'doctrine',
|
|
334
|
-
message:
|
|
342
|
+
message: options.then ? `${plain} ${options.then}` : plain
|
|
343
|
+
}, ruleText ? {
|
|
344
|
+
detail: result.message
|
|
345
|
+
} : {}, {
|
|
335
346
|
findings: result.violations.map(({ rule, code, message, nodeIds, paths })=>_extends({
|
|
336
347
|
rule,
|
|
337
348
|
code,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-generation.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 {\n REUSABLE_INSTANCE_COMPONENT_ID,\n REUSABLE_INSTANCE_PROP_VALUES_KEY,\n} from '@aglyn/aglyn/app-utils/reusable-component-keys'\nimport { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport {\n AI_BUILD_PLAN_EMBED_HOST_NAMES,\n AI_PLAN_NEW_REF_PREFIX,\n isAiPlanNewRef,\n type AiBuildPlanCreate,\n type AiBuildPlanCreateKind,\n} from '../model/ai-build-plan'\nimport type {\n AiJob,\n AiJobOutput,\n AiJobPlan,\n AiJobReview,\n AiJobReviewOutlineNode,\n} from '../model/ai-jobs.types'\nimport type { AiSiteInventory } from '../model/ai-site-inventory'\nimport { isAiUnreadToken } from '../model/ai-template-subjects'\nimport { aiAnswerTree, type AiGenerationSpend, type AiValidatedTree } from '../runtime/ai-doctrine'\nimport {\n aiBracketedFacts,\n aiTreeCopy,\n walkTree,\n type AiDoctrineNode,\n type AiDoctrineTree,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport { AI_INSTANCE_REF_PROP } from '../runtime/ai-node-tree'\nimport type { AssistTokenUsage } from '../usage/assist-usage'\nimport { AI_JOB_BRIEF_MAX_CHARS, type AiJobStepOutcome } from './ai-job-text-step'\n\n/**\n * What the generation steps share (AGL-2909): reading the plan a member\n * confirmed, stating it to the model as references, and turning what a\n * generation spent or refused into the outcome the machine records.\n *\n * A generation step runs after its job's plan step, once a member confirmed\n * the plan, so the plan is part of what it executes: the records it reuses,\n * and the creation it builds. The plan travels as references — ids, names,\n * reasons — never as a document (AGL-2937).\n */\n\nexport const AI_JOB_ZERO_USAGE: AssistTokenUsage = {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n}\n\n/** The job's plan once a member confirmed it; `null` before that, or for a job that never planned. */\nexport function aiConfirmedPlan(job: Pick<AiJob, 'plan'>): AiJobPlan | null {\n return job.plan?.status === 'confirmed' ? job.plan : null\n}\n\n/** The creation of one kind the plan names; the first when it names several. */\nexport function aiPlanCreation(\n plan: AiJobPlan | null,\n kind: AiBuildPlanCreateKind,\n): AiBuildPlanCreate | null {\n return plan?.create.find((entry) => entry.kind === kind) ?? null\n}\n\n/** A plan reference as the model reads it: the id with the name it was planned under. */\nfunction referenceOf(plan: AiJobPlan, ref: string): string {\n const label = plan.labels?.[ref]\n return label ? `${ref} (\"${label}\")` : ref\n}\n\n/** The user turn's opening: the brief, cut at the ceiling every job step keeps. */\nexport function aiJobBriefLine(job: Pick<AiJob, 'brief'>): string {\n return `Brief: ${job.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)}`\n}\n\n/** The confirmed plan as reference lines; none when there is no confirmed plan. */\nexport function aiPlanReferenceLines(plan: AiJobPlan | null): string[] {\n if (!plan) return []\n const lines = ['Confirmed plan:']\n for (const entry of plan.reuse) {\n lines.push(`- reuse the ${entry.kind === 'screen' ? 'page' : entry.kind} ${referenceOf(plan, entry.id)}: ${entry.purpose}`)\n }\n for (const entry of plan.create) {\n const from = entry.duplicateOf ? `, from a copy of ${referenceOf(plan, entry.duplicateOf)}` : ''\n // A template's slug or timestamp is filled, never shown, so a build told\n // it is a field prints it (AGL-3143 §16).\n const shown = entry.kind === 'template' ? entry.fields.filter((field) => !isAiUnreadToken(field)) : entry.fields\n const fields = shown.length ? ` Fields: ${shown.join(', ')}.` : ''\n lines.push(`- create the ${entry.kind} \"${entry.name}\"${from}: ${entry.why}${fields}`)\n }\n for (const screen of plan.screens) {\n const sections = screen.sections.map((section) => section.name).join(', ')\n lines.push(`- the page \"${screen.title}\" at ${screen.slug}${sections ? `: ${sections}` : ''}`)\n }\n // The one place a Video embed is admitted (AGL-3433): where the plan lists it, at the brief's link or none.\n for (const embed of plan.embeds ?? []) {\n const where = isAiPlanNewRef(embed.where)\n ? `the component \"${embed.where.slice(AI_PLAN_NEW_REF_PREFIX.length)}\"`\n : embed.where\n const link = embed.url ? `, url ${embed.url}` : ', url left empty for the site owner to paste'\n lines.push(`- embed a ${AI_BUILD_PLAN_EMBED_HOST_NAMES[embed.host]} player (videoEmbed) on ${where}${link}`)\n }\n return lines.length > 1 ? lines : []\n}\n\n/**\n * The most of a source's tree a build is shown, as JSON characters: about\n * 3,000 tokens, a whole layout or page template, never a site (AGL-3143 §14).\n */\nexport const AI_COPY_SOURCE_MAX_CHARS = 12_000\n\n/**\n * The record a plan's copy starts from, as the build that replaces the copy\n * is shown it (AGL-3143 §14): its tree in the node-map shape the tool takes,\n * with the one instruction that makes it a start rather than an example.\n * Empty when there is no tree to show or it is too large to show whole, and\n * the build then starts from the brief and the plan.\n *\n * A copy whose source cannot keep what the plan promised is never minted,\n * because the review after it would refuse it with certainty and the member\n * could only try the same copy again. Measured on 2026-09-22 (job\n * `Pq3pgvMH0D`): \"copy the interior layout and add a sidebar\" could only end\n * in review. Building from the source keeps what the plan does not change —\n * the firm's header, its footer, their copy — which a build from the brief\n * alone would draw again from nothing.\n */\nexport function aiCopySourceLines(noun: string, label: string, nodes: Readonly<Record<string, unknown>> | null): string[] {\n if (!nodes || !isRecordNode(nodes[CANVAS_ROOT_ELEMENT_ID])) return []\n const shaped: Record<string, Record<string, unknown>> = {}\n for (const [id, node] of Object.entries(nodes)) {\n if (!isRecordNode(node)) continue\n const { componentId, props, sx, nodes: children } = node\n shaped[id] = {\n componentId,\n ...(isRecordNode(props) && Object.keys(props).length ? { props } : {}),\n ...(isRecordNode(sx) && Object.keys(sx).length ? { sx } : {}),\n ...(Array.isArray(children) && children.length ? { nodes: children } : {}),\n }\n }\n const tree = JSON.stringify({ rootId: CANVAS_ROOT_ELEMENT_ID, nodes: shaped })\n if (tree.length > AI_COPY_SOURCE_MAX_CHARS) return []\n return [\n `The plan starts from a copy of the ${noun} \"${label}\", which lacks part of what the plan gives it. Build from it: keep every part the plan does not change, and add what the plan adds. Its tree, in the node-map shape the tool takes:`,\n tree,\n ]\n}\n\nfunction isRecordNode(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** The reusable components a tree places, by the id each instance names. */\nexport function aiPlacedComponentIds(tree: Pick<AiValidatedTree, 'rootId' | 'nodes'>): Set<string> {\n const placed = new Set<string>()\n const nodes = tree.nodes as unknown as Record<string, AiDoctrineNode>\n for (const { node } of walkTree({ rootId: tree.rootId, nodes })) {\n const ref = node.props?.[AI_INSTANCE_REF_PROP]\n if (node.componentId === REUSABLE_INSTANCE_COMPONENT_ID && typeof ref === 'string') {\n placed.add(ref)\n }\n }\n return placed\n}\n\n/**\n * Rule 7 on a generated tree: every component the confirmed plan reuses is\n * placed. `noun` names what the tree builds, in the finding.\n */\nexport function aiPlanReuseViolations(\n inventory: AiSiteInventory | null,\n plan: AiJobPlan | null,\n placed: ReadonlySet<string>,\n noun: string,\n): AiDoctrineViolation[] {\n const names = new Map((inventory?.components ?? []).map((component) => [component.id, component.name]))\n const reused = [\n ...new Set(\n (plan?.reuse ?? []).filter((entry) => entry.kind === 'component').map((entry) => entry.id),\n ),\n ]\n const missing = reused.filter((id) => !placed.has(id))\n if (!missing.length) return []\n const listed = missing.map((id) => `\"${names.get(id) ?? id}\"`).join(', ')\n return [\n {\n rule: 7,\n code: 'plan-reuse-not-placed',\n message: `The confirmed plan reuses ${listed}, and the ${noun} does not place ${\n missing.length === 1 ? 'it' : 'them'\n }. Place every component the plan reuses.`,\n },\n ]\n}\n\n/** What a generation spent, as the step's outcome carries it for the meter. */\nexport function aiGenerationSpent(\n spend: AiGenerationSpend,\n): Pick<AiJobStepOutcome, 'outputs' | 'usage' | 'estCostUsd' | 'model' | 'stopReason'> {\n return {\n outputs: [],\n usage: spend.usage,\n estCostUsd: spend.estCostUsd,\n model: spend.model,\n stopReason: spend.stopReason,\n }\n}\n\n/** The most node ids, or plan paths, one finding keeps on its review (AGL-3078). */\nexport const AI_JOB_REVIEW_REFS_MAX = 24\n/** The longest node id or plan path a review keeps, in characters. */\nexport const AI_JOB_REVIEW_REF_MAX_CHARS = 64\n/** How far below a node a finding names its outline goes: the node, its children and theirs. */\nexport const AI_JOB_REVIEW_OUTLINE_MAX_DEPTH = 2\n/** The most nodes a review outlines, across all its findings. */\nexport const AI_JOB_REVIEW_OUTLINE_MAX_NODES = 40\n/** The most names an outlined node lists of its props, of its sx keys, or of its children. */\nexport const AI_JOB_REVIEW_OUTLINE_MAX_NAMES = 16\n/** The most bytes a review's outline takes as JSON, whatever the counts above allow. */\nexport const AI_JOB_REVIEW_OUTLINE_MAX_BYTES = 4_096\n\n/** A Grid's layout props, whose written values an outline keeps. */\nconst AI_JOB_REVIEW_GRID_PROPS = [\n 'container',\n 'size',\n 'offset',\n 'direction',\n 'wrap',\n 'spacing',\n 'rowSpacing',\n 'columnSpacing',\n 'columns',\n] as const\n\n/** A node id or plan path a review keeps: no whitespace, slash or quote, so never copy or an address. */\nconst REVIEW_REF = /^[^\\s/\\\\\"'<>]+$/\n/** An element id an outline names. */\nconst OUTLINE_ELEMENT = /^[\\w.-]{1,64}$/\n/** A prop or sx name an outline lists. */\nconst OUTLINE_NAME = /^[A-Za-z_$][\\w$-]{0,39}$/\n/** The longest layout value an outline keeps as written. */\nconst OUTLINE_VALUE_MAX_CHARS = 40\n/**\n * The words a Grid's layout is written in, besides numbers: breakpoints, span\n * words, switch words, directions and wraps. Nothing else is kept, so no copy is.\n */\nconst OUTLINE_LAYOUT_WORDS = new Set([\n 'xs',\n 'sm',\n 'md',\n 'lg',\n 'xl',\n 'auto',\n 'grow',\n 'true',\n 'false',\n 'yes',\n 'no',\n 'on',\n 'off',\n 'row',\n 'row-reverse',\n 'column',\n 'column-reverse',\n 'wrap',\n 'nowrap',\n 'wrap-reverse',\n])\n/** A number a layout value is written in, with a CSS length unit where it has one. */\nconst OUTLINE_LAYOUT_NUMBER = /^[-+]?(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|%)?$/i\n/** What stands between the words of a layout value, as in `xs:12 md:4` or `{ xs: 12, md: 4 }`. */\nconst OUTLINE_LAYOUT_SEPARATORS = /[\\s,:;=/{}()[\\]\"']+/\n/** A binding token, which a component's Grid may carry for a layout value. */\nconst OUTLINE_BINDING = /^\\{\\{\\s*[\\w.-]+\\s*\\}\\}$/\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** Whether text is a layout value: a binding token, or only layout words and numbers. */\nfunction isLayoutText(value: string): boolean {\n if (value.length > OUTLINE_VALUE_MAX_CHARS) return false\n if (OUTLINE_BINDING.test(value.trim())) return true\n return value\n .split(OUTLINE_LAYOUT_SEPARATORS)\n .every((word) => word === '' || OUTLINE_LAYOUT_WORDS.has(word.toLowerCase()) || OUTLINE_LAYOUT_NUMBER.test(word))\n}\n\n/** An id or a path as a review keeps it: cut to its ceiling, and `?` for one it cannot show. */\nfunction reviewRef(ref: string): string {\n const cut = ref.slice(0, AI_JOB_REVIEW_REF_MAX_CHARS)\n return REVIEW_REF.test(cut) ? cut : '?'\n}\n\nfunction reviewRefs(refs: readonly string[]): string[] {\n return refs.slice(0, AI_JOB_REVIEW_REFS_MAX).map(reviewRef)\n}\n\n/** A written layout value as an outline keeps it; text that is no layout value is kept as a marker. */\nfunction outlineValue(value: unknown): string | number | boolean | null {\n if (value === null) return null\n if (typeof value === 'boolean') return value\n if (typeof value === 'number') return Number.isFinite(value) ? value : null\n if (typeof value === 'string') return isLayoutText(value) ? value : '<text>'\n return Array.isArray(value) ? '<list>' : '<object>'\n}\n\nfunction childIdsOf(node: Record<string, unknown>): string[] {\n return Array.isArray(node['nodes']) ? node['nodes'].filter((child): child is string => typeof child === 'string') : []\n}\n\nfunction elementOf(node: unknown): string {\n const componentId = isRecord(node) ? node['componentId'] : undefined\n return typeof componentId === 'string' && OUTLINE_ELEMENT.test(componentId) ? componentId : '?'\n}\n\n/** One node of an outline: where it sits, what it is and the names of what it sets, never a copy value. */\nfunction outlineNodeOf(\n id: string,\n depth: number,\n node: Record<string, unknown>,\n nodes: Readonly<Record<string, unknown>>,\n): AiJobReviewOutlineNode {\n const props = isRecord(node['props']) ? node['props'] : {}\n const sx = isRecord(node['sx']) ? node['sx'] : isRecord(props['sx']) ? props['sx'] : {}\n const names = (keys: string[]) =>\n keys.filter((key) => OUTLINE_NAME.test(key)).slice(0, AI_JOB_REVIEW_OUTLINE_MAX_NAMES)\n const componentId = elementOf(node)\n const grid =\n componentId === 'muiGrid'\n ? Object.fromEntries(\n AI_JOB_REVIEW_GRID_PROPS.filter((name) => props[name] !== undefined).map((name) => [\n name,\n outlineValue(props[name]),\n ]),\n )\n : {}\n const sxNames = names(Object.keys(sx))\n return {\n id: reviewRef(id),\n depth,\n componentId,\n props: names(Object.keys(props).filter((key) => key !== 'sx')),\n ...(sxNames.length ? { sx: sxNames } : {}),\n ...(Object.keys(grid).length ? { grid } : {}),\n children: childIdsOf(node)\n .filter((child) => isRecord(nodes[child]))\n .slice(0, AI_JOB_REVIEW_OUTLINE_MAX_NAMES)\n .map((child) => elementOf(nodes[child])),\n }\n}\n\n/**\n * The parts of a refused answer its findings name, as a review keeps them\n * (AGL-3078): each node a finding names by id, with the nodes below it to\n * `AI_JOB_REVIEW_OUTLINE_MAX_DEPTH`, in document order and each once. A node\n * keeps its id, its element, the names of its props and sx keys and its\n * children's elements, and a Grid the values of its layout props as written;\n * no copy, and no address, is kept. The outline stops at\n * `AI_JOB_REVIEW_OUTLINE_MAX_NODES` nodes or `AI_JOB_REVIEW_OUTLINE_MAX_BYTES`\n * bytes, whichever comes first. An answer that is no node map, and findings\n * that name no node, outline nothing.\n */\nexport function aiDoctrineReviewOutline(\n answer: Record<string, unknown> | null | undefined,\n violations: readonly AiDoctrineViolation[],\n): AiJobReviewOutlineNode[] {\n const outline: AiJobReviewOutlineNode[] = []\n const tree = answer ? aiAnswerTree(answer) : null\n const nodes = isRecord(tree) && isRecord(tree['nodes']) ? tree['nodes'] : null\n if (!nodes) return outline\n const encoder = new TextEncoder()\n const seen = new Set<string>()\n let bytes = 2\n // False once the outline is full, which ends the walk.\n const add = (id: string, depth: number): boolean => {\n const node = nodes[id]\n if (seen.has(id) || !isRecord(node)) return true\n seen.add(id)\n const entry = outlineNodeOf(id, depth, node, nodes)\n const size = encoder.encode(JSON.stringify(entry)).length + 1\n if (outline.length === AI_JOB_REVIEW_OUTLINE_MAX_NODES || bytes + size > AI_JOB_REVIEW_OUTLINE_MAX_BYTES) {\n return false\n }\n outline.push(entry)\n bytes += size\n return depth === AI_JOB_REVIEW_OUTLINE_MAX_DEPTH || childIdsOf(node).every((child) => add(child, depth + 1))\n }\n const named = violations.flatMap((violation) => (violation.nodeIds ?? []).slice(0, AI_JOB_REVIEW_REFS_MAX))\n for (const id of new Set(named)) {\n if (!add(id, 0)) break\n }\n return outline\n}\n\n/**\n * The review a generation stops its job for when the re-ask still broke a\n * rule: the sentence a person reads, each finding with the nodes or plan\n * entries it names, and an outline of those parts of the answer it was found\n * in (AGL-3078), so why a job stopped can be read back from the job once the\n * answer itself is gone.\n */\nexport function aiDoctrineReview(result: {\n message: string\n violations: readonly AiDoctrineViolation[]\n /** The refused answer, whose nodes the findings name by id; outlined, never kept. */\n answer?: Record<string, unknown> | null\n}): AiJobReview {\n const outline = aiDoctrineReviewOutline(result.answer, result.violations)\n return {\n reason: 'doctrine',\n message: result.message,\n findings: result.violations.map(({ rule, code, message, nodeIds, paths }) => ({\n rule,\n code,\n message,\n ...(nodeIds?.length ? { nodeIds: reviewRefs(nodeIds) } : {}),\n ...(paths?.length ? { paths: reviewRefs(paths) } : {}),\n })),\n ...(outline.length ? { outline } : {}),\n }\n}\n\n/** The review a step stops its job for when the site cannot take the draft. */\nexport function aiLimitReview(message: string): AiJobReview {\n return { reason: 'limit', message, findings: [] }\n}\n\n/**\n * An outcome that reached no model: a draft an earlier run already wrote, a\n * copy the plan asked for, or a person asked before anything was spent.\n */\nexport function aiUnspentOutcome(\n model: string,\n rest: { outputs?: AiJobOutput[]; review?: AiJobReview } = {},\n): AiJobStepOutcome {\n return {\n outputs: rest.outputs ?? [],\n usage: AI_JOB_ZERO_USAGE,\n estCostUsd: 0,\n model,\n stopReason: null,\n ...(rest.review ? { review: rest.review } : {}),\n }\n}\n\n/** Node ids as the MODEL wrote them, from the minted ids a check reads. */\nexport function aiModelNodeIds(\n ids: readonly string[],\n sourceIds: Readonly<Record<string, string>>,\n): string[] {\n return ids.map((id) => sourceIds[id] ?? id)\n}\n\n/** The most facts a note names before it counts the rest. */\nconst AI_NOTE_MAX_FACTS = 8\n\n/** Facts as a sentence lists them: \"a, b and c\", the ones past the cap counted. */\nfunction factList(facts: readonly string[]): string {\n const shown = facts.slice(0, AI_NOTE_MAX_FACTS)\n const items = facts.length > shown.length ? [...shown, `${facts.length - shown.length} more`] : shown\n return items.length <= 1 ? items.join('') : `${items.slice(0, -1).join(', ')} and ${items[items.length - 1]}`\n}\n\n/**\n * What a draft asks the member to fill in before it is published (AGL-3056),\n * in the words the job's output note shows: the facts in square brackets its\n * copy holds, which the brief did not give (rule 14), each once. A page's\n * placed components show their own defaults wherever the page sets no value\n * of its own, so a component whose defaults hold such facts is named once,\n * however often the page places it. `copy` is what the draft shows beside its\n * tree, such as a component's defaults. `null` when there is nothing to fill.\n */\nexport function aiBracketedFactsNote(input: {\n tree: AiDoctrineTree\n inventory: AiSiteInventory | null\n copy?: readonly string[]\n}): string | null {\n const components = input.inventory?.components ?? []\n const componentProps = Object.fromEntries(components.map((component) => [component.id, component.props]))\n const facts = aiBracketedFacts([\n ...aiTreeCopy(input.tree, { componentProps }).map((sample) => sample.text),\n ...(input.copy ?? []),\n ])\n const listed = new Set(facts.map((fact) => fact.toLowerCase()))\n const byComponent = new Map<string, string[]>()\n for (const { node } of walkTree(input.tree)) {\n const component = components.find((row) => row.id === node.props?.[AI_INSTANCE_REF_PROP])\n if (node.componentId !== REUSABLE_INSTANCE_COMPONENT_ID || !component?.bracketedDefaults) continue\n const values = node.props?.[REUSABLE_INSTANCE_PROP_VALUES_KEY]\n const set = (name: string): boolean => {\n const value = values && typeof values === 'object' ? (values as Record<string, unknown>)[name] : undefined\n return value !== undefined && value !== null && String(value).trim() !== ''\n }\n const shown = byComponent.get(component.name) ?? []\n for (const [name, defaults] of Object.entries(component.bracketedDefaults)) {\n if (set(name)) continue\n for (const fact of defaults) {\n if (!listed.has(fact.toLowerCase()) && !shown.some((known) => known.toLowerCase() === fact.toLowerCase())) {\n shown.push(fact)\n }\n }\n }\n if (shown.length) byComponent.set(component.name, shown)\n }\n const sentences = [\n ...(facts.length\n ? [`Before you publish, replace the facts in square brackets, which the brief did not give: ${factList(facts)}.`]\n : []),\n ...[...byComponent.entries()].map(\n ([name, shown]) =>\n `The \"${name}\" component on this page shows ${factList(shown)} until you replace ${shown.length === 1 ? 'it' : 'them'}.`,\n ),\n ]\n return sentences.length ? sentences.join(' ') : null\n}\n"],"names":["REUSABLE_INSTANCE_COMPONENT_ID","REUSABLE_INSTANCE_PROP_VALUES_KEY","CANVAS_ROOT_ELEMENT_ID","AI_BUILD_PLAN_EMBED_HOST_NAMES","AI_PLAN_NEW_REF_PREFIX","isAiPlanNewRef","isAiUnreadToken","aiAnswerTree","aiBracketedFacts","aiTreeCopy","walkTree","AI_INSTANCE_REF_PROP","AI_JOB_BRIEF_MAX_CHARS","AI_JOB_ZERO_USAGE","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","aiConfirmedPlan","job","plan","status","aiPlanCreation","kind","create","find","entry","referenceOf","ref","label","labels","aiJobBriefLine","brief","slice","aiPlanReferenceLines","lines","reuse","push","id","purpose","from","duplicateOf","shown","fields","filter","field","length","join","name","why","screen","screens","sections","map","section","title","slug","embed","embeds","where","link","url","host","AI_COPY_SOURCE_MAX_CHARS","aiCopySourceLines","noun","nodes","isRecordNode","shaped","node","Object","entries","componentId","props","sx","children","keys","Array","isArray","tree","JSON","stringify","rootId","value","aiPlacedComponentIds","placed","Set","add","aiPlanReuseViolations","inventory","names","Map","components","component","reused","missing","has","listed","get","rule","code","message","aiGenerationSpent","spend","outputs","usage","estCostUsd","model","stopReason","AI_JOB_REVIEW_REFS_MAX","AI_JOB_REVIEW_REF_MAX_CHARS","AI_JOB_REVIEW_OUTLINE_MAX_DEPTH","AI_JOB_REVIEW_OUTLINE_MAX_NODES","AI_JOB_REVIEW_OUTLINE_MAX_NAMES","AI_JOB_REVIEW_OUTLINE_MAX_BYTES","AI_JOB_REVIEW_GRID_PROPS","REVIEW_REF","OUTLINE_ELEMENT","OUTLINE_NAME","OUTLINE_VALUE_MAX_CHARS","OUTLINE_LAYOUT_WORDS","OUTLINE_LAYOUT_NUMBER","OUTLINE_LAYOUT_SEPARATORS","OUTLINE_BINDING","isRecord","isLayoutText","test","trim","split","every","word","toLowerCase","reviewRef","cut","reviewRefs","refs","outlineValue","Number","isFinite","childIdsOf","child","elementOf","undefined","outlineNodeOf","depth","key","grid","fromEntries","sxNames","aiDoctrineReviewOutline","answer","violations","outline","encoder","TextEncoder","seen","bytes","size","encode","named","flatMap","violation","nodeIds","aiDoctrineReview","result","reason","findings","paths","aiLimitReview","aiUnspentOutcome","rest","review","aiModelNodeIds","ids","sourceIds","AI_NOTE_MAX_FACTS","factList","facts","items","aiBracketedFactsNote","input","componentProps","sample","text","copy","fact","byComponent","row","bracketedDefaults","values","set","String","defaults","some","known","sentences"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-generation.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 {\n REUSABLE_INSTANCE_COMPONENT_ID,\n REUSABLE_INSTANCE_PROP_VALUES_KEY,\n} from '@aglyn/aglyn/app-utils/reusable-component-keys'\nimport { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport {\n AI_BUILD_PLAN_EMBED_HOST_NAMES,\n AI_PLAN_NEW_REF_PREFIX,\n isAiPlanNewRef,\n type AiBuildPlanCreate,\n type AiBuildPlanCreateKind,\n} from '../model/ai-build-plan'\nimport type {\n AiJob,\n AiJobOutput,\n AiJobPlan,\n AiJobReview,\n AiJobReviewOutlineNode,\n} from '../model/ai-jobs.types'\nimport { AI_DOCTRINE_REFUSED_COPY, AI_DOCTRINE_REFUSED_PAGE_COPY, aiIsDoctrineRuleText } from '../model/ai-job-failure-copy'\nimport type { AiSiteInventory } from '../model/ai-site-inventory'\nimport { isAiUnreadToken } from '../model/ai-template-subjects'\nimport { aiAnswerTree, type AiGenerationSpend, type AiValidatedTree } from '../runtime/ai-doctrine'\nimport {\n aiBracketedFacts,\n aiTreeCopy,\n walkTree,\n type AiDoctrineNode,\n type AiDoctrineTree,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport { AI_INSTANCE_REF_PROP } from '../runtime/ai-node-tree'\nimport type { AssistTokenUsage } from '../usage/assist-usage'\nimport { AI_JOB_BRIEF_MAX_CHARS, type AiJobStepOutcome } from './ai-job-text-step'\n\n/**\n * What the generation steps share (AGL-2909): reading the plan a member\n * confirmed, stating it to the model as references, and turning what a\n * generation spent or refused into the outcome the machine records.\n *\n * A generation step runs after its job's plan step, once a member confirmed\n * the plan, so the plan is part of what it executes: the records it reuses,\n * and the creation it builds. The plan travels as references — ids, names,\n * reasons — never as a document (AGL-2937).\n */\n\nexport const AI_JOB_ZERO_USAGE: AssistTokenUsage = {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n}\n\n/** The job's plan once a member confirmed it; `null` before that, or for a job that never planned. */\nexport function aiConfirmedPlan(job: Pick<AiJob, 'plan'>): AiJobPlan | null {\n return job.plan?.status === 'confirmed' ? job.plan : null\n}\n\n/** The creation of one kind the plan names; the first when it names several. */\nexport function aiPlanCreation(\n plan: AiJobPlan | null,\n kind: AiBuildPlanCreateKind,\n): AiBuildPlanCreate | null {\n return plan?.create.find((entry) => entry.kind === kind) ?? null\n}\n\n/** A plan reference as the model reads it: the id with the name it was planned under. */\nfunction referenceOf(plan: AiJobPlan, ref: string): string {\n const label = plan.labels?.[ref]\n return label ? `${ref} (\"${label}\")` : ref\n}\n\n/** The user turn's opening: the brief, cut at the ceiling every job step keeps. */\nexport function aiJobBriefLine(job: Pick<AiJob, 'brief'>): string {\n return `Brief: ${job.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)}`\n}\n\n/** The confirmed plan as reference lines; none when there is no confirmed plan. */\nexport function aiPlanReferenceLines(plan: AiJobPlan | null): string[] {\n if (!plan) return []\n const lines = ['Confirmed plan:']\n for (const entry of plan.reuse) {\n lines.push(`- reuse the ${entry.kind === 'screen' ? 'page' : entry.kind} ${referenceOf(plan, entry.id)}: ${entry.purpose}`)\n }\n for (const entry of plan.create) {\n const from = entry.duplicateOf ? `, from a copy of ${referenceOf(plan, entry.duplicateOf)}` : ''\n // A template's slug or timestamp is filled, never shown, so a build told\n // it is a field prints it (AGL-3143 §16).\n const shown = entry.kind === 'template' ? entry.fields.filter((field) => !isAiUnreadToken(field)) : entry.fields\n const fields = shown.length ? ` Fields: ${shown.join(', ')}.` : ''\n lines.push(`- create the ${entry.kind} \"${entry.name}\"${from}: ${entry.why}${fields}`)\n }\n for (const screen of plan.screens) {\n const sections = screen.sections.map((section) => section.name).join(', ')\n lines.push(`- the page \"${screen.title}\" at ${screen.slug}${sections ? `: ${sections}` : ''}`)\n }\n // The one place a Video embed is admitted (AGL-3433): where the plan lists it, at the brief's link or none.\n for (const embed of plan.embeds ?? []) {\n const where = isAiPlanNewRef(embed.where)\n ? `the component \"${embed.where.slice(AI_PLAN_NEW_REF_PREFIX.length)}\"`\n : embed.where\n const link = embed.url ? `, url ${embed.url}` : ', url left empty for the site owner to paste'\n lines.push(`- embed a ${AI_BUILD_PLAN_EMBED_HOST_NAMES[embed.host]} player (videoEmbed) on ${where}${link}`)\n }\n return lines.length > 1 ? lines : []\n}\n\n/**\n * The most of a source's tree a build is shown, as JSON characters: about\n * 3,000 tokens, a whole layout or page template, never a site (AGL-3143 §14).\n */\nexport const AI_COPY_SOURCE_MAX_CHARS = 12_000\n\n/**\n * The record a plan's copy starts from, as the build that replaces the copy\n * is shown it (AGL-3143 §14): its tree in the node-map shape the tool takes,\n * with the one instruction that makes it a start rather than an example.\n * Empty when there is no tree to show or it is too large to show whole, and\n * the build then starts from the brief and the plan.\n *\n * A copy whose source cannot keep what the plan promised is never minted,\n * because the review after it would refuse it with certainty and the member\n * could only try the same copy again. Measured on 2026-09-22 (job\n * `Pq3pgvMH0D`): \"copy the interior layout and add a sidebar\" could only end\n * in review. Building from the source keeps what the plan does not change —\n * the firm's header, its footer, their copy — which a build from the brief\n * alone would draw again from nothing.\n */\nexport function aiCopySourceLines(noun: string, label: string, nodes: Readonly<Record<string, unknown>> | null): string[] {\n if (!nodes || !isRecordNode(nodes[CANVAS_ROOT_ELEMENT_ID])) return []\n const shaped: Record<string, Record<string, unknown>> = {}\n for (const [id, node] of Object.entries(nodes)) {\n if (!isRecordNode(node)) continue\n const { componentId, props, sx, nodes: children } = node\n shaped[id] = {\n componentId,\n ...(isRecordNode(props) && Object.keys(props).length ? { props } : {}),\n ...(isRecordNode(sx) && Object.keys(sx).length ? { sx } : {}),\n ...(Array.isArray(children) && children.length ? { nodes: children } : {}),\n }\n }\n const tree = JSON.stringify({ rootId: CANVAS_ROOT_ELEMENT_ID, nodes: shaped })\n if (tree.length > AI_COPY_SOURCE_MAX_CHARS) return []\n return [\n `The plan starts from a copy of the ${noun} \"${label}\", which lacks part of what the plan gives it. Build from it: keep every part the plan does not change, and add what the plan adds. Its tree, in the node-map shape the tool takes:`,\n tree,\n ]\n}\n\nfunction isRecordNode(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** The reusable components a tree places, by the id each instance names. */\nexport function aiPlacedComponentIds(tree: Pick<AiValidatedTree, 'rootId' | 'nodes'>): Set<string> {\n const placed = new Set<string>()\n const nodes = tree.nodes as unknown as Record<string, AiDoctrineNode>\n for (const { node } of walkTree({ rootId: tree.rootId, nodes })) {\n const ref = node.props?.[AI_INSTANCE_REF_PROP]\n if (node.componentId === REUSABLE_INSTANCE_COMPONENT_ID && typeof ref === 'string') {\n placed.add(ref)\n }\n }\n return placed\n}\n\n/**\n * Rule 7 on a generated tree: every component the confirmed plan reuses is\n * placed. `noun` names what the tree builds, in the finding.\n */\nexport function aiPlanReuseViolations(\n inventory: AiSiteInventory | null,\n plan: AiJobPlan | null,\n placed: ReadonlySet<string>,\n noun: string,\n): AiDoctrineViolation[] {\n const names = new Map((inventory?.components ?? []).map((component) => [component.id, component.name]))\n const reused = [\n ...new Set(\n (plan?.reuse ?? []).filter((entry) => entry.kind === 'component').map((entry) => entry.id),\n ),\n ]\n const missing = reused.filter((id) => !placed.has(id))\n if (!missing.length) return []\n const listed = missing.map((id) => `\"${names.get(id) ?? id}\"`).join(', ')\n return [\n {\n rule: 7,\n code: 'plan-reuse-not-placed',\n message: `The confirmed plan reuses ${listed}, and the ${noun} does not place ${\n missing.length === 1 ? 'it' : 'them'\n }. Place every component the plan reuses.`,\n },\n ]\n}\n\n/** What a generation spent, as the step's outcome carries it for the meter. */\nexport function aiGenerationSpent(\n spend: AiGenerationSpend,\n): Pick<AiJobStepOutcome, 'outputs' | 'usage' | 'estCostUsd' | 'model' | 'stopReason'> {\n return {\n outputs: [],\n usage: spend.usage,\n estCostUsd: spend.estCostUsd,\n model: spend.model,\n stopReason: spend.stopReason,\n }\n}\n\n/** The most node ids, or plan paths, one finding keeps on its review (AGL-3078). */\nexport const AI_JOB_REVIEW_REFS_MAX = 24\n/** The longest node id or plan path a review keeps, in characters. */\nexport const AI_JOB_REVIEW_REF_MAX_CHARS = 64\n/** How far below a node a finding names its outline goes: the node, its children and theirs. */\nexport const AI_JOB_REVIEW_OUTLINE_MAX_DEPTH = 2\n/** The most nodes a review outlines, across all its findings. */\nexport const AI_JOB_REVIEW_OUTLINE_MAX_NODES = 40\n/** The most names an outlined node lists of its props, of its sx keys, or of its children. */\nexport const AI_JOB_REVIEW_OUTLINE_MAX_NAMES = 16\n/** The most bytes a review's outline takes as JSON, whatever the counts above allow. */\nexport const AI_JOB_REVIEW_OUTLINE_MAX_BYTES = 4_096\n\n/** A Grid's layout props, whose written values an outline keeps. */\nconst AI_JOB_REVIEW_GRID_PROPS = [\n 'container',\n 'size',\n 'offset',\n 'direction',\n 'wrap',\n 'spacing',\n 'rowSpacing',\n 'columnSpacing',\n 'columns',\n] as const\n\n/** A node id or plan path a review keeps: no whitespace, slash or quote, so never copy or an address. */\nconst REVIEW_REF = /^[^\\s/\\\\\"'<>]+$/\n/** An element id an outline names. */\nconst OUTLINE_ELEMENT = /^[\\w.-]{1,64}$/\n/** A prop or sx name an outline lists. */\nconst OUTLINE_NAME = /^[A-Za-z_$][\\w$-]{0,39}$/\n/** The longest layout value an outline keeps as written. */\nconst OUTLINE_VALUE_MAX_CHARS = 40\n/**\n * The words a Grid's layout is written in, besides numbers: breakpoints, span\n * words, switch words, directions and wraps. Nothing else is kept, so no copy is.\n */\nconst OUTLINE_LAYOUT_WORDS = new Set([\n 'xs',\n 'sm',\n 'md',\n 'lg',\n 'xl',\n 'auto',\n 'grow',\n 'true',\n 'false',\n 'yes',\n 'no',\n 'on',\n 'off',\n 'row',\n 'row-reverse',\n 'column',\n 'column-reverse',\n 'wrap',\n 'nowrap',\n 'wrap-reverse',\n])\n/** A number a layout value is written in, with a CSS length unit where it has one. */\nconst OUTLINE_LAYOUT_NUMBER = /^[-+]?(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|%)?$/i\n/** What stands between the words of a layout value, as in `xs:12 md:4` or `{ xs: 12, md: 4 }`. */\nconst OUTLINE_LAYOUT_SEPARATORS = /[\\s,:;=/{}()[\\]\"']+/\n/** A binding token, which a component's Grid may carry for a layout value. */\nconst OUTLINE_BINDING = /^\\{\\{\\s*[\\w.-]+\\s*\\}\\}$/\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** Whether text is a layout value: a binding token, or only layout words and numbers. */\nfunction isLayoutText(value: string): boolean {\n if (value.length > OUTLINE_VALUE_MAX_CHARS) return false\n if (OUTLINE_BINDING.test(value.trim())) return true\n return value\n .split(OUTLINE_LAYOUT_SEPARATORS)\n .every((word) => word === '' || OUTLINE_LAYOUT_WORDS.has(word.toLowerCase()) || OUTLINE_LAYOUT_NUMBER.test(word))\n}\n\n/** An id or a path as a review keeps it: cut to its ceiling, and `?` for one it cannot show. */\nfunction reviewRef(ref: string): string {\n const cut = ref.slice(0, AI_JOB_REVIEW_REF_MAX_CHARS)\n return REVIEW_REF.test(cut) ? cut : '?'\n}\n\nfunction reviewRefs(refs: readonly string[]): string[] {\n return refs.slice(0, AI_JOB_REVIEW_REFS_MAX).map(reviewRef)\n}\n\n/** A written layout value as an outline keeps it; text that is no layout value is kept as a marker. */\nfunction outlineValue(value: unknown): string | number | boolean | null {\n if (value === null) return null\n if (typeof value === 'boolean') return value\n if (typeof value === 'number') return Number.isFinite(value) ? value : null\n if (typeof value === 'string') return isLayoutText(value) ? value : '<text>'\n return Array.isArray(value) ? '<list>' : '<object>'\n}\n\nfunction childIdsOf(node: Record<string, unknown>): string[] {\n return Array.isArray(node['nodes']) ? node['nodes'].filter((child): child is string => typeof child === 'string') : []\n}\n\nfunction elementOf(node: unknown): string {\n const componentId = isRecord(node) ? node['componentId'] : undefined\n return typeof componentId === 'string' && OUTLINE_ELEMENT.test(componentId) ? componentId : '?'\n}\n\n/** One node of an outline: where it sits, what it is and the names of what it sets, never a copy value. */\nfunction outlineNodeOf(\n id: string,\n depth: number,\n node: Record<string, unknown>,\n nodes: Readonly<Record<string, unknown>>,\n): AiJobReviewOutlineNode {\n const props = isRecord(node['props']) ? node['props'] : {}\n const sx = isRecord(node['sx']) ? node['sx'] : isRecord(props['sx']) ? props['sx'] : {}\n const names = (keys: string[]) =>\n keys.filter((key) => OUTLINE_NAME.test(key)).slice(0, AI_JOB_REVIEW_OUTLINE_MAX_NAMES)\n const componentId = elementOf(node)\n const grid =\n componentId === 'muiGrid'\n ? Object.fromEntries(\n AI_JOB_REVIEW_GRID_PROPS.filter((name) => props[name] !== undefined).map((name) => [\n name,\n outlineValue(props[name]),\n ]),\n )\n : {}\n const sxNames = names(Object.keys(sx))\n return {\n id: reviewRef(id),\n depth,\n componentId,\n props: names(Object.keys(props).filter((key) => key !== 'sx')),\n ...(sxNames.length ? { sx: sxNames } : {}),\n ...(Object.keys(grid).length ? { grid } : {}),\n children: childIdsOf(node)\n .filter((child) => isRecord(nodes[child]))\n .slice(0, AI_JOB_REVIEW_OUTLINE_MAX_NAMES)\n .map((child) => elementOf(nodes[child])),\n }\n}\n\n/**\n * The parts of a refused answer its findings name, as a review keeps them\n * (AGL-3078): each node a finding names by id, with the nodes below it to\n * `AI_JOB_REVIEW_OUTLINE_MAX_DEPTH`, in document order and each once. A node\n * keeps its id, its element, the names of its props and sx keys and its\n * children's elements, and a Grid the values of its layout props as written;\n * no copy, and no address, is kept. The outline stops at\n * `AI_JOB_REVIEW_OUTLINE_MAX_NODES` nodes or `AI_JOB_REVIEW_OUTLINE_MAX_BYTES`\n * bytes, whichever comes first. An answer that is no node map, and findings\n * that name no node, outline nothing.\n */\nexport function aiDoctrineReviewOutline(\n answer: Record<string, unknown> | null | undefined,\n violations: readonly AiDoctrineViolation[],\n): AiJobReviewOutlineNode[] {\n const outline: AiJobReviewOutlineNode[] = []\n const tree = answer ? aiAnswerTree(answer) : null\n const nodes = isRecord(tree) && isRecord(tree['nodes']) ? tree['nodes'] : null\n if (!nodes) return outline\n const encoder = new TextEncoder()\n const seen = new Set<string>()\n let bytes = 2\n // False once the outline is full, which ends the walk.\n const add = (id: string, depth: number): boolean => {\n const node = nodes[id]\n if (seen.has(id) || !isRecord(node)) return true\n seen.add(id)\n const entry = outlineNodeOf(id, depth, node, nodes)\n const size = encoder.encode(JSON.stringify(entry)).length + 1\n if (outline.length === AI_JOB_REVIEW_OUTLINE_MAX_NODES || bytes + size > AI_JOB_REVIEW_OUTLINE_MAX_BYTES) {\n return false\n }\n outline.push(entry)\n bytes += size\n return depth === AI_JOB_REVIEW_OUTLINE_MAX_DEPTH || childIdsOf(node).every((child) => add(child, depth + 1))\n }\n const named = violations.flatMap((violation) => (violation.nodeIds ?? []).slice(0, AI_JOB_REVIEW_REFS_MAX))\n for (const id of new Set(named)) {\n if (!add(id, 0)) break\n }\n return outline\n}\n\n/**\n * The review a generation stops its job for when the re-ask still broke a\n * rule: the sentence a person reads, each finding with the nodes or plan\n * entries it names, and an outline of those parts of the answer it was found\n * in (AGL-3078), so why a job stopped can be read back from the job once the\n * answer itself is gone.\n *\n * The doctrine's own sentence (\"Rule 12 (Responsive by the theme's\n * breakpoints): ...\") is never what the customer reads (AGL-3596): it rides\n * as `detail`, for staff, and `message` is the plain refusal, a page's when\n * `page` says the refused thing is one, followed by `then` when given.\n */\nexport function aiDoctrineReview(\n result: {\n message: string\n violations: readonly AiDoctrineViolation[]\n /** The refused answer, whose nodes the findings name by id; outlined, never kept. */\n answer?: Record<string, unknown> | null\n },\n options: { page?: boolean; then?: string } = {},\n): AiJobReview {\n const outline = aiDoctrineReviewOutline(result.answer, result.violations)\n const ruleText = aiIsDoctrineRuleText(result.message)\n const plain = ruleText ? (options.page ? AI_DOCTRINE_REFUSED_PAGE_COPY : AI_DOCTRINE_REFUSED_COPY) : result.message\n return {\n reason: 'doctrine',\n message: options.then ? `${plain} ${options.then}` : plain,\n ...(ruleText ? { detail: result.message } : {}),\n findings: result.violations.map(({ rule, code, message, nodeIds, paths }) => ({\n rule,\n code,\n message,\n ...(nodeIds?.length ? { nodeIds: reviewRefs(nodeIds) } : {}),\n ...(paths?.length ? { paths: reviewRefs(paths) } : {}),\n })),\n ...(outline.length ? { outline } : {}),\n }\n}\n\n/** The review a step stops its job for when the site cannot take the draft. */\nexport function aiLimitReview(message: string): AiJobReview {\n return { reason: 'limit', message, findings: [] }\n}\n\n/**\n * An outcome that reached no model: a draft an earlier run already wrote, a\n * copy the plan asked for, or a person asked before anything was spent.\n */\nexport function aiUnspentOutcome(\n model: string,\n rest: { outputs?: AiJobOutput[]; review?: AiJobReview } = {},\n): AiJobStepOutcome {\n return {\n outputs: rest.outputs ?? [],\n usage: AI_JOB_ZERO_USAGE,\n estCostUsd: 0,\n model,\n stopReason: null,\n ...(rest.review ? { review: rest.review } : {}),\n }\n}\n\n/** Node ids as the MODEL wrote them, from the minted ids a check reads. */\nexport function aiModelNodeIds(\n ids: readonly string[],\n sourceIds: Readonly<Record<string, string>>,\n): string[] {\n return ids.map((id) => sourceIds[id] ?? id)\n}\n\n/** The most facts a note names before it counts the rest. */\nconst AI_NOTE_MAX_FACTS = 8\n\n/** Facts as a sentence lists them: \"a, b and c\", the ones past the cap counted. */\nfunction factList(facts: readonly string[]): string {\n const shown = facts.slice(0, AI_NOTE_MAX_FACTS)\n const items = facts.length > shown.length ? [...shown, `${facts.length - shown.length} more`] : shown\n return items.length <= 1 ? items.join('') : `${items.slice(0, -1).join(', ')} and ${items[items.length - 1]}`\n}\n\n/**\n * What a draft asks the member to fill in before it is published (AGL-3056),\n * in the words the job's output note shows: the facts in square brackets its\n * copy holds, which the brief did not give (rule 14), each once. A page's\n * placed components show their own defaults wherever the page sets no value\n * of its own, so a component whose defaults hold such facts is named once,\n * however often the page places it. `copy` is what the draft shows beside its\n * tree, such as a component's defaults. `null` when there is nothing to fill.\n */\nexport function aiBracketedFactsNote(input: {\n tree: AiDoctrineTree\n inventory: AiSiteInventory | null\n copy?: readonly string[]\n}): string | null {\n const components = input.inventory?.components ?? []\n const componentProps = Object.fromEntries(components.map((component) => [component.id, component.props]))\n const facts = aiBracketedFacts([\n ...aiTreeCopy(input.tree, { componentProps }).map((sample) => sample.text),\n ...(input.copy ?? []),\n ])\n const listed = new Set(facts.map((fact) => fact.toLowerCase()))\n const byComponent = new Map<string, string[]>()\n for (const { node } of walkTree(input.tree)) {\n const component = components.find((row) => row.id === node.props?.[AI_INSTANCE_REF_PROP])\n if (node.componentId !== REUSABLE_INSTANCE_COMPONENT_ID || !component?.bracketedDefaults) continue\n const values = node.props?.[REUSABLE_INSTANCE_PROP_VALUES_KEY]\n const set = (name: string): boolean => {\n const value = values && typeof values === 'object' ? (values as Record<string, unknown>)[name] : undefined\n return value !== undefined && value !== null && String(value).trim() !== ''\n }\n const shown = byComponent.get(component.name) ?? []\n for (const [name, defaults] of Object.entries(component.bracketedDefaults)) {\n if (set(name)) continue\n for (const fact of defaults) {\n if (!listed.has(fact.toLowerCase()) && !shown.some((known) => known.toLowerCase() === fact.toLowerCase())) {\n shown.push(fact)\n }\n }\n }\n if (shown.length) byComponent.set(component.name, shown)\n }\n const sentences = [\n ...(facts.length\n ? [`Before you publish, replace the facts in square brackets, which the brief did not give: ${factList(facts)}.`]\n : []),\n ...[...byComponent.entries()].map(\n ([name, shown]) =>\n `The \"${name}\" component on this page shows ${factList(shown)} until you replace ${shown.length === 1 ? 'it' : 'them'}.`,\n ),\n ]\n return sentences.length ? sentences.join(' ') : null\n}\n"],"names":["REUSABLE_INSTANCE_COMPONENT_ID","REUSABLE_INSTANCE_PROP_VALUES_KEY","CANVAS_ROOT_ELEMENT_ID","AI_BUILD_PLAN_EMBED_HOST_NAMES","AI_PLAN_NEW_REF_PREFIX","isAiPlanNewRef","AI_DOCTRINE_REFUSED_COPY","AI_DOCTRINE_REFUSED_PAGE_COPY","aiIsDoctrineRuleText","isAiUnreadToken","aiAnswerTree","aiBracketedFacts","aiTreeCopy","walkTree","AI_INSTANCE_REF_PROP","AI_JOB_BRIEF_MAX_CHARS","AI_JOB_ZERO_USAGE","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","aiConfirmedPlan","job","plan","status","aiPlanCreation","kind","create","find","entry","referenceOf","ref","label","labels","aiJobBriefLine","brief","slice","aiPlanReferenceLines","lines","reuse","push","id","purpose","from","duplicateOf","shown","fields","filter","field","length","join","name","why","screen","screens","sections","map","section","title","slug","embed","embeds","where","link","url","host","AI_COPY_SOURCE_MAX_CHARS","aiCopySourceLines","noun","nodes","isRecordNode","shaped","node","Object","entries","componentId","props","sx","children","keys","Array","isArray","tree","JSON","stringify","rootId","value","aiPlacedComponentIds","placed","Set","add","aiPlanReuseViolations","inventory","names","Map","components","component","reused","missing","has","listed","get","rule","code","message","aiGenerationSpent","spend","outputs","usage","estCostUsd","model","stopReason","AI_JOB_REVIEW_REFS_MAX","AI_JOB_REVIEW_REF_MAX_CHARS","AI_JOB_REVIEW_OUTLINE_MAX_DEPTH","AI_JOB_REVIEW_OUTLINE_MAX_NODES","AI_JOB_REVIEW_OUTLINE_MAX_NAMES","AI_JOB_REVIEW_OUTLINE_MAX_BYTES","AI_JOB_REVIEW_GRID_PROPS","REVIEW_REF","OUTLINE_ELEMENT","OUTLINE_NAME","OUTLINE_VALUE_MAX_CHARS","OUTLINE_LAYOUT_WORDS","OUTLINE_LAYOUT_NUMBER","OUTLINE_LAYOUT_SEPARATORS","OUTLINE_BINDING","isRecord","isLayoutText","test","trim","split","every","word","toLowerCase","reviewRef","cut","reviewRefs","refs","outlineValue","Number","isFinite","childIdsOf","child","elementOf","undefined","outlineNodeOf","depth","key","grid","fromEntries","sxNames","aiDoctrineReviewOutline","answer","violations","outline","encoder","TextEncoder","seen","bytes","size","encode","named","flatMap","violation","nodeIds","aiDoctrineReview","result","options","ruleText","plain","page","reason","then","detail","findings","paths","aiLimitReview","aiUnspentOutcome","rest","review","aiModelNodeIds","ids","sourceIds","AI_NOTE_MAX_FACTS","factList","facts","items","aiBracketedFactsNote","input","componentProps","sample","text","copy","fact","byComponent","row","bracketedDefaults","values","set","String","defaults","some","known","sentences"],"mappings":"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"}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
17
17
|
import { AI_INSIGHT_ASK_SURFACES, AI_INSIGHT_DIGEST_DAYS, AI_INSIGHT_DIGEST_READERS, AI_INSIGHT_QUESTION_MAX_CHARS, AI_INSIGHTS_COLLECTION, aiInsightDays, aiInsightOutput, aiInsightSurface, aiInsightSurfaceNeedsSite } from "../model/ai-insight.js";
|
|
18
|
+
import { aiHostPublishContext, aiSiteStatusTable } from "../model/ai-host-publish-context.js";
|
|
18
19
|
import { AI_ROUTING_TABLE, aiModelForStep } from "../providers/routing.js";
|
|
19
20
|
import { AI_ACCEPTABLE_USE_BLOCK, runAiRequest } from "../runtime/ai-runtime.js";
|
|
20
21
|
import { AI_GENERATION_MAX_ATTEMPTS } from "../runtime/ai-generation-bounds.js";
|
|
@@ -45,6 +46,7 @@ export const AI_INSIGHT_UNREADABLE_COPY = 'The AI did not answer with insights.
|
|
|
45
46
|
'- Name nobody, and write no email address or phone number.',
|
|
46
47
|
'- Plain sentences: no markdown, no headings, no lists.',
|
|
47
48
|
'- When the tables cannot answer part of the question, say so in gap in one sentence with no numbers; otherwise gap is null.',
|
|
49
|
+
'- Whether the site is published, and its address, come only from the Site status table. Page views and visitors never show whether a site is live: never say or suggest a site is not live, not launched or not published because of its traffic.',
|
|
48
50
|
'- Write in the language of the question.'
|
|
49
51
|
].join('\n');
|
|
50
52
|
/** The system blocks both calls send: the rules and the platform's acceptable-use rules. */ export const AI_JOB_INSIGHT_SYSTEM = [
|
|
@@ -234,7 +236,12 @@ export const runAiJobInsightStep = async (context)=>{
|
|
|
234
236
|
refusals: read.refusals
|
|
235
237
|
});
|
|
236
238
|
if (!read.tables.length) return stop(AI_INSIGHT_NO_TABLES_COPY);
|
|
237
|
-
|
|
239
|
+
// A site's question is told whether the site is live, so traffic is never
|
|
240
|
+
// read as launch status. Appended, so the readers' refs stay as they were.
|
|
241
|
+
const tables = !digest && site.host ? [
|
|
242
|
+
...read.tables,
|
|
243
|
+
aiSiteStatusTable(aiHostPublishContext(site.host), `t${read.tables.length + 1}`)
|
|
244
|
+
] : read.tables;
|
|
238
245
|
// 2. The answer, and at most one re-ask.
|
|
239
246
|
let messages = [
|
|
240
247
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-insight-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { ResolvedPluginFigureReader } from '@aglyn/aglyn/plugin-manager/plugin-figures'\nimport {\n AI_INSIGHT_ASK_SURFACES,\n AI_INSIGHT_DIGEST_DAYS,\n AI_INSIGHT_DIGEST_READERS,\n AI_INSIGHT_QUESTION_MAX_CHARS,\n AI_INSIGHTS_COLLECTION,\n aiInsightDays,\n aiInsightOutput,\n aiInsightSurface,\n aiInsightSurfaceNeedsSite,\n type AiInsightRecord,\n type AiInsightSurface,\n type AiInsightTable,\n} from '../model/ai-insight'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n AI_ACCEPTABLE_USE_BLOCK,\n runAiRequest,\n type AiMessage,\n type AiResult,\n type AiSystemBlock,\n type AiUsage,\n} from '../runtime/ai-runtime'\nimport { AI_GENERATION_MAX_ATTEMPTS } from '../runtime/ai-generation-bounds'\nimport { checkAiInsightAnswer, type AiInsightCheck } from '../runtime/ai-insight-check'\nimport {\n AI_INSIGHT_ANSWER_TOOL_NAME,\n AI_INSIGHT_READ_TOOL_NAME,\n aiInsightAnswerTool,\n aiInsightReadTool,\n parseAiInsightAnswer,\n parseAiInsightReads,\n type AiInsightReadRequest,\n} from '../tools/ai-insight-tool'\nimport { aiDatasetCatalog } from '../insights/ai-dataset-catalog'\nimport {\n aiInsightDatasetCatalog,\n aiInsightReaderCatalog,\n aiInsightReaderDays,\n aiInsightReaders,\n aiInsightTableText,\n readAiInsightTables,\n} from '../insights/ai-insight-readers'\nimport { assistExchangeExpiry } from '../usage/assist-usage'\nimport { registerAiJobAdmission, type AiJobAdmissionContext, type AiJobAdmissionRefusal } from './ai-job-admission'\nimport { AI_INSIGHT_READ_MAX_TOKENS, AI_JOB_INSIGHT_STEP_BUDGET, AI_JOB_INSIGHT_STEP_MINIMUM_MS } from './ai-job-insight-budget'\nimport type { AiJobStepContext, AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The `insight` step (AGL-2915): a question about a site's figures, answered\n * from tables code read, with every insight traced to the rows it cites.\n *\n * ## Two exchanges, and the model never writes a query\n *\n * 1. **The reads.** The model is shown the question and the readers this job\n * may read — each an id, what it answers, its windows and parameters — and\n * answers `read_figures` with a choice among them. It names nothing else: no\n * collection, no field path, no filter. A choice that names no reader this\n * job may read is dropped, and a job left with none reads the surface's\n * defaults. A weekly digest makes no read call: code reads the digest's\n * readers.\n * 2. **The answer.** Code reads the tables through the owners' readers, and the\n * model is shown them and answers `submit_insights`. The trace\n * (`runtime/ai-insight-check.ts`) keeps each insight whose every number is\n * in a row it cites and leaves out the rest. An answer that is not a call,\n * or whose every insight was left out, is asked for once more with the\n * reasons; what the second answer keeps is the answer.\n *\n * Both calls share one allowance of output, twice the routing ceiling, which\n * is what the step's least time is planned against.\n *\n * ## What reaches the model\n *\n * The question, the names and descriptions of the readers, the tables, and\n * for a datasets question the names of the datasets the member may see with\n * their fields' names and types. A table holds counts, sums and rates labeled\n * by a page path, a referring site, a form's, a service's, a product's or a\n * campaign's name, or the values of a dataset field with at most sixty\n * different values that at least three records share. No\n * record, visitor, contact, order, booking, submission or recipient is read\n * into a prompt, and every text cell is masked of email addresses and phone\n * numbers on its way into a table.\n *\n * ## What it writes\n *\n * The answer, at `orgs/{orgId}/aiInsights/{jobId}` under the job's own\n * expiry, and an output that names it and says how many insights it holds.\n * Nothing else: no draft, no setting, no message.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport const AI_INSIGHT_NO_SURFACE_COPY = 'This question does not say which page it was asked from.'\nexport const AI_INSIGHT_NO_SITE_COPY = 'Open the site whose numbers you are asking about.'\nexport const AI_INSIGHT_NO_READERS_COPY =\n 'There are no figures to answer from here yet: the features this page reports on are not on for this workspace.'\nexport const AI_INSIGHT_NO_TABLES_COPY = 'The figures could not be read. Try again in a moment.'\nexport const AI_INSIGHT_UNREADABLE_COPY = 'The AI did not answer with insights. Try asking in different words.'\n\n/** The digest's question: what a week's insights cover, in the order a person reads them. */\nexport const AI_INSIGHT_DIGEST_QUESTION =\n 'What changed on this site this week? Write the week’s insights: the change in traffic, the most viewed page, the form that turned the most views into submissions, a campaign that did better than the rest, and any day that stood out — only those the tables support.'\n\n/** The rules both calls are sent: static, byte-identical for every workspace. */\nexport const AI_INSIGHT_RULES = [\n 'You answer a workspace owner’s questions about their own figures: page views, orders, bookings, forms, campaigns, A/B tests and datasets. You never see a record, and you never write a query: code reads figures through fixed readers and gives you their tables.',\n '',\n `When you are given a question and a list of readers, call ${AI_INSIGHT_READ_TOOL_NAME} once with the readers whose tables answer it, most useful first, and the window each should cover. Choose only readers from the list, with their parameters' values taken from what the list and the question give you.`,\n '',\n `When you are given tables, call ${AI_INSIGHT_ANSWER_TOOL_NAME} once. Each table has a handle (t1, t2, …) and numbered rows. Rules for the insights:`,\n '- Say what matters most to the question first. Leave out what the tables do not support.',\n '- Every insight cites the table and the rows its figures come from.',\n '- Every number you write appears in a row you cite, as the table shows it or rounded. Never add, subtract, average or otherwise compute a figure yourself. The only other numbers you may write are the length of a table’s window, a date in it, and how many rows you cite.',\n '- Say a figure rose or fell only when a change you quote shows it.',\n '- Name nobody, and write no email address or phone number.',\n '- Plain sentences: no markdown, no headings, no lists.',\n '- When the tables cannot answer part of the question, say so in gap in one sentence with no numbers; otherwise gap is null.',\n '- Write in the language of the question.',\n].join('\\n')\n\n/** The system blocks both calls send: the rules and the platform's acceptable-use rules. */\nexport const AI_JOB_INSIGHT_SYSTEM: AiSystemBlock[] = [\n { text: `${AI_INSIGHT_RULES}\\n\\n${AI_ACCEPTABLE_USE_BLOCK}`, cacheBreakpoint: true },\n]\n\nconst ZERO_USAGE: AiUsage = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** What the step's calls have spent so far, in the outcome's shape. */\nclass InsightSpend {\n usage: AiUsage = { ...ZERO_USAGE }\n estCostUsd = 0\n stopReason: string | null = null\n\n constructor(readonly model: string) {}\n\n add(result: AiResult): void {\n this.usage = {\n inputTokens: this.usage.inputTokens + result.usage.inputTokens,\n outputTokens: this.usage.outputTokens + result.usage.outputTokens,\n cacheReadTokens: this.usage.cacheReadTokens + result.usage.cacheReadTokens,\n cacheWriteTokens: this.usage.cacheWriteTokens + result.usage.cacheWriteTokens,\n }\n this.estCostUsd = Math.round((this.estCostUsd + result.estCostUsd) * 1_000_000) / 1_000_000\n this.stopReason = result.stopReason\n }\n\n outcome(): Pick<AiJobStepOutcome, 'usage' | 'estCostUsd' | 'model' | 'stopReason' | 'effort'> {\n return { usage: this.usage, estCostUsd: this.estCostUsd, model: this.model, stopReason: this.stopReason, effort: null }\n }\n}\n\n/** The read call's user turn: the question, the window asked for, the readers and, for datasets, the datasets. */\nexport function aiInsightReadPrompt(input: {\n question: string\n days: number\n readers: readonly ResolvedPluginFigureReader[]\n datasets: string | null\n}): string {\n return [\n `Question: ${input.question}`,\n `The window picked on the page: ${input.days} days. Use it unless the question names another window a reader lists.`,\n 'Readers you may read here:',\n aiInsightReaderCatalog(input.readers),\n ...(input.datasets === null ? [] : ['', 'Datasets you may read, with their fields:', input.datasets]),\n ].join('\\n')\n}\n\n/** The answer call's user turn: the question and every table. */\nexport function aiInsightAnswerPrompt(question: string, tables: readonly AiInsightTable[]): string {\n return [`Question: ${question}`, '', 'Tables:', '', tables.map(aiInsightTableText).join('\\n\\n')].join('\\n')\n}\n\n/** What a re-ask tells the model: why nothing it wrote could be kept. */\nexport function aiInsightReaskMessage(check: AiInsightCheck | null): string {\n const reasons = check?.findings.length\n ? `None of those insights could be kept: ${[...new Set(check.findings)].slice(0, 8).join('; ')}.`\n : `That answer did not come through ${AI_INSIGHT_ANSWER_TOOL_NAME}.`\n return `${reasons} Answer again with ${AI_INSIGHT_ANSWER_TOOL_NAME}: cite the rows each insight's figures are in, and write every number as a row you cite shows it.`\n}\n\n/** The reads a surface falls back to when the model named none it may read: the digest's own readers. */\nfunction defaultReads(readers: readonly ResolvedPluginFigureReader[], days: number): AiInsightReadRequest[] {\n const ids = readers.map(({ reader }) => reader.id)\n const preferred = AI_INSIGHT_DIGEST_READERS.filter((id) => ids.includes(id))\n const chosen = preferred.length ? preferred : ids\n return chosen.map((id) => ({\n reader: id,\n days: aiInsightReaderDays(readers.find(({ reader }) => reader.id === id)?.reader.windows ?? [], days),\n params: {},\n }))\n}\n\nasync function readHost(\n firestore: Firestore,\n orgId: string,\n hostId: string | null,\n): Promise<{ host: Record<string, unknown> | null; ok: boolean }> {\n if (!hostId) return { host: null, ok: true }\n const snapshot = await firestore.collection('hosts').doc(hostId).get()\n const host = snapshot.exists ? ((snapshot.data() ?? {}) as Record<string, unknown>) : null\n return { host, ok: Boolean(host) && host?.['orgId'] === orgId }\n}\n\nexport const runAiJobInsightStep: AiJobStepRunner = async (context: AiJobStepContext) => {\n const { job, firestore, signal, now } = context\n const model = context.modelFor?.('job.insight') ?? aiModelForStep('job.insight')\n const spend = new InsightSpend(model)\n const stop = (failure: string): AiJobStepOutcome => ({ outputs: [], ...spend.outcome(), failure })\n\n const surface = aiInsightSurface(job.inputs)\n if (!surface) return stop(AI_INSIGHT_NO_SURFACE_COPY)\n const hostId = job.hostId ?? null\n if (aiInsightSurfaceNeedsSite(surface) && !hostId) return stop(AI_INSIGHT_NO_SITE_COPY)\n // The door checks the caller belongs to the org; that the site does is\n // checked here, before anything about it is read.\n const site = await readHost(firestore, job.orgId, hostId)\n if (!site.ok) return stop(AI_INSIGHT_NO_SITE_COPY)\n const readers = await aiInsightReaders({\n orgId: job.orgId,\n hostId,\n org: context.org ?? null,\n host: site.host,\n surface,\n })\n if (!readers.length) return stop(AI_INSIGHT_NO_READERS_COPY)\n\n const digest = surface === 'digest'\n const days = digest ? AI_INSIGHT_DIGEST_DAYS : aiInsightDays(job.inputs?.['days'])\n const question = digest ? AI_INSIGHT_DIGEST_QUESTION : job.brief.slice(0, AI_INSIGHT_QUESTION_MAX_CHARS)\n const maxTokens = AI_JOB_INSIGHT_STEP_BUDGET.maxTokens(model)\n const allowance = AI_GENERATION_MAX_ATTEMPTS * maxTokens\n const left = () => allowance - spend.usage.outputTokens\n const ask = (messages: AiMessage[], tool: 'read' | 'answer', ceiling: number) =>\n runAiRequest({\n model,\n system: AI_JOB_INSIGHT_SYSTEM,\n messages,\n tools: [tool === 'read' ? aiInsightReadTool() : aiInsightAnswerTool()],\n maxTokens: Math.max(1, Math.min(ceiling, left())),\n ...(AI_ROUTING_TABLE['job.insight'].thinking ? { thinking: AI_ROUTING_TABLE['job.insight'].thinking } : {}),\n stream: false,\n ...(signal ? { signal } : {}),\n })\n\n // 1. The reads.\n const base = { orgId: job.orgId, hostId, uid: digest ? null : job.createdBy, now }\n let requests: AiInsightReadRequest[]\n if (digest) {\n requests = defaultReads(readers, days)\n } else {\n const datasets =\n surface === 'datasets'\n ? aiInsightDatasetCatalog(await aiDatasetCatalog(firestore, { orgId: job.orgId, hostId, uid: job.createdBy }))\n : null\n const result = await ask(\n [{ role: 'user', content: aiInsightReadPrompt({ question, days, readers, datasets }) }],\n 'read',\n AI_INSIGHT_READ_MAX_TOKENS,\n )\n spend.add(result)\n if (result.kind === 'refusal') return { outputs: [], ...spend.outcome(), refused: true }\n requests = parseAiInsightReads(result.toolUse.find((use) => use.name === AI_INSIGHT_READ_TOOL_NAME)?.input)\n }\n const maxTables = digest ? AI_INSIGHT_DIGEST_READERS.length : undefined\n let read = await readAiInsightTables(readers, requests, base, maxTables)\n if (!read.tables.length && !digest) read = await readAiInsightTables(readers, defaultReads(readers, days), base)\n if (read.refusals.length) console.warn('ai insight reads refused', { jobId: job.$id, refusals: read.refusals })\n if (!read.tables.length) return stop(AI_INSIGHT_NO_TABLES_COPY)\n const { tables } = read\n\n // 2. The answer, and at most one re-ask.\n let messages: AiMessage[] = [{ role: 'user', content: aiInsightAnswerPrompt(question, tables) }]\n let checked: AiInsightCheck | null = null\n for (let attempt = 0; attempt < AI_GENERATION_MAX_ATTEMPTS && left() > 0; attempt += 1) {\n const result = await ask(messages, 'answer', maxTokens)\n spend.add(result)\n if (result.kind === 'refusal') return { outputs: [], ...spend.outcome(), refused: true }\n const answer = parseAiInsightAnswer(result.toolUse.find((use) => use.name === AI_INSIGHT_ANSWER_TOOL_NAME)?.input)\n const check = answer ? checkAiInsightAnswer(answer, tables) : null\n if (check) checked = check\n if (check && (check.kept.length || !answer?.insights.length)) break\n messages = [\n ...messages,\n { role: 'assistant', content: `Answered with ${AI_INSIGHT_ANSWER_TOOL_NAME}.` },\n { role: 'user', content: aiInsightReaskMessage(check) },\n ]\n }\n if (!checked) return stop(AI_INSIGHT_UNREADABLE_COPY)\n\n const record: AiInsightRecord = {\n jobId: job.$id,\n orgId: job.orgId,\n hostId,\n surface,\n createdBy: job.createdBy,\n question: digest ? str(job.brief) || 'Weekly insights' : question,\n days,\n insights: checked.kept,\n gap: checked.gap,\n tables,\n left: checked.left.length,\n week: digest ? str(job.inputs?.['week']) || null : null,\n createdAtMs: now.getTime(),\n }\n if (checked.left.length) {\n console.info('ai insight left out untraced insights', { jobId: job.$id, findings: checked.findings })\n }\n // Keyed by the job, so a step run again replaces its own answer.\n await firestore\n .collection('orgs')\n .doc(job.orgId)\n .collection(AI_INSIGHTS_COLLECTION)\n .doc(job.$id)\n .set({ ...(JSON.parse(JSON.stringify(record)) as AiInsightRecord), expiresAt: job.expiresAt ?? assistExchangeExpiry(now) })\n return {\n outputs: [aiInsightOutput(job, record, str(site.host?.['subdomain']) || null)],\n ...spend.outcome(),\n }\n}\n\n/**\n * Whether a question may be asked here: a surface a person asks from, a site\n * of the job's own org where the surface needs one, and at least one reader\n * the workspace may read. A digest is made by the weekly sweep and never at a\n * door.\n */\nexport async function aiInsightAdmissionRefusal(\n context: AiJobAdmissionContext,\n): Promise<AiJobAdmissionRefusal | null> {\n const surface: AiInsightSurface | null = aiInsightSurface(context.inputs)\n if (!surface || !AI_INSIGHT_ASK_SURFACES.includes(surface)) {\n return { status: 400, error: 'Ask about your numbers from a site’s Analytics, Data or CRM Reports page.' }\n }\n if (aiInsightSurfaceNeedsSite(surface) && !context.hostId) {\n return { status: 400, error: AI_INSIGHT_NO_SITE_COPY }\n }\n const site = await readHost(context.firestore, context.orgId, context.hostId)\n if (!site.ok) return { status: 404, error: 'Unknown site' }\n const readers = await aiInsightReaders({\n orgId: context.orgId,\n hostId: context.hostId,\n org: context.org,\n host: site.host,\n surface,\n })\n return readers.length ? null : { status: 403, error: AI_INSIGHT_NO_READERS_COPY }\n}\n\n/**\n * The insight kind's registrations, called by the console surface\n * (`registerAiJobKinds` in `server.ts`), never made by importing this module\n * (AGL-3025).\n */\nexport function registerAiInsightJob(): void {\n registerAiJobStep('insight', runAiJobInsightStep, { minimumMs: AI_JOB_INSIGHT_STEP_MINIMUM_MS })\n registerAiJobAdmission('insight', aiInsightAdmissionRefusal)\n}\n"],"names":["AI_INSIGHT_ASK_SURFACES","AI_INSIGHT_DIGEST_DAYS","AI_INSIGHT_DIGEST_READERS","AI_INSIGHT_QUESTION_MAX_CHARS","AI_INSIGHTS_COLLECTION","aiInsightDays","aiInsightOutput","aiInsightSurface","aiInsightSurfaceNeedsSite","AI_ROUTING_TABLE","aiModelForStep","AI_ACCEPTABLE_USE_BLOCK","runAiRequest","AI_GENERATION_MAX_ATTEMPTS","checkAiInsightAnswer","AI_INSIGHT_ANSWER_TOOL_NAME","AI_INSIGHT_READ_TOOL_NAME","aiInsightAnswerTool","aiInsightReadTool","parseAiInsightAnswer","parseAiInsightReads","aiDatasetCatalog","aiInsightDatasetCatalog","aiInsightReaderCatalog","aiInsightReaderDays","aiInsightReaders","aiInsightTableText","readAiInsightTables","assistExchangeExpiry","registerAiJobAdmission","AI_INSIGHT_READ_MAX_TOKENS","AI_JOB_INSIGHT_STEP_BUDGET","AI_JOB_INSIGHT_STEP_MINIMUM_MS","registerAiJobStep","AI_INSIGHT_NO_SURFACE_COPY","AI_INSIGHT_NO_SITE_COPY","AI_INSIGHT_NO_READERS_COPY","AI_INSIGHT_NO_TABLES_COPY","AI_INSIGHT_UNREADABLE_COPY","AI_INSIGHT_DIGEST_QUESTION","AI_INSIGHT_RULES","join","AI_JOB_INSIGHT_SYSTEM","text","cacheBreakpoint","ZERO_USAGE","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","str","value","trim","InsightSpend","add","result","usage","estCostUsd","Math","round","stopReason","outcome","model","effort","aiInsightReadPrompt","input","question","days","readers","datasets","aiInsightAnswerPrompt","tables","map","aiInsightReaskMessage","check","reasons","findings","length","Set","slice","defaultReads","ids","reader","id","preferred","filter","includes","chosen","find","windows","params","readHost","firestore","orgId","hostId","snapshot","host","ok","collection","doc","get","exists","data","Boolean","runAiJobInsightStep","context","job","site","signal","now","modelFor","spend","stop","failure","outputs","surface","inputs","org","digest","brief","maxTokens","allowance","left","ask","messages","tool","ceiling","system","tools","max","min","thinking","stream","base","uid","createdBy","requests","role","content","kind","refused","toolUse","use","name","maxTables","undefined","read","refusals","console","warn","jobId","$id","checked","attempt","answer","kept","insights","record","gap","week","createdAtMs","getTime","info","set","JSON","parse","stringify","expiresAt","aiInsightAdmissionRefusal","status","error","registerAiInsightJob","minimumMs"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAGD,SACEA,uBAAuB,EACvBC,sBAAsB,EACtBC,yBAAyB,EACzBC,6BAA6B,EAC7BC,sBAAsB,EACtBC,aAAa,EACbC,eAAe,EACfC,gBAAgB,EAChBC,yBAAyB,QAIpB,yBAAqB;AAC5B,SAASC,gBAAgB,EAAEC,cAAc,QAAQ,0BAAsB;AACvE,SACEC,uBAAuB,EACvBC,YAAY,QAKP,2BAAuB;AAC9B,SAASC,0BAA0B,QAAQ,qCAAiC;AAC5E,SAASC,oBAAoB,QAA6B,iCAA6B;AACvF,SACEC,2BAA2B,EAC3BC,yBAAyB,EACzBC,mBAAmB,EACnBC,iBAAiB,EACjBC,oBAAoB,EACpBC,mBAAmB,QAEd,8BAA0B;AACjC,SAASC,gBAAgB,QAAQ,oCAAgC;AACjE,SACEC,uBAAuB,EACvBC,sBAAsB,EACtBC,mBAAmB,EACnBC,gBAAgB,EAChBC,kBAAkB,EAClBC,mBAAmB,QACd,oCAAgC;AACvC,SAASC,oBAAoB,QAAQ,2BAAuB;AAC5D,SAASC,sBAAsB,QAAgE,wBAAoB;AACnH,SAASC,0BAA0B,EAAEC,0BAA0B,EAAEC,8BAA8B,QAAQ,6BAAyB;AAEhI,SAASC,iBAAiB,QAAQ,eAAW;AA8C7C,OAAO,MAAMC,6BAA6B,2DAA0D;AACpG,OAAO,MAAMC,0BAA0B,oDAAmD;AAC1F,OAAO,MAAMC,6BACX,iHAAgH;AAClH,OAAO,MAAMC,4BAA4B,wDAAuD;AAChG,OAAO,MAAMC,6BAA6B,sEAAqE;AAE/G,2FAA2F,GAC3F,OAAO,MAAMC,6BACX,2QAA0Q;AAE5Q,+EAA+E,GAC/E,OAAO,MAAMC,mBAAmB;IAC9B;IACA;IACA,CAAC,0DAA0D,EAAExB,0BAA0B,wNAAwN,CAAC;IAChT;IACA,CAAC,gCAAgC,EAAED,4BAA4B,qFAAqF,CAAC;IACrJ;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAC0B,IAAI,CAAC,MAAK;AAEZ,0FAA0F,GAC1F,OAAO,MAAMC,wBAAyC;IACpD;QAAEC,MAAM,GAAGH,iBAAiB,IAAI,EAAE7B,yBAAyB;QAAEiC,iBAAiB;IAAK;CACpF,CAAA;AAED,MAAMC,aAAsB;IAAEC,aAAa;IAAGC,cAAc;IAAGC,iBAAiB;IAAGC,kBAAkB;AAAE;AAEvG,MAAMC,MAAM,CAACC,QAA4B,OAAOA,UAAU,WAAWA,MAAMC,IAAI,KAAK;AAEpF,qEAAqE,GACrE,IAAA,AAAMC,eAAN,MAAMA;IAOJC,IAAIC,MAAgB,EAAQ;QAC1B,IAAI,CAACC,KAAK,GAAG;YACXV,aAAa,IAAI,CAACU,KAAK,CAACV,WAAW,GAAGS,OAAOC,KAAK,CAACV,WAAW;YAC9DC,cAAc,IAAI,CAACS,KAAK,CAACT,YAAY,GAAGQ,OAAOC,KAAK,CAACT,YAAY;YACjEC,iBAAiB,IAAI,CAACQ,KAAK,CAACR,eAAe,GAAGO,OAAOC,KAAK,CAACR,eAAe;YAC1EC,kBAAkB,IAAI,CAACO,KAAK,CAACP,gBAAgB,GAAGM,OAAOC,KAAK,CAACP,gBAAgB;QAC/E;QACA,IAAI,CAACQ,UAAU,GAAGC,KAAKC,KAAK,CAAC,AAAC,CAAA,IAAI,CAACF,UAAU,GAAGF,OAAOE,UAAU,AAAD,IAAK,WAAa;QAClF,IAAI,CAACG,UAAU,GAAGL,OAAOK,UAAU;IACrC;IAEAC,UAA8F;QAC5F,OAAO;YAAEL,OAAO,IAAI,CAACA,KAAK;YAAEC,YAAY,IAAI,CAACA,UAAU;YAAEK,OAAO,IAAI,CAACA,KAAK;YAAEF,YAAY,IAAI,CAACA,UAAU;YAAEG,QAAQ;QAAK;IACxH;IAfA,YAAY,AAASD,KAAa,CAAE;aAAfA,QAAAA;aAJrBN,QAAiB,aAAKX;aACtBY,aAAa;aACbG,aAA4B;IAES;AAgBvC;AAEA,gHAAgH,GAChH,OAAO,SAASI,oBAAoBC,KAKnC;IACC,OAAO;QACL,CAAC,UAAU,EAAEA,MAAMC,QAAQ,EAAE;QAC7B,CAAC,+BAA+B,EAAED,MAAME,IAAI,CAAC,sEAAsE,CAAC;QACpH;QACA5C,uBAAuB0C,MAAMG,OAAO;WAChCH,MAAMI,QAAQ,KAAK,OAAO,EAAE,GAAG;YAAC;YAAI;YAA6CJ,MAAMI,QAAQ;SAAC;KACrG,CAAC5B,IAAI,CAAC;AACT;AAEA,+DAA+D,GAC/D,OAAO,SAAS6B,sBAAsBJ,QAAgB,EAAEK,MAAiC;IACvF,OAAO;QAAC,CAAC,UAAU,EAAEL,UAAU;QAAE;QAAI;QAAW;QAAIK,OAAOC,GAAG,CAAC9C,oBAAoBe,IAAI,CAAC;KAAQ,CAACA,IAAI,CAAC;AACxG;AAEA,uEAAuE,GACvE,OAAO,SAASgC,sBAAsBC,KAA4B;IAChE,MAAMC,UAAUD,CAAAA,yBAAAA,MAAOE,QAAQ,CAACC,MAAM,IAClC,CAAC,sCAAsC,EAAE;WAAI,IAAIC,IAAIJ,MAAME,QAAQ;KAAE,CAACG,KAAK,CAAC,GAAG,GAAGtC,IAAI,CAAC,MAAM,CAAC,CAAC,GAC/F,CAAC,iCAAiC,EAAE1B,4BAA4B,CAAC,CAAC;IACtE,OAAO,GAAG4D,QAAQ,mBAAmB,EAAE5D,4BAA4B,iGAAiG,CAAC;AACvK;AAEA,uGAAuG,GACvG,SAASiE,aAAaZ,OAA8C,EAAED,IAAY;IAChF,MAAMc,MAAMb,QAAQI,GAAG,CAAC,CAAC,EAAEU,MAAM,EAAE,GAAKA,OAAOC,EAAE;IACjD,MAAMC,YAAYlF,0BAA0BmF,MAAM,CAAC,CAACF,KAAOF,IAAIK,QAAQ,CAACH;IACxE,MAAMI,SAASH,UAAUP,MAAM,GAAGO,YAAYH;IAC9C,OAAOM,OAAOf,GAAG,CAAC,CAACW;;YAESf;eAFD;YACzBc,QAAQC;YACRhB,MAAM3C,6BAAoB4C,gBAAAA,QAAQoB,IAAI,CAAC,CAAC,EAAEN,MAAM,EAAE,GAAKA,OAAOC,EAAE,KAAKA,wBAA3Cf,cAAgDc,MAAM,CAACO,OAAO,mBAAI,EAAE,EAAEtB;YAChGuB,QAAQ,CAAC;QACX;;AACF;AAEA,eAAeC,SACbC,SAAoB,EACpBC,KAAa,EACbC,MAAqB;QAIYC;IAFjC,IAAI,CAACD,QAAQ,OAAO;QAAEE,MAAM;QAAMC,IAAI;IAAK;IAC3C,MAAMF,WAAW,MAAMH,UAAUM,UAAU,CAAC,SAASC,GAAG,CAACL,QAAQM,GAAG;IACpE,MAAMJ,OAAOD,SAASM,MAAM,IAAKN,iBAAAA,SAASO,IAAI,cAAbP,iBAAmB,CAAC,IAAiC;IACtF,OAAO;QAAEC;QAAMC,IAAIM,QAAQP,SAASA,CAAAA,wBAAAA,IAAM,CAAC,QAAQ,MAAKH;IAAM;AAChE;AAEA,OAAO,MAAMW,sBAAuC,OAAOC;cAQ1CC,aASRD,cA6FyEC;QAtFnBA,aA0ExCA,cAcwBC;IA/G7C,MAAM,EAAED,GAAG,EAAEd,SAAS,EAAEgB,MAAM,EAAEC,GAAG,EAAE,GAAGJ;IACxC,MAAM3C,gBAAQ2C,QAAQK,QAAQ,oBAAhBL,QAAQK,QAAQ,MAAhBL,SAAmB,iCAAkB/F,eAAe;IAClE,MAAMqG,QAAQ,IAAI1D,aAAaS;IAC/B,MAAMkD,OAAO,CAACC,UAAuC;YAAEC,SAAS,EAAE;WAAKH,MAAMlD,OAAO;YAAIoD;;IAExF,MAAME,UAAU5G,iBAAiBmG,IAAIU,MAAM;IAC3C,IAAI,CAACD,SAAS,OAAOH,KAAK9E;IAC1B,MAAM4D,UAASY,cAAAA,IAAIZ,MAAM,YAAVY,cAAc;IAC7B,IAAIlG,0BAA0B2G,YAAY,CAACrB,QAAQ,OAAOkB,KAAK7E;IAC/D,uEAAuE;IACvE,kDAAkD;IAClD,MAAMwE,OAAO,MAAMhB,SAASC,WAAWc,IAAIb,KAAK,EAAEC;IAClD,IAAI,CAACa,KAAKV,EAAE,EAAE,OAAOe,KAAK7E;IAC1B,MAAMiC,UAAU,MAAM3C,iBAAiB;QACrCoE,OAAOa,IAAIb,KAAK;QAChBC;QACAuB,GAAG,GAAEZ,eAAAA,QAAQY,GAAG,YAAXZ,eAAe;QACpBT,MAAMW,KAAKX,IAAI;QACfmB;IACF;IACA,IAAI,CAAC/C,QAAQS,MAAM,EAAE,OAAOmC,KAAK5E;IAEjC,MAAMkF,SAASH,YAAY;IAC3B,MAAMhD,OAAOmD,SAASrH,yBAAyBI,eAAcqG,cAAAA,IAAIU,MAAM,qBAAVV,WAAY,CAAC,OAAO;IACjF,MAAMxC,WAAWoD,SAAS/E,6BAA6BmE,IAAIa,KAAK,CAACxC,KAAK,CAAC,GAAG5E;IAC1E,MAAMqH,YAAYzF,2BAA2ByF,SAAS,CAAC1D;IACvD,MAAM2D,YAAY5G,6BAA6B2G;IAC/C,MAAME,OAAO,IAAMD,YAAYV,MAAMvD,KAAK,CAACT,YAAY;IACvD,MAAM4E,MAAM,CAACC,UAAuBC,MAAyBC,UAC3DlH,aAAa;YACXkD;YACAiE,QAAQrF;YACRkF;YACAI,OAAO;gBAACH,SAAS,SAAS3G,sBAAsBD;aAAsB;YACtEuG,WAAW9D,KAAKuE,GAAG,CAAC,GAAGvE,KAAKwE,GAAG,CAACJ,SAASJ;WACrCjH,gBAAgB,CAAC,cAAc,CAAC0H,QAAQ,GAAG;YAAEA,UAAU1H,gBAAgB,CAAC,cAAc,CAAC0H,QAAQ;QAAC,IAAI,CAAC;YACzGC,QAAQ;WACJxB,SAAS;YAAEA;QAAO,IAAI,CAAC;IAG/B,gBAAgB;IAChB,MAAMyB,OAAO;QAAExC,OAAOa,IAAIb,KAAK;QAAEC;QAAQwC,KAAKhB,SAAS,OAAOZ,IAAI6B,SAAS;QAAE1B;IAAI;IACjF,IAAI2B;IACJ,IAAIlB,QAAQ;QACVkB,WAAWxD,aAAaZ,SAASD;IACnC,OAAO;YAY0BZ;QAX/B,MAAMc,WACJ8C,YAAY,aACR7F,wBAAwB,MAAMD,iBAAiBuE,WAAW;YAAEC,OAAOa,IAAIb,KAAK;YAAEC;YAAQwC,KAAK5B,IAAI6B,SAAS;QAAC,MACzG;QACN,MAAMhF,SAAS,MAAMoE,IACnB;YAAC;gBAAEc,MAAM;gBAAQC,SAAS1E,oBAAoB;oBAAEE;oBAAUC;oBAAMC;oBAASC;gBAAS;YAAG;SAAE,EACvF,QACAvC;QAEFiF,MAAMzD,GAAG,CAACC;QACV,IAAIA,OAAOoF,IAAI,KAAK,WAAW,OAAO;YAAEzB,SAAS,EAAE;WAAKH,MAAMlD,OAAO;YAAI+E,SAAS;;QAClFJ,WAAWpH,qBAAoBmC,uBAAAA,OAAOsF,OAAO,CAACrD,IAAI,CAAC,CAACsD,MAAQA,IAAIC,IAAI,KAAK/H,+CAA1CuC,qBAAsEU,KAAK;IAC5G;IACA,MAAM+E,YAAY1B,SAASpH,0BAA0B2E,MAAM,GAAGoE;IAC9D,IAAIC,OAAO,MAAMvH,oBAAoByC,SAASoE,UAAUH,MAAMW;IAC9D,IAAI,CAACE,KAAK3E,MAAM,CAACM,MAAM,IAAI,CAACyC,QAAQ4B,OAAO,MAAMvH,oBAAoByC,SAASY,aAAaZ,SAASD,OAAOkE;IAC3G,IAAIa,KAAKC,QAAQ,CAACtE,MAAM,EAAEuE,QAAQC,IAAI,CAAC,4BAA4B;QAAEC,OAAO5C,IAAI6C,GAAG;QAAEJ,UAAUD,KAAKC,QAAQ;IAAC;IAC7G,IAAI,CAACD,KAAK3E,MAAM,CAACM,MAAM,EAAE,OAAOmC,KAAK3E;IACrC,MAAM,EAAEkC,MAAM,EAAE,GAAG2E;IAEnB,yCAAyC;IACzC,IAAItB,WAAwB;QAAC;YAAEa,MAAM;YAAQC,SAASpE,sBAAsBJ,UAAUK;QAAQ;KAAE;IAChG,IAAIiF,UAAiC;IACrC,IAAK,IAAIC,UAAU,GAAGA,UAAU5I,8BAA8B6G,SAAS,GAAG+B,WAAW,EAAG;YAIlDlG;QAHpC,MAAMA,SAAS,MAAMoE,IAAIC,UAAU,UAAUJ;QAC7CT,MAAMzD,GAAG,CAACC;QACV,IAAIA,OAAOoF,IAAI,KAAK,WAAW,OAAO;YAAEzB,SAAS,EAAE;WAAKH,MAAMlD,OAAO;YAAI+E,SAAS;;QAClF,MAAMc,SAASvI,sBAAqBoC,wBAAAA,OAAOsF,OAAO,CAACrD,IAAI,CAAC,CAACsD,MAAQA,IAAIC,IAAI,KAAKhI,iDAA1CwC,sBAAwEU,KAAK;QACjH,MAAMS,QAAQgF,SAAS5I,qBAAqB4I,QAAQnF,UAAU;QAC9D,IAAIG,OAAO8E,UAAU9E;QACrB,IAAIA,SAAUA,CAAAA,MAAMiF,IAAI,CAAC9E,MAAM,IAAI,EAAC6E,0BAAAA,OAAQE,QAAQ,CAAC/E,MAAM,CAAD,GAAI;QAC9D+C,WAAW;eACNA;YACH;gBAAEa,MAAM;gBAAaC,SAAS,CAAC,cAAc,EAAE3H,4BAA4B,CAAC,CAAC;YAAC;YAC9E;gBAAE0H,MAAM;gBAAQC,SAASjE,sBAAsBC;YAAO;SACvD;IACH;IACA,IAAI,CAAC8E,SAAS,OAAOxC,KAAK1E;IAE1B,MAAMuH,SAA0B;QAC9BP,OAAO5C,IAAI6C,GAAG;QACd1D,OAAOa,IAAIb,KAAK;QAChBC;QACAqB;QACAoB,WAAW7B,IAAI6B,SAAS;QACxBrE,UAAUoD,SAASpE,IAAIwD,IAAIa,KAAK,KAAK,oBAAoBrD;QACzDC;QACAyF,UAAUJ,QAAQG,IAAI;QACtBG,KAAKN,QAAQM,GAAG;QAChBvF;QACAmD,MAAM8B,QAAQ9B,IAAI,CAAC7C,MAAM;QACzBkF,MAAMzC,SAASpE,KAAIwD,eAAAA,IAAIU,MAAM,qBAAVV,YAAY,CAAC,OAAO,KAAK,OAAO;QACnDsD,aAAanD,IAAIoD,OAAO;IAC1B;IACA,IAAIT,QAAQ9B,IAAI,CAAC7C,MAAM,EAAE;QACvBuE,QAAQc,IAAI,CAAC,yCAAyC;YAAEZ,OAAO5C,IAAI6C,GAAG;YAAE3E,UAAU4E,QAAQ5E,QAAQ;QAAC;IACrG;IACA,iEAAiE;IACjE,MAAMgB,UACHM,UAAU,CAAC,QACXC,GAAG,CAACO,IAAIb,KAAK,EACbK,UAAU,CAAC9F,wBACX+F,GAAG,CAACO,IAAI6C,GAAG,EACXY,GAAG,CAAC,aAAMC,KAAKC,KAAK,CAACD,KAAKE,SAAS,CAACT;QAA8BU,SAAS,GAAE7D,iBAAAA,IAAI6D,SAAS,YAAb7D,iBAAiB9E,qBAAqBiF;;IACtH,OAAO;QACLK,SAAS;YAAC5G,gBAAgBoG,KAAKmD,QAAQ3G,KAAIyD,aAAAA,KAAKX,IAAI,qBAATW,UAAW,CAAC,YAAY,KAAK;SAAM;OAC3EI,MAAMlD,OAAO;AAEpB,EAAC;AAED;;;;;CAKC,GACD,OAAO,eAAe2G,0BACpB/D,OAA8B;IAE9B,MAAMU,UAAmC5G,iBAAiBkG,QAAQW,MAAM;IACxE,IAAI,CAACD,WAAW,CAACnH,wBAAwBsF,QAAQ,CAAC6B,UAAU;QAC1D,OAAO;YAAEsD,QAAQ;YAAKC,OAAO;QAA4E;IAC3G;IACA,IAAIlK,0BAA0B2G,YAAY,CAACV,QAAQX,MAAM,EAAE;QACzD,OAAO;YAAE2E,QAAQ;YAAKC,OAAOvI;QAAwB;IACvD;IACA,MAAMwE,OAAO,MAAMhB,SAASc,QAAQb,SAAS,EAAEa,QAAQZ,KAAK,EAAEY,QAAQX,MAAM;IAC5E,IAAI,CAACa,KAAKV,EAAE,EAAE,OAAO;QAAEwE,QAAQ;QAAKC,OAAO;IAAe;IAC1D,MAAMtG,UAAU,MAAM3C,iBAAiB;QACrCoE,OAAOY,QAAQZ,KAAK;QACpBC,QAAQW,QAAQX,MAAM;QACtBuB,KAAKZ,QAAQY,GAAG;QAChBrB,MAAMW,KAAKX,IAAI;QACfmB;IACF;IACA,OAAO/C,QAAQS,MAAM,GAAG,OAAO;QAAE4F,QAAQ;QAAKC,OAAOtI;IAA2B;AAClF;AAEA;;;;CAIC,GACD,OAAO,SAASuI;IACd1I,kBAAkB,WAAWuE,qBAAqB;QAAEoE,WAAW5I;IAA+B;IAC9FH,uBAAuB,WAAW2I;AACpC"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-insight-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { ResolvedPluginFigureReader } from '@aglyn/aglyn/plugin-manager/plugin-figures'\nimport {\n AI_INSIGHT_ASK_SURFACES,\n AI_INSIGHT_DIGEST_DAYS,\n AI_INSIGHT_DIGEST_READERS,\n AI_INSIGHT_QUESTION_MAX_CHARS,\n AI_INSIGHTS_COLLECTION,\n aiInsightDays,\n aiInsightOutput,\n aiInsightSurface,\n aiInsightSurfaceNeedsSite,\n type AiInsightRecord,\n type AiInsightSurface,\n type AiInsightTable,\n} from '../model/ai-insight'\nimport { aiHostPublishContext, aiSiteStatusTable } from '../model/ai-host-publish-context'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n AI_ACCEPTABLE_USE_BLOCK,\n runAiRequest,\n type AiMessage,\n type AiResult,\n type AiSystemBlock,\n type AiUsage,\n} from '../runtime/ai-runtime'\nimport { AI_GENERATION_MAX_ATTEMPTS } from '../runtime/ai-generation-bounds'\nimport { checkAiInsightAnswer, type AiInsightCheck } from '../runtime/ai-insight-check'\nimport {\n AI_INSIGHT_ANSWER_TOOL_NAME,\n AI_INSIGHT_READ_TOOL_NAME,\n aiInsightAnswerTool,\n aiInsightReadTool,\n parseAiInsightAnswer,\n parseAiInsightReads,\n type AiInsightReadRequest,\n} from '../tools/ai-insight-tool'\nimport { aiDatasetCatalog } from '../insights/ai-dataset-catalog'\nimport {\n aiInsightDatasetCatalog,\n aiInsightReaderCatalog,\n aiInsightReaderDays,\n aiInsightReaders,\n aiInsightTableText,\n readAiInsightTables,\n} from '../insights/ai-insight-readers'\nimport { assistExchangeExpiry } from '../usage/assist-usage'\nimport { registerAiJobAdmission, type AiJobAdmissionContext, type AiJobAdmissionRefusal } from './ai-job-admission'\nimport { AI_INSIGHT_READ_MAX_TOKENS, AI_JOB_INSIGHT_STEP_BUDGET, AI_JOB_INSIGHT_STEP_MINIMUM_MS } from './ai-job-insight-budget'\nimport type { AiJobStepContext, AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The `insight` step (AGL-2915): a question about a site's figures, answered\n * from tables code read, with every insight traced to the rows it cites.\n *\n * ## Two exchanges, and the model never writes a query\n *\n * 1. **The reads.** The model is shown the question and the readers this job\n * may read — each an id, what it answers, its windows and parameters — and\n * answers `read_figures` with a choice among them. It names nothing else: no\n * collection, no field path, no filter. A choice that names no reader this\n * job may read is dropped, and a job left with none reads the surface's\n * defaults. A weekly digest makes no read call: code reads the digest's\n * readers.\n * 2. **The answer.** Code reads the tables through the owners' readers, and the\n * model is shown them and answers `submit_insights`. The trace\n * (`runtime/ai-insight-check.ts`) keeps each insight whose every number is\n * in a row it cites and leaves out the rest. An answer that is not a call,\n * or whose every insight was left out, is asked for once more with the\n * reasons; what the second answer keeps is the answer.\n *\n * Both calls share one allowance of output, twice the routing ceiling, which\n * is what the step's least time is planned against.\n *\n * ## What reaches the model\n *\n * The question, the names and descriptions of the readers, the tables, the\n * site's publish state (published or not, its address, its pages' paths), and\n * for a datasets question the names of the datasets the member may see with\n * their fields' names and types. A table holds counts, sums and rates labeled\n * by a page path, a referring site, a form's, a service's, a product's or a\n * campaign's name, or the values of a dataset field with at most sixty\n * different values that at least three records share. No\n * record, visitor, contact, order, booking, submission or recipient is read\n * into a prompt, and every text cell is masked of email addresses and phone\n * numbers on its way into a table.\n *\n * ## What it writes\n *\n * The answer, at `orgs/{orgId}/aiInsights/{jobId}` under the job's own\n * expiry, and an output that names it and says how many insights it holds.\n * Nothing else: no draft, no setting, no message.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport const AI_INSIGHT_NO_SURFACE_COPY = 'This question does not say which page it was asked from.'\nexport const AI_INSIGHT_NO_SITE_COPY = 'Open the site whose numbers you are asking about.'\nexport const AI_INSIGHT_NO_READERS_COPY =\n 'There are no figures to answer from here yet: the features this page reports on are not on for this workspace.'\nexport const AI_INSIGHT_NO_TABLES_COPY = 'The figures could not be read. Try again in a moment.'\nexport const AI_INSIGHT_UNREADABLE_COPY = 'The AI did not answer with insights. Try asking in different words.'\n\n/** The digest's question: what a week's insights cover, in the order a person reads them. */\nexport const AI_INSIGHT_DIGEST_QUESTION =\n 'What changed on this site this week? Write the week’s insights: the change in traffic, the most viewed page, the form that turned the most views into submissions, a campaign that did better than the rest, and any day that stood out — only those the tables support.'\n\n/** The rules both calls are sent: static, byte-identical for every workspace. */\nexport const AI_INSIGHT_RULES = [\n 'You answer a workspace owner’s questions about their own figures: page views, orders, bookings, forms, campaigns, A/B tests and datasets. You never see a record, and you never write a query: code reads figures through fixed readers and gives you their tables.',\n '',\n `When you are given a question and a list of readers, call ${AI_INSIGHT_READ_TOOL_NAME} once with the readers whose tables answer it, most useful first, and the window each should cover. Choose only readers from the list, with their parameters' values taken from what the list and the question give you.`,\n '',\n `When you are given tables, call ${AI_INSIGHT_ANSWER_TOOL_NAME} once. Each table has a handle (t1, t2, …) and numbered rows. Rules for the insights:`,\n '- Say what matters most to the question first. Leave out what the tables do not support.',\n '- Every insight cites the table and the rows its figures come from.',\n '- Every number you write appears in a row you cite, as the table shows it or rounded. Never add, subtract, average or otherwise compute a figure yourself. The only other numbers you may write are the length of a table’s window, a date in it, and how many rows you cite.',\n '- Say a figure rose or fell only when a change you quote shows it.',\n '- Name nobody, and write no email address or phone number.',\n '- Plain sentences: no markdown, no headings, no lists.',\n '- When the tables cannot answer part of the question, say so in gap in one sentence with no numbers; otherwise gap is null.',\n '- Whether the site is published, and its address, come only from the Site status table. Page views and visitors never show whether a site is live: never say or suggest a site is not live, not launched or not published because of its traffic.',\n '- Write in the language of the question.',\n].join('\\n')\n\n/** The system blocks both calls send: the rules and the platform's acceptable-use rules. */\nexport const AI_JOB_INSIGHT_SYSTEM: AiSystemBlock[] = [\n { text: `${AI_INSIGHT_RULES}\\n\\n${AI_ACCEPTABLE_USE_BLOCK}`, cacheBreakpoint: true },\n]\n\nconst ZERO_USAGE: AiUsage = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** What the step's calls have spent so far, in the outcome's shape. */\nclass InsightSpend {\n usage: AiUsage = { ...ZERO_USAGE }\n estCostUsd = 0\n stopReason: string | null = null\n\n constructor(readonly model: string) {}\n\n add(result: AiResult): void {\n this.usage = {\n inputTokens: this.usage.inputTokens + result.usage.inputTokens,\n outputTokens: this.usage.outputTokens + result.usage.outputTokens,\n cacheReadTokens: this.usage.cacheReadTokens + result.usage.cacheReadTokens,\n cacheWriteTokens: this.usage.cacheWriteTokens + result.usage.cacheWriteTokens,\n }\n this.estCostUsd = Math.round((this.estCostUsd + result.estCostUsd) * 1_000_000) / 1_000_000\n this.stopReason = result.stopReason\n }\n\n outcome(): Pick<AiJobStepOutcome, 'usage' | 'estCostUsd' | 'model' | 'stopReason' | 'effort'> {\n return { usage: this.usage, estCostUsd: this.estCostUsd, model: this.model, stopReason: this.stopReason, effort: null }\n }\n}\n\n/** The read call's user turn: the question, the window asked for, the readers and, for datasets, the datasets. */\nexport function aiInsightReadPrompt(input: {\n question: string\n days: number\n readers: readonly ResolvedPluginFigureReader[]\n datasets: string | null\n}): string {\n return [\n `Question: ${input.question}`,\n `The window picked on the page: ${input.days} days. Use it unless the question names another window a reader lists.`,\n 'Readers you may read here:',\n aiInsightReaderCatalog(input.readers),\n ...(input.datasets === null ? [] : ['', 'Datasets you may read, with their fields:', input.datasets]),\n ].join('\\n')\n}\n\n/** The answer call's user turn: the question and every table. */\nexport function aiInsightAnswerPrompt(question: string, tables: readonly AiInsightTable[]): string {\n return [`Question: ${question}`, '', 'Tables:', '', tables.map(aiInsightTableText).join('\\n\\n')].join('\\n')\n}\n\n/** What a re-ask tells the model: why nothing it wrote could be kept. */\nexport function aiInsightReaskMessage(check: AiInsightCheck | null): string {\n const reasons = check?.findings.length\n ? `None of those insights could be kept: ${[...new Set(check.findings)].slice(0, 8).join('; ')}.`\n : `That answer did not come through ${AI_INSIGHT_ANSWER_TOOL_NAME}.`\n return `${reasons} Answer again with ${AI_INSIGHT_ANSWER_TOOL_NAME}: cite the rows each insight's figures are in, and write every number as a row you cite shows it.`\n}\n\n/** The reads a surface falls back to when the model named none it may read: the digest's own readers. */\nfunction defaultReads(readers: readonly ResolvedPluginFigureReader[], days: number): AiInsightReadRequest[] {\n const ids = readers.map(({ reader }) => reader.id)\n const preferred = AI_INSIGHT_DIGEST_READERS.filter((id) => ids.includes(id))\n const chosen = preferred.length ? preferred : ids\n return chosen.map((id) => ({\n reader: id,\n days: aiInsightReaderDays(readers.find(({ reader }) => reader.id === id)?.reader.windows ?? [], days),\n params: {},\n }))\n}\n\nasync function readHost(\n firestore: Firestore,\n orgId: string,\n hostId: string | null,\n): Promise<{ host: Record<string, unknown> | null; ok: boolean }> {\n if (!hostId) return { host: null, ok: true }\n const snapshot = await firestore.collection('hosts').doc(hostId).get()\n const host = snapshot.exists ? ((snapshot.data() ?? {}) as Record<string, unknown>) : null\n return { host, ok: Boolean(host) && host?.['orgId'] === orgId }\n}\n\nexport const runAiJobInsightStep: AiJobStepRunner = async (context: AiJobStepContext) => {\n const { job, firestore, signal, now } = context\n const model = context.modelFor?.('job.insight') ?? aiModelForStep('job.insight')\n const spend = new InsightSpend(model)\n const stop = (failure: string): AiJobStepOutcome => ({ outputs: [], ...spend.outcome(), failure })\n\n const surface = aiInsightSurface(job.inputs)\n if (!surface) return stop(AI_INSIGHT_NO_SURFACE_COPY)\n const hostId = job.hostId ?? null\n if (aiInsightSurfaceNeedsSite(surface) && !hostId) return stop(AI_INSIGHT_NO_SITE_COPY)\n // The door checks the caller belongs to the org; that the site does is\n // checked here, before anything about it is read.\n const site = await readHost(firestore, job.orgId, hostId)\n if (!site.ok) return stop(AI_INSIGHT_NO_SITE_COPY)\n const readers = await aiInsightReaders({\n orgId: job.orgId,\n hostId,\n org: context.org ?? null,\n host: site.host,\n surface,\n })\n if (!readers.length) return stop(AI_INSIGHT_NO_READERS_COPY)\n\n const digest = surface === 'digest'\n const days = digest ? AI_INSIGHT_DIGEST_DAYS : aiInsightDays(job.inputs?.['days'])\n const question = digest ? AI_INSIGHT_DIGEST_QUESTION : job.brief.slice(0, AI_INSIGHT_QUESTION_MAX_CHARS)\n const maxTokens = AI_JOB_INSIGHT_STEP_BUDGET.maxTokens(model)\n const allowance = AI_GENERATION_MAX_ATTEMPTS * maxTokens\n const left = () => allowance - spend.usage.outputTokens\n const ask = (messages: AiMessage[], tool: 'read' | 'answer', ceiling: number) =>\n runAiRequest({\n model,\n system: AI_JOB_INSIGHT_SYSTEM,\n messages,\n tools: [tool === 'read' ? aiInsightReadTool() : aiInsightAnswerTool()],\n maxTokens: Math.max(1, Math.min(ceiling, left())),\n ...(AI_ROUTING_TABLE['job.insight'].thinking ? { thinking: AI_ROUTING_TABLE['job.insight'].thinking } : {}),\n stream: false,\n ...(signal ? { signal } : {}),\n })\n\n // 1. The reads.\n const base = { orgId: job.orgId, hostId, uid: digest ? null : job.createdBy, now }\n let requests: AiInsightReadRequest[]\n if (digest) {\n requests = defaultReads(readers, days)\n } else {\n const datasets =\n surface === 'datasets'\n ? aiInsightDatasetCatalog(await aiDatasetCatalog(firestore, { orgId: job.orgId, hostId, uid: job.createdBy }))\n : null\n const result = await ask(\n [{ role: 'user', content: aiInsightReadPrompt({ question, days, readers, datasets }) }],\n 'read',\n AI_INSIGHT_READ_MAX_TOKENS,\n )\n spend.add(result)\n if (result.kind === 'refusal') return { outputs: [], ...spend.outcome(), refused: true }\n requests = parseAiInsightReads(result.toolUse.find((use) => use.name === AI_INSIGHT_READ_TOOL_NAME)?.input)\n }\n const maxTables = digest ? AI_INSIGHT_DIGEST_READERS.length : undefined\n let read = await readAiInsightTables(readers, requests, base, maxTables)\n if (!read.tables.length && !digest) read = await readAiInsightTables(readers, defaultReads(readers, days), base)\n if (read.refusals.length) console.warn('ai insight reads refused', { jobId: job.$id, refusals: read.refusals })\n if (!read.tables.length) return stop(AI_INSIGHT_NO_TABLES_COPY)\n // A site's question is told whether the site is live, so traffic is never\n // read as launch status. Appended, so the readers' refs stay as they were.\n const tables =\n !digest && site.host\n ? [...read.tables, aiSiteStatusTable(aiHostPublishContext(site.host), `t${read.tables.length + 1}`)]\n : read.tables\n\n // 2. The answer, and at most one re-ask.\n let messages: AiMessage[] = [{ role: 'user', content: aiInsightAnswerPrompt(question, tables) }]\n let checked: AiInsightCheck | null = null\n for (let attempt = 0; attempt < AI_GENERATION_MAX_ATTEMPTS && left() > 0; attempt += 1) {\n const result = await ask(messages, 'answer', maxTokens)\n spend.add(result)\n if (result.kind === 'refusal') return { outputs: [], ...spend.outcome(), refused: true }\n const answer = parseAiInsightAnswer(result.toolUse.find((use) => use.name === AI_INSIGHT_ANSWER_TOOL_NAME)?.input)\n const check = answer ? checkAiInsightAnswer(answer, tables) : null\n if (check) checked = check\n if (check && (check.kept.length || !answer?.insights.length)) break\n messages = [\n ...messages,\n { role: 'assistant', content: `Answered with ${AI_INSIGHT_ANSWER_TOOL_NAME}.` },\n { role: 'user', content: aiInsightReaskMessage(check) },\n ]\n }\n if (!checked) return stop(AI_INSIGHT_UNREADABLE_COPY)\n\n const record: AiInsightRecord = {\n jobId: job.$id,\n orgId: job.orgId,\n hostId,\n surface,\n createdBy: job.createdBy,\n question: digest ? str(job.brief) || 'Weekly insights' : question,\n days,\n insights: checked.kept,\n gap: checked.gap,\n tables,\n left: checked.left.length,\n week: digest ? str(job.inputs?.['week']) || null : null,\n createdAtMs: now.getTime(),\n }\n if (checked.left.length) {\n console.info('ai insight left out untraced insights', { jobId: job.$id, findings: checked.findings })\n }\n // Keyed by the job, so a step run again replaces its own answer.\n await firestore\n .collection('orgs')\n .doc(job.orgId)\n .collection(AI_INSIGHTS_COLLECTION)\n .doc(job.$id)\n .set({ ...(JSON.parse(JSON.stringify(record)) as AiInsightRecord), expiresAt: job.expiresAt ?? assistExchangeExpiry(now) })\n return {\n outputs: [aiInsightOutput(job, record, str(site.host?.['subdomain']) || null)],\n ...spend.outcome(),\n }\n}\n\n/**\n * Whether a question may be asked here: a surface a person asks from, a site\n * of the job's own org where the surface needs one, and at least one reader\n * the workspace may read. A digest is made by the weekly sweep and never at a\n * door.\n */\nexport async function aiInsightAdmissionRefusal(\n context: AiJobAdmissionContext,\n): Promise<AiJobAdmissionRefusal | null> {\n const surface: AiInsightSurface | null = aiInsightSurface(context.inputs)\n if (!surface || !AI_INSIGHT_ASK_SURFACES.includes(surface)) {\n return { status: 400, error: 'Ask about your numbers from a site’s Analytics, Data or CRM Reports page.' }\n }\n if (aiInsightSurfaceNeedsSite(surface) && !context.hostId) {\n return { status: 400, error: AI_INSIGHT_NO_SITE_COPY }\n }\n const site = await readHost(context.firestore, context.orgId, context.hostId)\n if (!site.ok) return { status: 404, error: 'Unknown site' }\n const readers = await aiInsightReaders({\n orgId: context.orgId,\n hostId: context.hostId,\n org: context.org,\n host: site.host,\n surface,\n })\n return readers.length ? null : { status: 403, error: AI_INSIGHT_NO_READERS_COPY }\n}\n\n/**\n * The insight kind's registrations, called by the console surface\n * (`registerAiJobKinds` in `server.ts`), never made by importing this module\n * (AGL-3025).\n */\nexport function registerAiInsightJob(): void {\n registerAiJobStep('insight', runAiJobInsightStep, { minimumMs: AI_JOB_INSIGHT_STEP_MINIMUM_MS })\n registerAiJobAdmission('insight', aiInsightAdmissionRefusal)\n}\n"],"names":["AI_INSIGHT_ASK_SURFACES","AI_INSIGHT_DIGEST_DAYS","AI_INSIGHT_DIGEST_READERS","AI_INSIGHT_QUESTION_MAX_CHARS","AI_INSIGHTS_COLLECTION","aiInsightDays","aiInsightOutput","aiInsightSurface","aiInsightSurfaceNeedsSite","aiHostPublishContext","aiSiteStatusTable","AI_ROUTING_TABLE","aiModelForStep","AI_ACCEPTABLE_USE_BLOCK","runAiRequest","AI_GENERATION_MAX_ATTEMPTS","checkAiInsightAnswer","AI_INSIGHT_ANSWER_TOOL_NAME","AI_INSIGHT_READ_TOOL_NAME","aiInsightAnswerTool","aiInsightReadTool","parseAiInsightAnswer","parseAiInsightReads","aiDatasetCatalog","aiInsightDatasetCatalog","aiInsightReaderCatalog","aiInsightReaderDays","aiInsightReaders","aiInsightTableText","readAiInsightTables","assistExchangeExpiry","registerAiJobAdmission","AI_INSIGHT_READ_MAX_TOKENS","AI_JOB_INSIGHT_STEP_BUDGET","AI_JOB_INSIGHT_STEP_MINIMUM_MS","registerAiJobStep","AI_INSIGHT_NO_SURFACE_COPY","AI_INSIGHT_NO_SITE_COPY","AI_INSIGHT_NO_READERS_COPY","AI_INSIGHT_NO_TABLES_COPY","AI_INSIGHT_UNREADABLE_COPY","AI_INSIGHT_DIGEST_QUESTION","AI_INSIGHT_RULES","join","AI_JOB_INSIGHT_SYSTEM","text","cacheBreakpoint","ZERO_USAGE","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","str","value","trim","InsightSpend","add","result","usage","estCostUsd","Math","round","stopReason","outcome","model","effort","aiInsightReadPrompt","input","question","days","readers","datasets","aiInsightAnswerPrompt","tables","map","aiInsightReaskMessage","check","reasons","findings","length","Set","slice","defaultReads","ids","reader","id","preferred","filter","includes","chosen","find","windows","params","readHost","firestore","orgId","hostId","snapshot","host","ok","collection","doc","get","exists","data","Boolean","runAiJobInsightStep","context","job","site","signal","now","modelFor","spend","stop","failure","outputs","surface","inputs","org","digest","brief","maxTokens","allowance","left","ask","messages","tool","ceiling","system","tools","max","min","thinking","stream","base","uid","createdBy","requests","role","content","kind","refused","toolUse","use","name","maxTables","undefined","read","refusals","console","warn","jobId","$id","checked","attempt","answer","kept","insights","record","gap","week","createdAtMs","getTime","info","set","JSON","parse","stringify","expiresAt","aiInsightAdmissionRefusal","status","error","registerAiInsightJob","minimumMs"],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
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 { NodesMap } from '@aglyn/aglyn/types/nodes';
|
|
18
|
+
import type { AiLayoutPage, AiLayoutTargets } from '../layout-language/ai-layout-links';
|
|
19
|
+
import type { AiJob } from '../model/ai-jobs.types';
|
|
20
|
+
import { type AiSiteInventory } from '../model/ai-site-inventory';
|
|
21
|
+
import type { AiGenerationCheck, AiValidatedTree } from '../runtime/ai-doctrine';
|
|
22
|
+
import { type AiDoctrineViolation } from '../runtime/ai-doctrine-validators';
|
|
23
|
+
import type { AiLoadEstimate } from '../runtime/ai-palette';
|
|
24
|
+
import type { AiSystemBlock } from '../runtime/ai-runtime';
|
|
25
|
+
/**
|
|
26
|
+
* A site's layout built in the compact layout language (AGL-3660): the layout
|
|
27
|
+
* step's pass for a guided start's and a build's layout unit. The model
|
|
28
|
+
* designs the header and the footer — their bands, the call to action, the
|
|
29
|
+
* footer's columns and every word — in one short answer through
|
|
30
|
+
* `submit_frame`, and the compiler writes the layout around what the site
|
|
31
|
+
* owes it: its name as the brand, its pages as the navigation and the phone
|
|
32
|
+
* menu, the Layout Slot, and the footer at the bottom of a short page
|
|
33
|
+
* (`ai-layout-frame.ts`).
|
|
34
|
+
*/
|
|
35
|
+
/** The most a frame's answer asks for: two sections of a few blocks each. */
|
|
36
|
+
export declare const AI_JOB_LAYOUT_LANGUAGE_TOKENS = 1200;
|
|
37
|
+
/** A language layout pass's time, on the tier the layout step is served from. */
|
|
38
|
+
export declare const AI_JOB_LAYOUT_LANGUAGE_BUDGET: import("./ai-job-budget").AiJobStepBudget;
|
|
39
|
+
/** The generation kind a frame is asked under, which the doctrine scopes. */
|
|
40
|
+
export declare const AI_LAYOUT_FRAME_KIND = "layout-frame";
|
|
41
|
+
/** The language, as the frame door's cached instructions. */
|
|
42
|
+
export declare const AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS: readonly AiSystemBlock[];
|
|
43
|
+
/** The site's name as the brand: the name the job was given, else the token the site's own name fills. */
|
|
44
|
+
export declare function aiLayoutSiteName(job: Pick<AiJob, 'inputs'>): string;
|
|
45
|
+
/** The pages a frame's navigation links: the planned pages, else the site's own, in order. */
|
|
46
|
+
export declare function aiLayoutNavPages(job: Pick<AiJob, 'inputs'>, inventory: AiSiteInventory | null): AiLayoutPage[];
|
|
47
|
+
/** The home page the brand links: the planned home, else the site's. */
|
|
48
|
+
export declare function aiLayoutHomeId(pages: readonly AiLayoutPage[], inventory: AiSiteInventory | null): string | null;
|
|
49
|
+
/** The frame's user turn: the site, its brief, its pages and its form. */
|
|
50
|
+
export declare function aiLayoutFramePrompt(input: {
|
|
51
|
+
job: Pick<AiJob, 'brief' | '$id'>;
|
|
52
|
+
siteName: string;
|
|
53
|
+
pages: readonly AiLayoutPage[];
|
|
54
|
+
targets: AiLayoutTargets;
|
|
55
|
+
}): string;
|
|
56
|
+
/** A validated language layout. */
|
|
57
|
+
export interface AiLayoutFrameBuilt {
|
|
58
|
+
rootId: string;
|
|
59
|
+
nodes: NodesMap;
|
|
60
|
+
load: AiLoadEstimate | null;
|
|
61
|
+
/** What the last answer's gaps took out of the frame (`ai-layout-gaps.ts`). */
|
|
62
|
+
dropped: string[];
|
|
63
|
+
}
|
|
64
|
+
/** The check a frame answer is held to: read, compiled, stored and checked as a layout, with the layout door's own checks. */
|
|
65
|
+
export declare function aiLayoutFrameCheck(input: {
|
|
66
|
+
siteName: string;
|
|
67
|
+
homeId: string | null;
|
|
68
|
+
pages: readonly AiLayoutPage[];
|
|
69
|
+
targets: AiLayoutTargets;
|
|
70
|
+
extend: (tree: AiValidatedTree) => AiDoctrineViolation[];
|
|
71
|
+
}): AiGenerationCheck<AiLayoutFrameBuilt>;
|
|
72
|
+
/** What a frame's links resolve against: the site's pages and its form, as a page's do. */
|
|
73
|
+
export declare function aiLayoutFrameTargets(job: Pick<AiJob, 'brief' | 'inputs'>, inventory: AiSiteInventory | null): AiLayoutTargets;
|