@aglyn/plugins-ai 1.0.0-beta.244 → 1.0.0-beta.246

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.
Files changed (116) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.js +3 -1
  3. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  4. package/src/lib/components/ai-job-links.js +7 -0
  5. package/src/lib/components/ai-job-links.js.map +1 -1
  6. package/src/lib/components/ai-product-zones.d.ts +3 -1
  7. package/src/lib/components/ai-product-zones.js.map +1 -1
  8. package/src/lib/components/ai-products-hub-card.component.js +1 -1
  9. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  10. package/src/lib/components/ai-products-proposals.component.js +17 -9
  11. package/src/lib/components/ai-products-proposals.component.js.map +1 -1
  12. package/src/lib/components/ai-site-start-card.component.js +6 -4
  13. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  14. package/src/lib/components/ai-upsell-dialog.component.js +1 -1
  15. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  16. package/src/lib/jobs/ai-build-capabilities.js +162 -2
  17. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  18. package/src/lib/jobs/ai-job-build-step.d.ts +7 -0
  19. package/src/lib/jobs/ai-job-build-step.js +85 -16
  20. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  21. package/src/lib/jobs/ai-job-duplicate-step.d.ts +123 -0
  22. package/src/lib/jobs/ai-job-duplicate-step.js +269 -0
  23. package/src/lib/jobs/ai-job-duplicate-step.js.map +1 -0
  24. package/src/lib/jobs/ai-job-layout-language.js +13 -0
  25. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  26. package/src/lib/jobs/ai-job-navigation-step.d.ts +157 -0
  27. package/src/lib/jobs/ai-job-navigation-step.js +439 -0
  28. package/src/lib/jobs/ai-job-navigation-step.js.map +1 -0
  29. package/src/lib/jobs/ai-job-page-language.d.ts +2 -1
  30. package/src/lib/jobs/ai-job-page-language.js +21 -5
  31. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  32. package/src/lib/jobs/ai-job-page-sections.js +1 -1
  33. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  34. package/src/lib/jobs/ai-job-plan-step.d.ts +4 -2
  35. package/src/lib/jobs/ai-job-plan-step.js +16 -7
  36. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  37. package/src/lib/jobs/ai-job-products-step.d.ts +2 -2
  38. package/src/lib/jobs/ai-job-products-step.js +2 -2
  39. package/src/lib/jobs/ai-job-products-step.js.map +1 -1
  40. package/src/lib/jobs/ai-job-settings-step.d.ts +272 -0
  41. package/src/lib/jobs/ai-job-settings-step.js +695 -0
  42. package/src/lib/jobs/ai-job-settings-step.js.map +1 -0
  43. package/src/lib/jobs/ai-job-site-content.d.ts +20 -6
  44. package/src/lib/jobs/ai-job-site-content.js +28 -12
  45. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  46. package/src/lib/jobs/ai-job-site-datasets.d.ts +2 -0
  47. package/src/lib/jobs/ai-job-site-datasets.js +10 -3
  48. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  49. package/src/lib/jobs/ai-job-site-step.d.ts +3 -1
  50. package/src/lib/jobs/ai-job-site-step.js +70 -10
  51. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  52. package/src/lib/jobs/ai-site-publish.d.ts +62 -4
  53. package/src/lib/jobs/ai-site-publish.js +203 -56
  54. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  55. package/src/lib/layout-language/ai-layout-compiler.d.ts +7 -4
  56. package/src/lib/layout-language/ai-layout-compiler.js +26 -25
  57. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  58. package/src/lib/layout-language/ai-layout-frame-hollows.d.ts +41 -0
  59. package/src/lib/layout-language/ai-layout-frame-hollows.js +330 -0
  60. package/src/lib/layout-language/ai-layout-frame-hollows.js.map +1 -0
  61. package/src/lib/layout-language/ai-layout-frame.d.ts +18 -1
  62. package/src/lib/layout-language/ai-layout-frame.js +83 -13
  63. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  64. package/src/lib/layout-language/ai-layout-listings.d.ts +35 -0
  65. package/src/lib/layout-language/ai-layout-listings.js +34 -0
  66. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  67. package/src/lib/layout-language/ai-layout-tree.d.ts +2 -0
  68. package/src/lib/layout-language/ai-layout-tree.js +17 -0
  69. package/src/lib/layout-language/ai-layout-tree.js.map +1 -1
  70. package/src/lib/model/ai-build-job.d.ts +23 -0
  71. package/src/lib/model/ai-build-job.js +102 -13
  72. package/src/lib/model/ai-build-job.js.map +1 -1
  73. package/src/lib/model/ai-build-plan.d.ts +25 -0
  74. package/src/lib/model/ai-build-plan.js +45 -1
  75. package/src/lib/model/ai-build-plan.js.map +1 -1
  76. package/src/lib/model/ai-credit-estimate.d.ts +52 -0
  77. package/src/lib/model/ai-credit-estimate.js +51 -0
  78. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  79. package/src/lib/model/ai-job-activity.js +4 -1
  80. package/src/lib/model/ai-job-activity.js.map +1 -1
  81. package/src/lib/model/ai-jobs.types.d.ts +10 -2
  82. package/src/lib/model/ai-jobs.types.js +4 -1
  83. package/src/lib/model/ai-jobs.types.js.map +1 -1
  84. package/src/lib/model/ai-layout-menus.d.ts +104 -0
  85. package/src/lib/model/ai-layout-menus.js +518 -0
  86. package/src/lib/model/ai-layout-menus.js.map +1 -0
  87. package/src/lib/model/ai-products.d.ts +31 -1
  88. package/src/lib/model/ai-products.js +34 -1
  89. package/src/lib/model/ai-products.js.map +1 -1
  90. package/src/lib/model/ai-site-inventory.d.ts +15 -0
  91. package/src/lib/model/ai-site-inventory.js.map +1 -1
  92. package/src/lib/model/ai-site-job.d.ts +10 -0
  93. package/src/lib/model/ai-site-job.js +36 -9
  94. package/src/lib/model/ai-site-job.js.map +1 -1
  95. package/src/lib/model/ai-site-store-pages.js +1 -1
  96. package/src/lib/model/ai-site-store-pages.js.map +1 -1
  97. package/src/lib/runtime/ai-doctrine-validators.d.ts +53 -0
  98. package/src/lib/runtime/ai-doctrine-validators.js +166 -25
  99. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  100. package/src/lib/runtime/ai-doctrine.js +7 -2
  101. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  102. package/src/lib/runtime/ai-palette.generated.js +1 -1
  103. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  104. package/src/lib/runtime/ai-products-generation.d.ts +4 -2
  105. package/src/lib/runtime/ai-products-generation.js +12 -6
  106. package/src/lib/runtime/ai-products-generation.js.map +1 -1
  107. package/src/lib/runtime/site-inventory.d.ts +12 -0
  108. package/src/lib/runtime/site-inventory.js +29 -1
  109. package/src/lib/runtime/site-inventory.js.map +1 -1
  110. package/src/lib/server.js +12 -0
  111. package/src/lib/server.js.map +1 -1
  112. package/src/lib/testing/stand-in-dataset-index.js +37 -3
  113. package/src/lib/testing/stand-in-dataset-index.js.map +1 -1
  114. package/src/lib/tools/ai-products-tool.d.ts +3 -1
  115. package/src/lib/tools/ai-products-tool.js +15 -5
  116. package/src/lib/tools/ai-products-tool.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-upsell-dialog.component.tsx"],"sourcesContent":["'use client'\n\n/**\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 { aiAddonName } from '@aglyn/aglyn'\nimport { trackEvent } from '@aglyn/aglyn/app-utils/analytics-events'\nimport type { ConsoleWidgetUpgrade } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { mdiCreation } from '@aglyn/shared-data-mdi'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { HelpTip, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport {\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useEffect, useState, type ReactNode } from 'react'\nimport type { AiBriefKind } from './ai-brief-dialog.component'\n\n/**\n * \"Create with AI\" on a plan that could buy the AI add-on and has not\n * (AGL-3601). The shell mounts the entry with `entitled={false}` only where\n * the add-on is sold to this workspace, and the jobs route has said the plan\n * is the one thing missing; the entry then keeps its place on the page and\n * opens this instead of the brief, so the reader sees where the add-on is used.\n */\n\n/**\n * What a \"Create with AI\" or \"Ask AI\" entry makes: the brief kinds, an\n * automation, products, a function or variable, an overlay, and an answer\n * about the figures on a page.\n */\nexport type AiUpsellKind =\n | AiBriefKind\n | 'workflow'\n | 'product'\n | 'logic'\n | 'overlay'\n | 'funnel'\n | 'insight'\n\n/** What one entry's dialog says. */\nexport interface AiUpsellCopy {\n title: string\n does: string\n /** The entry's own label, which the button reads and the offer names; \"Create with AI\" when absent. */\n label?: string\n}\n\n/** The label every \"Create with AI\" entry reads. */\nexport const AI_UPSELL_CREATE_LABEL = 'Create with AI'\n\n/** What the dialog says one entry would do here. */\nexport const AI_UPSELL_COPY: Readonly<Record<AiUpsellKind, AiUpsellCopy>> = {\n page: {\n title: 'Create a page with AI',\n does:\n 'Describe the page you need and AI plans it from your brief, using your theme and ' +\n 'what the site already has. You confirm the plan, and the page is built as an ' +\n 'unpublished draft.',\n },\n template: {\n title: 'Create a page template with AI',\n does:\n 'Describe what each page should show and AI plans the template, filling what ' +\n 'changes from page to page from each record. You confirm the plan, and the template ' +\n 'is built as a draft in your library.',\n },\n layout: {\n title: 'Create a layout with AI',\n does:\n 'Describe what the layout should hold and AI plans it, linking your pages and placing ' +\n 'your navigation or menu component when the site has one. You confirm the plan, and ' +\n 'the layout is built as a draft no page uses until you assign it.',\n },\n form: {\n title: 'Create a form with AI',\n does:\n 'Describe what the form is for and AI plans its fields, its marketing consent and ' +\n 'where each submission goes. You confirm the plan, and the form is built as a draft.',\n },\n component: {\n title: 'Create a reusable component with AI',\n does:\n 'Describe what the component should show and AI plans it, making what each page can ' +\n 'change into its properties. You confirm the plan, and the component is built as a ' +\n 'draft.',\n },\n workflow: {\n title: 'Create an automation with AI',\n does:\n 'Describe what should happen, and when, and AI drafts the automation from the ' +\n 'triggers and steps your plan includes. It is saved switched off for you to review.',\n },\n email: {\n title: 'Create an email with AI',\n does:\n 'Describe the email you need and AI plans a design from the email blocks, with three ' +\n 'subject lines and three preheaders to choose between. You confirm the plan, and the ' +\n 'design is built as a draft that is sent to nobody.',\n },\n campaign: {\n title: 'Create a campaign with AI',\n does:\n 'Describe what the campaign is for and AI writes its email design and a draft campaign ' +\n 'that would send it. Nothing is sent or scheduled, and the campaign is aimed at nobody ' +\n 'until you pick its lists.',\n },\n product: {\n title: 'Create products with AI',\n does:\n 'Describe what the store sells and AI proposes up to twelve products. You review them ' +\n 'before any is created, and each is created as a draft that is not on your storefront ' +\n 'until you price it and activate it.',\n },\n logic: {\n title: 'Create a function or variable with AI',\n does:\n 'Describe what it should work out or hold and AI drafts the function or variable. It ' +\n 'opens unsaved in the editor for you to check and save.',\n },\n overlay: {\n title: 'Create an overlay with AI',\n does:\n 'Describe the announcement bar or popup you want and AI writes its copy within the ' +\n 'overlay’s limits. It is saved switched off, for you to check and turn on.',\n },\n funnel: {\n title: 'Create a funnel with AI',\n does:\n 'Describe the journey you want to measure and AI proposes its steps from the site’s ' +\n 'real pages, forms and products. It opens in the funnel editor for you to check and save.',\n },\n insight: {\n title: 'Ask AI about these numbers',\n label: 'Ask a question',\n does:\n 'Ask a question about the figures on this page and AI answers from them, every answer ' +\n 'traced to the figures it is built from. It reads; it changes nothing.',\n },\n}\n\n/** The label an entry's button reads. */\nexport function aiUpsellLabel(kind: AiUpsellKind): string {\n return AI_UPSELL_COPY[kind].label ?? AI_UPSELL_CREATE_LABEL\n}\n\n/**\n * The sentence that says where the feature comes from, and who can add it.\n * `feature` is what the entry is called: \"Create with AI\" unless it is an ask.\n */\nexport function aiUpsellOffer(canManageBilling: boolean, feature = AI_UPSELL_CREATE_LABEL): string {\n const addon = aiAddonName()\n return canManageBilling\n ? `${feature} comes with the ${addon} add-on, which you can add to this ` +\n 'workspace from Billing.'\n : `${feature} comes with the ${addon} add-on. Ask a workspace owner or admin ` +\n 'to add it from Billing.'\n}\n\nexport interface AiUpsellDialogProps {\n kind: AiUpsellKind\n open: boolean\n onClose: () => void\n upgrade: ConsoleWidgetUpgrade\n}\n\n/** The add-on's own section of the Create with AI guide (AGL-3660). */\nconst aiUpsellHelp = () =>\n pluginDocsHelp('aiCreate', {\n anchor: '#without-the-add-on',\n excerpt: `Create with AI needs the ${aiAddonName()} add-on on a paid plan. An owner or admin adds it from Billing; on Free, the monthly AI credits cover it.`,\n })\n\nexport function AiUpsellDialog({ kind, open, onClose, upgrade }: AiUpsellDialogProps) {\n const copy = AI_UPSELL_COPY[kind]\n const { canManageBilling, billingHref } = upgrade\n\n // Counted once per opening: the denominator the click is a rate against.\n useEffect(() => {\n if (open) trackEvent('ai_upsell_shown', { kind, can_manage: canManageBilling })\n }, [open, kind, canManageBilling])\n\n return (\n <Dialog open={open} onClose={onClose} fullWidth maxWidth=\"xs\">\n <DialogTitle sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>\n {copy.title}\n <HelpTip {...aiUpsellHelp()} />\n </DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <Typography variant=\"body2\">{copy.does}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {aiUpsellOffer(canManageBilling, kind === 'insight' ? copy.title : AI_UPSELL_CREATE_LABEL)}\n </Typography>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{canManageBilling ? 'Not now' : 'Close'}</Button>\n {canManageBilling ? (\n <Button\n variant=\"contained\"\n href={billingHref}\n onClick={() => trackEvent('ai_upsell_clicked', { kind })}\n >\n {`Add ${aiAddonName()}`}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n )\n}\n\n/**\n * The entry itself on a plan without the add-on: the same button, label and\n * icon as the entitled entry, opening {@link AiUpsellDialog}. Every entry that\n * draws it renders from the shell's client-side gates alone and asks the\n * server nothing until it is used (AGL-3601).\n */\nexport function AiUpsellButton({\n kind,\n upgrade,\n label,\n}: {\n kind: AiUpsellKind\n upgrade: ConsoleWidgetUpgrade\n /** The entitled entry's own label, when it is not the kind's. */\n label?: string\n}): ReactNode {\n const [open, setOpen] = useState(false)\n return (\n <>\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiCreation.path} />}\n onClick={() => setOpen(true)}\n >\n {label ?? aiUpsellLabel(kind)}\n </Button>\n <AiUpsellDialog kind={kind} open={open} onClose={() => setOpen(false)} upgrade={upgrade} />\n </>\n )\n}\n"],"names":["aiAddonName","trackEvent","mdiCreation","pluginDocsHelp","HelpTip","MdiIcon","Button","Dialog","DialogActions","DialogContent","DialogTitle","Stack","Typography","useEffect","useState","AI_UPSELL_CREATE_LABEL","AI_UPSELL_COPY","page","title","does","template","layout","form","component","workflow","email","campaign","product","logic","overlay","funnel","insight","label","aiUpsellLabel","kind","aiUpsellOffer","canManageBilling","feature","addon","aiUpsellHelp","anchor","excerpt","AiUpsellDialog","open","onClose","upgrade","copy","billingHref","can_manage","fullWidth","maxWidth","sx","display","alignItems","gap","spacing","variant","color","onClick","href","AiUpsellButton","setOpen","size","startIcon","path"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-upsell-dialog.component.tsx"],"sourcesContent":["'use client'\n\n/**\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 { aiAddonName } from '@aglyn/aglyn'\nimport { trackEvent } from '@aglyn/aglyn/app-utils/analytics-events'\nimport type { ConsoleWidgetUpgrade } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { mdiCreation } from '@aglyn/shared-data-mdi'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { HelpTip, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport {\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useEffect, useState, type ReactNode } from 'react'\nimport type { AiBriefKind } from './ai-brief-dialog.component'\n\n/**\n * \"Create with AI\" on a plan that could buy the AI add-on and has not\n * (AGL-3601). The shell mounts the entry with `entitled={false}` only where\n * the add-on is sold to this workspace, and the jobs route has said the plan\n * is the one thing missing; the entry then keeps its place on the page and\n * opens this instead of the brief, so the reader sees where the add-on is used.\n */\n\n/**\n * What a \"Create with AI\" or \"Ask AI\" entry makes: the brief kinds, an\n * automation, products, a function or variable, an overlay, and an answer\n * about the figures on a page.\n */\nexport type AiUpsellKind =\n | AiBriefKind\n | 'workflow'\n | 'product'\n | 'logic'\n | 'overlay'\n | 'funnel'\n | 'insight'\n\n/** What one entry's dialog says. */\nexport interface AiUpsellCopy {\n title: string\n does: string\n /** The entry's own label, which the button reads and the offer names; \"Create with AI\" when absent. */\n label?: string\n}\n\n/** The label every \"Create with AI\" entry reads. */\nexport const AI_UPSELL_CREATE_LABEL = 'Create with AI'\n\n/** What the dialog says one entry would do here. */\nexport const AI_UPSELL_COPY: Readonly<Record<AiUpsellKind, AiUpsellCopy>> = {\n page: {\n title: 'Create a page with AI',\n does:\n 'Describe the page you need and AI plans it from your brief, using your theme and ' +\n 'what the site already has. You confirm the plan, and the page is built as an ' +\n 'unpublished draft.',\n },\n template: {\n title: 'Create a page template with AI',\n does:\n 'Describe what each page should show and AI plans the template, filling what ' +\n 'changes from page to page from each record. You confirm the plan, and the template ' +\n 'is built as a draft in your library.',\n },\n layout: {\n title: 'Create a layout with AI',\n does:\n 'Describe what the layout should hold and AI plans it, linking your pages and placing ' +\n 'your navigation or menu component when the site has one. You confirm the plan, and ' +\n 'the layout is built as a draft no page uses until you assign it.',\n },\n form: {\n title: 'Create a form with AI',\n does:\n 'Describe what the form is for and AI plans its fields, its marketing consent and ' +\n 'where each submission goes. You confirm the plan, and the form is built as a draft.',\n },\n component: {\n title: 'Create a reusable component with AI',\n does:\n 'Describe what the component should show and AI plans it, making what each page can ' +\n 'change into its properties. You confirm the plan, and the component is built as a ' +\n 'draft.',\n },\n workflow: {\n title: 'Create an automation with AI',\n does:\n 'Describe what should happen, and when, and AI drafts the automation from the ' +\n 'triggers and steps your plan includes. It is saved switched off for you to review.',\n },\n email: {\n title: 'Create an email with AI',\n does:\n 'Describe the email you need and AI plans a design from the email blocks, with three ' +\n 'subject lines and three preheaders to choose between. You confirm the plan, and the ' +\n 'design is built as a draft that is sent to nobody.',\n },\n campaign: {\n title: 'Create a campaign with AI',\n does:\n 'Describe what the campaign is for and AI writes its email design and a draft campaign ' +\n 'that would send it. Nothing is sent or scheduled, and the campaign is aimed at nobody ' +\n 'until you pick its lists.',\n },\n product: {\n title: 'Create products with AI',\n does:\n 'Describe what the store sells and AI proposes up to twelve products. You review them ' +\n 'before any is created, and each is created as a draft at a suggested price that is not on ' +\n 'your storefront until you review it and activate it.',\n },\n logic: {\n title: 'Create a function or variable with AI',\n does:\n 'Describe what it should work out or hold and AI drafts the function or variable. It ' +\n 'opens unsaved in the editor for you to check and save.',\n },\n overlay: {\n title: 'Create an overlay with AI',\n does:\n 'Describe the announcement bar or popup you want and AI writes its copy within the ' +\n 'overlay’s limits. It is saved switched off, for you to check and turn on.',\n },\n funnel: {\n title: 'Create a funnel with AI',\n does:\n 'Describe the journey you want to measure and AI proposes its steps from the site’s ' +\n 'real pages, forms and products. It opens in the funnel editor for you to check and save.',\n },\n insight: {\n title: 'Ask AI about these numbers',\n label: 'Ask a question',\n does:\n 'Ask a question about the figures on this page and AI answers from them, every answer ' +\n 'traced to the figures it is built from. It reads; it changes nothing.',\n },\n}\n\n/** The label an entry's button reads. */\nexport function aiUpsellLabel(kind: AiUpsellKind): string {\n return AI_UPSELL_COPY[kind].label ?? AI_UPSELL_CREATE_LABEL\n}\n\n/**\n * The sentence that says where the feature comes from, and who can add it.\n * `feature` is what the entry is called: \"Create with AI\" unless it is an ask.\n */\nexport function aiUpsellOffer(canManageBilling: boolean, feature = AI_UPSELL_CREATE_LABEL): string {\n const addon = aiAddonName()\n return canManageBilling\n ? `${feature} comes with the ${addon} add-on, which you can add to this ` +\n 'workspace from Billing.'\n : `${feature} comes with the ${addon} add-on. Ask a workspace owner or admin ` +\n 'to add it from Billing.'\n}\n\nexport interface AiUpsellDialogProps {\n kind: AiUpsellKind\n open: boolean\n onClose: () => void\n upgrade: ConsoleWidgetUpgrade\n}\n\n/** The add-on's own section of the Create with AI guide (AGL-3660). */\nconst aiUpsellHelp = () =>\n pluginDocsHelp('aiCreate', {\n anchor: '#without-the-add-on',\n excerpt: `Create with AI needs the ${aiAddonName()} add-on on a paid plan. An owner or admin adds it from Billing; on Free, the monthly AI credits cover it.`,\n })\n\nexport function AiUpsellDialog({ kind, open, onClose, upgrade }: AiUpsellDialogProps) {\n const copy = AI_UPSELL_COPY[kind]\n const { canManageBilling, billingHref } = upgrade\n\n // Counted once per opening: the denominator the click is a rate against.\n useEffect(() => {\n if (open) trackEvent('ai_upsell_shown', { kind, can_manage: canManageBilling })\n }, [open, kind, canManageBilling])\n\n return (\n <Dialog open={open} onClose={onClose} fullWidth maxWidth=\"xs\">\n <DialogTitle sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>\n {copy.title}\n <HelpTip {...aiUpsellHelp()} />\n </DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <Typography variant=\"body2\">{copy.does}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {aiUpsellOffer(canManageBilling, kind === 'insight' ? copy.title : AI_UPSELL_CREATE_LABEL)}\n </Typography>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{canManageBilling ? 'Not now' : 'Close'}</Button>\n {canManageBilling ? (\n <Button\n variant=\"contained\"\n href={billingHref}\n onClick={() => trackEvent('ai_upsell_clicked', { kind })}\n >\n {`Add ${aiAddonName()}`}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n )\n}\n\n/**\n * The entry itself on a plan without the add-on: the same button, label and\n * icon as the entitled entry, opening {@link AiUpsellDialog}. Every entry that\n * draws it renders from the shell's client-side gates alone and asks the\n * server nothing until it is used (AGL-3601).\n */\nexport function AiUpsellButton({\n kind,\n upgrade,\n label,\n}: {\n kind: AiUpsellKind\n upgrade: ConsoleWidgetUpgrade\n /** The entitled entry's own label, when it is not the kind's. */\n label?: string\n}): ReactNode {\n const [open, setOpen] = useState(false)\n return (\n <>\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiCreation.path} />}\n onClick={() => setOpen(true)}\n >\n {label ?? aiUpsellLabel(kind)}\n </Button>\n <AiUpsellDialog kind={kind} open={open} onClose={() => setOpen(false)} upgrade={upgrade} />\n </>\n )\n}\n"],"names":["aiAddonName","trackEvent","mdiCreation","pluginDocsHelp","HelpTip","MdiIcon","Button","Dialog","DialogActions","DialogContent","DialogTitle","Stack","Typography","useEffect","useState","AI_UPSELL_CREATE_LABEL","AI_UPSELL_COPY","page","title","does","template","layout","form","component","workflow","email","campaign","product","logic","overlay","funnel","insight","label","aiUpsellLabel","kind","aiUpsellOffer","canManageBilling","feature","addon","aiUpsellHelp","anchor","excerpt","AiUpsellDialog","open","onClose","upgrade","copy","billingHref","can_manage","fullWidth","maxWidth","sx","display","alignItems","gap","spacing","variant","color","onClick","href","AiUpsellButton","setOpen","size","startIcon","path"],"mappings":"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"}
@@ -13,7 +13,8 @@
13
13
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
- */ import { checkEntitlement } from "@aglyn/aglyn/app-utils/plan-entitlements";
16
+ */ import { DUPLICABLE_HOST_RESOURCE_KINDS, DUPLICATE_NAME_MAX } from "@aglyn/aglyn/app-utils/duplicate-resource";
17
+ import { checkEntitlement } from "@aglyn/aglyn/app-utils/plan-entitlements";
17
18
  import { isHostPluginEnabled } from "@aglyn/aglyn/plugin-manager/enabled-plugins";
18
19
  import { pluginAiCapabilities, registerPluginAiCapability } from "@aglyn/aglyn/plugin-manager/plugin-ai-capabilities";
19
20
  import { filterEnabledPluginsByReleaseFlags } from "@aglyn/tenant-data-admin/server/release-flags";
@@ -25,6 +26,7 @@ import { AI_EDIT_TARGET_KINDS } from "../model/ai-edit-job.js";
25
26
  import { AI_CAMPAIGN_RESOURCE } from "./ai-job-campaign-step.js";
26
27
  import { aiPluginDraftOwner } from "./ai-job-plugin-drafts.js";
27
28
  import { aiJobStepRunnerFor } from "./ai-jobs.js";
29
+ import { AI_SETTINGS_ARGS_SCHEMA, AI_SETTINGS_WHERE } from "./ai-job-settings-step.js";
28
30
  /**
29
31
  * What THIS plugin lets a build make (AGL-3616), registered on the core's
30
32
  * capability contract exactly as any other plugin registers its own, so the
@@ -37,7 +39,10 @@ import { aiJobStepRunnerFor } from "./ai-jobs.js";
37
39
  * and workflows plugins, through the writers their runners already call, and
38
40
  * a site function by the logic plugin's `function` writer, which the `logic`
39
41
  * runner calls when its job is a build's unit. A change to a page or layout
40
- * the site already has is the `edit` runner's.
42
+ * the site already has is the `edit` runner's, and a change to its header or
43
+ * footer menu the `navigation` runner's (AGL-3728), a copy of something
44
+ * it has the `duplicate` runner's (AGL-3740), and a change to its settings
45
+ * the `settings` runner's (AGL-3748).
41
46
  */ /** This plugin's id, as its registrations name their owner. */ const AI_PLUGIN_ID = 'ai';
42
47
  const NO_ARGS = {
43
48
  type: 'object',
@@ -242,6 +247,161 @@ const passes = (count)=>()=>count * AI_SITE_PASS_CREDITS;
242
247
  runnerKind: 'edit',
243
248
  estimateCredits: passes(1),
244
249
  degrade: 'omit'
250
+ },
251
+ {
252
+ // The site's header and footer menus (AGL-3728): the Besigner's own Page
253
+ // Links and Dropdown Menus in the site's layout, changed by code from the
254
+ // words the planner wrote, saved as a version beside the live one. No
255
+ // model, so no credits. Not on the Free taste, as `edit`: a layout is
256
+ // always served, so without version history there is nowhere to land.
257
+ op: 'navigation',
258
+ noun: 'menu change',
259
+ where: 'the layout’s versions',
260
+ intents: [
261
+ 'changing the site’s header or footer menu — add, remove, rename, reorder or relink its items, or put items under a dropdown — saved as a layout version visitors do not see until it is published'
262
+ ],
263
+ argsSchema: {
264
+ type: 'object',
265
+ properties: {
266
+ menu: {
267
+ type: 'string',
268
+ description: 'Which menu: header (the default), footer, or both.',
269
+ enum: [
270
+ 'header',
271
+ 'footer',
272
+ 'both'
273
+ ]
274
+ },
275
+ layout: {
276
+ type: 'string',
277
+ description: 'The layout id from the site inventory, or new:<name> of a layout this plan makes; leave it out for the layout the site’s pages use.',
278
+ maxLength: 64
279
+ },
280
+ add: {
281
+ type: 'array',
282
+ description: 'Items to add, each "Label => destination", or "Parent > Label => destination" to put it under the dropdown Parent. A destination is a page id from the site inventory, new:<page title> of a page this plan makes, a path such as /contact, or an https:// address.',
283
+ items: {
284
+ type: 'string',
285
+ maxLength: 160
286
+ },
287
+ maxItems: 12
288
+ },
289
+ remove: {
290
+ type: 'array',
291
+ description: 'Labels of items to take out, as "Label" or "Parent > Label".',
292
+ items: {
293
+ type: 'string',
294
+ maxLength: 100
295
+ },
296
+ maxItems: 12
297
+ },
298
+ rename: {
299
+ type: 'array',
300
+ description: 'Items to rename, each "Old label => New label".',
301
+ items: {
302
+ type: 'string',
303
+ maxLength: 100
304
+ },
305
+ maxItems: 12
306
+ },
307
+ relink: {
308
+ type: 'array',
309
+ description: 'Items to point somewhere else, each "Label => destination", destinations as in add.',
310
+ items: {
311
+ type: 'string',
312
+ maxLength: 160
313
+ },
314
+ maxItems: 12
315
+ },
316
+ order: {
317
+ type: 'array',
318
+ description: 'Top-level labels in the order wanted; items not named keep their order after them.',
319
+ items: {
320
+ type: 'string',
321
+ maxLength: 60
322
+ },
323
+ maxItems: 12
324
+ }
325
+ },
326
+ additionalProperties: false
327
+ },
328
+ maxPerPlan: 1,
329
+ freeAllowed: false,
330
+ // Offered only where the change has somewhere to land: a plan with version history.
331
+ feature: 'versioning',
332
+ runnerKind: 'navigation',
333
+ // Written by code from the plan's words: no model pass.
334
+ estimateCredits: ()=>0,
335
+ degrade: 'omit'
336
+ },
337
+ {
338
+ // A copy of something the site already has (AGL-3740), made by core's
339
+ // `duplicateResource` — the console's own Duplicate… — so "another page
340
+ // like Services" is copied rather than generated again. No model, so no
341
+ // credits; the copy meets the plan's own counters as a create does,
342
+ // which is why the Free taste may ask for one.
343
+ op: 'duplicate',
344
+ noun: 'copy',
345
+ where: 'the list the original is on',
346
+ intents: [
347
+ 'a copy of a page, component, layout, template, form, email design or workflow the site already has, as an unpublished draft to change — rather than making it again'
348
+ ],
349
+ argsSchema: {
350
+ type: 'object',
351
+ properties: {
352
+ kind: {
353
+ type: 'string',
354
+ description: 'What to copy: screen (a page), component, layout, template, form, emailDesign or workflow.',
355
+ enum: DUPLICABLE_HOST_RESOURCE_KINDS
356
+ },
357
+ source: {
358
+ type: 'string',
359
+ description: 'The id of the thing to copy from the site inventory, or its exact name where the inventory does not list it.',
360
+ maxLength: 64
361
+ },
362
+ name: {
363
+ type: 'string',
364
+ description: 'What the copy is called; leave it out for "Copy of" the original’s name.',
365
+ maxLength: DUPLICATE_NAME_MAX
366
+ }
367
+ },
368
+ required: [
369
+ 'kind',
370
+ 'source'
371
+ ],
372
+ additionalProperties: false
373
+ },
374
+ maxPerPlan: 3,
375
+ freeAllowed: true,
376
+ runnerKind: 'duplicate',
377
+ // Copied by code: no model pass.
378
+ estimateCredits: ()=>0,
379
+ degrade: 'omit'
380
+ },
381
+ {
382
+ // The site's settings (AGL-3748): the fields the console's settings
383
+ // cards write — name, search defaults, favicon, logo and social image
384
+ // from the media library, language, time zone, contact details and the
385
+ // cookie banner's mode — held to the same limits and saved live when the
386
+ // plan is confirmed, as a settings save is. No model, so no credits; every
387
+ // plan may change its own settings, so the Free taste may too. Domains,
388
+ // members, billing, plugins, indexing and anything with a credential are
389
+ // not arguments, so they cannot be asked for.
390
+ op: 'settings',
391
+ noun: 'settings change',
392
+ where: AI_SETTINGS_WHERE,
393
+ intents: [
394
+ 'changing the site’s settings — its name, search title and description, title pattern, favicon, logo or social image (from images already in the media library), language, time zone, business name, description (tagline), phone, contact email or address, or the cookie banner’s mode — saved to the live site when the plan is confirmed'
395
+ ],
396
+ argsSchema: AI_SETTINGS_ARGS_SCHEMA,
397
+ maxPerPlan: 1,
398
+ freeAllowed: true,
399
+ runnerKind: 'settings',
400
+ // A setting has no draft state: saved, it is what the live site renders.
401
+ live: true,
402
+ // Written by code from the plan's values: no model pass.
403
+ estimateCredits: ()=>0,
404
+ degrade: 'omit'
245
405
  }
246
406
  ];
