@aglyn/plugins-ai 1.0.0-beta.232 → 1.0.0-beta.233
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/declarations.server.js +7 -4
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/jobs/ai-build-capabilities.js +8 -2
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +6 -1
- package/src/lib/jobs/ai-job-plan-step.js +15 -2
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs-notify.d.ts +14 -4
- package/src/lib/jobs/ai-jobs-notify.js +70 -4
- package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +14 -0
- package/src/lib/jobs/ai-jobs.js +28 -10
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/model/ai-plan-capabilities.d.ts +16 -0
- package/src/lib/model/ai-plan-capabilities.js +5 -0
- package/src/lib/model/ai-plan-capabilities.js.map +1 -1
- package/src/lib/operator-alerts.d.ts +18 -0
- package/src/lib/operator-alerts.js +42 -0
- package/src/lib/operator-alerts.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +7 -0
- package/src/lib/runtime/ai-doctrine-validators.js +9 -2
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-ai",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.233",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,22 +25,22 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-enums": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-data-types": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
34
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
35
|
-
"@aglyn/shared-ui-theme": "1.0.0-beta.
|
|
36
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
37
|
-
"@aglyn/shared-util-errors": "1.0.0-beta.
|
|
38
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
39
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
40
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
41
|
-
"@aglyn/tenant-data-admin": "1.0.0-beta.
|
|
42
|
-
"@aglyn/tenant-feature-instance": "1.0.0-beta.
|
|
43
|
-
"@aglyn/tenant-runtime": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.233",
|
|
29
|
+
"@aglyn/shared-data-enums": "1.0.0-beta.233",
|
|
30
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.233",
|
|
31
|
+
"@aglyn/shared-data-types": "1.0.0-beta.233",
|
|
32
|
+
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.233",
|
|
33
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.233",
|
|
34
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.233",
|
|
35
|
+
"@aglyn/shared-ui-theme": "1.0.0-beta.233",
|
|
36
|
+
"@aglyn/shared-util-email": "1.0.0-beta.233",
|
|
37
|
+
"@aglyn/shared-util-errors": "1.0.0-beta.233",
|
|
38
|
+
"@aglyn/shared-util-http": "1.0.0-beta.233",
|
|
39
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.233",
|
|
40
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.233",
|
|
41
|
+
"@aglyn/tenant-data-admin": "1.0.0-beta.233",
|
|
42
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.233",
|
|
43
|
+
"@aglyn/tenant-runtime": "1.0.0-beta.233",
|
|
44
44
|
"@swc/helpers": "0.5.23"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
@@ -23,7 +23,7 @@ import { listUsageAlertContributors, registerUsageAlertContributor } from "@agly
|
|
|
23
23
|
import { registerPluginUsageMeter } from "@aglyn/aglyn/plugin-manager/plugin-usage-meters";
|
|
24
24
|
import { registerOperatorAlerts } from "@aglyn/aglyn/plugin-manager/operator-alerts";
|
|
25
25
|
import { AI_PLUGIN_ID, AI_USAGE_METER_ID } from "./constants.js";
|
|
26
|
-
import { AI_PROVIDER_UNAVAILABLE } from "./operator-alerts.js";
|
|
26
|
+
import { AI_BUILD_PARTLY_FAILED, AI_JOB_FAILED, AI_PROVIDER_UNAVAILABLE } from "./operator-alerts.js";
|
|
27
27
|
import { registerAiDeclarations } from "./declarations.js";
|
|
28
28
|
/** The provider-spend staff alerts' contributor id under the plugin. */ const AI_USAGE_ALERTS_ID = 'provider-spend';
|
|
29
29
|
/**
|
|
@@ -134,10 +134,13 @@ import { registerAiDeclarations } from "./declarations.js";
|
|
|
134
134
|
pluginId: AI_PLUGIN_ID
|
|
135
135
|
});
|
|
136
136
|
}
|
|
137
|
-
// The platform provider account alert (AGL-3377),
|
|
138
|
-
//
|
|
137
|
+
// The platform provider account alert (AGL-3377), and a job failing or a
|
|
138
|
+
// build finishing with parts failed on our side (AGL-3683), listed on
|
|
139
|
+
// Staff → Operator alerts before the first one is raised.
|
|
139
140
|
registerOperatorAlerts([
|
|
140
|
-
AI_PROVIDER_UNAVAILABLE
|
|
141
|
+
AI_PROVIDER_UNAVAILABLE,
|
|
142
|
+
AI_JOB_FAILED,
|
|
143
|
+
AI_BUILD_PARTLY_FAILED
|
|
141
144
|
], {
|
|
142
145
|
pluginId: AI_PLUGIN_ID
|
|
143
146
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/declarations.server.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\n// Each registry from its own module, not the `@aglyn/aglyn/server` barrel:\n// boot needs three registries rather than the whole server surface, and a\n// suite that stubs that barrel wholesale still loads these declarations\n// against the real registries.\nimport {\n listPluginEventHandlers,\n registerPluginEventHandler,\n} from '@aglyn/aglyn/plugin-manager/plugin-events'\nimport {\n listPluginUserErasers,\n registerPluginUserEraser,\n} from '@aglyn/aglyn/plugin-manager/plugin-user-erasure'\nimport {\n listUsageAlertContributors,\n registerUsageAlertContributor,\n} from '@aglyn/aglyn/plugin-manager/usage-alert-contributors'\nimport { registerPluginUsageMeter } from '@aglyn/aglyn/plugin-manager/plugin-usage-meters'\nimport { registerOperatorAlerts } from '@aglyn/aglyn/plugin-manager/operator-alerts'\nimport { AI_PLUGIN_ID, AI_USAGE_METER_ID } from './constants'\nimport { AI_PROVIDER_UNAVAILABLE } from './operator-alerts'\nimport { registerAiDeclarations } from './declarations'\n\n/** The provider-spend staff alerts' contributor id under the plugin. */\nconst AI_USAGE_ALERTS_ID = 'provider-spend'\n\n/**\n * The plugin's SERVER declarations (AGL-2939): what the server must know\n * at boot, before any door of the plugin has been called. The two platform\n * events the plugin writes activity for — the AI add-on bought or dropped,\n * an `ai.*` permission moved — are raised by core billing and membership\n * routes that never load the plugin's API surface, so the subscriptions\n * have to be in place from the first request. So does the eraser: an\n * account erasure is a core route too, and a person's AI usage months sit\n * under each org, keyed by the uid, where no core delete reaches them. So\n * does the usage alert contributor (AGL-2984): the usage-alerts sweep is a\n * core cron, and it evaluates the plugin's staff alerts on provider spend for\n * every org it reads.\n *\n * Light at boot by construction: the writers behind the handlers are\n * imported when the first event arrives, and the alert rules when the sweep\n * first evaluates an org, not when the process starts, so the boot cost is\n * the registration and nothing that touches Firestore.\n */\nexport function registerAiServerDeclarations(): void {\n registerAiDeclarations()\n // Idempotent against each REGISTRY rather than a module flag, so a\n // registry reset (a spec) or a second module instance registers again.\n if (!listPluginEventHandlers('org.seatAddons.changed').includes(AI_PLUGIN_ID)) {\n registerPluginEventHandler(\n 'org.seatAddons.changed',\n async ({ orgId, actor, before, after }) => {\n const { logAiAddonChanged } = await import('./activity/ai-activity')\n await logAiAddonChanged(orgId, actor, { before, after })\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'org.permissions.changed',\n async ({ orgId, actor, subject, permission, granted }) => {\n if (!permission.startsWith('ai.')) return\n const { logAiPermissionChanged } = await import('./activity/ai-activity')\n await logAiPermissionChanged(orgId, actor, { subject, permission, granted })\n },\n { pluginId: AI_PLUGIN_ID },\n )\n }\n // THE PLATFORM'S BILLING EVENTS (AGL-3011). Subscribed at boot for the\n // same reason as the two above: they are raised by the billing webhook,\n // which never loads an AI door, and a workspace's overage standing moving\n // late is a workspace charged or refused against a fact we already had.\n //\n // The handler modules are imported when the first event arrives, so a\n // process that never receives one pays only for the registration.\n if (!listPluginEventHandlers('billing.invoice.paid').includes(AI_PLUGIN_ID)) {\n registerPluginEventHandler(\n 'billing.invoice.paid',\n async (payload) => {\n const { onAiBillingInvoicePaid } = await import('./billing/ai-overage-events')\n await onAiBillingInvoicePaid(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'billing.invoice.failed',\n async (payload) => {\n const { onAiBillingInvoiceFailed } = await import('./billing/ai-overage-events')\n await onAiBillingInvoiceFailed(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'billing.invoice.closed',\n async (payload) => {\n const { onAiBillingInvoiceClosed } = await import('./billing/ai-overage-events')\n await onAiBillingInvoiceClosed(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'billing.dispute.opened',\n async (payload) => {\n const { onAiBillingDisputeOpened } = await import('./billing/ai-overage-events')\n await onAiBillingDisputeOpened(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'billing.paymentMethod.changed',\n async (payload) => {\n const { onAiBillingPaymentMethodChanged } = await import(\n './billing/ai-overage-events'\n )\n await onAiBillingPaymentMethodChanged(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n }\n // The plugin's meter in the monthly usage sweep: the month's provider spend\n // onto the rollup, the credits past the band onto the invoice, and — from\n // the cutover month (AGL-3011) — the close-out of what the plugin invoices\n // itself. Registered at boot because the sweep is a core cron, and declared\n // in `usageAxes` so the sweep refuses to bill a month without it. The same\n // registration again replaces itself.\n registerPluginUsageMeter({\n pluginId: AI_PLUGIN_ID,\n id: AI_USAGE_METER_ID,\n measure: async (context) => {\n const { measureAiMonth } = await import('./billing/ai-month-meter')\n return measureAiMonth(context)\n },\n closeMonth: async (context) => {\n const { closeAiMonth } = await import('./billing/ai-month-meter')\n await closeAiMonth(context)\n },\n })\n if (!listPluginUserErasers().includes(AI_PLUGIN_ID)) {\n registerPluginUserEraser(\n async (request) => {\n const { eraseAiUsageForUser } = await import('./usage/ai-usage-eraser')\n return eraseAiUsageForUser(request)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n }\n // The platform provider account alert (AGL-3377), listed on Staff →\n // Operator alerts before the first one is ever raised.\n registerOperatorAlerts([AI_PROVIDER_UNAVAILABLE], { pluginId: AI_PLUGIN_ID })\n if (\n !listUsageAlertContributors().some(\n (contributor) =>\n contributor.pluginId === AI_PLUGIN_ID &&\n contributor.id === AI_USAGE_ALERTS_ID,\n )\n ) {\n registerUsageAlertContributor({\n pluginId: AI_PLUGIN_ID,\n id: AI_USAGE_ALERTS_ID,\n evaluate: async (context) => {\n const { evaluateAiUsageAlerts } = await import('./usage/ai-usage-alerts')\n await evaluateAiUsageAlerts(context)\n },\n quotaChecks: async (context) => {\n const { aiQuotaChecks } = await import('./usage/ai-quota-checks')\n return aiQuotaChecks(context)\n },\n })\n }\n}\n\nregisterAiServerDeclarations()\n"],"names":["listPluginEventHandlers","registerPluginEventHandler","listPluginUserErasers","registerPluginUserEraser","listUsageAlertContributors","registerUsageAlertContributor","registerPluginUsageMeter","registerOperatorAlerts","AI_PLUGIN_ID","AI_USAGE_METER_ID","AI_PROVIDER_UNAVAILABLE","registerAiDeclarations","AI_USAGE_ALERTS_ID","registerAiServerDeclarations","includes","orgId","actor","before","after","logAiAddonChanged","pluginId","subject","permission","granted","startsWith","logAiPermissionChanged","payload","onAiBillingInvoicePaid","onAiBillingInvoiceFailed","onAiBillingInvoiceClosed","onAiBillingDisputeOpened","onAiBillingPaymentMethodChanged","id","measure","context","measureAiMonth","closeMonth","closeAiMonth","request","eraseAiUsageForUser","some","contributor","evaluate","evaluateAiUsageAlerts","quotaChecks","aiQuotaChecks"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/declarations.server.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\n// Each registry from its own module, not the `@aglyn/aglyn/server` barrel:\n// boot needs three registries rather than the whole server surface, and a\n// suite that stubs that barrel wholesale still loads these declarations\n// against the real registries.\nimport {\n listPluginEventHandlers,\n registerPluginEventHandler,\n} from '@aglyn/aglyn/plugin-manager/plugin-events'\nimport {\n listPluginUserErasers,\n registerPluginUserEraser,\n} from '@aglyn/aglyn/plugin-manager/plugin-user-erasure'\nimport {\n listUsageAlertContributors,\n registerUsageAlertContributor,\n} from '@aglyn/aglyn/plugin-manager/usage-alert-contributors'\nimport { registerPluginUsageMeter } from '@aglyn/aglyn/plugin-manager/plugin-usage-meters'\nimport { registerOperatorAlerts } from '@aglyn/aglyn/plugin-manager/operator-alerts'\nimport { AI_PLUGIN_ID, AI_USAGE_METER_ID } from './constants'\nimport { AI_BUILD_PARTLY_FAILED, AI_JOB_FAILED, AI_PROVIDER_UNAVAILABLE } from './operator-alerts'\nimport { registerAiDeclarations } from './declarations'\n\n/** The provider-spend staff alerts' contributor id under the plugin. */\nconst AI_USAGE_ALERTS_ID = 'provider-spend'\n\n/**\n * The plugin's SERVER declarations (AGL-2939): what the server must know\n * at boot, before any door of the plugin has been called. The two platform\n * events the plugin writes activity for — the AI add-on bought or dropped,\n * an `ai.*` permission moved — are raised by core billing and membership\n * routes that never load the plugin's API surface, so the subscriptions\n * have to be in place from the first request. So does the eraser: an\n * account erasure is a core route too, and a person's AI usage months sit\n * under each org, keyed by the uid, where no core delete reaches them. So\n * does the usage alert contributor (AGL-2984): the usage-alerts sweep is a\n * core cron, and it evaluates the plugin's staff alerts on provider spend for\n * every org it reads.\n *\n * Light at boot by construction: the writers behind the handlers are\n * imported when the first event arrives, and the alert rules when the sweep\n * first evaluates an org, not when the process starts, so the boot cost is\n * the registration and nothing that touches Firestore.\n */\nexport function registerAiServerDeclarations(): void {\n registerAiDeclarations()\n // Idempotent against each REGISTRY rather than a module flag, so a\n // registry reset (a spec) or a second module instance registers again.\n if (!listPluginEventHandlers('org.seatAddons.changed').includes(AI_PLUGIN_ID)) {\n registerPluginEventHandler(\n 'org.seatAddons.changed',\n async ({ orgId, actor, before, after }) => {\n const { logAiAddonChanged } = await import('./activity/ai-activity')\n await logAiAddonChanged(orgId, actor, { before, after })\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'org.permissions.changed',\n async ({ orgId, actor, subject, permission, granted }) => {\n if (!permission.startsWith('ai.')) return\n const { logAiPermissionChanged } = await import('./activity/ai-activity')\n await logAiPermissionChanged(orgId, actor, { subject, permission, granted })\n },\n { pluginId: AI_PLUGIN_ID },\n )\n }\n // THE PLATFORM'S BILLING EVENTS (AGL-3011). Subscribed at boot for the\n // same reason as the two above: they are raised by the billing webhook,\n // which never loads an AI door, and a workspace's overage standing moving\n // late is a workspace charged or refused against a fact we already had.\n //\n // The handler modules are imported when the first event arrives, so a\n // process that never receives one pays only for the registration.\n if (!listPluginEventHandlers('billing.invoice.paid').includes(AI_PLUGIN_ID)) {\n registerPluginEventHandler(\n 'billing.invoice.paid',\n async (payload) => {\n const { onAiBillingInvoicePaid } = await import('./billing/ai-overage-events')\n await onAiBillingInvoicePaid(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'billing.invoice.failed',\n async (payload) => {\n const { onAiBillingInvoiceFailed } = await import('./billing/ai-overage-events')\n await onAiBillingInvoiceFailed(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'billing.invoice.closed',\n async (payload) => {\n const { onAiBillingInvoiceClosed } = await import('./billing/ai-overage-events')\n await onAiBillingInvoiceClosed(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'billing.dispute.opened',\n async (payload) => {\n const { onAiBillingDisputeOpened } = await import('./billing/ai-overage-events')\n await onAiBillingDisputeOpened(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'billing.paymentMethod.changed',\n async (payload) => {\n const { onAiBillingPaymentMethodChanged } = await import(\n './billing/ai-overage-events'\n )\n await onAiBillingPaymentMethodChanged(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n }\n // The plugin's meter in the monthly usage sweep: the month's provider spend\n // onto the rollup, the credits past the band onto the invoice, and — from\n // the cutover month (AGL-3011) — the close-out of what the plugin invoices\n // itself. Registered at boot because the sweep is a core cron, and declared\n // in `usageAxes` so the sweep refuses to bill a month without it. The same\n // registration again replaces itself.\n registerPluginUsageMeter({\n pluginId: AI_PLUGIN_ID,\n id: AI_USAGE_METER_ID,\n measure: async (context) => {\n const { measureAiMonth } = await import('./billing/ai-month-meter')\n return measureAiMonth(context)\n },\n closeMonth: async (context) => {\n const { closeAiMonth } = await import('./billing/ai-month-meter')\n await closeAiMonth(context)\n },\n })\n if (!listPluginUserErasers().includes(AI_PLUGIN_ID)) {\n registerPluginUserEraser(\n async (request) => {\n const { eraseAiUsageForUser } = await import('./usage/ai-usage-eraser')\n return eraseAiUsageForUser(request)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n }\n // The platform provider account alert (AGL-3377), and a job failing or a\n // build finishing with parts failed on our side (AGL-3683), listed on\n // Staff → Operator alerts before the first one is raised.\n registerOperatorAlerts([AI_PROVIDER_UNAVAILABLE, AI_JOB_FAILED, AI_BUILD_PARTLY_FAILED], {\n pluginId: AI_PLUGIN_ID,\n })\n if (\n !listUsageAlertContributors().some(\n (contributor) =>\n contributor.pluginId === AI_PLUGIN_ID &&\n contributor.id === AI_USAGE_ALERTS_ID,\n )\n ) {\n registerUsageAlertContributor({\n pluginId: AI_PLUGIN_ID,\n id: AI_USAGE_ALERTS_ID,\n evaluate: async (context) => {\n const { evaluateAiUsageAlerts } = await import('./usage/ai-usage-alerts')\n await evaluateAiUsageAlerts(context)\n },\n quotaChecks: async (context) => {\n const { aiQuotaChecks } = await import('./usage/ai-quota-checks')\n return aiQuotaChecks(context)\n },\n })\n }\n}\n\nregisterAiServerDeclarations()\n"],"names":["listPluginEventHandlers","registerPluginEventHandler","listPluginUserErasers","registerPluginUserEraser","listUsageAlertContributors","registerUsageAlertContributor","registerPluginUsageMeter","registerOperatorAlerts","AI_PLUGIN_ID","AI_USAGE_METER_ID","AI_BUILD_PARTLY_FAILED","AI_JOB_FAILED","AI_PROVIDER_UNAVAILABLE","registerAiDeclarations","AI_USAGE_ALERTS_ID","registerAiServerDeclarations","includes","orgId","actor","before","after","logAiAddonChanged","pluginId","subject","permission","granted","startsWith","logAiPermissionChanged","payload","onAiBillingInvoicePaid","onAiBillingInvoiceFailed","onAiBillingInvoiceClosed","onAiBillingDisputeOpened","onAiBillingPaymentMethodChanged","id","measure","context","measureAiMonth","closeMonth","closeAiMonth","request","eraseAiUsageForUser","some","contributor","evaluate","evaluateAiUsageAlerts","quotaChecks","aiQuotaChecks"],"mappings":"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"}
|
|
@@ -329,13 +329,19 @@ const passes = (count)=>()=>count * AI_SITE_PASS_CREDITS;
|
|
|
329
329
|
for (const one of items){
|
|
330
330
|
var _one_pageBlocks;
|
|
331
331
|
const args = Object.entries(one.argsSchema.properties).map(([name, property])=>{
|
|
332
|
-
var _one_argsSchema_required;
|
|
332
|
+
var _one_argsSchema_required, _property_items;
|
|
333
333
|
const required = ((_one_argsSchema_required = one.argsSchema.required) == null ? void 0 : _one_argsSchema_required.includes(name)) ? '' : ' (optional)';
|
|
334
|
-
|
|
334
|
+
// An array's values are named too (AGL-3616): told only "days: array",
|
|
335
|
+
// the live canonical plan wrote "monday" for the enum's "mon".
|
|
336
|
+
const choices = property.enum ? ` one of ${property.enum.join('|')}` : ((_property_items = property.items) == null ? void 0 : _property_items.enum) ? ` of ${property.items.enum.join('|')}` : '';
|
|
335
337
|
return `${name}: ${property.type}${choices}${required} — ${property.description}`;
|
|
336
338
|
});
|
|
337
339
|
lines.push(`- ${one.op}: ${aiNounWithArticle(one.noun)}, at most ${one.maxPerPlan}; for ${one.intents.join('; ')}.${((_one_pageBlocks = one.pageBlocks) == null ? void 0 : _one_pageBlocks.length) ? ` A page section that uses it places the ${one.pageBlocks.join(' or ')} block: put new:<name> in that section's uses.` : ''} Args: ${args.length ? `{ ${args.join('; ')} }` : '{}'}.`);
|
|
338
340
|
}
|
|
341
|
+
// Measured live on the canonical request (AGL-3616): asked for "forms for
|
|
342
|
+
// bookings", the planner made a plain form rather than the operation that
|
|
343
|
+
// takes bookings. An operation does the job; a form that imitates it does not.
|
|
344
|
+
lines.push('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.');
|
|
339
345
|
return lines;
|
|
340
346
|
}
|
|
341
347
|
function aiNounWithArticle(noun) {
|
|
@@ -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 const choices = property.enum ? ` one of ${property.enum.join('|')}` : ''\n return `${name}: ${property.type}${choices}${required} — ${property.description}`\n })\n lines.push(\n `- ${one.op}: ${aiNounWithArticle(one.noun)}, at most ${one.maxPerPlan}; for ${one.intents.join('; ')}.${\n one.pageBlocks?.length ? ` A page section that uses it places the ${one.pageBlocks.join(' or ')} block: put new:<name> in that section's uses.` : ''\n } Args: ${args.length ? `{ ${args.join('; ')} }` : '{}'}.`,\n )\n }\n return lines\n}\n\nfunction aiNounWithArticle(noun: string): string {\n return `${/^[aeiou]/i.test(noun) ? 'an' : 'a'} ${noun}`\n}\n\n/** What a build can make here, in the owner's words, for the chat's instructions. */\nexport function aiBuildIntents(ops: AiBuildOps): string[] {\n return [...ops.values()].flatMap((one) => one.intents)\n}\n"],"names":["checkEntitlement","isHostPluginEnabled","pluginAiCapabilities","registerPluginAiCapability","filterEnabledPluginsByReleaseFlags","AI_TEMPLATE_SUBJECTS","AI_SITE_PASS_CREDITS","AI_LOGIC_FUNCTION_RESOURCE","AI_AUTOMATION_RESOURCE","AI_EDIT_TARGET_KINDS","AI_CAMPAIGN_RESOURCE","aiPluginDraftOwner","aiJobStepRunnerFor","AI_PLUGIN_ID","NO_ARGS","type","properties","additionalProperties","passes","count","AI_OWNED_CAPABILITIES","op","noun","where","intents","argsSchema","maxPerPlan","freeAllowed","runnerKind","estimateCredits","degrade","subject","description","enum","collectionId","maxLength","required","name","quota","target","targetKind","AI_OWNED_OP_WRITERS","function","campaign","workflow","registerAiBuildCapabilities","capability","pluginId","aiBuildOps","context","deps","all","capabilities","hasRunner","kind","writerOwner","writtenBy","Map","resource","undefined","set","owners","Set","map","one","values","filter","id","released","length","releasedPlugins","ids","orgId","authorization","ops","runsOn","get","has","org","host","feature","freeTaste","aiBuildOpLines","structural","items","includes","lines","args","Object","entries","property","choices","join","push","aiNounWithArticle","pageBlocks","test","aiBuildIntents","flatMap"],"mappings":"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"}
|
|
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":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,gBAAgB,QAAQ,2CAA0C;AAE3E,SAASC,mBAAmB,QAAQ,8CAA6C;AACjF,SACEC,oBAAoB,EACpBC,0BAA0B,QAGrB,qDAAoD;AAC3D,SAASC,kCAAkC,QAAQ,gDAA+C;AAClG,SAASC,oBAAoB,QAAQ,mCAA+B;AACpE,SAASC,oBAAoB,QAAQ,0BAAsB;AAE3D,SAASC,0BAA0B,QAAQ,2BAAuB;AAClE,SAASC,sBAAsB,QAAQ,8BAA0B;AACjE,SAASC,oBAAoB,QAAQ,0BAAsB;AAC3D,SAASC,oBAAoB,QAAQ,4BAAwB;AAC7D,SAASC,kBAAkB,QAAuC,4BAAwB;AAC1F,SAASC,kBAAkB,QAAQ,eAAW;AAE9C;;;;;;;;;;;;;CAaC,GAED,6DAA6D,GAC7D,MAAMC,eAAe;AAErB,MAAMC,UAAU;IAAEC,MAAM;IAAUC,YAAY,CAAC;IAAGC,sBAAsB;AAAM;AAE9E,MAAMC,SAAS,CAACC,QAAkB,IAAMA,QAAQb;AAEhD,2DAA2D,GAC3D,OAAO,MAAMc,wBAAuD;IAClE;QACEC,IAAI;QACJC,MAAM;QACNC,OAAO;QACPC,SAAS;YAAC;SAAqE;QAC/EC,YAAYX;QACZY,YAAY;QACZC,aAAa;QACbC,YAAY;QACZC,iBAAiBX,OAAO;QACxBY,SAAS;IACX;IACA;QACET,IAAI;QACJC,MAAM;QACNC,OAAO;QACPC,SAAS;YAAC;SAAsC;QAChDC,YAAYX;QACZY,YAAY;QACZC,aAAa;QACbC,YAAY;QACZC,iBAAiBX,OAAO;QACxBY,SAAS;IACX;IACA;QACET,IAAI;QACJC,MAAM;QACNC,OAAO;QACPC,SAAS;YAAC;SAAkD;QAC5DC,YAAYX;QACZY,YAAY;QACZC,aAAa;QACbC,YAAY;QACZC,iBAAiBX,OAAO;QACxBY,SAAS;IACX;IACA;QACET,IAAI;QACJC,MAAM;QACNC,OAAO;QACPC,SAAS;YAAC;SAAmD;QAC7DC,YAAYX;QACZY,YAAY;QACZC,aAAa;QACbC,YAAY;QACZC,iBAAiBX,OAAO;QACxBY,SAAS;IACX;IACA;QACET,IAAI;QACJC,MAAM;QACNC,OAAO;QACPC,SAAS;YAAC;SAAuD;QACjEC,YAAYX;QACZY,YAAY;QACZC,aAAa;QACbC,YAAY;QACZC,iBAAiBX,OAAO;QACxBY,SAAS;IACX;IACA;QACET,IAAI;QACJC,MAAM;QACNC,OAAO;QACPC,SAAS;YAAC;SAAsD;QAChEC,YAAY;YACVV,MAAM;YACNC,YAAY;gBACVe,SAAS;oBACPhB,MAAM;oBACNiB,aAAa;oBACbC,MAAM5B;gBACR;gBACA6B,cAAc;oBACZnB,MAAM;oBACNiB,aAAa;oBACbG,WAAW;gBACb;YACF;YACAC,UAAU;gBAAC;aAAU;YACrBnB,sBAAsB;QACxB;QACAS,YAAY;QACZC,aAAa;QACbC,YAAY;QACZC,iBAAiBX,OAAO;QACxBY,SAAS;IACX;IACA;QACET,IAAI;QACJC,MAAM;QACNC,OAAO;QACPC,SAAS;YAAC;SAA4C;QACtDC,YAAY;YACVV,MAAM;YACNC,YAAY;gBACVqB,MAAM;oBAAEtB,MAAM;oBAAUiB,aAAa;oBAAgCG,WAAW;gBAAI;YACtF;YACAlB,sBAAsB;QACxB;QACAS,YAAY;QACZC,aAAa;QACbC,YAAY;QACZC,iBAAiBX,OAAO;QACxBY,SAAS;IACX;IACA;QACET,IAAI;QACJC,MAAM;QACNC,OAAO;QACPC,SAAS;YAAC;SAAmD;QAC7DC,YAAYX;QACZY,YAAY;QACZC,aAAa;QACbC,YAAY;QACZC,iBAAiBX,OAAO;QACxBY,SAAS;IACX;IACA;QACE,wEAAwE;QACxE,yEAAyE;QACzE,gDAAgD;QAChDT,IAAI;QACJC,MAAM;QACNC,OAAO;QACPC,SAAS;YAAC;SAA0F;QACpGC,YAAYX;QACZY,YAAY;QACZ,4EAA4E;QAC5EC,aAAa;QACbW,OAAO;QACPV,YAAY;QACZC,iBAAiBX,OAAO;QACxBY,SAAS;IACX;IACA;QACE,qEAAqE;QACrE,oEAAoE;QACpE,oEAAoE;QACpE,qEAAqE;QACrE,sEAAsE;QACtE,sDAAsD;QACtDT,IAAI;QACJC,MAAM;QACNC,OAAO;QACPC,SAAS;YACP;SACD;QACDC,YAAY;YACVV,MAAM;YACNC,YAAY;gBACVuB,QAAQ;oBACNxB,MAAM;oBACNiB,aAAa;oBACbG,WAAW;gBACb;gBACAK,YAAY;oBACVzB,MAAM;oBACNiB,aAAa;oBACbC,MAAMxB;gBACR;YACF;YACA2B,UAAU;gBAAC;aAAS;YACpBnB,sBAAsB;QACxB;QACAS,YAAY;QACZC,aAAa;QACbC,YAAY;QACZC,iBAAiBX,OAAO;QACxBY,SAAS;IACX;CACD,CAAA;AAED;;;;;CAKC,GACD,OAAO,MAAMW,sBAAiE;IAC5EC,UAAUnC;IACV,sEAAsE;IACtE,qEAAqE;IACrE,gEAAgE;IAChEoC,UAAUjC;IACVkC,UAAUpC;AACZ,EAAC;AAED,4EAA4E,GAC5E,OAAO,SAASqC;IACd,KAAK,MAAMC,cAAc1B,sBAAuB;QAC9CjB,2BAA2B2C,YAAY;YAAEC,UAAUlC;QAAa;IAClE;AACF;AAuBA;;;;;;;;;;CAUC,GACD,OAAO,eAAemC,WAAWC,OAA0B,EAAEC,OAAuB,CAAC,CAAC;QACvEA,oBACKA,iBACEA,mBAgBPA;IAlBb,MAAMC,MAAM,EAACD,qBAAAA,KAAKE,YAAY,YAAjBF,qBAAqBhD;IAClC,MAAMmD,aAAYH,kBAAAA,KAAKG,SAAS,YAAdH,kBAAmB,CAACI,OAAiB1C,mBAAmB0C,UAAmB;IAC7F,MAAMC,eAAcL,oBAAAA,KAAKK,WAAW,YAAhBL,oBAAoBvC;IACxC,4GAA4G;IAC5G,MAAM6C,YAAY,IAAIC;IACtB,KAAK,MAAM,EAAEV,QAAQ,EAAED,UAAU,EAAE,IAAIK,IAAK;QAC1C,MAAMO,WAAWX,aAAalC,eAAe4B,mBAAmB,CAACK,WAAWzB,EAAE,CAAC,GAAGsC;QAClF,IAAID,UAAUF,UAAUI,GAAG,CAACd,WAAWzB,EAAE,EAAEkC,YAAYG;IACzD;IACA,MAAMG,SAAS;WACV,IAAIC,IACL;eAAIX,IAAIY,GAAG,CAAC,CAACC,MAAQA,IAAIjB,QAAQ;eAAMS,UAAUS,MAAM;SAAG,CAACC,MAAM,CAC/D,CAACC,KAAqB,CAAC,CAACA,MAAMA,OAAOtD;KAG1C;IACD,MAAMuD,WAAW,IAAIN,IACnBD,OAAOQ,MAAM,GACT,MAAM,EAACnB,wBAAAA,KAAKoB,eAAe,YAApBpB,wBACJ,CAACqB,KAAKC,QAAUpE,mCAAmCmE,KAAK;YAAEC;YAAOC,eAAe;QAAK,IAAKZ,QAAQZ,QAAQuB,KAAK,IAClH,EAAE;IAER,MAAME,MAAM,IAAIjB;IAChB,KAAK,MAAM,EAAEV,QAAQ,EAAED,UAAU,EAAE,IAAIK,IAAK;QAC1C,wEAAwE;QACxE,oDAAoD;QACpD,MAAMwB,SAAS5B,aAAalC,eAAe2C,UAAUoB,GAAG,CAAC9B,WAAWzB,EAAE,IAAI0B;QAC1E,IAAI4B,WAAW,MAAM;QACrB,IAAIA,WAAWhB,aAAagB,WAAW9D,cAAc;YACnD,IAAI,CAACuD,SAASS,GAAG,CAACF,SAAS;YAC3B,IAAI,CAAC1E,oBAAoBgD,QAAQ6B,GAAG,EAA0C7B,QAAQ8B,IAAI,EAAEJ,SAAS;QACvG;QACA,IAAI7B,WAAWkC,OAAO,IAAI,CAAChF,iBAAiBiD,QAAQ6B,GAAG,EAAEhC,WAAWkC,OAAO,GAAG;QAC9E,IAAI/B,QAAQgC,SAAS,IAAI,CAACnC,WAAWnB,WAAW,EAAE;QAClD,IAAImB,WAAWlB,UAAU,IAAI,CAACyB,UAAUP,WAAWlB,UAAU,GAAG;QAChE8C,IAAId,GAAG,CAACd,WAAWzB,EAAE,EAAEyB;IACzB;IACA,OAAO4B;AACT;AAEA;;;;CAIC,GACD,OAAO,SAASQ,eAAeR,GAAe,EAAES,UAA6B;IAC3E,MAAMC,QAAQ;WAAIV,IAAIT,MAAM;KAAG,CAACC,MAAM,CAAC,CAACF,MAAQ,CAACmB,WAAWE,QAAQ,CAACrB,IAAI3C,EAAE;IAC3E,IAAI,CAAC+D,MAAMf,MAAM,EAAE,OAAO;QAAC;KAA8D;IACzF,MAAMiB,QAAQ;QAAC;KAAkE;IACjF,KAAK,MAAMtB,OAAOoB,MAAO;YAcnBpB;QAbJ,MAAMuB,OAAOC,OAAOC,OAAO,CAACzB,IAAIvC,UAAU,CAACT,UAAU,EAAE+C,GAAG,CAAC,CAAC,CAAC1B,MAAMqD,SAAS;gBACzD1B,0BAKb0B;YALJ,MAAMtD,WAAW4B,EAAAA,2BAAAA,IAAIvC,UAAU,CAACW,QAAQ,qBAAvB4B,yBAAyBqB,QAAQ,CAAChD,SAAQ,KAAK;YAChE,uEAAuE;YACvE,+DAA+D;YAC/D,MAAMsD,UAAUD,SAASzD,IAAI,GACzB,CAAC,QAAQ,EAAEyD,SAASzD,IAAI,CAAC2D,IAAI,CAAC,MAAM,GACpCF,EAAAA,kBAAAA,SAASN,KAAK,qBAAdM,gBAAgBzD,IAAI,IAClB,CAAC,IAAI,EAAEyD,SAASN,KAAK,CAACnD,IAAI,CAAC2D,IAAI,CAAC,MAAM,GACtC;YACN,OAAO,GAAGvD,KAAK,EAAE,EAAEqD,SAAS3E,IAAI,GAAG4E,UAAUvD,SAAS,GAAG,EAAEsD,SAAS1D,WAAW,EAAE;QACnF;QACAsD,MAAMO,IAAI,CACR,CAAC,EAAE,EAAE7B,IAAI3C,EAAE,CAAC,EAAE,EAAEyE,kBAAkB9B,IAAI1C,IAAI,EAAE,UAAU,EAAE0C,IAAItC,UAAU,CAAC,MAAM,EAAEsC,IAAIxC,OAAO,CAACoE,IAAI,CAAC,MAAM,CAAC,EACrG5B,EAAAA,kBAAAA,IAAI+B,UAAU,qBAAd/B,gBAAgBK,MAAM,IAAG,CAAC,wCAAwC,EAAEL,IAAI+B,UAAU,CAACH,IAAI,CAAC,QAAQ,8CAA8C,CAAC,GAAG,GACnJ,OAAO,EAAEL,KAAKlB,MAAM,GAAG,CAAC,EAAE,EAAEkB,KAAKK,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC;IAE9D;IACA,0EAA0E;IAC1E,0EAA0E;IAC1E,+EAA+E;IAC/EN,MAAMO,IAAI,CACR;IAEF,OAAOP;AACT;AAEA,SAASQ,kBAAkBxE,IAAY;IACrC,OAAO,GAAG,YAAY0E,IAAI,CAAC1E,QAAQ,OAAO,IAAI,CAAC,EAAEA,MAAM;AACzD;AAEA,mFAAmF,GACnF,OAAO,SAAS2E,eAAevB,GAAe;IAC5C,OAAO;WAAIA,IAAIT,MAAM;KAAG,CAACiC,OAAO,CAAC,CAAClC,MAAQA,IAAIxC,OAAO;AACvD"}
|
|
@@ -268,7 +268,12 @@ export declare function createAiJobPlanStep(deps?: AiJobPlanStepDeps): AiJobStep
|
|
|
268
268
|
* A site job's capabilities (AGL-3594, AGL-3660): it changes no theme in its
|
|
269
269
|
* plan — every scaffold designs its look first, in a unit of its own — and on
|
|
270
270
|
* the Free taste its plan holds at most the pages the member asked for within
|
|
271
|
-
* the Free band, which the Free wall then holds it to.
|
|
271
|
+
* the Free band, which the Free wall then holds it to.
|
|
272
|
+
*
|
|
273
|
+
* A build's pages are written in the layout language (`aiBuildUnitJob`), whose
|
|
274
|
+
* compiler draws a section's repeated items itself, so its plan is not asked
|
|
275
|
+
* for a component for them (`repeatsCompiled`, AGL-3616). It still may plan
|
|
276
|
+
* one, and a section two pages share is still one. Every other job's
|
|
272
277
|
* capabilities pass through.
|
|
273
278
|
*/
|
|
274
279
|
export declare function aiSitePlanCapabilities(job: Pick<AiJob, 'kind' | 'inputs'>, capabilities: AiPlanCapabilities | null): AiPlanCapabilities | null;
|
|
@@ -20,7 +20,7 @@ import { AI_BUILD_PLAN_TOOL, aiBuildPlanToolFor, isAiPlanNewRef } from "../model
|
|
|
20
20
|
import { AI_TEMPLATE_SUBJECT_DEFINITIONS, aiTemplateShownTokens } from "../model/ai-template-subjects.js";
|
|
21
21
|
import { AI_JOB_CREATE_KINDS, AI_JOB_CREATE_NOUNS } from "../model/ai-job-creations.js";
|
|
22
22
|
import { AI_PAGE_CREATE_KINDS, aiPagePlanShapeRefusal } from "../model/ai-page-job.js";
|
|
23
|
-
import { AI_PLAN_NO_FORM_SENTENCE, aiPlanCanPlaceForm, aiPlanCapabilitiesForJob, aiPlanCapabilityLines } from "../model/ai-plan-capabilities.js";
|
|
23
|
+
import { AI_PLAN_COMPILED_REPEATS_SENTENCE, AI_PLAN_NO_FORM_SENTENCE, aiPlanCanPlaceForm, aiPlanCapabilitiesForJob, aiPlanCapabilityLines } from "../model/ai-plan-capabilities.js";
|
|
24
24
|
import { AI_FREE_SITE_WORST_CASE_CREDITS, AI_SITE_CREATE_KINDS, AI_SITE_FREE_PAGES, AI_SITE_PLAN_MAX_TOKENS, aiFreeSiteSectionsWithin, aiSiteNameSentence, aiSitePlanShapeRefusal } from "../model/ai-site-job.js";
|
|
25
25
|
import { aiPlanFailureCopy, aiPlanRetryRefusal } from "../model/ai-job-failure-copy.js";
|
|
26
26
|
import { FREE_AI_TASTE_CREDITS_PER_MONTH } from "../plan-entitlements.js";
|
|
@@ -138,6 +138,11 @@ import { AI_JOBS_COLLECTION, registerAiJobPlanStep } from "./ai-jobs.js";
|
|
|
138
138
|
return [
|
|
139
139
|
`This is a build from one request: plan each page it asks for in screens (at most ${pages}), each new layout, form, component or email design in create, and anything else in items. Plan only what the request asks for; reuse what the site has.`,
|
|
140
140
|
'A page section that shows a form or an item places it: put its new:<name> in that section\'s uses. An item built after another lists it in dependsOn.',
|
|
141
|
+
// The layout language draws a section's repeated items (AGL-3660), so the
|
|
142
|
+
// doctrine's "repeated items place a component" is not asked of a build.
|
|
143
|
+
...(capabilities == null ? void 0 : capabilities.repeatsCompiled) ? [
|
|
144
|
+
AI_PLAN_COMPILED_REPEATS_SENTENCE
|
|
145
|
+
] : [],
|
|
141
146
|
...ops ? aiBuildOpLines(ops, AI_BUILD_STRUCTURAL_OPS) : []
|
|
142
147
|
];
|
|
143
148
|
}
|
|
@@ -677,10 +682,18 @@ export function createAiJobPlanStep(deps = {}) {
|
|
|
677
682
|
* A site job's capabilities (AGL-3594, AGL-3660): it changes no theme in its
|
|
678
683
|
* plan — every scaffold designs its look first, in a unit of its own — and on
|
|
679
684
|
* the Free taste its plan holds at most the pages the member asked for within
|
|
680
|
-
* the Free band, which the Free wall then holds it to.
|
|
685
|
+
* the Free band, which the Free wall then holds it to.
|
|
686
|
+
*
|
|
687
|
+
* A build's pages are written in the layout language (`aiBuildUnitJob`), whose
|
|
688
|
+
* compiler draws a section's repeated items itself, so its plan is not asked
|
|
689
|
+
* for a component for them (`repeatsCompiled`, AGL-3616). It still may plan
|
|
690
|
+
* one, and a section two pages share is still one. Every other job's
|
|
681
691
|
* capabilities pass through.
|
|
682
692
|
*/ export function aiSitePlanCapabilities(job, capabilities) {
|
|
683
693
|
var _job_inputs;
|
|
694
|
+
if (job.kind === 'build' && capabilities) return _extends({}, capabilities, {
|
|
695
|
+
repeatsCompiled: true
|
|
696
|
+
});
|
|
684
697
|
if (job.kind !== 'site' || !capabilities) return capabilities;
|
|
685
698
|
const create = _extends({}, capabilities.create, {
|
|
686
699
|
'theme-change': {
|