@aglyn/plugins-ai 1.0.0-beta.231 → 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/activity/ai-activity-actions.js +8 -4
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-job-follow.component.js +1 -0
- package/src/lib/components/ai-job-follow.component.js.map +1 -1
- package/src/lib/components/ai-job-links.js +13 -0
- package/src/lib/components/ai-job-links.js.map +1 -1
- package/src/lib/components/ai-jobs-list-page.component.js +1 -0
- package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
- package/src/lib/components/ai-media-create.component.d.ts +20 -7
- package/src/lib/components/ai-media-create.component.js +107 -55
- package/src/lib/components/ai-media-create.component.js.map +1 -1
- package/src/lib/components/ai-seo-fields-card.component.js +1 -0
- package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
- package/src/lib/components/ai-site-memory-card.component.d.ts +32 -0
- package/src/lib/components/ai-site-memory-card.component.js +187 -0
- package/src/lib/components/ai-site-memory-card.component.js.map +1 -0
- package/src/lib/components/ai-site-start-card.component.js +80 -77
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-theme-proposal-card.component.js +4 -0
- package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
- package/src/lib/components/ai-usage-strip.component.js +1 -0
- package/src/lib/components/ai-usage-strip.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.d.ts +10 -0
- package/src/lib/components/assist-panel.component.js +119 -9
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/assist-signals-page.component.js +2 -0
- package/src/lib/components/assist-signals-page.component.js.map +1 -1
- package/src/lib/components/billing-ai-allotments.component.js +3 -0
- package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
- package/src/lib/components/billing-ai-top-users.component.js +1 -0
- package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
- package/src/lib/components/billing-assist-overage-card.component.js +2 -0
- package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
- package/src/lib/components/member-ai-usage-card.component.js +10 -0
- package/src/lib/components/member-ai-usage-card.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-card.component.js +1 -0
- package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-conversations.component.d.ts +23 -0
- package/src/lib/components/staff-org-ai-conversations.component.js +346 -0
- package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -0
- package/src/lib/declarations.server.js +7 -4
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/jobs/ai-build-capabilities.d.ts +13 -1
- package/src/lib/jobs/ai-build-capabilities.js +109 -9
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
- package/src/lib/jobs/ai-business-profile-prefill.d.ts +29 -0
- package/src/lib/jobs/ai-business-profile-prefill.js +85 -0
- package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -0
- package/src/lib/jobs/ai-job-build-step.js +3 -1
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-edit-step.d.ts +225 -0
- package/src/lib/jobs/ai-job-edit-step.js +631 -0
- package/src/lib/jobs/ai-job-edit-step.js.map +1 -0
- package/src/lib/jobs/ai-job-insight-step.js +21 -4
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +8 -3
- package/src/lib/jobs/ai-job-layout-language.js +8 -2
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +7 -6
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-logic-step.d.ts +20 -0
- package/src/lib/jobs/ai-job-logic-step.js +87 -6
- package/src/lib/jobs/ai-job-logic-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +52 -7
- package/src/lib/jobs/ai-job-page-language.js +72 -9
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +12 -2
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +24 -5
- package/src/lib/jobs/ai-job-plan-step.js +68 -21
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +163 -0
- package/src/lib/jobs/ai-job-site-content.js +443 -0
- package/src/lib/jobs/ai-job-site-content.js.map +1 -0
- package/src/lib/jobs/ai-job-site-look.d.ts +112 -0
- package/src/lib/jobs/ai-job-site-look.js +322 -0
- package/src/lib/jobs/ai-job-site-look.js.map +1 -0
- package/src/lib/jobs/ai-job-site-step.d.ts +26 -5
- package/src/lib/jobs/ai-job-site-step.js +163 -24
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs-notify.d.ts +16 -1
- package/src/lib/jobs/ai-jobs-notify.js +81 -3
- 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/layout-language/ai-layout-compiler.d.ts +9 -0
- package/src/lib/layout-language/ai-layout-compiler.js +120 -33
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame.js +5 -4
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-language.d.ts +12 -5
- package/src/lib/layout-language/ai-layout-language.js +92 -9
- package/src/lib/layout-language/ai-layout-language.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +144 -0
- package/src/lib/layout-language/ai-layout-pictures.js +214 -0
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -0
- package/src/lib/layout-language/ai-layout-store.d.ts +2 -0
- package/src/lib/layout-language/ai-layout-store.js +7 -1
- package/src/lib/layout-language/ai-layout-store.js.map +1 -1
- package/src/lib/model/ai-build-progress.js +4 -1
- package/src/lib/model/ai-build-progress.js.map +1 -1
- package/src/lib/model/ai-edit-job.d.ts +109 -0
- package/src/lib/model/ai-edit-job.js +157 -0
- package/src/lib/model/ai-edit-job.js.map +1 -0
- package/src/lib/model/ai-job-activity.js +1 -0
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +3 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-logic-job.d.ts +18 -1
- package/src/lib/model/ai-logic-job.js +24 -3
- package/src/lib/model/ai-logic-job.js.map +1 -1
- package/src/lib/model/ai-media-credits.d.ts +8 -4
- package/src/lib/model/ai-media-credits.js +11 -4
- package/src/lib/model/ai-media-credits.js.map +1 -1
- package/src/lib/model/ai-media-kinds.d.ts +68 -0
- package/src/lib/model/ai-media-kinds.js +98 -0
- package/src/lib/model/ai-media-kinds.js.map +1 -0
- package/src/lib/model/ai-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/model/ai-site-build-progress.d.ts +4 -0
- package/src/lib/model/ai-site-build-progress.js +28 -11
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-site-context.d.ts +111 -0
- package/src/lib/model/ai-site-context.js +222 -0
- package/src/lib/model/ai-site-context.js.map +1 -0
- package/src/lib/model/ai-site-job.d.ts +10 -2
- package/src/lib/model/ai-site-job.js +12 -5
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-kinds.d.ts +164 -0
- package/src/lib/model/ai-site-kinds.js +1942 -0
- package/src/lib/model/ai-site-kinds.js.map +1 -0
- package/src/lib/model/ai-site-look.d.ts +150 -0
- package/src/lib/model/ai-site-look.js +1236 -0
- package/src/lib/model/ai-site-look.js.map +1 -0
- package/src/lib/model/ai-site-memory.d.ts +80 -0
- package/src/lib/model/ai-site-memory.js +171 -0
- package/src/lib/model/ai-site-memory.js.map +1 -0
- package/src/lib/model/ai-site-start.d.ts +10 -1
- package/src/lib/model/ai-site-start.js +16 -2
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/model/assist-follow-up.d.ts +92 -0
- package/src/lib/model/assist-follow-up.js +128 -0
- package/src/lib/model/assist-follow-up.js.map +1 -0
- package/src/lib/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/plugin-config.js +2 -0
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/plugin.js +20 -1
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/anthropic.d.ts +9 -1
- package/src/lib/providers/anthropic.js +143 -9
- package/src/lib/providers/anthropic.js.map +1 -1
- package/src/lib/providers/catalog.d.ts +37 -7
- package/src/lib/providers/catalog.js +103 -11
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/providers/contract.d.ts +29 -0
- package/src/lib/providers/contract.js.map +1 -1
- package/src/lib/providers/image-contract.d.ts +22 -1
- package/src/lib/providers/image-contract.js +40 -1
- package/src/lib/providers/image-contract.js.map +1 -1
- package/src/lib/providers/image-endpoints.d.ts +24 -0
- package/src/lib/providers/image-endpoints.js +28 -0
- package/src/lib/providers/image-endpoints.js.map +1 -0
- package/src/lib/providers/model-choice.js +23 -2
- package/src/lib/providers/model-choice.js.map +1 -1
- package/src/lib/providers/routing.js +18 -0
- package/src/lib/providers/routing.js.map +1 -1
- package/src/lib/providers/vertex-image-endpoint.d.ts +48 -0
- package/src/lib/providers/vertex-image-endpoint.js +80 -0
- package/src/lib/providers/vertex-image-endpoint.js.map +1 -0
- package/src/lib/providers/vertex-image.d.ts +13 -27
- package/src/lib/providers/vertex-image.js +15 -42
- package/src/lib/providers/vertex-image.js.map +1 -1
- package/src/lib/runtime/ai-blog-post-generation.d.ts +62 -0
- package/src/lib/runtime/ai-blog-post-generation.js +89 -0
- package/src/lib/runtime/ai-blog-post-generation.js.map +1 -0
- package/src/lib/runtime/ai-dev-env.d.ts +53 -0
- package/src/lib/runtime/ai-dev-env.js +72 -0
- package/src/lib/runtime/ai-dev-env.js.map +1 -0
- package/src/lib/runtime/ai-dev-replay.d.ts +116 -0
- package/src/lib/runtime/ai-dev-replay.js +313 -0
- package/src/lib/runtime/ai-dev-replay.js.map +1 -0
- package/src/lib/runtime/ai-doctrine-validators.d.ts +7 -0
- package/src/lib/runtime/ai-doctrine-validators.js +9 -2
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +5 -1
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.js +4 -0
- package/src/lib/runtime/ai-eval-live.js.map +1 -1
- package/src/lib/runtime/ai-eval.d.ts +13 -1
- package/src/lib/runtime/ai-eval.js +84 -1
- package/src/lib/runtime/ai-eval.js.map +1 -1
- package/src/lib/runtime/ai-live-batch.d.ts +45 -0
- package/src/lib/runtime/ai-live-batch.js +89 -0
- package/src/lib/runtime/ai-live-batch.js.map +1 -0
- package/src/lib/runtime/ai-runtime.js +46 -2
- package/src/lib/runtime/ai-runtime.js.map +1 -1
- package/src/lib/runtime/site-context.d.ts +58 -0
- package/src/lib/runtime/site-context.js +127 -0
- package/src/lib/runtime/site-context.js.map +1 -0
- package/src/lib/server/ai-admin-conversations.d.ts +22 -0
- package/src/lib/server/ai-admin-conversations.js +197 -0
- package/src/lib/server/ai-admin-conversations.js.map +1 -0
- package/src/lib/server/ai-media-image.d.ts +5 -5
- package/src/lib/server/ai-media-image.js +25 -11
- package/src/lib/server/ai-media-image.js.map +1 -1
- package/src/lib/server/ai-media-raster-prompt.d.ts +34 -0
- package/src/lib/server/ai-media-raster-prompt.js +53 -0
- package/src/lib/server/ai-media-raster-prompt.js.map +1 -0
- package/src/lib/server/assist-chat.d.ts +11 -0
- package/src/lib/server/assist-chat.js +57 -18
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-edit-applied.js +16 -3
- package/src/lib/server/assist-edit-applied.js.map +1 -1
- package/src/lib/server/assist-view-context.d.ts +19 -1
- package/src/lib/server/assist-view-context.js +38 -1
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/server.js +10 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/subprocessors.js +17 -1
- package/src/lib/subprocessors.js.map +1 -1
- package/src/lib/tools/ai-blog-post-tool.d.ts +60 -0
- package/src/lib/tools/ai-blog-post-tool.js +175 -0
- package/src/lib/tools/ai-blog-post-tool.js.map +1 -0
- package/src/lib/usage/assist-usage.d.ts +3 -0
- package/src/lib/usage/assist-usage.js +4 -2
- package/src/lib/usage/assist-usage.js.map +1 -1
- package/src/lib/usage/staff-org-ai-conversations.d.ts +103 -0
- package/src/lib/usage/staff-org-ai-conversations.js +152 -0
- package/src/lib/usage/staff-org-ai-conversations.js.map +1 -0
|
@@ -19,6 +19,11 @@ import { pluginAiCapabilities, registerPluginAiCapability } from "@aglyn/aglyn/p
|
|
|
19
19
|
import { filterEnabledPluginsByReleaseFlags } from "@aglyn/tenant-data-admin/server/release-flags";
|
|
20
20
|
import { AI_TEMPLATE_SUBJECTS } from "../model/ai-template-subjects.js";
|
|
21
21
|
import { AI_SITE_PASS_CREDITS } from "../model/ai-site-job.js";
|
|
22
|
+
import { AI_LOGIC_FUNCTION_RESOURCE } from "../model/ai-logic-job.js";
|
|
23
|
+
import { AI_AUTOMATION_RESOURCE } from "../model/ai-workflow-job.js";
|
|
24
|
+
import { AI_EDIT_TARGET_KINDS } from "../model/ai-edit-job.js";
|
|
25
|
+
import { AI_CAMPAIGN_RESOURCE } from "./ai-job-campaign-step.js";
|
|
26
|
+
import { aiPluginDraftOwner } from "./ai-job-plugin-drafts.js";
|
|
22
27
|
import { aiJobStepRunnerFor } from "./ai-jobs.js";
|
|
23
28
|
/**
|
|
24
29
|
* What THIS plugin lets a build make (AGL-3616), registered on the core's
|
|
@@ -29,7 +34,10 @@ import { aiJobStepRunnerFor } from "./ai-jobs.js";
|
|
|
29
34
|
* pages, layouts, forms, components and email designs are the plan's
|
|
30
35
|
* creations and screens; templates, campaigns and automations are items with
|
|
31
36
|
* arguments. A campaign and an automation are still WRITTEN by the marketing
|
|
32
|
-
* and workflows plugins, through the writers their runners already call
|
|
37
|
+
* and workflows plugins, through the writers their runners already call, and
|
|
38
|
+
* a site function by the logic plugin's `function` writer, which the `logic`
|
|
39
|
+
* 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.
|
|
33
41
|
*/ /** This plugin's id, as its registrations name their owner. */ const AI_PLUGIN_ID = 'ai';
|
|
34
42
|
const NO_ARGS = {
|
|
35
43
|
type: 'object',
|
|
@@ -177,8 +185,78 @@ const passes = (count)=>()=>count * AI_SITE_PASS_CREDITS;
|
|
|
177
185
|
runnerKind: 'workflow',
|
|
178
186
|
estimateCredits: passes(1),
|
|
179
187
|
degrade: 'omit'
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
// A function needs a model, so it is this plugin's operation; the logic
|
|
191
|
+
// plugin owns the record and writes it (AGL-3616). New functions only: a
|
|
192
|
+
// build never changes one the site already has.
|
|
193
|
+
op: 'function',
|
|
194
|
+
noun: 'site function',
|
|
195
|
+
where: 'Logic → Functions',
|
|
196
|
+
intents: [
|
|
197
|
+
'a site function that works a value out from what it is given: a price, a total, a quote'
|
|
198
|
+
],
|
|
199
|
+
argsSchema: NO_ARGS,
|
|
200
|
+
maxPerPlan: 2,
|
|
201
|
+
// Free includes one function (`functionsPerHost: 1`), and Logic no feature.
|
|
202
|
+
freeAllowed: true,
|
|
203
|
+
quota: 'functionsPerHost',
|
|
204
|
+
runnerKind: 'logic',
|
|
205
|
+
estimateCredits: passes(1),
|
|
206
|
+
degrade: 'omit'
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
// A change to something the site already has (AGL-3616), made by the
|
|
210
|
+
// `edit` runner: the Assist edit rung's protocol against the stored
|
|
211
|
+
// document, saved as a version visitors do not see. Not on the Free
|
|
212
|
+
// taste: without version history only an unpublished page can take a
|
|
213
|
+
// change, which is rarely the page a request means, and its admission
|
|
214
|
+
// would refuse the item after the plan was confirmed.
|
|
215
|
+
op: 'edit',
|
|
216
|
+
noun: 'page change',
|
|
217
|
+
where: 'the page’s or layout’s versions',
|
|
218
|
+
intents: [
|
|
219
|
+
'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'
|
|
220
|
+
],
|
|
221
|
+
argsSchema: {
|
|
222
|
+
type: 'object',
|
|
223
|
+
properties: {
|
|
224
|
+
target: {
|
|
225
|
+
type: 'string',
|
|
226
|
+
description: 'The id of an existing page or layout from the site inventory.',
|
|
227
|
+
maxLength: 64
|
|
228
|
+
},
|
|
229
|
+
targetKind: {
|
|
230
|
+
type: 'string',
|
|
231
|
+
description: 'screen when target is a page, layout when it is a layout.',
|
|
232
|
+
enum: AI_EDIT_TARGET_KINDS
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
required: [
|
|
236
|
+
'target'
|
|
237
|
+
],
|
|
238
|
+
additionalProperties: false
|
|
239
|
+
},
|
|
240
|
+
maxPerPlan: 3,
|
|
241
|
+
freeAllowed: false,
|
|
242
|
+
runnerKind: 'edit',
|
|
243
|
+
estimateCredits: passes(1),
|
|
244
|
+
degrade: 'omit'
|
|
180
245
|
}
|
|
181
246
|
];
|
|
247
|
+
/**
|
|
248
|
+
* The resource another plugin writes for each operation of this plugin's that
|
|
249
|
+
* cannot be built without it. The operation runs where that writer's owner
|
|
250
|
+
* runs — registered, switched on for the workspace and the site, and past its
|
|
251
|
+
* release flag — exactly as an operation that owner registered would.
|
|
252
|
+
*/ export const AI_OWNED_OP_WRITERS = {
|
|
253
|
+
function: AI_LOGIC_FUNCTION_RESOURCE,
|
|
254
|
+
// A campaign is written by the marketing plugin's campaign writer, an
|
|
255
|
+
// automation by the workflows plugin's: neither is offered where its
|
|
256
|
+
// writer's plugin is off, rather than planned and then skipped.
|
|
257
|
+
campaign: AI_CAMPAIGN_RESOURCE,
|
|
258
|
+
workflow: AI_AUTOMATION_RESOURCE
|
|
259
|
+
};
|
|
182
260
|
/** Registers this plugin's build operations; the console surface calls it. */ export function registerAiBuildCapabilities() {
|
|
183
261
|
for (const capability of AI_OWNED_CAPABILITIES){
|
|
184
262
|
registerPluginAiCapability(capability, {
|
|
@@ -189,17 +267,29 @@ const passes = (count)=>()=>count * AI_SITE_PASS_CREDITS;
|
|
|
189
267
|
/**
|
|
190
268
|
* The operations a build may plan on this site, by op (AGL-3616): every
|
|
191
269
|
* registered capability whose owner runs here — switched on for the
|
|
192
|
-
* workspace and the site, past its release flag
|
|
270
|
+
* workspace and the site, past its release flag; for one of this plugin's
|
|
271
|
+
* operations another plugin writes (`AI_OWNED_OP_WRITERS`), that writer's
|
|
272
|
+
* owner, and a writer registered at all — whose entitlement the plan
|
|
193
273
|
* holds, which the Free taste may use where the workspace is on it, and,
|
|
194
274
|
* for an AI-run operation, whose runner this process loaded. One release-flag
|
|
195
275
|
* read for every owner at once. What a member may do and what an allowance
|
|
196
276
|
* has left are the owner's to say when the item runs (`refusal`).
|
|
197
277
|
*/ export async function aiBuildOps(context, deps = {}) {
|
|
198
|
-
var _deps_capabilities, _deps_hasRunner, _deps_releasedPlugins;
|
|
278
|
+
var _deps_capabilities, _deps_hasRunner, _deps_writerOwner, _deps_releasedPlugins;
|
|
199
279
|
const all = ((_deps_capabilities = deps.capabilities) != null ? _deps_capabilities : pluginAiCapabilities)();
|
|
200
280
|
const hasRunner = (_deps_hasRunner = deps.hasRunner) != null ? _deps_hasRunner : (kind)=>aiJobStepRunnerFor(kind) !== null;
|
|
281
|
+
const writerOwner = (_deps_writerOwner = deps.writerOwner) != null ? _deps_writerOwner : aiPluginDraftOwner;
|
|
282
|
+
// The plugin each of this plugin's operations depends on, by op; `null` where its writer is not registered.
|
|
283
|
+
const writtenBy = new Map();
|
|
284
|
+
for (const { pluginId, capability } of all){
|
|
285
|
+
const resource = pluginId === AI_PLUGIN_ID ? AI_OWNED_OP_WRITERS[capability.op] : undefined;
|
|
286
|
+
if (resource) writtenBy.set(capability.op, writerOwner(resource));
|
|
287
|
+
}
|
|
201
288
|
const owners = [
|
|
202
|
-
...new Set(
|
|
289
|
+
...new Set([
|
|
290
|
+
...all.map((one)=>one.pluginId),
|
|
291
|
+
...writtenBy.values()
|
|
292
|
+
].filter((id)=>!!id && id !== AI_PLUGIN_ID))
|
|
203
293
|
];
|
|
204
294
|
const released = new Set(owners.length ? await ((_deps_releasedPlugins = deps.releasedPlugins) != null ? _deps_releasedPlugins : (ids, orgId)=>filterEnabledPluginsByReleaseFlags(ids, {
|
|
205
295
|
orgId,
|
|
@@ -207,9 +297,13 @@ const passes = (count)=>()=>count * AI_SITE_PASS_CREDITS;
|
|
|
207
297
|
}))(owners, context.orgId) : []);
|
|
208
298
|
const ops = new Map();
|
|
209
299
|
for (const { pluginId, capability } of all){
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
300
|
+
// The plugin that must run here: the owner, or for one of this plugin's
|
|
301
|
+
// operations, the plugin that writes what it makes.
|
|
302
|
+
const runsOn = pluginId === AI_PLUGIN_ID ? writtenBy.get(capability.op) : pluginId;
|
|
303
|
+
if (runsOn === null) continue;
|
|
304
|
+
if (runsOn !== undefined && runsOn !== AI_PLUGIN_ID) {
|
|
305
|
+
if (!released.has(runsOn)) continue;
|
|
306
|
+
if (!isHostPluginEnabled(context.org, context.host, runsOn)) continue;
|
|
213
307
|
}
|
|
214
308
|
if (capability.feature && !checkEntitlement(context.org, capability.feature)) continue;
|
|
215
309
|
if (context.freeTaste && !capability.freeAllowed) continue;
|
|
@@ -235,13 +329,19 @@ const passes = (count)=>()=>count * AI_SITE_PASS_CREDITS;
|
|
|
235
329
|
for (const one of items){
|
|
236
330
|
var _one_pageBlocks;
|
|
237
331
|
const args = Object.entries(one.argsSchema.properties).map(([name, property])=>{
|
|
238
|
-
var _one_argsSchema_required;
|
|
332
|
+
var _one_argsSchema_required, _property_items;
|
|
239
333
|
const required = ((_one_argsSchema_required = one.argsSchema.required) == null ? void 0 : _one_argsSchema_required.includes(name)) ? '' : ' (optional)';
|
|
240
|
-
|
|
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('|')}` : '';
|
|
241
337
|
return `${name}: ${property.type}${choices}${required} — ${property.description}`;
|
|
242
338
|
});
|
|
243
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('; ')} }` : '{}'}.`);
|
|
244
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.');
|
|
245
345
|
return lines;
|
|
246
346
|
}
|
|
247
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 { aiJobStepRunnerFor } from './ai-jobs'\n\n/**\n * What THIS plugin lets a build make (AGL-3616), registered on the core's\n * capability contract exactly as any other plugin registers its own, so the\n * planner, the gates and the estimate read one list and name no plugin.\n *\n * Every one is built by a runner this plugin already has (`runnerKind`):\n * pages, layouts, forms, components and email designs are the plan's\n * creations and screens; templates, campaigns and automations are items with\n * arguments. A campaign and an automation are still WRITTEN by the marketing\n * and workflows plugins, through the writers their runners already call.\n */\n\n/** This plugin's id, as its registrations name their owner. */\nconst AI_PLUGIN_ID = 'ai'\n\nconst NO_ARGS = { type: 'object', properties: {}, additionalProperties: false } as const\n\nconst passes = (count: number) => () => count * AI_SITE_PASS_CREDITS\n\n/** The operations this plugin owns, as it registers them. */\nexport const AI_OWNED_CAPABILITIES: readonly PluginAiCapability[] = [\n {\n op: 'page',\n noun: 'page',\n where: 'Pages',\n intents: ['new pages: home, about, services, contact, pricing, a landing page'],\n argsSchema: NO_ARGS,\n maxPerPlan: 8,\n freeAllowed: true,\n runnerKind: 'page',\n estimateCredits: passes(6),\n degrade: 'omit',\n },\n {\n op: 'layout',\n noun: 'layout',\n where: 'Layouts',\n intents: ['a header and footer shared by pages'],\n argsSchema: NO_ARGS,\n maxPerPlan: 1,\n freeAllowed: true,\n runnerKind: 'layout',\n estimateCredits: passes(1),\n degrade: 'fallback',\n },\n {\n op: 'form',\n noun: 'form',\n where: 'Forms',\n intents: ['a contact form, a quote request, a sign-up form'],\n argsSchema: NO_ARGS,\n maxPerPlan: 3,\n freeAllowed: true,\n runnerKind: 'form',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n op: 'component',\n noun: 'component',\n where: 'Components',\n intents: ['a reusable card or block placed on several pages'],\n argsSchema: NO_ARGS,\n maxPerPlan: 4,\n freeAllowed: false,\n runnerKind: 'component',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n op: 'email',\n noun: 'email design',\n where: 'Emails → Templates',\n intents: ['an email design: a welcome, a reply, an announcement'],\n argsSchema: NO_ARGS,\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'email',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n op: 'template',\n noun: 'page template',\n where: 'Templates',\n intents: ['a page template for blog posts, products or authors'],\n argsSchema: {\n type: 'object',\n properties: {\n subject: {\n type: 'string',\n description: 'What each page shows: entry (a collection entry), product or author.',\n enum: AI_TEMPLATE_SUBJECTS,\n },\n collectionId: {\n type: 'string',\n description: 'For entry: the collection id from the site inventory.',\n maxLength: 100,\n },\n },\n required: ['subject'],\n additionalProperties: false,\n },\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'template',\n estimateCredits: passes(6),\n degrade: 'omit',\n },\n {\n op: 'campaign',\n noun: 'email campaign',\n where: 'Marketing → Campaigns',\n intents: ['an email campaign drafted and left unsent'],\n argsSchema: {\n type: 'object',\n properties: {\n name: { type: 'string', description: 'What the campaign is called.', maxLength: 120 },\n },\n additionalProperties: false,\n },\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'campaign',\n estimateCredits: passes(2),\n degrade: 'omit',\n },\n {\n op: 'workflow',\n noun: 'automation',\n where: 'Automations',\n intents: ['an automation: when a form is sent, do something'],\n argsSchema: NO_ARGS,\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'workflow',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n]\n\n/** Registers this plugin's build operations; the console surface calls it. */\nexport function registerAiBuildCapabilities(): void {\n for (const capability of AI_OWNED_CAPABILITIES) {\n registerPluginAiCapability(capability, { pluginId: AI_PLUGIN_ID })\n }\n}\n\n/** What decides which operations a build may use here. */\nexport interface AiBuildOpsContext {\n orgId: string\n hostId: string | null\n org: Partial<AglynOrgBilling> | null\n /** The site document, for the plugins switched on and off on it. */\n host: Readonly<Record<string, unknown>> | null\n freeTaste: boolean\n}\n\nexport interface AiBuildOpsDeps {\n /** The registered capabilities; the core registry's otherwise. */\n capabilities?: () => ResolvedPluginAiCapability[]\n /** The release-flag read; the platform's otherwise. */\n releasedPlugins?: (pluginIds: string[], orgId: string) => Promise<string[]>\n /** Whether a runner is registered for a kind; the machine's otherwise. */\n hasRunner?: (kind: string) => boolean\n}\n\n/**\n * The operations a build may plan on this site, by op (AGL-3616): every\n * registered capability whose owner runs here — switched on for the\n * workspace and the site, past its release flag — whose entitlement the plan\n * holds, which the Free taste may use where the workspace is on it, and,\n * for an AI-run operation, whose runner this process loaded. One release-flag\n * read for every owner at once. What a member may do and what an allowance\n * has left are the owner's to say when the item runs (`refusal`).\n */\nexport async function aiBuildOps(context: AiBuildOpsContext, deps: AiBuildOpsDeps = {}): Promise<AiBuildOps> {\n const all = (deps.capabilities ?? pluginAiCapabilities)()\n const hasRunner = deps.hasRunner ?? ((kind: string) => aiJobStepRunnerFor(kind as never) !== null)\n const owners = [...new Set(all.map((one) => one.pluginId).filter((id) => id !== AI_PLUGIN_ID))]\n const released = new Set(\n owners.length\n ? await (deps.releasedPlugins ??\n ((ids, orgId) => filterEnabledPluginsByReleaseFlags(ids, { orgId, authorization: null })))(owners, context.orgId)\n : [],\n )\n const ops = new Map<string, PluginAiCapability>()\n for (const { pluginId, capability } of all) {\n if (pluginId !== AI_PLUGIN_ID) {\n if (!released.has(pluginId)) continue\n if (!isHostPluginEnabled(context.org as { enabledPlugins?: string[] } | null, context.host, pluginId)) continue\n }\n if (capability.feature && !checkEntitlement(context.org, capability.feature)) continue\n if (context.freeTaste && !capability.freeAllowed) continue\n if (capability.runnerKind && !hasRunner(capability.runnerKind)) continue\n ops.set(capability.op, capability)\n }\n return ops\n}\n\n/**\n * The lines the plan step's request lists the item operations in: each op\n * with what it makes, its arguments and their meaning. Per site, so they ride\n * the job's own turn and never a cached system block.\n */\nexport function aiBuildOpLines(ops: AiBuildOps, structural: readonly string[]): string[] {\n const items = [...ops.values()].filter((one) => !structural.includes(one.op))\n if (!items.length) return ['Operations for items: none on this site. Leave items empty.']\n const lines = ['Operations for items (anything else goes in create or screens):']\n for (const one of items) {\n const args = Object.entries(one.argsSchema.properties).map(([name, property]) => {\n const required = one.argsSchema.required?.includes(name) ? '' : ' (optional)'\n const choices = property.enum ? ` one of ${property.enum.join('|')}` : ''\n return `${name}: ${property.type}${choices}${required} — ${property.description}`\n })\n lines.push(\n `- ${one.op}: ${aiNounWithArticle(one.noun)}, at most ${one.maxPerPlan}; for ${one.intents.join('; ')}.${\n one.pageBlocks?.length ? ` A page section that uses it places the ${one.pageBlocks.join(' or ')} block: put new:<name> in that section's uses.` : ''\n } Args: ${args.length ? `{ ${args.join('; ')} }` : '{}'}.`,\n )\n }\n return lines\n}\n\nfunction aiNounWithArticle(noun: string): string {\n return `${/^[aeiou]/i.test(noun) ? 'an' : 'a'} ${noun}`\n}\n\n/** What a build can make here, in the owner's words, for the chat's instructions. */\nexport function aiBuildIntents(ops: AiBuildOps): string[] {\n return [...ops.values()].flatMap((one) => one.intents)\n}\n"],"names":["checkEntitlement","isHostPluginEnabled","pluginAiCapabilities","registerPluginAiCapability","filterEnabledPluginsByReleaseFlags","AI_TEMPLATE_SUBJECTS","AI_SITE_PASS_CREDITS","aiJobStepRunnerFor","AI_PLUGIN_ID","NO_ARGS","type","properties","additionalProperties","passes","count","AI_OWNED_CAPABILITIES","op","noun","where","intents","argsSchema","maxPerPlan","freeAllowed","runnerKind","estimateCredits","degrade","subject","description","enum","collectionId","maxLength","required","name","registerAiBuildCapabilities","capability","pluginId","aiBuildOps","context","deps","all","capabilities","hasRunner","kind","owners","Set","map","one","filter","id","released","length","releasedPlugins","ids","orgId","authorization","ops","Map","has","org","host","feature","freeTaste","set","aiBuildOpLines","structural","items","values","includes","lines","args","Object","entries","property","choices","join","push","aiNounWithArticle","pageBlocks","test","aiBuildIntents","flatMap"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-build-capabilities.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { isHostPluginEnabled } from '@aglyn/aglyn/plugin-manager/enabled-plugins'\nimport {\n pluginAiCapabilities,\n registerPluginAiCapability,\n type PluginAiCapability,\n type ResolvedPluginAiCapability,\n} from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities'\nimport { filterEnabledPluginsByReleaseFlags } from '@aglyn/tenant-data-admin/server/release-flags'\nimport { AI_TEMPLATE_SUBJECTS } from '../model/ai-template-subjects'\nimport { AI_SITE_PASS_CREDITS } from '../model/ai-site-job'\nimport type { AiBuildOps } from '../model/ai-build-job'\nimport { AI_LOGIC_FUNCTION_RESOURCE } from '../model/ai-logic-job'\nimport { AI_AUTOMATION_RESOURCE } from '../model/ai-workflow-job'\nimport { AI_EDIT_TARGET_KINDS } from '../model/ai-edit-job'\nimport { AI_CAMPAIGN_RESOURCE } from './ai-job-campaign-step'\nimport { aiPluginDraftOwner, type AiPluginDraftOwnerLookup } from './ai-job-plugin-drafts'\nimport { aiJobStepRunnerFor } from './ai-jobs'\n\n/**\n * What THIS plugin lets a build make (AGL-3616), registered on the core's\n * capability contract exactly as any other plugin registers its own, so the\n * planner, the gates and the estimate read one list and name no plugin.\n *\n * Every one is built by a runner this plugin already has (`runnerKind`):\n * pages, layouts, forms, components and email designs are the plan's\n * creations and screens; templates, campaigns and automations are items with\n * arguments. A campaign and an automation are still WRITTEN by the marketing\n * and workflows plugins, through the writers their runners already call, and\n * a site function by the logic plugin's `function` writer, which the `logic`\n * runner calls when its job is a build's unit. A change to a page or layout\n * the site already has is the `edit` runner's.\n */\n\n/** This plugin's id, as its registrations name their owner. */\nconst AI_PLUGIN_ID = 'ai'\n\nconst NO_ARGS = { type: 'object', properties: {}, additionalProperties: false } as const\n\nconst passes = (count: number) => () => count * AI_SITE_PASS_CREDITS\n\n/** The operations this plugin owns, as it registers them. */\nexport const AI_OWNED_CAPABILITIES: readonly PluginAiCapability[] = [\n {\n op: 'page',\n noun: 'page',\n where: 'Pages',\n intents: ['new pages: home, about, services, contact, pricing, a landing page'],\n argsSchema: NO_ARGS,\n maxPerPlan: 8,\n freeAllowed: true,\n runnerKind: 'page',\n estimateCredits: passes(6),\n degrade: 'omit',\n },\n {\n op: 'layout',\n noun: 'layout',\n where: 'Layouts',\n intents: ['a header and footer shared by pages'],\n argsSchema: NO_ARGS,\n maxPerPlan: 1,\n freeAllowed: true,\n runnerKind: 'layout',\n estimateCredits: passes(1),\n degrade: 'fallback',\n },\n {\n op: 'form',\n noun: 'form',\n where: 'Forms',\n intents: ['a contact form, a quote request, a sign-up form'],\n argsSchema: NO_ARGS,\n maxPerPlan: 3,\n freeAllowed: true,\n runnerKind: 'form',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n op: 'component',\n noun: 'component',\n where: 'Components',\n intents: ['a reusable card or block placed on several pages'],\n argsSchema: NO_ARGS,\n maxPerPlan: 4,\n freeAllowed: false,\n runnerKind: 'component',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n op: 'email',\n noun: 'email design',\n where: 'Emails → Templates',\n intents: ['an email design: a welcome, a reply, an announcement'],\n argsSchema: NO_ARGS,\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'email',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n op: 'template',\n noun: 'page template',\n where: 'Templates',\n intents: ['a page template for blog posts, products or authors'],\n argsSchema: {\n type: 'object',\n properties: {\n subject: {\n type: 'string',\n description: 'What each page shows: entry (a collection entry), product or author.',\n enum: AI_TEMPLATE_SUBJECTS,\n },\n collectionId: {\n type: 'string',\n description: 'For entry: the collection id from the site inventory.',\n maxLength: 100,\n },\n },\n required: ['subject'],\n additionalProperties: false,\n },\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'template',\n estimateCredits: passes(6),\n degrade: 'omit',\n },\n {\n op: 'campaign',\n noun: 'email campaign',\n where: 'Marketing → Campaigns',\n intents: ['an email campaign drafted and left unsent'],\n argsSchema: {\n type: 'object',\n properties: {\n name: { type: 'string', description: 'What the campaign is called.', maxLength: 120 },\n },\n additionalProperties: false,\n },\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'campaign',\n estimateCredits: passes(2),\n degrade: 'omit',\n },\n {\n op: 'workflow',\n noun: 'automation',\n where: 'Automations',\n intents: ['an automation: when a form is sent, do something'],\n argsSchema: NO_ARGS,\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'workflow',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n // A function needs a model, so it is this plugin's operation; the logic\n // plugin owns the record and writes it (AGL-3616). New functions only: a\n // build never changes one the site already has.\n op: 'function',\n noun: 'site function',\n where: 'Logic → Functions',\n intents: ['a site function that works a value out from what it is given: a price, a total, a quote'],\n argsSchema: NO_ARGS,\n maxPerPlan: 2,\n // Free includes one function (`functionsPerHost: 1`), and Logic no feature.\n freeAllowed: true,\n quota: 'functionsPerHost',\n runnerKind: 'logic',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n // A change to something the site already has (AGL-3616), made by the\n // `edit` runner: the Assist edit rung's protocol against the stored\n // document, saved as a version visitors do not see. Not on the Free\n // taste: without version history only an unpublished page can take a\n // change, which is rarely the page a request means, and its admission\n // would refuse the item after the plan was confirmed.\n op: 'edit',\n noun: 'page change',\n where: 'the page’s or layout’s versions',\n intents: [\n 'a change to a page or layout the site already has — shorter copy, a new section, different wording — saved as a version visitors do not see until it is published',\n ],\n argsSchema: {\n type: 'object',\n properties: {\n target: {\n type: 'string',\n description: 'The id of an existing page or layout from the site inventory.',\n maxLength: 64,\n },\n targetKind: {\n type: 'string',\n description: 'screen when target is a page, layout when it is a layout.',\n enum: AI_EDIT_TARGET_KINDS,\n },\n },\n required: ['target'],\n additionalProperties: false,\n },\n maxPerPlan: 3,\n freeAllowed: false,\n runnerKind: 'edit',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n]\n\n/**\n * The resource another plugin writes for each operation of this plugin's that\n * cannot be built without it. The operation runs where that writer's owner\n * runs — registered, switched on for the workspace and the site, and past its\n * release flag — exactly as an operation that owner registered would.\n */\nexport const AI_OWNED_OP_WRITERS: Readonly<Partial<Record<string, string>>> = {\n function: AI_LOGIC_FUNCTION_RESOURCE,\n // A campaign is written by the marketing plugin's campaign writer, an\n // automation by the workflows plugin's: neither is offered where its\n // writer's plugin is off, rather than planned and then skipped.\n campaign: AI_CAMPAIGN_RESOURCE,\n workflow: AI_AUTOMATION_RESOURCE,\n}\n\n/** Registers this plugin's build operations; the console surface calls it. */\nexport function registerAiBuildCapabilities(): void {\n for (const capability of AI_OWNED_CAPABILITIES) {\n registerPluginAiCapability(capability, { pluginId: AI_PLUGIN_ID })\n }\n}\n\n/** What decides which operations a build may use here. */\nexport interface AiBuildOpsContext {\n orgId: string\n hostId: string | null\n org: Partial<AglynOrgBilling> | null\n /** The site document, for the plugins switched on and off on it. */\n host: Readonly<Record<string, unknown>> | null\n freeTaste: boolean\n}\n\nexport interface AiBuildOpsDeps {\n /** The registered capabilities; the core registry's otherwise. */\n capabilities?: () => ResolvedPluginAiCapability[]\n /** The release-flag read; the platform's otherwise. */\n releasedPlugins?: (pluginIds: string[], orgId: string) => Promise<string[]>\n /** Whether a runner is registered for a kind; the machine's otherwise. */\n hasRunner?: (kind: string) => boolean\n /** Which plugin writes a resource; the core's registry otherwise. */\n writerOwner?: AiPluginDraftOwnerLookup\n}\n\n/**\n * The operations a build may plan on this site, by op (AGL-3616): every\n * registered capability whose owner runs here — switched on for the\n * workspace and the site, past its release flag; for one of this plugin's\n * operations another plugin writes (`AI_OWNED_OP_WRITERS`), that writer's\n * owner, and a writer registered at all — whose entitlement the plan\n * holds, which the Free taste may use where the workspace is on it, and,\n * for an AI-run operation, whose runner this process loaded. One release-flag\n * read for every owner at once. What a member may do and what an allowance\n * has left are the owner's to say when the item runs (`refusal`).\n */\nexport async function aiBuildOps(context: AiBuildOpsContext, deps: AiBuildOpsDeps = {}): Promise<AiBuildOps> {\n const all = (deps.capabilities ?? pluginAiCapabilities)()\n const hasRunner = deps.hasRunner ?? ((kind: string) => aiJobStepRunnerFor(kind as never) !== null)\n const writerOwner = deps.writerOwner ?? aiPluginDraftOwner\n // The plugin each of this plugin's operations depends on, by op; `null` where its writer is not registered.\n const writtenBy = new Map<string, string | null>()\n for (const { pluginId, capability } of all) {\n const resource = pluginId === AI_PLUGIN_ID ? AI_OWNED_OP_WRITERS[capability.op] : undefined\n if (resource) writtenBy.set(capability.op, writerOwner(resource))\n }\n const owners = [\n ...new Set(\n [...all.map((one) => one.pluginId), ...writtenBy.values()].filter(\n (id): id is string => !!id && id !== AI_PLUGIN_ID,\n ),\n ),\n ]\n const released = new Set(\n owners.length\n ? await (deps.releasedPlugins ??\n ((ids, orgId) => filterEnabledPluginsByReleaseFlags(ids, { orgId, authorization: null })))(owners, context.orgId)\n : [],\n )\n const ops = new Map<string, PluginAiCapability>()\n for (const { pluginId, capability } of all) {\n // The plugin that must run here: the owner, or for one of this plugin's\n // operations, the plugin that writes what it makes.\n const runsOn = pluginId === AI_PLUGIN_ID ? writtenBy.get(capability.op) : pluginId\n if (runsOn === null) continue\n if (runsOn !== undefined && runsOn !== AI_PLUGIN_ID) {\n if (!released.has(runsOn)) continue\n if (!isHostPluginEnabled(context.org as { enabledPlugins?: string[] } | null, context.host, runsOn)) continue\n }\n if (capability.feature && !checkEntitlement(context.org, capability.feature)) continue\n if (context.freeTaste && !capability.freeAllowed) continue\n if (capability.runnerKind && !hasRunner(capability.runnerKind)) continue\n ops.set(capability.op, capability)\n }\n return ops\n}\n\n/**\n * The lines the plan step's request lists the item operations in: each op\n * with what it makes, its arguments and their meaning. Per site, so they ride\n * the job's own turn and never a cached system block.\n */\nexport function aiBuildOpLines(ops: AiBuildOps, structural: readonly string[]): string[] {\n const items = [...ops.values()].filter((one) => !structural.includes(one.op))\n if (!items.length) return ['Operations for items: none on this site. Leave items empty.']\n const lines = ['Operations for items (anything else goes in create or screens):']\n for (const one of items) {\n const args = Object.entries(one.argsSchema.properties).map(([name, property]) => {\n const required = one.argsSchema.required?.includes(name) ? '' : ' (optional)'\n // 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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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { BusinessProfileValues } from '@aglyn/aglyn/app-utils/business-profile';
|
|
18
|
+
import type { AiJob } from '../model/ai-jobs.types';
|
|
19
|
+
import type { AiJobTransitionListener } from './ai-jobs';
|
|
20
|
+
/** The guided start's answers, as profile values; `null` when it gave none. */
|
|
21
|
+
export declare function aiBusinessProfileStartValues(job: Pick<AiJob, 'kind' | 'inputs'>): BusinessProfileValues | null;
|
|
22
|
+
/** The plan's home page description, as a line about the business; `null` when it has none. */
|
|
23
|
+
export declare function aiBusinessProfilePlanValues(job: Pick<AiJob, 'kind' | 'plan'>): BusinessProfileValues | null;
|
|
24
|
+
/**
|
|
25
|
+
* The transition listener's half that prefills (AGL-3661): on a site job's
|
|
26
|
+
* plan and on its finish, the start's answers and then the plan's guess, each
|
|
27
|
+
* a no-op once written. A failure is logged and never fails the job.
|
|
28
|
+
*/
|
|
29
|
+
export declare function aiBusinessProfilePrefiller(firestoreOf: () => FirebaseFirestore.Firestore): AiJobTransitionListener;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { prefillAiBusinessProfile } from "../runtime/site-context.js";
|
|
17
|
+
/**
|
|
18
|
+
* A site job fills in the site's business profile (AGL-3661), so the next job
|
|
19
|
+
* — and the owner, on Setup → Business profile — starts from what the guided
|
|
20
|
+
* start was told rather than from nothing.
|
|
21
|
+
*
|
|
22
|
+
* Two sources, each recorded as itself:
|
|
23
|
+
* - `start`: the answers the owner gave the guided start — what the business
|
|
24
|
+
* is, who it is for, its city. Their words, given in another place.
|
|
25
|
+
* - `ai`: what the plan wrote for the home page's search description, used
|
|
26
|
+
* only where the start said nothing about what the business does.
|
|
27
|
+
*
|
|
28
|
+
* Neither ever replaces what the owner typed into the profile; the merge in
|
|
29
|
+
* `@aglyn/aglyn/app-utils/business-profile` decides that, not this file.
|
|
30
|
+
*/ const text = (value)=>typeof value === 'string' ? value.trim() : '';
|
|
31
|
+
/** "a local dog groomer" reads as a line once its first letter is a capital. */ const sentence = (value)=>value ? value[0].toUpperCase() + value.slice(1) : value;
|
|
32
|
+
/** The guided start's answers, as profile values; `null` when it gave none. */ export function aiBusinessProfileStartValues(job) {
|
|
33
|
+
var _job_inputs;
|
|
34
|
+
if (job.kind !== 'site') return null;
|
|
35
|
+
const inputs = (_job_inputs = job.inputs) != null ? _job_inputs : {};
|
|
36
|
+
const values = {};
|
|
37
|
+
const businessType = text(inputs['businessType']);
|
|
38
|
+
const audience = text(inputs['audience']);
|
|
39
|
+
const city = text(inputs['city']);
|
|
40
|
+
if (businessType) values.whatYouDo = sentence(businessType);
|
|
41
|
+
if (audience) values.audience = audience;
|
|
42
|
+
if (city) values.serviceArea = city;
|
|
43
|
+
return Object.keys(values).length ? values : null;
|
|
44
|
+
}
|
|
45
|
+
/** The plan's home page description, as a line about the business; `null` when it has none. */ export function aiBusinessProfilePlanValues(job) {
|
|
46
|
+
var _ref;
|
|
47
|
+
var _job_plan;
|
|
48
|
+
if (job.kind !== 'site') return null;
|
|
49
|
+
const screens = (_ref = (_job_plan = job.plan) == null ? void 0 : _job_plan.screens) != null ? _ref : [];
|
|
50
|
+
const home = screens.find((screen)=>[
|
|
51
|
+
'',
|
|
52
|
+
'/',
|
|
53
|
+
'home',
|
|
54
|
+
'index'
|
|
55
|
+
].includes(text(screen.slug).replace(/^\/+|\/+$/g, '')));
|
|
56
|
+
const description = text(home == null ? void 0 : home.seoDescription);
|
|
57
|
+
return description ? {
|
|
58
|
+
whatYouDo: description
|
|
59
|
+
} : null;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The transition listener's half that prefills (AGL-3661): on a site job's
|
|
63
|
+
* plan and on its finish, the start's answers and then the plan's guess, each
|
|
64
|
+
* a no-op once written. A failure is logged and never fails the job.
|
|
65
|
+
*/ export function aiBusinessProfilePrefiller(firestoreOf) {
|
|
66
|
+
return async ({ job, to })=>{
|
|
67
|
+
if (job.kind !== 'site' || !job.hostId || to === 'failed') return;
|
|
68
|
+
const start = aiBusinessProfileStartValues(job);
|
|
69
|
+
const plan = aiBusinessProfilePlanValues(job);
|
|
70
|
+
if (!start && !plan) return;
|
|
71
|
+
try {
|
|
72
|
+
const firestore = firestoreOf();
|
|
73
|
+
if (start) await prefillAiBusinessProfile(firestore, job.hostId, start, 'start');
|
|
74
|
+
if (plan) await prefillAiBusinessProfile(firestore, job.hostId, plan, 'ai');
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.warn('ai business profile prefill failed', {
|
|
77
|
+
orgId: job.orgId,
|
|
78
|
+
jobId: job.$id,
|
|
79
|
+
error
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
//# sourceMappingURL=ai-business-profile-prefill.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-business-profile-prefill.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { BusinessProfileValues } from '@aglyn/aglyn/app-utils/business-profile'\nimport type { AiJob } from '../model/ai-jobs.types'\nimport { prefillAiBusinessProfile } from '../runtime/site-context'\nimport type { AiJobTransitionListener } from './ai-jobs'\n\n/**\n * A site job fills in the site's business profile (AGL-3661), so the next job\n * — and the owner, on Setup → Business profile — starts from what the guided\n * start was told rather than from nothing.\n *\n * Two sources, each recorded as itself:\n * - `start`: the answers the owner gave the guided start — what the business\n * is, who it is for, its city. Their words, given in another place.\n * - `ai`: what the plan wrote for the home page's search description, used\n * only where the start said nothing about what the business does.\n *\n * Neither ever replaces what the owner typed into the profile; the merge in\n * `@aglyn/aglyn/app-utils/business-profile` decides that, not this file.\n */\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** \"a local dog groomer\" reads as a line once its first letter is a capital. */\nconst sentence = (value: string): string => (value ? value[0].toUpperCase() + value.slice(1) : value)\n\n/** The guided start's answers, as profile values; `null` when it gave none. */\nexport function aiBusinessProfileStartValues(job: Pick<AiJob, 'kind' | 'inputs'>): BusinessProfileValues | null {\n if (job.kind !== 'site') return null\n const inputs = job.inputs ?? {}\n const values: BusinessProfileValues = {}\n const businessType = text(inputs['businessType'])\n const audience = text(inputs['audience'])\n const city = text(inputs['city'])\n if (businessType) values.whatYouDo = sentence(businessType)\n if (audience) values.audience = audience\n if (city) values.serviceArea = city\n return Object.keys(values).length ? values : null\n}\n\n/** The plan's home page description, as a line about the business; `null` when it has none. */\nexport function aiBusinessProfilePlanValues(job: Pick<AiJob, 'kind' | 'plan'>): BusinessProfileValues | null {\n if (job.kind !== 'site') return null\n const screens = job.plan?.screens ?? []\n const home = screens.find((screen) => ['', '/', 'home', 'index'].includes(text(screen.slug).replace(/^\\/+|\\/+$/g, '')))\n const description = text(home?.seoDescription)\n return description ? { whatYouDo: description } : null\n}\n\n/**\n * The transition listener's half that prefills (AGL-3661): on a site job's\n * plan and on its finish, the start's answers and then the plan's guess, each\n * a no-op once written. A failure is logged and never fails the job.\n */\nexport function aiBusinessProfilePrefiller(\n firestoreOf: () => FirebaseFirestore.Firestore,\n): AiJobTransitionListener {\n return async ({ job, to }) => {\n if (job.kind !== 'site' || !job.hostId || to === 'failed') return\n const start = aiBusinessProfileStartValues(job)\n const plan = aiBusinessProfilePlanValues(job)\n if (!start && !plan) return\n try {\n const firestore = firestoreOf()\n if (start) await prefillAiBusinessProfile(firestore, job.hostId, start, 'start')\n if (plan) await prefillAiBusinessProfile(firestore, job.hostId, plan, 'ai')\n } catch (error) {\n console.warn('ai business profile prefill failed', { orgId: job.orgId, jobId: job.$id, error })\n }\n }\n}\n"],"names":["prefillAiBusinessProfile","text","value","trim","sentence","toUpperCase","slice","aiBusinessProfileStartValues","job","kind","inputs","values","businessType","audience","city","whatYouDo","serviceArea","Object","keys","length","aiBusinessProfilePlanValues","screens","plan","home","find","screen","includes","slug","replace","description","seoDescription","aiBusinessProfilePrefiller","firestoreOf","to","hostId","start","firestore","error","console","warn","orgId","jobId","$id"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAID,SAASA,wBAAwB,QAAQ,6BAAyB;AAGlE;;;;;;;;;;;;;CAaC,GAED,MAAMC,OAAO,CAACC,QAA4B,OAAOA,UAAU,WAAWA,MAAMC,IAAI,KAAK;AAErF,8EAA8E,GAC9E,MAAMC,WAAW,CAACF,QAA2BA,QAAQA,KAAK,CAAC,EAAE,CAACG,WAAW,KAAKH,MAAMI,KAAK,CAAC,KAAKJ;AAE/F,6EAA6E,GAC7E,OAAO,SAASK,6BAA6BC,GAAmC;QAE/DA;IADf,IAAIA,IAAIC,IAAI,KAAK,QAAQ,OAAO;IAChC,MAAMC,UAASF,cAAAA,IAAIE,MAAM,YAAVF,cAAc,CAAC;IAC9B,MAAMG,SAAgC,CAAC;IACvC,MAAMC,eAAeX,KAAKS,MAAM,CAAC,eAAe;IAChD,MAAMG,WAAWZ,KAAKS,MAAM,CAAC,WAAW;IACxC,MAAMI,OAAOb,KAAKS,MAAM,CAAC,OAAO;IAChC,IAAIE,cAAcD,OAAOI,SAAS,GAAGX,SAASQ;IAC9C,IAAIC,UAAUF,OAAOE,QAAQ,GAAGA;IAChC,IAAIC,MAAMH,OAAOK,WAAW,GAAGF;IAC/B,OAAOG,OAAOC,IAAI,CAACP,QAAQQ,MAAM,GAAGR,SAAS;AAC/C;AAEA,6FAA6F,GAC7F,OAAO,SAASS,4BAA4BZ,GAAiC;;QAE3DA;IADhB,IAAIA,IAAIC,IAAI,KAAK,QAAQ,OAAO;IAChC,MAAMY,mBAAUb,YAAAA,IAAIc,IAAI,qBAARd,UAAUa,OAAO,mBAAI,EAAE;IACvC,MAAME,OAAOF,QAAQG,IAAI,CAAC,CAACC,SAAW;YAAC;YAAI;YAAK;YAAQ;SAAQ,CAACC,QAAQ,CAACzB,KAAKwB,OAAOE,IAAI,EAAEC,OAAO,CAAC,cAAc;IAClH,MAAMC,cAAc5B,KAAKsB,wBAAAA,KAAMO,cAAc;IAC7C,OAAOD,cAAc;QAAEd,WAAWc;IAAY,IAAI;AACpD;AAEA;;;;CAIC,GACD,OAAO,SAASE,2BACdC,WAA8C;IAE9C,OAAO,OAAO,EAAExB,GAAG,EAAEyB,EAAE,EAAE;QACvB,IAAIzB,IAAIC,IAAI,KAAK,UAAU,CAACD,IAAI0B,MAAM,IAAID,OAAO,UAAU;QAC3D,MAAME,QAAQ5B,6BAA6BC;QAC3C,MAAMc,OAAOF,4BAA4BZ;QACzC,IAAI,CAAC2B,SAAS,CAACb,MAAM;QACrB,IAAI;YACF,MAAMc,YAAYJ;YAClB,IAAIG,OAAO,MAAMnC,yBAAyBoC,WAAW5B,IAAI0B,MAAM,EAAEC,OAAO;YACxE,IAAIb,MAAM,MAAMtB,yBAAyBoC,WAAW5B,IAAI0B,MAAM,EAAEZ,MAAM;QACxE,EAAE,OAAOe,OAAO;YACdC,QAAQC,IAAI,CAAC,sCAAsC;gBAAEC,OAAOhC,IAAIgC,KAAK;gBAAEC,OAAOjC,IAAIkC,GAAG;gBAAEL;YAAM;QAC/F;IACF;AACF"}
|
|
@@ -567,7 +567,9 @@ export const runAiJobBuildStep = createAiJobBuildStep();
|
|
|
567
567
|
/** The runner kinds this plugin's own item operations use, for the time a pass needs without the registry. */ const AI_BUILD_ITEM_RUNNER_KINDS = {
|
|
568
568
|
template: 'template',
|
|
569
569
|
campaign: 'campaign',
|
|
570
|
-
workflow: 'workflow'
|
|
570
|
+
workflow: 'workflow',
|
|
571
|
+
function: 'logic',
|
|
572
|
+
edit: 'edit'
|
|
571
573
|
};
|
|
572
574
|
/**
|
|
573
575
|
* Why a Free build cannot be confirmed: its estimate is more than the owner's
|