247
407
  /**
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-build-capabilities.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { isHostPluginEnabled } from '@aglyn/aglyn/plugin-manager/enabled-plugins'\nimport {\n pluginAiCapabilities,\n registerPluginAiCapability,\n type PluginAiCapability,\n type ResolvedPluginAiCapability,\n} from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities'\nimport { filterEnabledPluginsByReleaseFlags } from '@aglyn/tenant-data-admin/server/release-flags'\nimport { AI_TEMPLATE_SUBJECTS } from '../model/ai-template-subjects'\nimport { AI_SITE_PASS_CREDITS } from '../model/ai-site-job'\nimport type { AiBuildOps } from '../model/ai-build-job'\nimport { AI_LOGIC_FUNCTION_RESOURCE } from '../model/ai-logic-job'\nimport { AI_AUTOMATION_RESOURCE } from '../model/ai-workflow-job'\nimport { AI_EDIT_TARGET_KINDS } from '../model/ai-edit-job'\nimport { AI_CAMPAIGN_RESOURCE } from './ai-job-campaign-step'\nimport { aiPluginDraftOwner, type AiPluginDraftOwnerLookup } from './ai-job-plugin-drafts'\nimport { aiJobStepRunnerFor } from './ai-jobs'\n\n/**\n * What THIS plugin lets a build make (AGL-3616), registered on the core's\n * capability contract exactly as any other plugin registers its own, so the\n * planner, the gates and the estimate read one list and name no plugin.\n *\n * Every one is built by a runner this plugin already has (`runnerKind`):\n * pages, layouts, forms, components and email designs are the plan's\n * creations and screens; templates, campaigns and automations are items with\n * arguments. A campaign and an automation are still WRITTEN by the marketing\n * and workflows plugins, through the writers their runners already call, and\n * a site function by the logic plugin's `function` writer, which the `logic`\n * runner calls when its job is a build's unit. A change to a page or layout\n * the site already has is the `edit` runner's.\n */\n\n/** This plugin's id, as its registrations name their owner. */\nconst AI_PLUGIN_ID = 'ai'\n\nconst NO_ARGS = { type: 'object', properties: {}, additionalProperties: false } as const\n\nconst passes = (count: number) => () => count * AI_SITE_PASS_CREDITS\n\n/** The operations this plugin owns, as it registers them. */\nexport const AI_OWNED_CAPABILITIES: readonly PluginAiCapability[] = [\n {\n op: 'page',\n noun: 'page',\n where: 'Pages',\n intents: ['new pages: home, about, services, contact, pricing, a landing page'],\n argsSchema: NO_ARGS,\n maxPerPlan: 8,\n freeAllowed: true,\n runnerKind: 'page',\n estimateCredits: passes(6),\n degrade: 'omit',\n },\n {\n op: 'layout',\n noun: 'layout',\n where: 'Layouts',\n intents: ['a header and footer shared by pages'],\n argsSchema: NO_ARGS,\n maxPerPlan: 1,\n freeAllowed: true,\n runnerKind: 'layout',\n estimateCredits: passes(1),\n degrade: 'fallback',\n },\n {\n op: 'form',\n noun: 'form',\n where: 'Forms',\n intents: ['a contact form, a quote request, a sign-up form'],\n argsSchema: NO_ARGS,\n maxPerPlan: 3,\n freeAllowed: true,\n runnerKind: 'form',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n op: 'component',\n noun: 'component',\n where: 'Components',\n intents: ['a reusable card or block placed on several pages'],\n argsSchema: NO_ARGS,\n maxPerPlan: 4,\n freeAllowed: false,\n runnerKind: 'component',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n op: 'email',\n noun: 'email design',\n where: 'Emails → Templates',\n intents: ['an email design: a welcome, a reply, an announcement'],\n argsSchema: NO_ARGS,\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'email',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n op: 'template',\n noun: 'page template',\n where: 'Templates',\n intents: ['a page template for blog posts, products or authors'],\n argsSchema: {\n type: 'object',\n properties: {\n subject: {\n type: 'string',\n description: 'What each page shows: entry (a collection entry), product or author.',\n enum: AI_TEMPLATE_SUBJECTS,\n },\n collectionId: {\n type: 'string',\n description: 'For entry: the collection id from the site inventory.',\n maxLength: 100,\n },\n },\n required: ['subject'],\n additionalProperties: false,\n },\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'template',\n estimateCredits: passes(6),\n degrade: 'omit',\n },\n {\n op: 'campaign',\n noun: 'email campaign',\n where: 'Marketing → Campaigns',\n intents: ['an email campaign drafted and left unsent'],\n argsSchema: {\n type: 'object',\n properties: {\n name: { type: 'string', description: 'What the campaign is called.', maxLength: 120 },\n },\n additionalProperties: false,\n },\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'campaign',\n estimateCredits: passes(2),\n degrade: 'omit',\n },\n {\n op: 'workflow',\n noun: 'automation',\n where: 'Automations',\n intents: ['an automation: when a form is sent, do something'],\n argsSchema: NO_ARGS,\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'workflow',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n // A function needs a model, so it is this plugin's operation; the logic\n // plugin owns the record and writes it (AGL-3616). New functions only: a\n // build never changes one the site already has.\n op: 'function',\n noun: 'site function',\n where: 'Logic → Functions',\n intents: ['a site function that works a value out from what it is given: a price, a total, a quote'],\n argsSchema: NO_ARGS,\n maxPerPlan: 2,\n // Free includes one function (`functionsPerHost: 1`), and Logic no feature.\n freeAllowed: true,\n quota: 'functionsPerHost',\n runnerKind: 'logic',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n // A change to something the site already has (AGL-3616), made by the\n // `edit` runner: the Assist edit rung's protocol against the stored\n // document, saved as a version visitors do not see. Not on the Free\n // taste: without version history only an unpublished page can take a\n // change, which is rarely the page a request means, and its admission\n // would refuse the item after the plan was confirmed.\n op: 'edit',\n noun: 'page change',\n where: 'the page’s or layout’s versions',\n intents: [\n 'a change to a page or layout the site already has — shorter copy, a new section, different wording — saved as a version visitors do not see until it is published',\n ],\n argsSchema: {\n type: 'object',\n properties: {\n target: {\n type: 'string',\n description: 'The id of an existing page or layout from the site inventory.',\n maxLength: 64,\n },\n targetKind: {\n type: 'string',\n description: 'screen when target is a page, layout when it is a layout.',\n enum: AI_EDIT_TARGET_KINDS,\n },\n },\n required: ['target'],\n additionalProperties: false,\n },\n maxPerPlan: 3,\n freeAllowed: false,\n runnerKind: 'edit',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n]\n\n/**\n * The resource another plugin writes for each operation of this plugin's that\n * cannot be built without it. The operation runs where that writer's owner\n * runs — registered, switched on for the workspace and the site, and past its\n * release flag — exactly as an operation that owner registered would.\n */\nexport const AI_OWNED_OP_WRITERS: Readonly<Partial<Record<string, string>>> = {\n function: AI_LOGIC_FUNCTION_RESOURCE,\n // A campaign is written by the marketing plugin's campaign writer, an\n // automation by the workflows plugin's: neither is offered where its\n // writer's plugin is off, rather than planned and then skipped.\n campaign: AI_CAMPAIGN_RESOURCE,\n workflow: AI_AUTOMATION_RESOURCE,\n}\n\n/** Registers this plugin's build operations; the console surface calls it. */\nexport function registerAiBuildCapabilities(): void {\n for (const capability of AI_OWNED_CAPABILITIES) {\n registerPluginAiCapability(capability, { pluginId: AI_PLUGIN_ID })\n }\n}\n\n/** What decides which operations a build may use here. */\nexport interface AiBuildOpsContext {\n orgId: string\n hostId: string | null\n org: Partial<AglynOrgBilling> | null\n /** The site document, for the plugins switched on and off on it. */\n host: Readonly<Record<string, unknown>> | null\n freeTaste: boolean\n}\n\nexport interface AiBuildOpsDeps {\n /** The registered capabilities; the core registry's otherwise. */\n capabilities?: () => ResolvedPluginAiCapability[]\n /** The release-flag read; the platform's otherwise. */\n releasedPlugins?: (pluginIds: string[], orgId: string) => Promise<string[]>\n /** Whether a runner is registered for a kind; the machine's otherwise. */\n hasRunner?: (kind: string) => boolean\n /** Which plugin writes a resource; the core's registry otherwise. */\n writerOwner?: AiPluginDraftOwnerLookup\n}\n\n/**\n * The operations a build may plan on this site, by op (AGL-3616): every\n * registered capability whose owner runs here — switched on for the\n * workspace and the site, past its release flag; for one of this plugin's\n * operations another plugin writes (`AI_OWNED_OP_WRITERS`), that writer's\n * owner, and a writer registered at all — whose entitlement the plan\n * holds, which the Free taste may use where the workspace is on it, and,\n * for an AI-run operation, whose runner this process loaded. One release-flag\n * read for every owner at once. What a member may do and what an allowance\n * has left are the owner's to say when the item runs (`refusal`).\n */\nexport async function aiBuildOps(context: AiBuildOpsContext, deps: AiBuildOpsDeps = {}): Promise<AiBuildOps> {\n const all = (deps.capabilities ?? pluginAiCapabilities)()\n const hasRunner = deps.hasRunner ?? ((kind: string) => aiJobStepRunnerFor(kind as never) !== null)\n const writerOwner = deps.writerOwner ?? aiPluginDraftOwner\n // The plugin each of this plugin's operations depends on, by op; `null` where its writer is not registered.\n const writtenBy = new Map<string, string | null>()\n for (const { pluginId, capability } of all) {\n const resource = pluginId === AI_PLUGIN_ID ? AI_OWNED_OP_WRITERS[capability.op] : undefined\n if (resource) writtenBy.set(capability.op, writerOwner(resource))\n }\n const owners = [\n ...new Set(\n [...all.map((one) => one.pluginId), ...writtenBy.values()].filter(\n (id): id is string => !!id && id !== AI_PLUGIN_ID,\n ),\n ),\n ]\n const released = new Set(\n owners.length\n ? await (deps.releasedPlugins ??\n ((ids, orgId) => filterEnabledPluginsByReleaseFlags(ids, { orgId, authorization: null })))(owners, context.orgId)\n : [],\n )\n const ops = new Map<string, PluginAiCapability>()\n for (const { pluginId, capability } of all) {\n // The plugin that must run here: the owner, or for one of this plugin's\n // operations, the plugin that writes what it makes.\n const runsOn = pluginId === AI_PLUGIN_ID ? writtenBy.get(capability.op) : pluginId\n if (runsOn === null) continue\n if (runsOn !== undefined && runsOn !== AI_PLUGIN_ID) {\n if (!released.has(runsOn)) continue\n if (!isHostPluginEnabled(context.org as { enabledPlugins?: string[] } | null, context.host, runsOn)) continue\n }\n if (capability.feature && !checkEntitlement(context.org, capability.feature)) continue\n if (context.freeTaste && !capability.freeAllowed) continue\n if (capability.runnerKind && !hasRunner(capability.runnerKind)) continue\n ops.set(capability.op, capability)\n }\n return ops\n}\n\n/**\n * The lines the plan step's request lists the item operations in: each op\n * with what it makes, its arguments and their meaning. Per site, so they ride\n * the job's own turn and never a cached system block.\n */\nexport function aiBuildOpLines(ops: AiBuildOps, structural: readonly string[]): string[] {\n const items = [...ops.values()].filter((one) => !structural.includes(one.op))\n if (!items.length) return ['Operations for items: none on this site. Leave items empty.']\n const lines = ['Operations for items (anything else goes in create or screens):']\n for (const one of items) {\n const args = Object.entries(one.argsSchema.properties).map(([name, property]) => {\n const required = one.argsSchema.required?.includes(name) ? '' : ' (optional)'\n // An array's values are named too (AGL-3616): told only \"days: array\",\n // the live canonical plan wrote \"monday\" for the enum's \"mon\".\n const choices = property.enum\n ? ` one of ${property.enum.join('|')}`\n : property.items?.enum\n ? ` of ${property.items.enum.join('|')}`\n : ''\n return `${name}: ${property.type}${choices}${required} — ${property.description}`\n })\n lines.push(\n `- ${one.op}: ${aiNounWithArticle(one.noun)}, at most ${one.maxPerPlan}; for ${one.intents.join('; ')}.${\n one.pageBlocks?.length ? ` A page section that uses it places the ${one.pageBlocks.join(' or ')} block: put new:<name> in that section's uses.` : ''\n } Args: ${args.length ? `{ ${args.join('; ')} }` : '{}'}.`,\n )\n }\n // Measured live on the canonical request (AGL-3616): asked for \"forms for\n // bookings\", the planner made a plain form rather than the operation that\n // takes bookings. An operation does the job; a form that imitates it does not.\n lines.push(\n 'When the request asks for what one of these operations makes, plan that operation as an item, and place it on the page that offers it. Never stand in for it with a form, page or component that only imitates it: a form that collects what the operation would handle does not do its job. A form is for what no operation here makes, such as general inquiries.',\n )\n return lines\n}\n\nfunction aiNounWithArticle(noun: string): string {\n return `${/^[aeiou]/i.test(noun) ? 'an' : 'a'} ${noun}`\n}\n\n/** What a build can make here, in the owner's words, for the chat's instructions. */\nexport function aiBuildIntents(ops: AiBuildOps): string[] {\n return [...ops.values()].flatMap((one) => one.intents)\n}\n"],"names":["checkEntitlement","isHostPluginEnabled","pluginAiCapabilities","registerPluginAiCapability","filterEnabledPluginsByReleaseFlags","AI_TEMPLATE_SUBJECTS","AI_SITE_PASS_CREDITS","AI_LOGIC_FUNCTION_RESOURCE","AI_AUTOMATION_RESOURCE","AI_EDIT_TARGET_KINDS","AI_CAMPAIGN_RESOURCE","aiPluginDraftOwner","aiJobStepRunnerFor","AI_PLUGIN_ID","NO_ARGS","type","properties","additionalProperties","passes","count","AI_OWNED_CAPABILITIES","op","noun","where","intents","argsSchema","maxPerPlan","freeAllowed","runnerKind","estimateCredits","degrade","subject","description","enum","collectionId","maxLength","required","name","quota","target","targetKind","AI_OWNED_OP_WRITERS","function","campaign","workflow","registerAiBuildCapabilities","capability","pluginId","aiBuildOps","context","deps","all","capabilities","hasRunner","kind","writerOwner","writtenBy","Map","resource","undefined","set","owners","Set","map","one","values","filter","id","released","length","releasedPlugins","ids","orgId","authorization","ops","runsOn","get","has","org","host","feature","freeTaste","aiBuildOpLines","structural","items","includes","lines","args","Object","entries","property","choices","join","push","aiNounWithArticle","pageBlocks","test","aiBuildIntents","flatMap"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-build-capabilities.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DUPLICABLE_HOST_RESOURCE_KINDS, DUPLICATE_NAME_MAX } from '@aglyn/aglyn/app-utils/duplicate-resource'\nimport { checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { isHostPluginEnabled } from '@aglyn/aglyn/plugin-manager/enabled-plugins'\nimport {\n pluginAiCapabilities,\n registerPluginAiCapability,\n type PluginAiCapability,\n type ResolvedPluginAiCapability,\n} from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities'\nimport { filterEnabledPluginsByReleaseFlags } from '@aglyn/tenant-data-admin/server/release-flags'\nimport { AI_TEMPLATE_SUBJECTS } from '../model/ai-template-subjects'\nimport { AI_SITE_PASS_CREDITS } from '../model/ai-site-job'\nimport type { AiBuildOps } from '../model/ai-build-job'\nimport { AI_LOGIC_FUNCTION_RESOURCE } from '../model/ai-logic-job'\nimport { AI_AUTOMATION_RESOURCE } from '../model/ai-workflow-job'\nimport { AI_EDIT_TARGET_KINDS } from '../model/ai-edit-job'\nimport { AI_CAMPAIGN_RESOURCE } from './ai-job-campaign-step'\nimport { aiPluginDraftOwner, type AiPluginDraftOwnerLookup } from './ai-job-plugin-drafts'\nimport { aiJobStepRunnerFor } from './ai-jobs'\nimport { AI_SETTINGS_ARGS_SCHEMA, AI_SETTINGS_WHERE } from './ai-job-settings-step'\n\n/**\n * What THIS plugin lets a build make (AGL-3616), registered on the core's\n * capability contract exactly as any other plugin registers its own, so the\n * planner, the gates and the estimate read one list and name no plugin.\n *\n * Every one is built by a runner this plugin already has (`runnerKind`):\n * pages, layouts, forms, components and email designs are the plan's\n * creations and screens; templates, campaigns and automations are items with\n * arguments. A campaign and an automation are still WRITTEN by the marketing\n * and workflows plugins, through the writers their runners already call, and\n * a site function by the logic plugin's `function` writer, which the `logic`\n * runner calls when its job is a build's unit. A change to a page or layout\n * the site already has is the `edit` runner's, and a change to its header or\n * footer menu the `navigation` runner's (AGL-3728), a copy of something\n * it has the `duplicate` runner's (AGL-3740), and a change to its settings\n * the `settings` runner's (AGL-3748).\n */\n\n/** This plugin's id, as its registrations name their owner. */\nconst AI_PLUGIN_ID = 'ai'\n\nconst NO_ARGS = { type: 'object', properties: {}, additionalProperties: false } as const\n\nconst passes = (count: number) => () => count * AI_SITE_PASS_CREDITS\n\n/** The operations this plugin owns, as it registers them. */\nexport const AI_OWNED_CAPABILITIES: readonly PluginAiCapability[] = [\n {\n op: 'page',\n noun: 'page',\n where: 'Pages',\n intents: ['new pages: home, about, services, contact, pricing, a landing page'],\n argsSchema: NO_ARGS,\n maxPerPlan: 8,\n freeAllowed: true,\n runnerKind: 'page',\n estimateCredits: passes(6),\n degrade: 'omit',\n },\n {\n op: 'layout',\n noun: 'layout',\n where: 'Layouts',\n intents: ['a header and footer shared by pages'],\n argsSchema: NO_ARGS,\n maxPerPlan: 1,\n freeAllowed: true,\n runnerKind: 'layout',\n estimateCredits: passes(1),\n degrade: 'fallback',\n },\n {\n op: 'form',\n noun: 'form',\n where: 'Forms',\n intents: ['a contact form, a quote request, a sign-up form'],\n argsSchema: NO_ARGS,\n maxPerPlan: 3,\n freeAllowed: true,\n runnerKind: 'form',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n op: 'component',\n noun: 'component',\n where: 'Components',\n intents: ['a reusable card or block placed on several pages'],\n argsSchema: NO_ARGS,\n maxPerPlan: 4,\n freeAllowed: false,\n runnerKind: 'component',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n op: 'email',\n noun: 'email design',\n where: 'Emails → Templates',\n intents: ['an email design: a welcome, a reply, an announcement'],\n argsSchema: NO_ARGS,\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'email',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n op: 'template',\n noun: 'page template',\n where: 'Templates',\n intents: ['a page template for blog posts, products or authors'],\n argsSchema: {\n type: 'object',\n properties: {\n subject: {\n type: 'string',\n description: 'What each page shows: entry (a collection entry), product or author.',\n enum: AI_TEMPLATE_SUBJECTS,\n },\n collectionId: {\n type: 'string',\n description: 'For entry: the collection id from the site inventory.',\n maxLength: 100,\n },\n },\n required: ['subject'],\n additionalProperties: false,\n },\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'template',\n estimateCredits: passes(6),\n degrade: 'omit',\n },\n {\n op: 'campaign',\n noun: 'email campaign',\n where: 'Marketing → Campaigns',\n intents: ['an email campaign drafted and left unsent'],\n argsSchema: {\n type: 'object',\n properties: {\n name: { type: 'string', description: 'What the campaign is called.', maxLength: 120 },\n },\n additionalProperties: false,\n },\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'campaign',\n estimateCredits: passes(2),\n degrade: 'omit',\n },\n {\n op: 'workflow',\n noun: 'automation',\n where: 'Automations',\n intents: ['an automation: when a form is sent, do something'],\n argsSchema: NO_ARGS,\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'workflow',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n // A function needs a model, so it is this plugin's operation; the logic\n // plugin owns the record and writes it (AGL-3616). New functions only: a\n // build never changes one the site already has.\n op: 'function',\n noun: 'site function',\n where: 'Logic → Functions',\n intents: ['a site function that works a value out from what it is given: a price, a total, a quote'],\n argsSchema: NO_ARGS,\n maxPerPlan: 2,\n // Free includes one function (`functionsPerHost: 1`), and Logic no feature.\n freeAllowed: true,\n quota: 'functionsPerHost',\n runnerKind: 'logic',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n // A change to something the site already has (AGL-3616), made by the\n // `edit` runner: the Assist edit rung's protocol against the stored\n // document, saved as a version visitors do not see. Not on the Free\n // taste: without version history only an unpublished page can take a\n // change, which is rarely the page a request means, and its admission\n // would refuse the item after the plan was confirmed.\n op: 'edit',\n noun: 'page change',\n where: 'the page’s or layout’s versions',\n intents: [\n 'a change to a page or layout the site already has — shorter copy, a new section, different wording — saved as a version visitors do not see until it is published',\n ],\n argsSchema: {\n type: 'object',\n properties: {\n target: {\n type: 'string',\n description: 'The id of an existing page or layout from the site inventory.',\n maxLength: 64,\n },\n targetKind: {\n type: 'string',\n description: 'screen when target is a page, layout when it is a layout.',\n enum: AI_EDIT_TARGET_KINDS,\n },\n },\n required: ['target'],\n additionalProperties: false,\n },\n maxPerPlan: 3,\n freeAllowed: false,\n runnerKind: 'edit',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n // The site's header and footer menus (AGL-3728): the Besigner's own Page\n // Links and Dropdown Menus in the site's layout, changed by code from the\n // words the planner wrote, saved as a version beside the live one. No\n // model, so no credits. Not on the Free taste, as `edit`: a layout is\n // always served, so without version history there is nowhere to land.\n op: 'navigation',\n noun: 'menu change',\n where: 'the layout’s versions',\n intents: [\n 'changing the site’s header or footer menu — add, remove, rename, reorder or relink its items, or put items under a dropdown — saved as a layout version visitors do not see until it is published',\n ],\n argsSchema: {\n type: 'object',\n properties: {\n menu: {\n type: 'string',\n description: 'Which menu: header (the default), footer, or both.',\n enum: ['header', 'footer', 'both'],\n },\n layout: {\n type: 'string',\n description: 'The layout id from the site inventory, or new:<name> of a layout this plan makes; leave it out for the layout the site’s pages use.',\n maxLength: 64,\n },\n add: {\n type: 'array',\n description:\n 'Items to add, each \"Label => destination\", or \"Parent > Label => destination\" to put it under the dropdown Parent. A destination is a page id from the site inventory, new:<page title> of a page this plan makes, a path such as /contact, or an https:// address.',\n items: { type: 'string', maxLength: 160 },\n maxItems: 12,\n },\n remove: {\n type: 'array',\n description: 'Labels of items to take out, as \"Label\" or \"Parent > Label\".',\n items: { type: 'string', maxLength: 100 },\n maxItems: 12,\n },\n rename: {\n type: 'array',\n description: 'Items to rename, each \"Old label => New label\".',\n items: { type: 'string', maxLength: 100 },\n maxItems: 12,\n },\n relink: {\n type: 'array',\n description: 'Items to point somewhere else, each \"Label => destination\", destinations as in add.',\n items: { type: 'string', maxLength: 160 },\n maxItems: 12,\n },\n order: {\n type: 'array',\n description: 'Top-level labels in the order wanted; items not named keep their order after them.',\n items: { type: 'string', maxLength: 60 },\n maxItems: 12,\n },\n },\n additionalProperties: false,\n },\n maxPerPlan: 1,\n freeAllowed: false,\n // Offered only where the change has somewhere to land: a plan with version history.\n feature: 'versioning',\n runnerKind: 'navigation',\n // Written by code from the plan's words: no model pass.\n estimateCredits: () => 0,\n degrade: 'omit',\n },\n {\n // A copy of something the site already has (AGL-3740), made by core's\n // `duplicateResource` — the console's own Duplicate… — so \"another page\n // like Services\" is copied rather than generated again. No model, so no\n // credits; the copy meets the plan's own counters as a create does,\n // which is why the Free taste may ask for one.\n op: 'duplicate',\n noun: 'copy',\n where: 'the list the original is on',\n intents: [\n 'a copy of a page, component, layout, template, form, email design or workflow the site already has, as an unpublished draft to change — rather than making it again',\n ],\n argsSchema: {\n type: 'object',\n properties: {\n kind: {\n type: 'string',\n description: 'What to copy: screen (a page), component, layout, template, form, emailDesign or workflow.',\n enum: DUPLICABLE_HOST_RESOURCE_KINDS,\n },\n source: {\n type: 'string',\n description: 'The id of the thing to copy from the site inventory, or its exact name where the inventory does not list it.',\n maxLength: 64,\n },\n name: {\n type: 'string',\n description: 'What the copy is called; leave it out for \"Copy of\" the original’s name.',\n maxLength: DUPLICATE_NAME_MAX,\n },\n },\n required: ['kind', 'source'],\n additionalProperties: false,\n },\n maxPerPlan: 3,\n freeAllowed: true,\n runnerKind: 'duplicate',\n // Copied by code: no model pass.\n estimateCredits: () => 0,\n degrade: 'omit',\n },\n {\n // The site's settings (AGL-3748): the fields the console's settings\n // cards write — name, search defaults, favicon, logo and social image\n // from the media library, language, time zone, contact details and the\n // cookie banner's mode — held to the same limits and saved live when the\n // plan is confirmed, as a settings save is. No model, so no credits; every\n // plan may change its own settings, so the Free taste may too. Domains,\n // members, billing, plugins, indexing and anything with a credential are\n // not arguments, so they cannot be asked for.\n op: 'settings',\n noun: 'settings change',\n where: AI_SETTINGS_WHERE,\n intents: [\n 'changing the site’s settings — its name, search title and description, title pattern, favicon, logo or social image (from images already in the media library), language, time zone, business name, description (tagline), phone, contact email or address, or the cookie banner’s mode — saved to the live site when the plan is confirmed',\n ],\n argsSchema: AI_SETTINGS_ARGS_SCHEMA,\n maxPerPlan: 1,\n freeAllowed: true,\n runnerKind: 'settings',\n // A setting has no draft state: saved, it is what the live site renders.\n live: true,\n // Written by code from the plan's values: no model pass.\n estimateCredits: () => 0,\n degrade: 'omit',\n },\n]\n\n/**\n * The resource another plugin writes for each operation of this plugin's that\n * cannot be built without it. The operation runs where that writer's owner\n * runs — registered, switched on for the workspace and the site, and past its\n * release flag — exactly as an operation that owner registered would.\n */\nexport const AI_OWNED_OP_WRITERS: Readonly<Partial<Record<string, string>>> = {\n function: AI_LOGIC_FUNCTION_RESOURCE,\n // A campaign is written by the marketing plugin's campaign writer, an\n // automation by the workflows plugin's: neither is offered where its\n // writer's plugin is off, rather than planned and then skipped.\n campaign: AI_CAMPAIGN_RESOURCE,\n workflow: AI_AUTOMATION_RESOURCE,\n}\n\n/** Registers this plugin's build operations; the console surface calls it. */\nexport function registerAiBuildCapabilities(): void {\n for (const capability of AI_OWNED_CAPABILITIES) {\n registerPluginAiCapability(capability, { pluginId: AI_PLUGIN_ID })\n }\n}\n\n/** What decides which operations a build may use here. */\nexport interface AiBuildOpsContext {\n orgId: string\n hostId: string | null\n org: Partial<AglynOrgBilling> | null\n /** The site document, for the plugins switched on and off on it. */\n host: Readonly<Record<string, unknown>> | null\n freeTaste: boolean\n}\n\nexport interface AiBuildOpsDeps {\n /** The registered capabilities; the core registry's otherwise. */\n capabilities?: () => ResolvedPluginAiCapability[]\n /** The release-flag read; the platform's otherwise. */\n releasedPlugins?: (pluginIds: string[], orgId: string) => Promise<string[]>\n /** Whether a runner is registered for a kind; the machine's otherwise. */\n hasRunner?: (kind: string) => boolean\n /** Which plugin writes a resource; the core's registry otherwise. */\n writerOwner?: AiPluginDraftOwnerLookup\n}\n\n/**\n * The operations a build may plan on this site, by op (AGL-3616): every\n * registered capability whose owner runs here — switched on for the\n * workspace and the site, past its release flag; for one of this plugin's\n * operations another plugin writes (`AI_OWNED_OP_WRITERS`), that writer's\n * owner, and a writer registered at all — whose entitlement the plan\n * holds, which the Free taste may use where the workspace is on it, and,\n * for an AI-run operation, whose runner this process loaded. One release-flag\n * read for every owner at once. What a member may do and what an allowance\n * has left are the owner's to say when the item runs (`refusal`).\n */\nexport async function aiBuildOps(context: AiBuildOpsContext, deps: AiBuildOpsDeps = {}): Promise<AiBuildOps> {\n const all = (deps.capabilities ?? pluginAiCapabilities)()\n const hasRunner = deps.hasRunner ?? ((kind: string) => aiJobStepRunnerFor(kind as never) !== null)\n const writerOwner = deps.writerOwner ?? aiPluginDraftOwner\n // The plugin each of this plugin's operations depends on, by op; `null` where its writer is not registered.\n const writtenBy = new Map<string, string | null>()\n for (const { pluginId, capability } of all) {\n const resource = pluginId === AI_PLUGIN_ID ? AI_OWNED_OP_WRITERS[capability.op] : undefined\n if (resource) writtenBy.set(capability.op, writerOwner(resource))\n }\n const owners = [\n ...new Set(\n [...all.map((one) => one.pluginId), ...writtenBy.values()].filter(\n (id): id is string => !!id && id !== AI_PLUGIN_ID,\n ),\n ),\n ]\n const released = new Set(\n owners.length\n ? await (deps.releasedPlugins ??\n ((ids, orgId) => filterEnabledPluginsByReleaseFlags(ids, { orgId, authorization: null })))(owners, context.orgId)\n : [],\n )\n const ops = new Map<string, PluginAiCapability>()\n for (const { pluginId, capability } of all) {\n // The plugin that must run here: the owner, or for one of this plugin's\n // operations, the plugin that writes what it makes.\n const runsOn = pluginId === AI_PLUGIN_ID ? writtenBy.get(capability.op) : pluginId\n if (runsOn === null) continue\n if (runsOn !== undefined && runsOn !== AI_PLUGIN_ID) {\n if (!released.has(runsOn)) continue\n if (!isHostPluginEnabled(context.org as { enabledPlugins?: string[] } | null, context.host, runsOn)) continue\n }\n if (capability.feature && !checkEntitlement(context.org, capability.feature)) continue\n if (context.freeTaste && !capability.freeAllowed) continue\n if (capability.runnerKind && !hasRunner(capability.runnerKind)) continue\n ops.set(capability.op, capability)\n }\n return ops\n}\n\n/**\n * The lines the plan step's request lists the item operations in: each op\n * with what it makes, its arguments and their meaning. Per site, so they ride\n * the job's own turn and never a cached system block.\n */\nexport function aiBuildOpLines(ops: AiBuildOps, structural: readonly string[]): string[] {\n const items = [...ops.values()].filter((one) => !structural.includes(one.op))\n if (!items.length) return ['Operations for items: none on this site. Leave items empty.']\n const lines = ['Operations for items (anything else goes in create or screens):']\n for (const one of items) {\n const args = Object.entries(one.argsSchema.properties).map(([name, property]) => {\n const required = one.argsSchema.required?.includes(name) ? '' : ' (optional)'\n // An array's values are named too (AGL-3616): told only \"days: array\",\n // the live canonical plan wrote \"monday\" for the enum's \"mon\".\n const choices = property.enum\n ? ` one of ${property.enum.join('|')}`\n : property.items?.enum\n ? ` of ${property.items.enum.join('|')}`\n : ''\n return `${name}: ${property.type}${choices}${required} — ${property.description}`\n })\n lines.push(\n `- ${one.op}: ${aiNounWithArticle(one.noun)}, at most ${one.maxPerPlan}; for ${one.intents.join('; ')}.${\n one.pageBlocks?.length ? ` A page section that uses it places the ${one.pageBlocks.join(' or ')} block: put new:<name> in that section's uses.` : ''\n } Args: ${args.length ? `{ ${args.join('; ')} }` : '{}'}.`,\n )\n }\n // Measured live on the canonical request (AGL-3616): asked for \"forms for\n // bookings\", the planner made a plain form rather than the operation that\n // takes bookings. An operation does the job; a form that imitates it does not.\n lines.push(\n 'When the request asks for what one of these operations makes, plan that operation as an item, and place it on the page that offers it. Never stand in for it with a form, page or component that only imitates it: a form that collects what the operation would handle does not do its job. A form is for what no operation here makes, such as general inquiries.',\n )\n return lines\n}\n\nfunction aiNounWithArticle(noun: string): string {\n return `${/^[aeiou]/i.test(noun) ? 'an' : 'a'} ${noun}`\n}\n\n/** What a build can make here, in the owner's words, for the chat's instructions. */\nexport function aiBuildIntents(ops: AiBuildOps): string[] {\n return [...ops.values()].flatMap((one) => one.intents)\n}\n"],"names":["DUPLICABLE_HOST_RESOURCE_KINDS","DUPLICATE_NAME_MAX","checkEntitlement","isHostPluginEnabled","pluginAiCapabilities","registerPluginAiCapability","filterEnabledPluginsByReleaseFlags","AI_TEMPLATE_SUBJECTS","AI_SITE_PASS_CREDITS","AI_LOGIC_FUNCTION_RESOURCE","AI_AUTOMATION_RESOURCE","AI_EDIT_TARGET_KINDS","AI_CAMPAIGN_RESOURCE","aiPluginDraftOwner","aiJobStepRunnerFor","AI_SETTINGS_ARGS_SCHEMA","AI_SETTINGS_WHERE","AI_PLUGIN_ID","NO_ARGS","type","properties","additionalProperties","passes","count","AI_OWNED_CAPABILITIES","op","noun","where","intents","argsSchema","maxPerPlan","freeAllowed","runnerKind","estimateCredits","degrade","subject","description","enum","collectionId","maxLength","required","name","quota","target","targetKind","menu","layout","add","items","maxItems","remove","rename","relink","order","feature","kind","source","live","AI_OWNED_OP_WRITERS","function","campaign","workflow","registerAiBuildCapabilities","capability","pluginId","aiBuildOps","context","deps","all","capabilities","hasRunner","writerOwner","writtenBy","Map","resource","undefined","set","owners","Set","map","one","values","filter","id","released","length","releasedPlugins","ids","orgId","authorization","ops","runsOn","get","has","org","host","freeTaste","aiBuildOpLines","structural","includes","lines","args","Object","entries","property","choices","join","push","aiNounWithArticle","pageBlocks","test","aiBuildIntents","flatMap"],"mappings":"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"}
@@ -14,6 +14,7 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
+ import type { PluginAiCapability } from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities';
17
18
  import { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations';
18
19
  import { type AiBuildOps, type AiBuildUnit } from '../model/ai-build-job';
19
20
  import type { AiJob, AiJobItemLedger, AiJobKind, AiJobOutput } from '../model/ai-jobs.types';
@@ -75,6 +76,12 @@ export declare const AI_BUILD_PAGE_NOT_WRITTEN_COPY = "The page was not written
75
76
  export declare const AI_BUILD_MAX_PASSES: number;
76
77
  /** The job kind a unit is built by, where an AI runner builds it. */
77
78
  export declare function aiBuildUnitJobKind(unit: AiBuildUnit, ops: AiBuildOps): AiJobKind | null;
79
+ /**
80
+ * What the job drawer says under an item another plugin's writer made: a
81
+ * draft, or, for setup its writer saves live (`live`, a CRM pipeline or
82
+ * field, AGL-3746), that it was saved.
83
+ */
84
+ export declare function aiBuildItemOutputNote(capability: Pick<PluginAiCapability, 'where' | 'live'>): string;
78
85
  /** The id a unit's job, and so its draft, is named by. */
79
86
  export declare function aiBuildUnitJobId(job: Pick<AiJob, '$id'>, unit: AiBuildUnit): string;
80
87
  /**
@@ -17,7 +17,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
17
17
  */ import { aiJobCancelAsked } from "./ai-job-cancel.js";
18
18
  import { resolveEffectivePlan } from "@aglyn/aglyn/app-utils/plan-entitlements";
19
19
  import { resolveOrgIdForHost } from "@aglyn/tenant-data-admin/server/organizations";
20
- import { AI_BUILD_LIMITS, AI_BUILD_PUBLISH_INPUT, aiArticle, aiBuildCreditRange, aiBuildDegradation, aiBuildInitialLedger, aiBuildItemDelivered, aiBuildItemOpen, aiBuildNextUnit, aiBuildOrder, aiBuildPlacedItemLines, aiBuildPlanShapeRefusal, aiBuildSmaller, aiBuildUnits } from "../model/ai-build-job.js";
20
+ import { AI_BUILD_LIMITS, AI_BUILD_MADE_INPUT, AI_BUILD_PUBLISH_INPUT, aiArticle, aiBuildCreditRange, aiBuildDegradation, aiBuildInitialLedger, aiBuildItemDelivered, aiBuildItemOpen, aiBuildNextUnit, aiBuildOrder, aiBuildPlacedItemLines, aiBuildPlanShapeRefusal, aiBuildSmaller, aiBuildUnits } from "../model/ai-build-job.js";
21
21
  import { isAiPlanNewRef } from "../model/ai-build-plan.js";
22
22
  import { AI_SITE_MAX_SECTIONS } from "../model/ai-site-job.js";
23
23
  import { AI_CREDITS_CONFIRM_CODE, aiCreditsPromptFor, aiCreditsPromptText } from "../model/ai-credit-estimate.js";
@@ -43,7 +43,7 @@ import { writeAiStoreLinksLayoutDraft } from "./ai-job-store-links-layout.js";
43
43
  import { AI_SITE_STORE_LINKS_INPUT, AI_SITE_STORE_PAGES_NOTE, aiStoreFrameLinks, aiStorePagesToWrite } from "../model/ai-site-store-pages.js";
44
44
  import { AI_SITE_STORE_PAGES_INPUT, aiSiteStorePageId, aiSiteStorePagesRefusal, runAiSiteStorePagesUnit } from "./ai-job-site-store-pages.js";
45
45
  import { AI_JOB_BRIEF_MAX_CHARS } from "./ai-job-text-step.js";
46
- import { aiJobPublishesBuild, aiPublishGuidedSite } from "./ai-site-publish.js";
46
+ import { aiJobPublishesBuild, aiMergeSitePublish, aiPublishGuidedSite } from "./ai-site-publish.js";
47
47
  import { AI_PLANNED_JOB_KINDS, aiJobStepRunMinimumMs, aiJobStepRunnerFor, registerAiJobStep, registerAiJobStepPasses } from "./ai-jobs.js";
48
48
  /**
49
49
  * The build step (AGL-3616): the generation step of a `build` job, run once
@@ -97,6 +97,13 @@ import { AI_PLANNED_JOB_KINDS, aiJobStepRunMinimumMs, aiJobStepRunnerFor, regist
97
97
  const runnerKind = (_ops_get = ops.get(unit.op)) == null ? void 0 : _ops_get.runnerKind;
98
98
  return runnerKind ? runnerKind : null;
99
99
  }
100
+ /**
101
+ * What the job drawer says under an item another plugin's writer made: a
102
+ * draft, or, for setup its writer saves live (`live`, a CRM pipeline or
103
+ * field, AGL-3746), that it was saved.
104
+ */ export function aiBuildItemOutputNote(capability) {
105
+ return capability.live ? `Live now, in ${capability.where}.` : `A draft, in ${capability.where}.`;
106
+ }
100
107
  /** The id a unit's job, and so its draft, is named by. */ export function aiBuildUnitJobId(job, unit) {
101
108
  var _ref, _ref1, _ref2;
102
109
  var _unit_creation, _unit_screen, _unit_item;
@@ -197,6 +204,33 @@ import { AI_PLANNED_JOB_KINDS, aiJobStepRunMinimumMs, aiJobStepRunnerFor, regist
197
204
  originJobId: aiOriginJobId(job)
198
205
  });
199
206
  delete inputs[AI_BUILD_PUBLISH_INPUT];
207
+ // An item is told the pages and layouts this build made before it, which
208
+ // its arguments may link as `new:<name>` (a menu item, AGL-3728).
209
+ if (item) {
210
+ const rows = new Map(context.ledger.map((row)=>[
211
+ row.slot,
212
+ row
213
+ ]));
214
+ const made = {
215
+ pages: context.units.flatMap((one)=>{
216
+ var _one_screen;
217
+ const row = rows.get(one.slot);
218
+ return ((_one_screen = one.screen) == null ? void 0 : _one_screen.id) && row && aiBuildItemDelivered(row) ? [
219
+ {
220
+ title: one.screen.title,
221
+ id: one.screen.id
222
+ }
223
+ ] : [];
224
+ }),
225
+ layouts: [
226
+ ...built.entries()
227
+ ].filter(([, entry])=>entry.kind === 'layout').map(([name, entry])=>({
228
+ name,
229
+ id: entry.id
230
+ }))
231
+ };
232
+ if (made.pages.length || made.layouts.length) inputs[AI_BUILD_MADE_INPUT] = made;
233
+ }
200
234
  // A dataset this build made for its form, which the form may bind (AGL-3616).
201
235
  const madeFor = ((_unit_creation = unit.creation) == null ? void 0 : _unit_creation.kind) === 'form' && isAiPlanNewRef(unit.creation.writesTo) ? (_unitPlan_create_ = unitPlan.create[0]) == null ? void 0 : _unitPlan_create_.writesTo : null;
202
236
  if (madeFor) inputs[AI_FORM_DATASET_MADE_INPUT] = madeFor;
@@ -208,7 +242,9 @@ import { AI_PLANNED_JOB_KINDS, aiJobStepRunMinimumMs, aiJobStepRunnerFor, regist
208
242
  if (pages.length) inputs[AI_LAYOUT_SITE_PAGES_INPUT] = pages;
209
243
  const formPage = aiLayoutFormPageOfPlan(plan);
210
244
  if (formPage) inputs[AI_LAYOUT_FORM_PAGE_INPUT] = formPage;
211
- // The datasets this build made, repeated in the sections that name them (AGL-3616).
245
+ // The datasets this build made, repeated in the sections that name them (AGL-3616),
246
+ // and the site's own the plan reuses (AGL-3747): the page reads their fields,
247
+ // and the record pages the site already serves for them, off its inventory.
212
248
  const datasets = [
213
249
  ...built.entries()
214
250
  ].filter(([, entry])=>entry.kind === 'dataset').map(([name, entry])=>{
@@ -222,6 +258,15 @@ import { AI_PLANNED_JOB_KINDS, aiJobStepRunMinimumMs, aiJobStepRunnerFor, regist
222
258
  })) == null ? void 0 : _context_units_find.label) != null ? _ref : entry.label
223
259
  };
224
260
  });
261
+ for (const entry of plan.reuse){
262
+ var _ref2;
263
+ var _plan_labels1;
264
+ if (entry.kind !== 'dataset' || datasets.some((one)=>one.id === entry.id)) continue;
265
+ datasets.push({
266
+ id: entry.id,
267
+ name: (_ref2 = (_plan_labels1 = plan.labels) == null ? void 0 : _plan_labels1[entry.id]) != null ? _ref2 : entry.id
268
+ });
269
+ }
225
270
  const listings = aiDatasetListingsOf(datasets, plan.screens);
226
271
  if (listings.length) inputs[AI_LAYOUT_LISTINGS_INPUT] = listings;
227
272
  }
@@ -368,7 +413,10 @@ import { AI_PLANNED_JOB_KINDS, aiJobStepRunMinimumMs, aiJobStepRunnerFor, regist
368
413
  /** The store's links a build drafted into the site's layout (AGL-3676), as its publish puts them live; `null` where it drafted none. */ export function aiBuildStoreLinksToPublish(outputs) {
369
414
  var _ref;
370
415
  var _draft_proposal, _draft_proposal1;
371
- const draft = outputs.find((output)=>{
416
+ // The newest: a retried store drafts its links again after the first attempt's (AGL-3739).
417
+ const draft = [
418
+ ...outputs
419
+ ].reverse().find((output)=>{
372
420
  var _output_proposal;
373
421
  return output.resource === 'layout' && ((_output_proposal = output.proposal) == null ? void 0 : _output_proposal['storeLinks']) === 'draft' && output.versionId;
374
422
  });
@@ -437,9 +485,17 @@ export function createAiJobBuildStep(deps = {}) {
437
485
  status: 'pending'
438
486
  });
439
487
  });
440
- /** The build's last pass puts its pages live, where it was asked and confirmed. */ const finish = async (outcome, extraPages = [], extraOutputs = [])=>{
441
- var _job_outputs, _job_outputs1;
442
- if (!aiJobPublishesBuild(job) || job.sitePublish || !job.hostId) return outcome;
488
+ /**
489
+ * The build's last pass puts its pages live, where it was asked and
490
+ * confirmed. Once it has, Try again's last pass puts only the pages the
491
+ * retry built live (AGL-3739, AGL-3730's fix for a site start),
492
+ * merged into the record of the first.
493
+ */ const finish = async (outcome, extraPages = [], extraOutputs = [])=>{
494
+ var _job_sitePublish, _ref, _job_outputs, _job_outputs1;
495
+ if (!aiJobPublishesBuild(job) || !job.hostId) return outcome;
496
+ const previous = (_job_sitePublish = job.sitePublish) != null ? _job_sitePublish : null;
497
+ const retry = Boolean(previous == null ? void 0 : previous.published.length);
498
+ const already = new Set(((_ref = previous == null ? void 0 : previous.published) != null ? _ref : []).map((page)=>page.id));
443
499
  // A record template renders once per record only once its binding is
444
500
  // saved, so it is never published at its own address (AGL-3616).
445
501
  // A store's own pages (AGL-3676) go live with the pages the build planned.
@@ -453,24 +509,30 @@ export function createAiJobBuildStep(deps = {}) {
453
509
  var _rows_get;
454
510
  return (_ref = (_rows_get = rows.get(one.slot)) == null ? void 0 : _rows_get.outputs) != null ? _ref : [];
455
511
  }));
456
- const pages = [
512
+ const built = [
457
513
  ...((_job_outputs = job.outputs) != null ? _job_outputs : []).filter((output)=>output.resource === 'screen' && pageIds.has(output.id)),
458
514
  ...extraPages
459
515
  ];
516
+ const pages = built.filter((page)=>!already.has(page.id));
460
517
  if (!pages.length) return outcome;
461
518
  // A canceled build never puts anything live (AGL-3616): its pages stay drafts.
462
519
  if (await aiJobCancelAsked(firestore, job, context.signal)) return outcome;
463
520
  // The store pages a layout this build made links and that were not written come out of it.
464
521
  const storePages = aiBuildStorePagesOf(plan);
465
- const live = new Set(pages.map((page)=>{
522
+ const live = new Set(built.map((page)=>{
466
523
  var _page_proposal;
467
524
  return (_page_proposal = page.proposal) == null ? void 0 : _page_proposal['storePage'];
468
525
  }).filter(Boolean));
469
526
  const unwrittenHrefs = storePages ? aiStorePagesToWrite(storePages.pages).filter((page)=>!live.has(page.key)).map((page)=>page.href) : [];
470
- const storeLinks = aiBuildStoreLinksToPublish([
527
+ // A retry puts the store's links live only with store pages it built:
528
+ // the first publish's links draft is live already, and a person may have edited it since.
529
+ const storeLinks = !retry || pages.some((page)=>{
530
+ var _page_proposal;
531
+ return (_page_proposal = page.proposal) == null ? void 0 : _page_proposal['storePage'];
532
+ }) ? aiBuildStoreLinksToPublish([
471
533
  ...(_job_outputs1 = job.outputs) != null ? _job_outputs1 : [],
472
534
  ...extraOutputs
473
- ]);
535
+ ]) : null;
474
536
  const sitePublish = await publish(firestore, _extends({
475
537
  job,
476
538
  outputs: pages,
@@ -479,6 +541,8 @@ export function createAiJobBuildStep(deps = {}) {
479
541
  unwrittenHrefs
480
542
  } : {}, storeLinks ? {
481
543
  storeLinks
544
+ } : {}, retry ? {
545
+ retry: true
482
546
  } : {})).catch((error)=>{
483
547
  console.error('ai build publish threw', {
484
548
  orgId: job.orgId,
@@ -487,9 +551,10 @@ export function createAiJobBuildStep(deps = {}) {
487
551
  });
488
552
  return null;
489
553
  });
490
- return sitePublish ? _extends({}, outcome, {
491
- sitePublish
492
- }) : outcome;
554
+ if (!sitePublish) return outcome;
555
+ return _extends({}, outcome, {
556
+ sitePublish: previous ? aiMergeSitePublish(previous, sitePublish) : sitePublish
557
+ });
493
558
  };
494
559
  if (!unit) return finish(_extends({}, aiUnspentOutcome(model), init));
495
560
  const settle = (item, spent = aiUnspentOutcome(model))=>_extends({}, spent, init, {
@@ -723,7 +788,8 @@ export function createAiJobBuildStep(deps = {}) {
723
788
  hostId: job.hostId,
724
789
  label: written.name,
725
790
  draftResource: capability.draftResource,
726
- note: `A draft, in ${capability.where}.`
791
+ // A resource with no draft state is live once written (AGL-3745, AGL-3746).
792
+ note: aiBuildItemOutputNote(capability)
727
793
  };
728
794
  return settle(_extends({
729
795
  slot: unit.slot,
@@ -874,7 +940,10 @@ export const runAiJobBuildStep = createAiJobBuildStep();
874
940
  campaign: 'campaign',
875
941
  workflow: 'workflow',
876
942
  function: 'logic',
877
- edit: 'edit'
943
+ edit: 'edit',
944
+ navigation: 'navigation',
945
+ duplicate: 'duplicate',
946
+ settings: 'settings'
878
947
  };
879
948
  /**
880
949
  * What a Free build's credits say before it runs (AGL-3722), on its measured