@aglyn/plugins-ai 1.0.0-beta.284 → 1.0.0-beta.287
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/assets/site-styles/nonprofit.webp +0 -0
- package/src/lib/assets/site-styles/professional.webp +0 -0
- package/src/lib/assets/site-styles/store.webp +0 -0
- package/src/lib/assets/site-styles/trades.webp +0 -0
- package/src/lib/components/ai-theme-proposal-card.component.js +12 -3
- package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
- package/src/lib/declarations.js +5 -2
- package/src/lib/declarations.js.map +1 -1
- package/src/lib/declarations.server.js +12 -0
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/jobs/ai-build-capabilities.d.ts +3 -1
- package/src/lib/jobs/ai-build-capabilities.js +27 -2
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
- package/src/lib/jobs/ai-image-reads.d.ts +59 -0
- package/src/lib/jobs/ai-image-reads.js +138 -0
- package/src/lib/jobs/ai-image-reads.js.map +1 -0
- package/src/lib/jobs/ai-job-build-step.d.ts +29 -0
- package/src/lib/jobs/ai-job-build-step.js +83 -12
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-duplicate-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-duplicate-step.js +6 -6
- package/src/lib/jobs/ai-job-duplicate-step.js.map +1 -1
- package/src/lib/jobs/ai-job-import-step.d.ts +4 -2
- package/src/lib/jobs/ai-job-import-step.js +15 -11
- package/src/lib/jobs/ai-job-import-step.js.map +1 -1
- package/src/lib/jobs/ai-job-insight-step.d.ts +10 -0
- package/src/lib/jobs/ai-job-insight-step.js +23 -5
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.js +2 -1
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-media-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-media-step.js +6 -8
- package/src/lib/jobs/ai-job-media-step.js.map +1 -1
- package/src/lib/jobs/ai-job-navigation-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-navigation-step.js +7 -9
- package/src/lib/jobs/ai-job-navigation-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.js +7 -1
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.js +16 -3
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +9 -1
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +30 -8
- package/src/lib/jobs/ai-job-plan-step.js +78 -13
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-privacy-step.d.ts +4 -2
- package/src/lib/jobs/ai-job-privacy-step.js +14 -15
- package/src/lib/jobs/ai-job-privacy-step.js.map +1 -1
- package/src/lib/jobs/ai-job-products-step.js +5 -3
- package/src/lib/jobs/ai-job-products-step.js.map +1 -1
- package/src/lib/jobs/ai-job-settings-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-settings-step.js +7 -8
- package/src/lib/jobs/ai-job-settings-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +34 -1
- package/src/lib/jobs/ai-job-site-content.js +67 -8
- package/src/lib/jobs/ai-job-site-content.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +53 -3
- package/src/lib/jobs/ai-job-site-datasets.js +298 -30
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.js +6 -3
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-store-pages.js +3 -3
- package/src/lib/jobs/ai-job-site-store-pages.js.map +1 -1
- package/src/lib/jobs/ai-job-tracking-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-tracking-step.js +7 -8
- package/src/lib/jobs/ai-job-tracking-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs-beat.d.ts +11 -7
- package/src/lib/jobs/ai-jobs-beat.js +18 -8
- package/src/lib/jobs/ai-jobs-beat.js.map +1 -1
- package/src/lib/jobs/ai-jobs-notify.d.ts +66 -1
- package/src/lib/jobs/ai-jobs-notify.js +136 -3
- package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +59 -4
- package/src/lib/jobs/ai-layout-stock-photos.js +897 -81
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/ai-product-image.d.ts +14 -0
- package/src/lib/jobs/ai-product-image.js +73 -21
- package/src/lib/jobs/ai-product-image.js.map +1 -1
- package/src/lib/jobs/ai-theme-brand-inputs.d.ts +9 -1
- package/src/lib/jobs/ai-theme-brand-inputs.js +28 -2
- package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.d.ts +175 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.js +601 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-pages.d.ts +46 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-pages.js +2862 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-pages.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-plans.d.ts +74 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-plans.js +888 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-plans.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.d.ts +29 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.js +81 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.d.ts +87 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.js +225 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.d.ts +11 -0
- package/src/lib/layout-language/ai-layout-compiler.js +194 -18
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-copy.d.ts +37 -0
- package/src/lib/layout-language/ai-layout-copy.js +383 -3
- package/src/lib/layout-language/ai-layout-copy.js.map +1 -1
- package/src/lib/layout-language/ai-layout-design.d.ts +19 -0
- package/src/lib/layout-language/ai-layout-design.js +31 -11
- package/src/lib/layout-language/ai-layout-design.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +7 -1
- package/src/lib/layout-language/ai-layout-listings.js +9 -1
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +30 -0
- package/src/lib/layout-language/ai-layout-pictures.js +131 -3
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/layout-language/ai-layout-storefront.d.ts +18 -4
- package/src/lib/layout-language/ai-layout-storefront.js +100 -20
- package/src/lib/layout-language/ai-layout-storefront.js.map +1 -1
- package/src/lib/model/ai-build-job.d.ts +7 -0
- package/src/lib/model/ai-build-job.js +36 -7
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-build-plan.d.ts +28 -4
- package/src/lib/model/ai-build-plan.js +49 -9
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-build-progress.js +3 -1
- package/src/lib/model/ai-build-progress.js.map +1 -1
- package/src/lib/model/ai-job-notice.d.ts +8 -0
- package/src/lib/model/ai-job-notice.js +33 -0
- package/src/lib/model/ai-job-notice.js.map +1 -1
- package/src/lib/model/ai-job-plan-lines.d.ts +2 -1
- package/src/lib/model/ai-job-plan-lines.js +6 -1
- package/src/lib/model/ai-job-plan-lines.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +8 -0
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-permissions.d.ts +13 -0
- package/src/lib/model/ai-permissions.js +29 -1
- package/src/lib/model/ai-permissions.js.map +1 -1
- package/src/lib/model/ai-products.d.ts +18 -0
- package/src/lib/model/ai-products.js +32 -0
- package/src/lib/model/ai-products.js.map +1 -1
- package/src/lib/model/ai-site-inspiration.d.ts +3 -2
- package/src/lib/model/ai-site-inspiration.js +3 -2
- package/src/lib/model/ai-site-inspiration.js.map +1 -1
- package/src/lib/plugin.js +4 -13
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/anthropic.js +9 -2
- package/src/lib/providers/anthropic.js.map +1 -1
- package/src/lib/providers/catalog.js +14 -5
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/providers/contract.d.ts +24 -1
- package/src/lib/providers/contract.js.map +1 -1
- package/src/lib/providers/routing.js +5 -2
- package/src/lib/providers/routing.js.map +1 -1
- package/src/lib/runtime/ai-dataset-generation.d.ts +99 -1
- package/src/lib/runtime/ai-dataset-generation.js +251 -3
- package/src/lib/runtime/ai-dataset-generation.js.map +1 -1
- package/src/lib/runtime/ai-dev-replay.d.ts +1 -0
- package/src/lib/runtime/ai-dev-replay.js +4 -2
- package/src/lib/runtime/ai-dev-replay.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +47 -3
- package/src/lib/runtime/ai-doctrine-validators.js +343 -27
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +8 -2
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-icon-library.d.ts +27 -1
- package/src/lib/runtime/ai-icon-library.js +30 -3
- package/src/lib/runtime/ai-icon-library.js.map +1 -1
- package/src/lib/runtime/ai-products-generation.d.ts +11 -1
- package/src/lib/runtime/ai-products-generation.js +51 -4
- package/src/lib/runtime/ai-products-generation.js.map +1 -1
- package/src/lib/runtime/assist-answer-cache.d.ts +2 -0
- package/src/lib/runtime/assist-answer-cache.js +5 -2
- package/src/lib/runtime/assist-answer-cache.js.map +1 -1
- package/src/lib/server/ai-allotments.js +7 -2
- package/src/lib/server/ai-allotments.js.map +1 -1
- package/src/lib/server/ai-experiment-versions.js +5 -5
- package/src/lib/server/ai-experiment-versions.js.map +1 -1
- package/src/lib/server/ai-host-permissions-roster.d.ts +25 -0
- package/src/lib/server/ai-host-permissions-roster.js +33 -0
- package/src/lib/server/ai-host-permissions-roster.js.map +1 -0
- package/src/lib/server/ai-host-permissions.js +8 -6
- package/src/lib/server/ai-host-permissions.js.map +1 -1
- package/src/lib/server/ai-image-reads-route.d.ts +17 -0
- package/src/lib/server/ai-image-reads-route.js +106 -0
- package/src/lib/server/ai-image-reads-route.js.map +1 -0
- package/src/lib/server/ai-seo-apply.js +5 -5
- package/src/lib/server/ai-seo-apply.js.map +1 -1
- package/src/lib/server/ai-site-import-route.js +7 -4
- package/src/lib/server/ai-site-import-route.js.map +1 -1
- package/src/lib/server/assist-build.js +1 -0
- package/src/lib/server/assist-build.js.map +1 -1
- package/src/lib/server/assist-chat.d.ts +11 -7
- package/src/lib/server/assist-chat.js +185 -48
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-docs-query.d.ts +51 -0
- package/src/lib/server/assist-docs-query.js +57 -0
- package/src/lib/server/assist-docs-query.js.map +1 -0
- package/src/lib/server/assist-jobs-context.d.ts +102 -0
- package/src/lib/server/assist-jobs-context.js +227 -0
- package/src/lib/server/assist-jobs-context.js.map +1 -0
- package/src/lib/server/assist-view-context.d.ts +8 -4
- package/src/lib/server/assist-view-context.js +12 -7
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/server.js +12 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/tools/ai-dataset-tool.d.ts +11 -1
- package/src/lib/tools/ai-dataset-tool.js +24 -7
- package/src/lib/tools/ai-dataset-tool.js.map +1 -1
- package/src/lib/tools/ai-duplicate-resource-tool.js +5 -6
- package/src/lib/tools/ai-duplicate-resource-tool.js.map +1 -1
- package/src/lib/components/ai-collaborator-permissions-column.component.d.ts +0 -32
- package/src/lib/components/ai-collaborator-permissions-column.component.js +0 -132
- package/src/lib/components/ai-collaborator-permissions-column.component.js.map +0 -1
|
@@ -72,8 +72,10 @@ export declare const AI_PRIVACY_KIND: "privacy";
|
|
|
72
72
|
/** ASSUMED: the least time the step needs — the site, the org, the pages named and one transaction. */
|
|
73
73
|
export declare const AI_JOB_PRIVACY_STEP_MINIMUM_MS = 5000;
|
|
74
74
|
export declare const AI_PRIVACY_NO_SITE_COPY = "Open the site whose privacy should change.";
|
|
75
|
-
|
|
76
|
-
export declare const
|
|
75
|
+
/** Names "Edit site content" (`site.edit`, AGL-4047), never a role. */
|
|
76
|
+
export declare const AI_PRIVACY_ROLE_COPY: string;
|
|
77
|
+
/** Names "Manage site settings" (`site.manage`, AGL-4047), never a role. */
|
|
78
|
+
export declare const AI_PRIVACY_ADMIN_COPY: string;
|
|
77
79
|
export declare const AI_PRIVACY_LOCKED_COPY = "This site is not accepting changes right now.";
|
|
78
80
|
export declare const AI_PRIVACY_NOTHING_COPY = "The request named no privacy change this could make.";
|
|
79
81
|
export declare const AI_PRIVACY_PASSWORD_COPY: string;
|
|
@@ -15,7 +15,8 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/ import { createResourceUid } from "@aglyn/aglyn/app-utils/create-resource-uid";
|
|
18
|
-
import {
|
|
18
|
+
import { holdsSiteKey } from "@aglyn/aglyn/app-utils/host-permissions";
|
|
19
|
+
import { orgPermissionLabel } from "@aglyn/aglyn/app-utils/org-permissions";
|
|
19
20
|
import { aiAddonName, checkEntitlement } from "@aglyn/aglyn/app-utils/plan-entitlements";
|
|
20
21
|
import { PLATFORM_BRAND_NAME } from "@aglyn/aglyn/app-utils/platform-brand";
|
|
21
22
|
import { SCREEN_ROOT_PATH } from "@aglyn/aglyn/app-utils/screen-route";
|
|
@@ -77,8 +78,8 @@ import { registerAiJobStep } from "./ai-jobs.js";
|
|
|
77
78
|
*/ export const AI_PRIVACY_KIND = 'privacy';
|
|
78
79
|
/** ASSUMED: the least time the step needs — the site, the org, the pages named and one transaction. */ export const AI_JOB_PRIVACY_STEP_MINIMUM_MS = 5000;
|
|
79
80
|
export const AI_PRIVACY_NO_SITE_COPY = 'Open the site whose privacy should change.';
|
|
80
|
-
export const AI_PRIVACY_ROLE_COPY =
|
|
81
|
-
|
|
81
|
+
/** Names "Edit site content" (`site.edit`, AGL-4047), never a role. */ export const AI_PRIVACY_ROLE_COPY = `Changing who can see the site needs the "${orgPermissionLabel('site.edit')}" permission on this site — ask an organization admin.`;
|
|
82
|
+
/** Names "Manage site settings" (`site.manage`, AGL-4047), never a role. */ export const AI_PRIVACY_ADMIN_COPY = `Turning maintenance mode on or off needs the "${orgPermissionLabel('site.manage')}" permission, so it was left as it is.`;
|
|
82
83
|
export const AI_PRIVACY_LOCKED_COPY = 'This site is not accepting changes right now.';
|
|
83
84
|
export const AI_PRIVACY_NOTHING_COPY = 'The request named no privacy change this could make.';
|
|
84
85
|
export const AI_PRIVACY_PASSWORD_COPY = `To put a page behind a password, set it in that page’s Page Access card; ${aiAddonName()} never handles passwords.`;
|
|
@@ -212,17 +213,16 @@ const flag = (value)=>typeof value === 'boolean' ? value : null;
|
|
|
212
213
|
const hostRef = firestore.collection('hosts').doc(input.hostId);
|
|
213
214
|
const screens = hostRef.collection('screens');
|
|
214
215
|
return firestore.runTransaction(async (tx)=>{
|
|
215
|
-
var _host_get;
|
|
216
216
|
const host = await tx.get(hostRef);
|
|
217
217
|
if (!host.exists) return {
|
|
218
218
|
ok: false,
|
|
219
219
|
error: 'Unknown site'
|
|
220
220
|
};
|
|
221
|
-
|
|
222
|
-
if (!hostRoleCanWrite(role)) return {
|
|
221
|
+
if (!holdsSiteKey(host, input.uid, 'site.edit')) return {
|
|
223
222
|
ok: false,
|
|
224
223
|
error: AI_PRIVACY_ROLE_COPY
|
|
225
224
|
};
|
|
225
|
+
const managesSite = holdsSiteKey(host, input.uid, 'site.manage');
|
|
226
226
|
const pageDocs = await Promise.all(input.request.pages.map((one)=>tx.get(screens.doc(one.page))));
|
|
227
227
|
const done = [];
|
|
228
228
|
const notDone = [];
|
|
@@ -230,14 +230,14 @@ const flag = (value)=>typeof value === 'boolean' ? value : null;
|
|
|
230
230
|
const pageUpdates = [];
|
|
231
231
|
const { maintenance, discourageSearchEngines } = input.request;
|
|
232
232
|
if (maintenance !== null) {
|
|
233
|
-
if (
|
|
233
|
+
if (!managesSite) {
|
|
234
234
|
notDone.push(AI_PRIVACY_ADMIN_COPY);
|
|
235
235
|
} else {
|
|
236
|
-
var
|
|
236
|
+
var _host_get;
|
|
237
237
|
if (host.get('maintenance') === true !== maintenance) {
|
|
238
238
|
hostUpdates['maintenance'] = maintenance || FieldValue.delete();
|
|
239
239
|
}
|
|
240
|
-
const assigned = !!((
|
|
240
|
+
const assigned = !!((_host_get = host.get('errorScreens')) == null ? void 0 : _host_get['unavailable']);
|
|
241
241
|
done.push({
|
|
242
242
|
output: 'maintenance',
|
|
243
243
|
said: maintenance ? `Maintenance mode is on: every address shows ${assigned ? 'your 503 page' : `${PLATFORM_BRAND_NAME}’s plain “undergoing maintenance” notice, because no 503 page is assigned yet`} to every visitor.` : 'Maintenance mode is off: the site is open to visitors again.'
|
|
@@ -245,8 +245,8 @@ const flag = (value)=>typeof value === 'boolean' ? value : null;
|
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
247
|
if (discourageSearchEngines !== null) {
|
|
248
|
-
var
|
|
249
|
-
const stored = ((
|
|
248
|
+
var _host_get1;
|
|
249
|
+
const stored = ((_host_get1 = host.get('seo')) == null ? void 0 : _host_get1['discourageSearchEngines']) === true;
|
|
250
250
|
if (stored !== discourageSearchEngines) {
|
|
251
251
|
hostUpdates['seo.discourageSearchEngines'] = discourageSearchEngines || FieldValue.delete();
|
|
252
252
|
}
|
|
@@ -322,13 +322,12 @@ const flag = (value)=>typeof value === 'boolean' ? value : null;
|
|
|
322
322
|
}
|
|
323
323
|
// ── The step ──────────────────────────────────────────────────────────────
|
|
324
324
|
/** Why this member may not change who can see this site now, or `null`. */ export async function aiPrivacyAccessRefusal(input, deps = {}) {
|
|
325
|
-
var
|
|
325
|
+
var _deps_lockdown;
|
|
326
326
|
if (!input.host.exists) return 'Unknown site';
|
|
327
|
-
|
|
328
|
-
if (!hostRoleCanWrite(role)) return AI_PRIVACY_ROLE_COPY;
|
|
327
|
+
if (!holdsSiteKey(input.host, input.uid, 'site.edit')) return AI_PRIVACY_ROLE_COPY;
|
|
329
328
|
// A request that is ONLY maintenance has nothing a non-admin may make.
|
|
330
329
|
const onlyMaintenance = input.request.maintenance !== null && input.request.discourageSearchEngines === null && !input.request.pages.length;
|
|
331
|
-
if (onlyMaintenance &&
|
|
330
|
+
if (onlyMaintenance && !holdsSiteKey(input.host, input.uid, 'site.manage')) return AI_PRIVACY_ADMIN_COPY;
|
|
332
331
|
const locked = await ((_deps_lockdown = deps.lockdown) != null ? _deps_lockdown : getLockdownVerdict)({
|
|
333
332
|
staff: false,
|
|
334
333
|
uid: input.uid,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-privacy-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { hostRoleCanWrite } from '@aglyn/aglyn/app-utils/organizations'\nimport { aiAddonName, checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { PLATFORM_BRAND_NAME } from '@aglyn/aglyn/app-utils/platform-brand'\nimport { SCREEN_ROOT_PATH } from '@aglyn/aglyn/app-utils/screen-route'\nimport { HostScreenVisibility } from '@aglyn/aglyn/foundation/definitions/platform.types'\nimport { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache'\nimport type { PluginAiCapabilityArgsSchema } from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities'\nimport { getLockdownVerdict } from '@aglyn/tenant-data-admin/server/lockdown'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport { FieldValue, type Firestore } from 'firebase-admin/firestore'\nimport type { AiJobOutput } from '../model/ai-jobs.types'\nimport { registerAiJobAdmission, type AiJobAdmission, type AiJobAdmissionRefusal } from './ai-job-admission'\nimport { aiJobEditModel } from './ai-job-edit-step'\nimport { aiLimitReview, aiUnspentOutcome } from './ai-job-generation'\nimport type { AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The `privacy` step (AGL-4033): who can see the site, as the console's own\n * three controls set it — maintenance mode (Admin → Error pages), the\n * Discourage search engines switch (Setup → SEO) and a page's Visibility\n * (its Page Access card) — as a first-class build operation, so \"make my site\n * private until launch\" or \"hide the pricing page\" is one item on Assist's\n * plan card. The docs page it mirrors is Site Protection → Make your site\n * private.\n *\n * NO MODEL. The planner already wrote the choices as the item's arguments;\n * this checks each against the rule the console's own control holds it to and\n * writes it, so it costs no credits.\n *\n * ── Only the controls' own fields ────────────────────────────────────────\n *\n * `maintenance` (true, or deleted on the way off, as the Error pages card\n * writes it), `seo.discourageSearchEngines` (likewise, as the Search engines\n * card does), and a page's `visibility` as Public, Unlisted or Members only.\n * Leaving password mode drops the page's stored hash, as the Page Access card\n * does, so protection never lingers on a page that no longer asks for it.\n *\n * NEVER: a page password. Setting one would put the password in the chat and\n * the plan, so it is not an argument; the note sends the person to the Page\n * Access card instead. Nor layouts, emails or record templates, which have no\n * Page Access card, nor the error pages themselves.\n *\n * ── Who may ──────────────────────────────────────────────────────────────\n *\n * Search indexing and page visibility: the Firestore rules' own for those\n * keys, a member with a write role on the site (admin, editor or author)\n * while no lockdown freezes it. Maintenance mode takes every page of the site\n * offline for every visitor, so, as the console keeps its card in the Admin\n * area, only a site ADMIN may change it here. A non-admin's request still\n * makes the rest of its changes and says why that one was not made.\n *\n * Members only is made only where the plan has content gating (Business and\n * above): the tenant serves gated pages nowhere else, so on a lower plan the\n * write would close the page to everyone, and the note says why it was not.\n *\n * ── Live on confirm, idempotent ──────────────────────────────────────────\n *\n * As the `settings` and `tracking` steps: none of these has a draft, so the\n * plan card's confirm is the agreement and the write is live. One transaction\n * re-reads the host and each page, re-checks the role, writes only what\n * differs, and queues a site-wide publish outbox entry, because visibility\n * changes navigation and the sitemap and both switches change every page.\n */\n\nexport const AI_PRIVACY_KIND = 'privacy' as const\n\n/** ASSUMED: the least time the step needs — the site, the org, the pages named and one transaction. */\nexport const AI_JOB_PRIVACY_STEP_MINIMUM_MS = 5_000\n\nexport const AI_PRIVACY_NO_SITE_COPY = 'Open the site whose privacy should change.'\nexport const AI_PRIVACY_ROLE_COPY = 'Changing who can see the site needs the admin, editor or author role on this site.'\nexport const AI_PRIVACY_ADMIN_COPY = 'Only a site admin can turn maintenance mode on or off, so it was left as it is.'\nexport const AI_PRIVACY_LOCKED_COPY = 'This site is not accepting changes right now.'\nexport const AI_PRIVACY_NOTHING_COPY = 'The request named no privacy change this could make.'\nexport const AI_PRIVACY_PASSWORD_COPY =\n `To put a page behind a password, set it in that page’s Page Access card; ${aiAddonName()} never handles passwords.`\n\n/** The most pages one item may change. */\nexport const AI_PRIVACY_MAX_PAGES = 20\n\n/** The visibilities a build may set, by the Page Access card's labels. */\nexport const AI_PRIVACY_VISIBILITIES = ['public', 'unlisted', 'members'] as const\nexport type AiPrivacyVisibility = (typeof AI_PRIVACY_VISIBILITIES)[number]\n\nconst VISIBILITY_VALUES: Readonly<Record<AiPrivacyVisibility, HostScreenVisibility>> = {\n public: HostScreenVisibility.PUBLIC,\n unlisted: HostScreenVisibility.UNLISTED,\n members: HostScreenVisibility.AUTHENTICATED,\n}\n\nconst VISIBILITY_LABELS: Readonly<Record<AiPrivacyVisibility, string>> = {\n public: 'Public',\n unlisted: 'Unlisted',\n members: 'Members only',\n}\n\n/** Where a person finds each change, as the output links and notes name it. */\nexport const AI_PRIVACY_OUTPUTS = {\n maintenance: { id: 'maintenance', label: 'Maintenance mode', where: 'Admin → Error pages' },\n search: { id: 'search', label: 'Search engines', where: 'Setup → SEO' },\n pages: { id: 'pages', label: 'Page access', where: 'each page’s Page Access card' },\n} as const\n\n/** Where the operation's changes are made, as the build's notes name it. */\nexport const AI_PRIVACY_WHERE = 'the site’s privacy settings'\n\n/** The item's arguments, as the planner fills them in. Every one optional. */\nexport const AI_PRIVACY_ARGS_SCHEMA: PluginAiCapabilityArgsSchema = {\n type: 'object',\n properties: {\n maintenance: {\n type: 'boolean',\n description:\n 'true takes the whole site offline: every address shows its 503 page to every visitor, including anyone with a link, until it is turned off. false brings the site back. Leave it out unless the person asked to take the whole site offline, keep it from everyone until launch, or bring it back.',\n },\n discourageSearchEngines: {\n type: 'boolean',\n description:\n 'true asks search engines not to index any page of the site (it stays open to anyone with a link); false lets them index it again. Leave it out unless the person asked about search engines or Google.',\n },\n publicPages: {\n type: 'array',\n description: 'Pages to make Public: anyone can open them, and they are offered to search engines. Each by its id from the site inventory.',\n items: { type: 'string', maxLength: 64 },\n maxItems: AI_PRIVACY_MAX_PAGES,\n },\n unlistedPages: {\n type: 'array',\n description: 'Pages to make Unlisted: anyone with the link can open them, kept out of search results, the sitemap and navigation. Each by its id from the site inventory.',\n items: { type: 'string', maxLength: 64 },\n maxItems: AI_PRIVACY_MAX_PAGES,\n },\n membersPages: {\n type: 'array',\n description: 'Pages to make Members only: only visitors signed in to the site can open them (Business plan and above). Each by its id from the site inventory.',\n items: { type: 'string', maxLength: 64 },\n maxItems: AI_PRIVACY_MAX_PAGES,\n },\n },\n additionalProperties: false,\n}\n\n/** Each visibility's argument, as the planner lists the pages for it. */\nconst VISIBILITY_ARGS: Readonly<Record<AiPrivacyVisibility, string>> = {\n public: 'publicPages',\n unlisted: 'unlistedPages',\n members: 'membersPages',\n}\n\n/** What an item asks: each switch to set, and each page's new visibility. */\nexport interface AiPrivacyRequest {\n maintenance: boolean | null\n discourageSearchEngines: boolean | null\n pages: { page: string; visibility: AiPrivacyVisibility }[]\n}\n\nexport interface AiPrivacyRead {\n request: AiPrivacyRequest\n /** A sentence per value that could not be used, so the rest still are. */\n problems: string[]\n}\n\nconst flag = (value: unknown): boolean | null => (typeof value === 'boolean' ? value : null)\n\n/** A page id as the site inventory gives it: one document id, never a path. */\nconst PAGE_ID = /^[A-Za-z0-9_-]{1,64}$/\n\n/** The item's arguments read: what may be written, and a sentence for each that may not. */\nexport function aiPrivacyRequestOf(inputs: Readonly<Record<string, unknown>> | null | undefined): AiPrivacyRead {\n const problems: string[] = []\n const pages: AiPrivacyRequest['pages'] = []\n const asked = new Map<string, Set<AiPrivacyVisibility>>()\n for (const visibility of AI_PRIVACY_VISIBILITIES) {\n const listed = inputs?.[VISIBILITY_ARGS[visibility]]\n for (const entry of Array.isArray(listed) ? listed : []) {\n const page = typeof entry === 'string' ? entry.trim() : ''\n if (PAGE_ID.test(page)) asked.set(page, (asked.get(page) ?? new Set()).add(visibility))\n }\n }\n for (const [page, visibilities] of asked) {\n // A page asked to be two things is left alone rather than guessed at.\n if (visibilities.size > 1) problems.push('A page was asked to be two things at once, so it was left as it is.')\n else pages.push({ page, visibility: [...visibilities][0] })\n }\n if (pages.length > AI_PRIVACY_MAX_PAGES) {\n pages.splice(AI_PRIVACY_MAX_PAGES)\n problems.push(`Only the first ${AI_PRIVACY_MAX_PAGES} pages were changed; ask again for the rest.`)\n }\n return {\n request: {\n maintenance: flag(inputs?.['maintenance']),\n discourageSearchEngines: flag(inputs?.['discourageSearchEngines']),\n pages,\n },\n problems,\n }\n}\n\n/** Whether a read request asks for anything at all. */\nexport function aiPrivacyRequestEmpty(request: AiPrivacyRequest): boolean {\n return request.maintenance === null && request.discourageSearchEngines === null && request.pages.length === 0\n}\n\n// ── The write ─────────────────────────────────────────────────────────────\n\nexport type AiPrivacyWrite =\n | {\n ok: true\n /** A sentence per change asked, made now or already so, by output id. */\n done: { output: keyof typeof AI_PRIVACY_OUTPUTS; said: string }[]\n /** A sentence per part that was not made, and why. */\n notDone: string[]\n changedAny: boolean\n outboxId: string | null\n }\n | { ok: false; error: string }\n\n/**\n * The changes written, in one transaction that reads the host and each named\n * page as they are stored NOW: the member still holds a write role (and is an\n * admin, for maintenance), each page exists and is a page, and only what\n * differs from what is stored is written, with a site-wide publish outbox\n * entry beside it so the live site's cached pages are dropped.\n */\nexport async function writeAiSitePrivacy(\n firestore: Firestore,\n input: { hostId: string; uid: string; request: AiPrivacyRequest; contentGating: boolean; now: Date },\n): Promise<AiPrivacyWrite> {\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n const screens = hostRef.collection('screens')\n return firestore.runTransaction(async (tx): Promise<AiPrivacyWrite> => {\n const host = await tx.get(hostRef)\n if (!host.exists) return { ok: false, error: 'Unknown site' }\n const role = ((host.get('memberRoles') ?? {}) as Record<string, unknown>)[input.uid]\n if (!hostRoleCanWrite(role)) return { ok: false, error: AI_PRIVACY_ROLE_COPY }\n const pageDocs = await Promise.all(input.request.pages.map((one) => tx.get(screens.doc(one.page))))\n\n const done: { output: keyof typeof AI_PRIVACY_OUTPUTS; said: string }[] = []\n const notDone: string[] = []\n const hostUpdates: Record<string, unknown> = {}\n const pageUpdates: { ref: FirebaseFirestore.DocumentReference; update: Record<string, unknown> }[] = []\n\n const { maintenance, discourageSearchEngines } = input.request\n if (maintenance !== null) {\n if (role !== 'admin') {\n notDone.push(AI_PRIVACY_ADMIN_COPY)\n } else {\n if ((host.get('maintenance') === true) !== maintenance) {\n hostUpdates['maintenance'] = maintenance || FieldValue.delete()\n }\n const assigned = !!(host.get('errorScreens') as Record<string, unknown> | undefined)?.['unavailable']\n done.push({\n output: 'maintenance',\n said: maintenance\n ? `Maintenance mode is on: every address shows ${assigned ? 'your 503 page' : `${PLATFORM_BRAND_NAME}’s plain “undergoing maintenance” notice, because no 503 page is assigned yet`} to every visitor.`\n : 'Maintenance mode is off: the site is open to visitors again.',\n })\n }\n }\n if (discourageSearchEngines !== null) {\n const stored = (host.get('seo') as Record<string, unknown> | undefined)?.['discourageSearchEngines'] === true\n if (stored !== discourageSearchEngines) {\n hostUpdates['seo.discourageSearchEngines'] = discourageSearchEngines || FieldValue.delete()\n }\n done.push({\n output: 'search',\n said: discourageSearchEngines\n ? 'Search engines are asked not to index this site. Anyone with a link can still open it.'\n : 'Search engines may index this site again. They take days to come back.',\n })\n }\n input.request.pages.forEach((asked, index) => {\n const page = pageDocs[index]\n if (!page.exists || page.get('deletedAt') != null || page.get('kind')) {\n notDone.push('A page named is not one of this site’s pages, so it was left out.')\n return\n }\n const name = String(page.get('displayName') ?? '').trim() || 'A page'\n if (asked.visibility === 'members' && !input.contentGating) {\n // The tenant serves gated pages only on plans with content gating, so\n // writing it here would close the page to everyone.\n notDone.push(`“${name}” was not made Members only: members-only pages need the Business plan or above.`)\n return\n }\n const value = VISIBILITY_VALUES[asked.visibility]\n const update: Record<string, unknown> = {}\n if ((page.get('visibility') ?? HostScreenVisibility.PUBLIC) !== value) update['visibility'] = value\n const hadPassword = !!(page.get('protection') as Record<string, unknown> | undefined)?.['passwordHash']\n if (hadPassword) update['protection'] = FieldValue.delete()\n if (Object.keys(update).length) pageUpdates.push({ ref: page.ref, update: { ...update, updatedAt: input.now } })\n const extra = hadPassword ? ' Its page password was removed.' : ''\n done.push({ output: 'pages', said: `“${name}” is now ${VISIBILITY_LABELS[asked.visibility]}.${extra}` })\n })\n\n const changedAny = Object.keys(hostUpdates).length > 0 || pageUpdates.length > 0\n if (!changedAny) return { ok: true, done, notDone, changedAny, outboxId: null }\n if (Object.keys(hostUpdates).length) tx.update(hostRef, { ...hostUpdates, updatedAt: input.now })\n for (const { ref, update } of pageUpdates) tx.update(ref, update)\n // Both switches change every page, and a page's visibility changes the\n // navigation and the sitemap every other page carries.\n const outboxRef = firestore.collection('publishOutbox').doc(createResourceUid())\n tx.set(outboxRef, {\n hostId: input.hostId,\n paths: [SCREEN_ROOT_PATH],\n entireHost: true,\n createdAt: FieldValue.serverTimestamp(),\n attempts: 0,\n })\n return { ok: true, done, notDone, changedAny, outboxId: outboxRef.id }\n })\n}\n\n// ── The step ──────────────────────────────────────────────────────────────\n\n/** Why this member may not change who can see this site now, or `null`. */\nexport async function aiPrivacyAccessRefusal(\n input: { host: FirebaseFirestore.DocumentSnapshot; uid: string; org: object | null; request: AiPrivacyRequest },\n deps: { lockdown?: typeof getLockdownVerdict } = {},\n): Promise<string | null> {\n if (!input.host.exists) return 'Unknown site'\n const role = ((input.host.get('memberRoles') ?? {}) as Record<string, unknown>)[input.uid]\n if (!hostRoleCanWrite(role)) return AI_PRIVACY_ROLE_COPY\n // A request that is ONLY maintenance has nothing a non-admin may make.\n const onlyMaintenance =\n input.request.maintenance !== null && input.request.discourageSearchEngines === null && !input.request.pages.length\n if (onlyMaintenance && role !== 'admin') return AI_PRIVACY_ADMIN_COPY\n const locked = await (deps.lockdown ?? getLockdownVerdict)({\n staff: false,\n uid: input.uid,\n org: input.org as never,\n host: input.host.data() as never,\n intent: 'write',\n })\n return locked ? AI_PRIVACY_LOCKED_COPY : null\n}\n\n/** One output's note: what changed, that it is live, and what was not done. */\nexport function aiPrivacyOutputNote(said: readonly string[], where: string, notDone: readonly string[], changedAny: boolean): string {\n const did = said.length\n ? `${said.join(' ')} Live now, in ${where}.${changedAny ? '' : ' It already was, so nothing was written.'}`\n : 'Nothing needed changing.'\n const not = notDone.length ? ` Not done: ${notDone.join(' ')}` : ''\n return `${did}${not}`.slice(0, 1_000)\n}\n\nexport interface AiJobPrivacyStepDeps {\n write?: typeof writeAiSitePrivacy\n lockdown?: typeof getLockdownVerdict\n dropCache?: typeof dropPluginSiteCache\n}\n\nexport function createAiJobPrivacyStep(deps: AiJobPrivacyStepDeps = {}): AiJobStepRunner {\n const write = deps.write ?? writeAiSitePrivacy\n const dropCache = deps.dropCache ?? dropPluginSiteCache\n return async ({ job, now, firestore, org, modelFor }): Promise<AiJobStepOutcome> => {\n // No model is asked: the outcome records the edit step's at no cost.\n const model = modelFor?.('job.edit') ?? aiJobEditModel()\n const unspent = (failure: string): AiJobStepOutcome => ({ ...aiUnspentOutcome(model), failure })\n const hostId = job.hostId\n if (!hostId) return unspent(AI_PRIVACY_NO_SITE_COPY)\n const read = aiPrivacyRequestOf(job.inputs)\n if (aiPrivacyRequestEmpty(read.request)) return unspent([AI_PRIVACY_NOTHING_COPY, ...read.problems].join(' '))\n const orgData = (org ?? ((await firestore.collection('orgs').doc(job.orgId).get()).data() ?? null)) as Record<string, unknown> | null\n const host = await firestore.collection('hosts').doc(hostId).get()\n const refused = await aiPrivacyAccessRefusal({ host, uid: job.createdBy, org: orgData, request: read.request }, { lockdown: deps.lockdown })\n if (refused) return aiUnspentOutcome(model, { review: aiLimitReview(refused) })\n const written = await write(firestore, {\n hostId,\n uid: job.createdBy,\n request: read.request,\n contentGating: checkEntitlement(orgData as never, 'contentGating'),\n now,\n })\n if (written.ok === false) return unspent(written.error)\n if (written.outboxId) {\n // The live site's cached pages go now; the outbox entry stays for the\n // drain unless the drop certainly happened.\n const dropped = await dropCache({ hostIds: [hostId], reason: 'AI build changed site privacy' }).catch(() => ({ complete: false }))\n if (dropped.complete) await firestore.collection('publishOutbox').doc(written.outboxId).delete().catch(() => undefined)\n }\n const notDone = [...read.problems, ...written.notDone]\n const hostSubdomain = (typeof host.get('subdomain') === 'string' && host.get('subdomain')) || null\n const outputs: AiJobOutput[] = (Object.keys(AI_PRIVACY_OUTPUTS) as (keyof typeof AI_PRIVACY_OUTPUTS)[])\n .filter((key) => written.done.some((one) => one.output === key))\n .map((key, index) => ({\n resource: 'privacy',\n id: AI_PRIVACY_OUTPUTS[key].id,\n hostId,\n hostSubdomain,\n label: AI_PRIVACY_OUTPUTS[key].label,\n note: aiPrivacyOutputNote(\n written.done.filter((one) => one.output === key).map((one) => one.said),\n AI_PRIVACY_OUTPUTS[key].where,\n // What was not done is said once, on the first output.\n index === 0 ? notDone : [],\n written.changedAny,\n ),\n }))\n if (!outputs.length) return unspent([AI_PRIVACY_NOTHING_COPY, ...notDone].join(' '))\n return aiUnspentOutcome(model, { outputs })\n }\n}\n\nexport const runAiJobPrivacyStep = createAiJobPrivacyStep()\n\n// ── Admission ─────────────────────────────────────────────────────────────\n\nexport interface AiPrivacyJobAdmissionDeps {\n ownerOf?: typeof resolveOrgIdForHost\n lockdown?: typeof getLockdownVerdict\n}\n\n/**\n * A privacy change is admitted on a site of the job's own org, for a member\n * who may write it now, where the request names at least one change this can\n * make. Refused in those words before anything is written.\n */\nexport function createAiPrivacyJobAdmission(deps: AiPrivacyJobAdmissionDeps = {}): AiJobAdmission {\n const ownerOf = deps.ownerOf ?? resolveOrgIdForHost\n return async (context): Promise<AiJobAdmissionRefusal | null> => {\n if (!context.hostId) return { status: 400, error: AI_PRIVACY_NO_SITE_COPY }\n const owner = await ownerOf(context.hostId)\n if (!owner || owner !== context.orgId) return { status: 404, error: 'Unknown site' }\n const read = aiPrivacyRequestOf(context.inputs as Record<string, unknown>)\n if (aiPrivacyRequestEmpty(read.request)) return { status: 400, error: [AI_PRIVACY_NOTHING_COPY, ...read.problems].join(' ') }\n const host = await context.firestore.collection('hosts').doc(context.hostId).get()\n const refused = await aiPrivacyAccessRefusal(\n { host, uid: context.uid ?? '', org: context.org, request: read.request },\n { lockdown: deps.lockdown },\n )\n return refused ? { status: 403, error: refused } : null\n }\n}\n\nexport const aiPrivacyJobAdmission = createAiPrivacyJobAdmission()\n\n/** Registers the privacy step and its door (AGL-4033). */\nexport function registerAiPrivacyJob(): void {\n registerAiJobStep(AI_PRIVACY_KIND, runAiJobPrivacyStep, { minimumMs: AI_JOB_PRIVACY_STEP_MINIMUM_MS })\n registerAiJobAdmission(AI_PRIVACY_KIND, aiPrivacyJobAdmission)\n}\n"],"names":["createResourceUid","hostRoleCanWrite","aiAddonName","checkEntitlement","PLATFORM_BRAND_NAME","SCREEN_ROOT_PATH","HostScreenVisibility","dropPluginSiteCache","getLockdownVerdict","resolveOrgIdForHost","FieldValue","registerAiJobAdmission","aiJobEditModel","aiLimitReview","aiUnspentOutcome","registerAiJobStep","AI_PRIVACY_KIND","AI_JOB_PRIVACY_STEP_MINIMUM_MS","AI_PRIVACY_NO_SITE_COPY","AI_PRIVACY_ROLE_COPY","AI_PRIVACY_ADMIN_COPY","AI_PRIVACY_LOCKED_COPY","AI_PRIVACY_NOTHING_COPY","AI_PRIVACY_PASSWORD_COPY","AI_PRIVACY_MAX_PAGES","AI_PRIVACY_VISIBILITIES","VISIBILITY_VALUES","public","PUBLIC","unlisted","UNLISTED","members","AUTHENTICATED","VISIBILITY_LABELS","AI_PRIVACY_OUTPUTS","maintenance","id","label","where","search","pages","AI_PRIVACY_WHERE","AI_PRIVACY_ARGS_SCHEMA","type","properties","description","discourageSearchEngines","publicPages","items","maxLength","maxItems","unlistedPages","membersPages","additionalProperties","VISIBILITY_ARGS","flag","value","PAGE_ID","aiPrivacyRequestOf","inputs","problems","asked","Map","visibility","listed","entry","Array","isArray","page","trim","test","set","get","Set","add","visibilities","size","push","length","splice","request","aiPrivacyRequestEmpty","writeAiSitePrivacy","firestore","input","hostRef","collection","doc","hostId","screens","runTransaction","tx","host","exists","ok","error","role","uid","pageDocs","Promise","all","map","one","done","notDone","hostUpdates","pageUpdates","delete","assigned","output","said","stored","forEach","index","name","String","contentGating","update","hadPassword","Object","keys","ref","updatedAt","now","extra","changedAny","outboxId","outboxRef","paths","entireHost","createdAt","serverTimestamp","attempts","aiPrivacyAccessRefusal","deps","onlyMaintenance","locked","lockdown","staff","org","data","intent","aiPrivacyOutputNote","did","join","not","slice","createAiJobPrivacyStep","write","dropCache","job","modelFor","model","unspent","failure","read","orgData","orgId","refused","createdBy","review","written","dropped","hostIds","reason","catch","complete","undefined","hostSubdomain","outputs","filter","key","some","resource","note","runAiJobPrivacyStep","createAiPrivacyJobAdmission","ownerOf","context","status","owner","aiPrivacyJobAdmission","registerAiPrivacyJob","minimumMs"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-privacy-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { holdsSiteKey } from '@aglyn/aglyn/app-utils/host-permissions'\nimport { orgPermissionLabel } from '@aglyn/aglyn/app-utils/org-permissions'\nimport { aiAddonName, checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { PLATFORM_BRAND_NAME } from '@aglyn/aglyn/app-utils/platform-brand'\nimport { SCREEN_ROOT_PATH } from '@aglyn/aglyn/app-utils/screen-route'\nimport { HostScreenVisibility } from '@aglyn/aglyn/foundation/definitions/platform.types'\nimport { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache'\nimport type { PluginAiCapabilityArgsSchema } from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities'\nimport { getLockdownVerdict } from '@aglyn/tenant-data-admin/server/lockdown'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport { FieldValue, type Firestore } from 'firebase-admin/firestore'\nimport type { AiJobOutput } from '../model/ai-jobs.types'\nimport { registerAiJobAdmission, type AiJobAdmission, type AiJobAdmissionRefusal } from './ai-job-admission'\nimport { aiJobEditModel } from './ai-job-edit-step'\nimport { aiLimitReview, aiUnspentOutcome } from './ai-job-generation'\nimport type { AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The `privacy` step (AGL-4033): who can see the site, as the console's own\n * three controls set it — maintenance mode (Admin → Error pages), the\n * Discourage search engines switch (Setup → SEO) and a page's Visibility\n * (its Page Access card) — as a first-class build operation, so \"make my site\n * private until launch\" or \"hide the pricing page\" is one item on Assist's\n * plan card. The docs page it mirrors is Site Protection → Make your site\n * private.\n *\n * NO MODEL. The planner already wrote the choices as the item's arguments;\n * this checks each against the rule the console's own control holds it to and\n * writes it, so it costs no credits.\n *\n * ── Only the controls' own fields ────────────────────────────────────────\n *\n * `maintenance` (true, or deleted on the way off, as the Error pages card\n * writes it), `seo.discourageSearchEngines` (likewise, as the Search engines\n * card does), and a page's `visibility` as Public, Unlisted or Members only.\n * Leaving password mode drops the page's stored hash, as the Page Access card\n * does, so protection never lingers on a page that no longer asks for it.\n *\n * NEVER: a page password. Setting one would put the password in the chat and\n * the plan, so it is not an argument; the note sends the person to the Page\n * Access card instead. Nor layouts, emails or record templates, which have no\n * Page Access card, nor the error pages themselves.\n *\n * ── Who may ──────────────────────────────────────────────────────────────\n *\n * Search indexing and page visibility: the Firestore rules' own for those\n * keys, a member with a write role on the site (admin, editor or author)\n * while no lockdown freezes it. Maintenance mode takes every page of the site\n * offline for every visitor, so, as the console keeps its card in the Admin\n * area, only a site ADMIN may change it here. A non-admin's request still\n * makes the rest of its changes and says why that one was not made.\n *\n * Members only is made only where the plan has content gating (Business and\n * above): the tenant serves gated pages nowhere else, so on a lower plan the\n * write would close the page to everyone, and the note says why it was not.\n *\n * ── Live on confirm, idempotent ──────────────────────────────────────────\n *\n * As the `settings` and `tracking` steps: none of these has a draft, so the\n * plan card's confirm is the agreement and the write is live. One transaction\n * re-reads the host and each page, re-checks the role, writes only what\n * differs, and queues a site-wide publish outbox entry, because visibility\n * changes navigation and the sitemap and both switches change every page.\n */\n\nexport const AI_PRIVACY_KIND = 'privacy' as const\n\n/** ASSUMED: the least time the step needs — the site, the org, the pages named and one transaction. */\nexport const AI_JOB_PRIVACY_STEP_MINIMUM_MS = 5_000\n\nexport const AI_PRIVACY_NO_SITE_COPY = 'Open the site whose privacy should change.'\n/** Names \"Edit site content\" (`site.edit`, AGL-4047), never a role. */\nexport const AI_PRIVACY_ROLE_COPY = `Changing who can see the site needs the \"${orgPermissionLabel('site.edit')}\" permission on this site — ask an organization admin.`\n/** Names \"Manage site settings\" (`site.manage`, AGL-4047), never a role. */\nexport const AI_PRIVACY_ADMIN_COPY = `Turning maintenance mode on or off needs the \"${orgPermissionLabel('site.manage')}\" permission, so it was left as it is.`\nexport const AI_PRIVACY_LOCKED_COPY = 'This site is not accepting changes right now.'\nexport const AI_PRIVACY_NOTHING_COPY = 'The request named no privacy change this could make.'\nexport const AI_PRIVACY_PASSWORD_COPY =\n `To put a page behind a password, set it in that page’s Page Access card; ${aiAddonName()} never handles passwords.`\n\n/** The most pages one item may change. */\nexport const AI_PRIVACY_MAX_PAGES = 20\n\n/** The visibilities a build may set, by the Page Access card's labels. */\nexport const AI_PRIVACY_VISIBILITIES = ['public', 'unlisted', 'members'] as const\nexport type AiPrivacyVisibility = (typeof AI_PRIVACY_VISIBILITIES)[number]\n\nconst VISIBILITY_VALUES: Readonly<Record<AiPrivacyVisibility, HostScreenVisibility>> = {\n public: HostScreenVisibility.PUBLIC,\n unlisted: HostScreenVisibility.UNLISTED,\n members: HostScreenVisibility.AUTHENTICATED,\n}\n\nconst VISIBILITY_LABELS: Readonly<Record<AiPrivacyVisibility, string>> = {\n public: 'Public',\n unlisted: 'Unlisted',\n members: 'Members only',\n}\n\n/** Where a person finds each change, as the output links and notes name it. */\nexport const AI_PRIVACY_OUTPUTS = {\n maintenance: { id: 'maintenance', label: 'Maintenance mode', where: 'Admin → Error pages' },\n search: { id: 'search', label: 'Search engines', where: 'Setup → SEO' },\n pages: { id: 'pages', label: 'Page access', where: 'each page’s Page Access card' },\n} as const\n\n/** Where the operation's changes are made, as the build's notes name it. */\nexport const AI_PRIVACY_WHERE = 'the site’s privacy settings'\n\n/** The item's arguments, as the planner fills them in. Every one optional. */\nexport const AI_PRIVACY_ARGS_SCHEMA: PluginAiCapabilityArgsSchema = {\n type: 'object',\n properties: {\n maintenance: {\n type: 'boolean',\n description:\n 'true takes the whole site offline: every address shows its 503 page to every visitor, including anyone with a link, until it is turned off. false brings the site back. Leave it out unless the person asked to take the whole site offline, keep it from everyone until launch, or bring it back.',\n },\n discourageSearchEngines: {\n type: 'boolean',\n description:\n 'true asks search engines not to index any page of the site (it stays open to anyone with a link); false lets them index it again. Leave it out unless the person asked about search engines or Google.',\n },\n publicPages: {\n type: 'array',\n description: 'Pages to make Public: anyone can open them, and they are offered to search engines. Each by its id from the site inventory.',\n items: { type: 'string', maxLength: 64 },\n maxItems: AI_PRIVACY_MAX_PAGES,\n },\n unlistedPages: {\n type: 'array',\n description: 'Pages to make Unlisted: anyone with the link can open them, kept out of search results, the sitemap and navigation. Each by its id from the site inventory.',\n items: { type: 'string', maxLength: 64 },\n maxItems: AI_PRIVACY_MAX_PAGES,\n },\n membersPages: {\n type: 'array',\n description: 'Pages to make Members only: only visitors signed in to the site can open them (Business plan and above). Each by its id from the site inventory.',\n items: { type: 'string', maxLength: 64 },\n maxItems: AI_PRIVACY_MAX_PAGES,\n },\n },\n additionalProperties: false,\n}\n\n/** Each visibility's argument, as the planner lists the pages for it. */\nconst VISIBILITY_ARGS: Readonly<Record<AiPrivacyVisibility, string>> = {\n public: 'publicPages',\n unlisted: 'unlistedPages',\n members: 'membersPages',\n}\n\n/** What an item asks: each switch to set, and each page's new visibility. */\nexport interface AiPrivacyRequest {\n maintenance: boolean | null\n discourageSearchEngines: boolean | null\n pages: { page: string; visibility: AiPrivacyVisibility }[]\n}\n\nexport interface AiPrivacyRead {\n request: AiPrivacyRequest\n /** A sentence per value that could not be used, so the rest still are. */\n problems: string[]\n}\n\nconst flag = (value: unknown): boolean | null => (typeof value === 'boolean' ? value : null)\n\n/** A page id as the site inventory gives it: one document id, never a path. */\nconst PAGE_ID = /^[A-Za-z0-9_-]{1,64}$/\n\n/** The item's arguments read: what may be written, and a sentence for each that may not. */\nexport function aiPrivacyRequestOf(inputs: Readonly<Record<string, unknown>> | null | undefined): AiPrivacyRead {\n const problems: string[] = []\n const pages: AiPrivacyRequest['pages'] = []\n const asked = new Map<string, Set<AiPrivacyVisibility>>()\n for (const visibility of AI_PRIVACY_VISIBILITIES) {\n const listed = inputs?.[VISIBILITY_ARGS[visibility]]\n for (const entry of Array.isArray(listed) ? listed : []) {\n const page = typeof entry === 'string' ? entry.trim() : ''\n if (PAGE_ID.test(page)) asked.set(page, (asked.get(page) ?? new Set()).add(visibility))\n }\n }\n for (const [page, visibilities] of asked) {\n // A page asked to be two things is left alone rather than guessed at.\n if (visibilities.size > 1) problems.push('A page was asked to be two things at once, so it was left as it is.')\n else pages.push({ page, visibility: [...visibilities][0] })\n }\n if (pages.length > AI_PRIVACY_MAX_PAGES) {\n pages.splice(AI_PRIVACY_MAX_PAGES)\n problems.push(`Only the first ${AI_PRIVACY_MAX_PAGES} pages were changed; ask again for the rest.`)\n }\n return {\n request: {\n maintenance: flag(inputs?.['maintenance']),\n discourageSearchEngines: flag(inputs?.['discourageSearchEngines']),\n pages,\n },\n problems,\n }\n}\n\n/** Whether a read request asks for anything at all. */\nexport function aiPrivacyRequestEmpty(request: AiPrivacyRequest): boolean {\n return request.maintenance === null && request.discourageSearchEngines === null && request.pages.length === 0\n}\n\n// ── The write ─────────────────────────────────────────────────────────────\n\nexport type AiPrivacyWrite =\n | {\n ok: true\n /** A sentence per change asked, made now or already so, by output id. */\n done: { output: keyof typeof AI_PRIVACY_OUTPUTS; said: string }[]\n /** A sentence per part that was not made, and why. */\n notDone: string[]\n changedAny: boolean\n outboxId: string | null\n }\n | { ok: false; error: string }\n\n/**\n * The changes written, in one transaction that reads the host and each named\n * page as they are stored NOW: the member still holds a write role (and is an\n * admin, for maintenance), each page exists and is a page, and only what\n * differs from what is stored is written, with a site-wide publish outbox\n * entry beside it so the live site's cached pages are dropped.\n */\nexport async function writeAiSitePrivacy(\n firestore: Firestore,\n input: { hostId: string; uid: string; request: AiPrivacyRequest; contentGating: boolean; now: Date },\n): Promise<AiPrivacyWrite> {\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n const screens = hostRef.collection('screens')\n return firestore.runTransaction(async (tx): Promise<AiPrivacyWrite> => {\n const host = await tx.get(hostRef)\n if (!host.exists) return { ok: false, error: 'Unknown site' }\n if (!holdsSiteKey(host, input.uid, 'site.edit')) return { ok: false, error: AI_PRIVACY_ROLE_COPY }\n const managesSite = holdsSiteKey(host, input.uid, 'site.manage')\n const pageDocs = await Promise.all(input.request.pages.map((one) => tx.get(screens.doc(one.page))))\n\n const done: { output: keyof typeof AI_PRIVACY_OUTPUTS; said: string }[] = []\n const notDone: string[] = []\n const hostUpdates: Record<string, unknown> = {}\n const pageUpdates: { ref: FirebaseFirestore.DocumentReference; update: Record<string, unknown> }[] = []\n\n const { maintenance, discourageSearchEngines } = input.request\n if (maintenance !== null) {\n if (!managesSite) {\n notDone.push(AI_PRIVACY_ADMIN_COPY)\n } else {\n if ((host.get('maintenance') === true) !== maintenance) {\n hostUpdates['maintenance'] = maintenance || FieldValue.delete()\n }\n const assigned = !!(host.get('errorScreens') as Record<string, unknown> | undefined)?.['unavailable']\n done.push({\n output: 'maintenance',\n said: maintenance\n ? `Maintenance mode is on: every address shows ${assigned ? 'your 503 page' : `${PLATFORM_BRAND_NAME}’s plain “undergoing maintenance” notice, because no 503 page is assigned yet`} to every visitor.`\n : 'Maintenance mode is off: the site is open to visitors again.',\n })\n }\n }\n if (discourageSearchEngines !== null) {\n const stored = (host.get('seo') as Record<string, unknown> | undefined)?.['discourageSearchEngines'] === true\n if (stored !== discourageSearchEngines) {\n hostUpdates['seo.discourageSearchEngines'] = discourageSearchEngines || FieldValue.delete()\n }\n done.push({\n output: 'search',\n said: discourageSearchEngines\n ? 'Search engines are asked not to index this site. Anyone with a link can still open it.'\n : 'Search engines may index this site again. They take days to come back.',\n })\n }\n input.request.pages.forEach((asked, index) => {\n const page = pageDocs[index]\n if (!page.exists || page.get('deletedAt') != null || page.get('kind')) {\n notDone.push('A page named is not one of this site’s pages, so it was left out.')\n return\n }\n const name = String(page.get('displayName') ?? '').trim() || 'A page'\n if (asked.visibility === 'members' && !input.contentGating) {\n // The tenant serves gated pages only on plans with content gating, so\n // writing it here would close the page to everyone.\n notDone.push(`“${name}” was not made Members only: members-only pages need the Business plan or above.`)\n return\n }\n const value = VISIBILITY_VALUES[asked.visibility]\n const update: Record<string, unknown> = {}\n if ((page.get('visibility') ?? HostScreenVisibility.PUBLIC) !== value) update['visibility'] = value\n const hadPassword = !!(page.get('protection') as Record<string, unknown> | undefined)?.['passwordHash']\n if (hadPassword) update['protection'] = FieldValue.delete()\n if (Object.keys(update).length) pageUpdates.push({ ref: page.ref, update: { ...update, updatedAt: input.now } })\n const extra = hadPassword ? ' Its page password was removed.' : ''\n done.push({ output: 'pages', said: `“${name}” is now ${VISIBILITY_LABELS[asked.visibility]}.${extra}` })\n })\n\n const changedAny = Object.keys(hostUpdates).length > 0 || pageUpdates.length > 0\n if (!changedAny) return { ok: true, done, notDone, changedAny, outboxId: null }\n if (Object.keys(hostUpdates).length) tx.update(hostRef, { ...hostUpdates, updatedAt: input.now })\n for (const { ref, update } of pageUpdates) tx.update(ref, update)\n // Both switches change every page, and a page's visibility changes the\n // navigation and the sitemap every other page carries.\n const outboxRef = firestore.collection('publishOutbox').doc(createResourceUid())\n tx.set(outboxRef, {\n hostId: input.hostId,\n paths: [SCREEN_ROOT_PATH],\n entireHost: true,\n createdAt: FieldValue.serverTimestamp(),\n attempts: 0,\n })\n return { ok: true, done, notDone, changedAny, outboxId: outboxRef.id }\n })\n}\n\n// ── The step ──────────────────────────────────────────────────────────────\n\n/** Why this member may not change who can see this site now, or `null`. */\nexport async function aiPrivacyAccessRefusal(\n input: { host: FirebaseFirestore.DocumentSnapshot; uid: string; org: object | null; request: AiPrivacyRequest },\n deps: { lockdown?: typeof getLockdownVerdict } = {},\n): Promise<string | null> {\n if (!input.host.exists) return 'Unknown site'\n if (!holdsSiteKey(input.host, input.uid, 'site.edit')) return AI_PRIVACY_ROLE_COPY\n // A request that is ONLY maintenance has nothing a non-admin may make.\n const onlyMaintenance =\n input.request.maintenance !== null && input.request.discourageSearchEngines === null && !input.request.pages.length\n if (onlyMaintenance && !holdsSiteKey(input.host, input.uid, 'site.manage')) return AI_PRIVACY_ADMIN_COPY\n const locked = await (deps.lockdown ?? getLockdownVerdict)({\n staff: false,\n uid: input.uid,\n org: input.org as never,\n host: input.host.data() as never,\n intent: 'write',\n })\n return locked ? AI_PRIVACY_LOCKED_COPY : null\n}\n\n/** One output's note: what changed, that it is live, and what was not done. */\nexport function aiPrivacyOutputNote(said: readonly string[], where: string, notDone: readonly string[], changedAny: boolean): string {\n const did = said.length\n ? `${said.join(' ')} Live now, in ${where}.${changedAny ? '' : ' It already was, so nothing was written.'}`\n : 'Nothing needed changing.'\n const not = notDone.length ? ` Not done: ${notDone.join(' ')}` : ''\n return `${did}${not}`.slice(0, 1_000)\n}\n\nexport interface AiJobPrivacyStepDeps {\n write?: typeof writeAiSitePrivacy\n lockdown?: typeof getLockdownVerdict\n dropCache?: typeof dropPluginSiteCache\n}\n\nexport function createAiJobPrivacyStep(deps: AiJobPrivacyStepDeps = {}): AiJobStepRunner {\n const write = deps.write ?? writeAiSitePrivacy\n const dropCache = deps.dropCache ?? dropPluginSiteCache\n return async ({ job, now, firestore, org, modelFor }): Promise<AiJobStepOutcome> => {\n // No model is asked: the outcome records the edit step's at no cost.\n const model = modelFor?.('job.edit') ?? aiJobEditModel()\n const unspent = (failure: string): AiJobStepOutcome => ({ ...aiUnspentOutcome(model), failure })\n const hostId = job.hostId\n if (!hostId) return unspent(AI_PRIVACY_NO_SITE_COPY)\n const read = aiPrivacyRequestOf(job.inputs)\n if (aiPrivacyRequestEmpty(read.request)) return unspent([AI_PRIVACY_NOTHING_COPY, ...read.problems].join(' '))\n const orgData = (org ?? ((await firestore.collection('orgs').doc(job.orgId).get()).data() ?? null)) as Record<string, unknown> | null\n const host = await firestore.collection('hosts').doc(hostId).get()\n const refused = await aiPrivacyAccessRefusal({ host, uid: job.createdBy, org: orgData, request: read.request }, { lockdown: deps.lockdown })\n if (refused) return aiUnspentOutcome(model, { review: aiLimitReview(refused) })\n const written = await write(firestore, {\n hostId,\n uid: job.createdBy,\n request: read.request,\n contentGating: checkEntitlement(orgData as never, 'contentGating'),\n now,\n })\n if (written.ok === false) return unspent(written.error)\n if (written.outboxId) {\n // The live site's cached pages go now; the outbox entry stays for the\n // drain unless the drop certainly happened.\n const dropped = await dropCache({ hostIds: [hostId], reason: 'AI build changed site privacy' }).catch(() => ({ complete: false }))\n if (dropped.complete) await firestore.collection('publishOutbox').doc(written.outboxId).delete().catch(() => undefined)\n }\n const notDone = [...read.problems, ...written.notDone]\n const hostSubdomain = (typeof host.get('subdomain') === 'string' && host.get('subdomain')) || null\n const outputs: AiJobOutput[] = (Object.keys(AI_PRIVACY_OUTPUTS) as (keyof typeof AI_PRIVACY_OUTPUTS)[])\n .filter((key) => written.done.some((one) => one.output === key))\n .map((key, index) => ({\n resource: 'privacy',\n id: AI_PRIVACY_OUTPUTS[key].id,\n hostId,\n hostSubdomain,\n label: AI_PRIVACY_OUTPUTS[key].label,\n note: aiPrivacyOutputNote(\n written.done.filter((one) => one.output === key).map((one) => one.said),\n AI_PRIVACY_OUTPUTS[key].where,\n // What was not done is said once, on the first output.\n index === 0 ? notDone : [],\n written.changedAny,\n ),\n }))\n if (!outputs.length) return unspent([AI_PRIVACY_NOTHING_COPY, ...notDone].join(' '))\n return aiUnspentOutcome(model, { outputs })\n }\n}\n\nexport const runAiJobPrivacyStep = createAiJobPrivacyStep()\n\n// ── Admission ─────────────────────────────────────────────────────────────\n\nexport interface AiPrivacyJobAdmissionDeps {\n ownerOf?: typeof resolveOrgIdForHost\n lockdown?: typeof getLockdownVerdict\n}\n\n/**\n * A privacy change is admitted on a site of the job's own org, for a member\n * who may write it now, where the request names at least one change this can\n * make. Refused in those words before anything is written.\n */\nexport function createAiPrivacyJobAdmission(deps: AiPrivacyJobAdmissionDeps = {}): AiJobAdmission {\n const ownerOf = deps.ownerOf ?? resolveOrgIdForHost\n return async (context): Promise<AiJobAdmissionRefusal | null> => {\n if (!context.hostId) return { status: 400, error: AI_PRIVACY_NO_SITE_COPY }\n const owner = await ownerOf(context.hostId)\n if (!owner || owner !== context.orgId) return { status: 404, error: 'Unknown site' }\n const read = aiPrivacyRequestOf(context.inputs as Record<string, unknown>)\n if (aiPrivacyRequestEmpty(read.request)) return { status: 400, error: [AI_PRIVACY_NOTHING_COPY, ...read.problems].join(' ') }\n const host = await context.firestore.collection('hosts').doc(context.hostId).get()\n const refused = await aiPrivacyAccessRefusal(\n { host, uid: context.uid ?? '', org: context.org, request: read.request },\n { lockdown: deps.lockdown },\n )\n return refused ? { status: 403, error: refused } : null\n }\n}\n\nexport const aiPrivacyJobAdmission = createAiPrivacyJobAdmission()\n\n/** Registers the privacy step and its door (AGL-4033). */\nexport function registerAiPrivacyJob(): void {\n registerAiJobStep(AI_PRIVACY_KIND, runAiJobPrivacyStep, { minimumMs: AI_JOB_PRIVACY_STEP_MINIMUM_MS })\n registerAiJobAdmission(AI_PRIVACY_KIND, aiPrivacyJobAdmission)\n}\n"],"names":["createResourceUid","holdsSiteKey","orgPermissionLabel","aiAddonName","checkEntitlement","PLATFORM_BRAND_NAME","SCREEN_ROOT_PATH","HostScreenVisibility","dropPluginSiteCache","getLockdownVerdict","resolveOrgIdForHost","FieldValue","registerAiJobAdmission","aiJobEditModel","aiLimitReview","aiUnspentOutcome","registerAiJobStep","AI_PRIVACY_KIND","AI_JOB_PRIVACY_STEP_MINIMUM_MS","AI_PRIVACY_NO_SITE_COPY","AI_PRIVACY_ROLE_COPY","AI_PRIVACY_ADMIN_COPY","AI_PRIVACY_LOCKED_COPY","AI_PRIVACY_NOTHING_COPY","AI_PRIVACY_PASSWORD_COPY","AI_PRIVACY_MAX_PAGES","AI_PRIVACY_VISIBILITIES","VISIBILITY_VALUES","public","PUBLIC","unlisted","UNLISTED","members","AUTHENTICATED","VISIBILITY_LABELS","AI_PRIVACY_OUTPUTS","maintenance","id","label","where","search","pages","AI_PRIVACY_WHERE","AI_PRIVACY_ARGS_SCHEMA","type","properties","description","discourageSearchEngines","publicPages","items","maxLength","maxItems","unlistedPages","membersPages","additionalProperties","VISIBILITY_ARGS","flag","value","PAGE_ID","aiPrivacyRequestOf","inputs","problems","asked","Map","visibility","listed","entry","Array","isArray","page","trim","test","set","get","Set","add","visibilities","size","push","length","splice","request","aiPrivacyRequestEmpty","writeAiSitePrivacy","firestore","input","hostRef","collection","doc","hostId","screens","runTransaction","tx","host","exists","ok","error","uid","managesSite","pageDocs","Promise","all","map","one","done","notDone","hostUpdates","pageUpdates","delete","assigned","output","said","stored","forEach","index","name","String","contentGating","update","hadPassword","Object","keys","ref","updatedAt","now","extra","changedAny","outboxId","outboxRef","paths","entireHost","createdAt","serverTimestamp","attempts","aiPrivacyAccessRefusal","deps","onlyMaintenance","locked","lockdown","staff","org","data","intent","aiPrivacyOutputNote","did","join","not","slice","createAiJobPrivacyStep","write","dropCache","job","modelFor","model","unspent","failure","read","orgData","orgId","refused","createdBy","review","written","dropped","hostIds","reason","catch","complete","undefined","hostSubdomain","outputs","filter","key","some","resource","note","runAiJobPrivacyStep","createAiPrivacyJobAdmission","ownerOf","context","status","owner","aiPrivacyJobAdmission","registerAiPrivacyJob","minimumMs"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,iBAAiB,QAAQ,6CAA4C;AAC9E,SAASC,YAAY,QAAQ,0CAAyC;AACtE,SAASC,kBAAkB,QAAQ,yCAAwC;AAC3E,SAASC,WAAW,EAAEC,gBAAgB,QAAQ,2CAA0C;AACxF,SAASC,mBAAmB,QAAQ,wCAAuC;AAC3E,SAASC,gBAAgB,QAAQ,sCAAqC;AACtE,SAASC,oBAAoB,QAAQ,qDAAoD;AACzF,SAASC,mBAAmB,QAAQ,gDAA+C;AAEnF,SAASC,kBAAkB,QAAQ,2CAA0C;AAC7E,SAASC,mBAAmB,QAAQ,gDAA+C;AACnF,SAASC,UAAU,QAAwB,2BAA0B;AAErE,SAASC,sBAAsB,QAAyD,wBAAoB;AAC5G,SAASC,cAAc,QAAQ,wBAAoB;AACnD,SAASC,aAAa,EAAEC,gBAAgB,QAAQ,yBAAqB;AAErE,SAASC,iBAAiB,QAAQ,eAAW;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8CC,GAED,OAAO,MAAMC,kBAAkB,UAAkB;AAEjD,qGAAqG,GACrG,OAAO,MAAMC,iCAAiC,KAAK;AAEnD,OAAO,MAAMC,0BAA0B,6CAA4C;AACnF,qEAAqE,GACrE,OAAO,MAAMC,uBAAuB,CAAC,yCAAyC,EAAElB,mBAAmB,aAAa,sDAAsD,CAAC,CAAA;AACvK,0EAA0E,GAC1E,OAAO,MAAMmB,wBAAwB,CAAC,8CAA8C,EAAEnB,mBAAmB,eAAe,sCAAsC,CAAC,CAAA;AAC/J,OAAO,MAAMoB,yBAAyB,gDAA+C;AACrF,OAAO,MAAMC,0BAA0B,uDAAsD;AAC7F,OAAO,MAAMC,2BACX,CAAC,yEAAyE,EAAErB,cAAc,yBAAyB,CAAC,CAAA;AAEtH,wCAAwC,GACxC,OAAO,MAAMsB,uBAAuB,GAAE;AAEtC,wEAAwE,GACxE,OAAO,MAAMC,0BAA0B;IAAC;IAAU;IAAY;CAAU,CAAS;AAGjF,MAAMC,oBAAiF;IACrFC,QAAQrB,qBAAqBsB,MAAM;IACnCC,UAAUvB,qBAAqBwB,QAAQ;IACvCC,SAASzB,qBAAqB0B,aAAa;AAC7C;AAEA,MAAMC,oBAAmE;IACvEN,QAAQ;IACRE,UAAU;IACVE,SAAS;AACX;AAEA,6EAA6E,GAC7E,OAAO,MAAMG,qBAAqB;IAChCC,aAAa;QAAEC,IAAI;QAAeC,OAAO;QAAoBC,OAAO;IAAsB;IAC1FC,QAAQ;QAAEH,IAAI;QAAUC,OAAO;QAAkBC,OAAO;IAAc;IACtEE,OAAO;QAAEJ,IAAI;QAASC,OAAO;QAAeC,OAAO;IAA+B;AACpF,EAAU;AAEV,0EAA0E,GAC1E,OAAO,MAAMG,mBAAmB,8BAA6B;AAE7D,4EAA4E,GAC5E,OAAO,MAAMC,yBAAuD;IAClEC,MAAM;IACNC,YAAY;QACVT,aAAa;YACXQ,MAAM;YACNE,aACE;QACJ;QACAC,yBAAyB;YACvBH,MAAM;YACNE,aACE;QACJ;QACAE,aAAa;YACXJ,MAAM;YACNE,aAAa;YACbG,OAAO;gBAAEL,MAAM;gBAAUM,WAAW;YAAG;YACvCC,UAAU1B;QACZ;QACA2B,eAAe;YACbR,MAAM;YACNE,aAAa;YACbG,OAAO;gBAAEL,MAAM;gBAAUM,WAAW;YAAG;YACvCC,UAAU1B;QACZ;QACA4B,cAAc;YACZT,MAAM;YACNE,aAAa;YACbG,OAAO;gBAAEL,MAAM;gBAAUM,WAAW;YAAG;YACvCC,UAAU1B;QACZ;IACF;IACA6B,sBAAsB;AACxB,EAAC;AAED,uEAAuE,GACvE,MAAMC,kBAAiE;IACrE3B,QAAQ;IACRE,UAAU;IACVE,SAAS;AACX;AAeA,MAAMwB,OAAO,CAACC,QAAoC,OAAOA,UAAU,YAAYA,QAAQ;AAEvF,6EAA6E,GAC7E,MAAMC,UAAU;AAEhB,0FAA0F,GAC1F,OAAO,SAASC,mBAAmBC,MAA4D;IAC7F,MAAMC,WAAqB,EAAE;IAC7B,MAAMpB,QAAmC,EAAE;IAC3C,MAAMqB,QAAQ,IAAIC;IAClB,KAAK,MAAMC,cAActC,wBAAyB;QAChD,MAAMuC,SAASL,0BAAAA,MAAQ,CAACL,eAAe,CAACS,WAAW,CAAC;QACpD,KAAK,MAAME,SAASC,MAAMC,OAAO,CAACH,UAAUA,SAAS,EAAE,CAAE;gBAEdH;YADzC,MAAMO,OAAO,OAAOH,UAAU,WAAWA,MAAMI,IAAI,KAAK;YACxD,IAAIZ,QAAQa,IAAI,CAACF,OAAOP,MAAMU,GAAG,CAACH,MAAM,EAACP,aAAAA,MAAMW,GAAG,CAACJ,iBAAVP,aAAmB,IAAIY,OAAOC,GAAG,CAACX;QAC7E;IACF;IACA,KAAK,MAAM,CAACK,MAAMO,aAAa,IAAId,MAAO;QACxC,sEAAsE;QACtE,IAAIc,aAAaC,IAAI,GAAG,GAAGhB,SAASiB,IAAI,CAAC;aACpCrC,MAAMqC,IAAI,CAAC;YAAET;YAAML,YAAY;mBAAIY;aAAa,CAAC,EAAE;QAAC;IAC3D;IACA,IAAInC,MAAMsC,MAAM,GAAGtD,sBAAsB;QACvCgB,MAAMuC,MAAM,CAACvD;QACboC,SAASiB,IAAI,CAAC,CAAC,eAAe,EAAErD,qBAAqB,4CAA4C,CAAC;IACpG;IACA,OAAO;QACLwD,SAAS;YACP7C,aAAaoB,KAAKI,0BAAAA,MAAQ,CAAC,cAAc;YACzCb,yBAAyBS,KAAKI,0BAAAA,MAAQ,CAAC,0BAA0B;YACjEnB;QACF;QACAoB;IACF;AACF;AAEA,qDAAqD,GACrD,OAAO,SAASqB,sBAAsBD,OAAyB;IAC7D,OAAOA,QAAQ7C,WAAW,KAAK,QAAQ6C,QAAQlC,uBAAuB,KAAK,QAAQkC,QAAQxC,KAAK,CAACsC,MAAM,KAAK;AAC9G;AAgBA;;;;;;CAMC,GACD,OAAO,eAAeI,mBACpBC,SAAoB,EACpBC,KAAoG;IAEpG,MAAMC,UAAUF,UAAUG,UAAU,CAAC,SAASC,GAAG,CAACH,MAAMI,MAAM;IAC9D,MAAMC,UAAUJ,QAAQC,UAAU,CAAC;IACnC,OAAOH,UAAUO,cAAc,CAAC,OAAOC;QACrC,MAAMC,OAAO,MAAMD,GAAGnB,GAAG,CAACa;QAC1B,IAAI,CAACO,KAAKC,MAAM,EAAE,OAAO;YAAEC,IAAI;YAAOC,OAAO;QAAe;QAC5D,IAAI,CAAC/F,aAAa4F,MAAMR,MAAMY,GAAG,EAAE,cAAc,OAAO;YAAEF,IAAI;YAAOC,OAAO5E;QAAqB;QACjG,MAAM8E,cAAcjG,aAAa4F,MAAMR,MAAMY,GAAG,EAAE;QAClD,MAAME,WAAW,MAAMC,QAAQC,GAAG,CAAChB,MAAMJ,OAAO,CAACxC,KAAK,CAAC6D,GAAG,CAAC,CAACC,MAAQX,GAAGnB,GAAG,CAACiB,QAAQF,GAAG,CAACe,IAAIlC,IAAI;QAE/F,MAAMmC,OAAoE,EAAE;QAC5E,MAAMC,UAAoB,EAAE;QAC5B,MAAMC,cAAuC,CAAC;QAC9C,MAAMC,cAA+F,EAAE;QAEvG,MAAM,EAAEvE,WAAW,EAAEW,uBAAuB,EAAE,GAAGsC,MAAMJ,OAAO;QAC9D,IAAI7C,gBAAgB,MAAM;YACxB,IAAI,CAAC8D,aAAa;gBAChBO,QAAQ3B,IAAI,CAACzD;YACf,OAAO;oBAIewE;gBAHpB,IAAI,AAACA,KAAKpB,GAAG,CAAC,mBAAmB,SAAUrC,aAAa;oBACtDsE,WAAW,CAAC,cAAc,GAAGtE,eAAezB,WAAWiG,MAAM;gBAC/D;gBACA,MAAMC,WAAW,CAAC,GAAEhB,YAAAA,KAAKpB,GAAG,CAAC,oCAAV,AAACoB,SAAkE,CAAC,cAAc;gBACrGW,KAAK1B,IAAI,CAAC;oBACRgC,QAAQ;oBACRC,MAAM3E,cACF,CAAC,4CAA4C,EAAEyE,WAAW,kBAAkB,GAAGxG,oBAAoB,6EAA6E,CAAC,CAAC,kBAAkB,CAAC,GACrM;gBACN;YACF;QACF;QACA,IAAI0C,4BAA4B,MAAM;gBACpB8C;YAAhB,MAAMmB,SAAS,EAACnB,aAAAA,KAAKpB,GAAG,CAAC,2BAAV,AAACoB,UAAyD,CAAC,0BAA0B,MAAK;YACzG,IAAImB,WAAWjE,yBAAyB;gBACtC2D,WAAW,CAAC,8BAA8B,GAAG3D,2BAA2BpC,WAAWiG,MAAM;YAC3F;YACAJ,KAAK1B,IAAI,CAAC;gBACRgC,QAAQ;gBACRC,MAAMhE,0BACF,2FACA;YACN;QACF;QACAsC,MAAMJ,OAAO,CAACxC,KAAK,CAACwE,OAAO,CAAC,CAACnD,OAAOoD;gBAMd7C,WASfA;gBACkBA;YAfvB,MAAMA,OAAO8B,QAAQ,CAACe,MAAM;YAC5B,IAAI,CAAC7C,KAAKyB,MAAM,IAAIzB,KAAKI,GAAG,CAAC,gBAAgB,QAAQJ,KAAKI,GAAG,CAAC,SAAS;gBACrEgC,QAAQ3B,IAAI,CAAC;gBACb;YACF;YACA,MAAMqC,OAAOC,QAAO/C,YAAAA,KAAKI,GAAG,CAAC,0BAATJ,YAA2B,IAAIC,IAAI,MAAM;YAC7D,IAAIR,MAAME,UAAU,KAAK,aAAa,CAACqB,MAAMgC,aAAa,EAAE;gBAC1D,sEAAsE;gBACtE,oDAAoD;gBACpDZ,QAAQ3B,IAAI,CAAC,CAAC,CAAC,EAAEqC,KAAK,gFAAgF,CAAC;gBACvG;YACF;YACA,MAAM1D,QAAQ9B,iBAAiB,CAACmC,MAAME,UAAU,CAAC;YACjD,MAAMsD,SAAkC,CAAC;YACzC,IAAI,EAACjD,aAAAA,KAAKI,GAAG,CAAC,yBAATJ,aAA0B9D,qBAAqBsB,MAAM,MAAM4B,OAAO6D,MAAM,CAAC,aAAa,GAAG7D;YAC9F,MAAM8D,cAAc,CAAC,GAAElD,aAAAA,KAAKI,GAAG,CAAC,kCAAV,AAACJ,UAAgE,CAAC,eAAe;YACvG,IAAIkD,aAAaD,MAAM,CAAC,aAAa,GAAG3G,WAAWiG,MAAM;YACzD,IAAIY,OAAOC,IAAI,CAACH,QAAQvC,MAAM,EAAE4B,YAAY7B,IAAI,CAAC;gBAAE4C,KAAKrD,KAAKqD,GAAG;gBAAEJ,QAAQ,aAAKA;oBAAQK,WAAWtC,MAAMuC,GAAG;;YAAG;YAC9G,MAAMC,QAAQN,cAAc,oCAAoC;YAChEf,KAAK1B,IAAI,CAAC;gBAAEgC,QAAQ;gBAASC,MAAM,CAAC,CAAC,EAAEI,KAAK,SAAS,EAAEjF,iBAAiB,CAAC4B,MAAME,UAAU,CAAC,CAAC,CAAC,EAAE6D,OAAO;YAAC;QACxG;QAEA,MAAMC,aAAaN,OAAOC,IAAI,CAACf,aAAa3B,MAAM,GAAG,KAAK4B,YAAY5B,MAAM,GAAG;QAC/E,IAAI,CAAC+C,YAAY,OAAO;YAAE/B,IAAI;YAAMS;YAAMC;YAASqB;YAAYC,UAAU;QAAK;QAC9E,IAAIP,OAAOC,IAAI,CAACf,aAAa3B,MAAM,EAAEa,GAAG0B,MAAM,CAAChC,SAAS,aAAKoB;YAAaiB,WAAWtC,MAAMuC,GAAG;;QAC9F,KAAK,MAAM,EAAEF,GAAG,EAAEJ,MAAM,EAAE,IAAIX,YAAaf,GAAG0B,MAAM,CAACI,KAAKJ;QAC1D,uEAAuE;QACvE,uDAAuD;QACvD,MAAMU,YAAY5C,UAAUG,UAAU,CAAC,iBAAiBC,GAAG,CAACxF;QAC5D4F,GAAGpB,GAAG,CAACwD,WAAW;YAChBvC,QAAQJ,MAAMI,MAAM;YACpBwC,OAAO;gBAAC3H;aAAiB;YACzB4H,YAAY;YACZC,WAAWxH,WAAWyH,eAAe;YACrCC,UAAU;QACZ;QACA,OAAO;YAAEtC,IAAI;YAAMS;YAAMC;YAASqB;YAAYC,UAAUC,UAAU3F,EAAE;QAAC;IACvE;AACF;AAEA,6EAA6E;AAE7E,yEAAyE,GACzE,OAAO,eAAeiG,uBACpBjD,KAA+G,EAC/GkD,OAAiD,CAAC,CAAC;QAQ7BA;IANtB,IAAI,CAAClD,MAAMQ,IAAI,CAACC,MAAM,EAAE,OAAO;IAC/B,IAAI,CAAC7F,aAAaoF,MAAMQ,IAAI,EAAER,MAAMY,GAAG,EAAE,cAAc,OAAO7E;IAC9D,uEAAuE;IACvE,MAAMoH,kBACJnD,MAAMJ,OAAO,CAAC7C,WAAW,KAAK,QAAQiD,MAAMJ,OAAO,CAAClC,uBAAuB,KAAK,QAAQ,CAACsC,MAAMJ,OAAO,CAACxC,KAAK,CAACsC,MAAM;IACrH,IAAIyD,mBAAmB,CAACvI,aAAaoF,MAAMQ,IAAI,EAAER,MAAMY,GAAG,EAAE,gBAAgB,OAAO5E;IACnF,MAAMoH,SAAS,MAAM,EAACF,iBAAAA,KAAKG,QAAQ,YAAbH,iBAAiB9H,oBAAoB;QACzDkI,OAAO;QACP1C,KAAKZ,MAAMY,GAAG;QACd2C,KAAKvD,MAAMuD,GAAG;QACd/C,MAAMR,MAAMQ,IAAI,CAACgD,IAAI;QACrBC,QAAQ;IACV;IACA,OAAOL,SAASnH,yBAAyB;AAC3C;AAEA,6EAA6E,GAC7E,OAAO,SAASyH,oBAAoBhC,IAAuB,EAAExE,KAAa,EAAEkE,OAA0B,EAAEqB,UAAmB;IACzH,MAAMkB,MAAMjC,KAAKhC,MAAM,GACnB,GAAGgC,KAAKkC,IAAI,CAAC,KAAK,cAAc,EAAE1G,MAAM,CAAC,EAAEuF,aAAa,KAAK,4CAA4C,GACzG;IACJ,MAAMoB,MAAMzC,QAAQ1B,MAAM,GAAG,CAAC,WAAW,EAAE0B,QAAQwC,IAAI,CAAC,MAAM,GAAG;IACjE,OAAO,GAAGD,MAAME,KAAK,CAACC,KAAK,CAAC,GAAG;AACjC;AAQA,OAAO,SAASC,uBAAuBb,OAA6B,CAAC,CAAC;QACtDA,aACIA;IADlB,MAAMc,SAAQd,cAAAA,KAAKc,KAAK,YAAVd,cAAcpD;IAC5B,MAAMmE,aAAYf,kBAAAA,KAAKe,SAAS,YAAdf,kBAAkB/H;IACpC,OAAO,OAAO,EAAE+I,GAAG,EAAE3B,GAAG,EAAExC,SAAS,EAAEwD,GAAG,EAAEY,QAAQ,EAAE;kBAQzB;QAPzB,qEAAqE;QACrE,MAAMC,gBAAQD,4BAAAA,SAAW,8BAAe3I;QACxC,MAAM6I,UAAU,CAACC,UAAuC,aAAK5I,iBAAiB0I;gBAAQE;;QACtF,MAAMlE,SAAS8D,IAAI9D,MAAM;QACzB,IAAI,CAACA,QAAQ,OAAOiE,QAAQvI;QAC5B,MAAMyI,OAAOjG,mBAAmB4F,IAAI3F,MAAM;QAC1C,IAAIsB,sBAAsB0E,KAAK3E,OAAO,GAAG,OAAOyE,QAAQ;YAACnI;eAA4BqI,KAAK/F,QAAQ;SAAC,CAACoF,IAAI,CAAC;QACzG,MAAMY,UAAWjB,cAAAA,OAAQ,QAAA,AAAC,CAAA,MAAMxD,UAAUG,UAAU,CAAC,QAAQC,GAAG,CAAC+D,IAAIO,KAAK,EAAErF,GAAG,EAAC,EAAGoE,IAAI,cAA9D,QAAoE;QAC7F,MAAMhD,OAAO,MAAMT,UAAUG,UAAU,CAAC,SAASC,GAAG,CAACC,QAAQhB,GAAG;QAChE,MAAMsF,UAAU,MAAMzB,uBAAuB;YAAEzC;YAAMI,KAAKsD,IAAIS,SAAS;YAAEpB,KAAKiB;YAAS5E,SAAS2E,KAAK3E,OAAO;QAAC,GAAG;YAAEyD,UAAUH,KAAKG,QAAQ;QAAC;QAC1I,IAAIqB,SAAS,OAAOhJ,iBAAiB0I,OAAO;YAAEQ,QAAQnJ,cAAciJ;QAAS;QAC7E,MAAMG,UAAU,MAAMb,MAAMjE,WAAW;YACrCK;YACAQ,KAAKsD,IAAIS,SAAS;YAClB/E,SAAS2E,KAAK3E,OAAO;YACrBoC,eAAejH,iBAAiByJ,SAAkB;YAClDjC;QACF;QACA,IAAIsC,QAAQnE,EAAE,KAAK,OAAO,OAAO2D,QAAQQ,QAAQlE,KAAK;QACtD,IAAIkE,QAAQnC,QAAQ,EAAE;YACpB,sEAAsE;YACtE,4CAA4C;YAC5C,MAAMoC,UAAU,MAAMb,UAAU;gBAAEc,SAAS;oBAAC3E;iBAAO;gBAAE4E,QAAQ;YAAgC,GAAGC,KAAK,CAAC,IAAO,CAAA;oBAAEC,UAAU;gBAAM,CAAA;YAC/H,IAAIJ,QAAQI,QAAQ,EAAE,MAAMnF,UAAUG,UAAU,CAAC,iBAAiBC,GAAG,CAAC0E,QAAQnC,QAAQ,EAAEnB,MAAM,GAAG0D,KAAK,CAAC,IAAME;QAC/G;QACA,MAAM/D,UAAU;eAAImD,KAAK/F,QAAQ;eAAKqG,QAAQzD,OAAO;SAAC;QACtD,MAAMgE,gBAAgB,AAAC,OAAO5E,KAAKpB,GAAG,CAAC,iBAAiB,YAAYoB,KAAKpB,GAAG,CAAC,gBAAiB;QAC9F,MAAMiG,UAAyB,AAAClD,OAAOC,IAAI,CAACtF,oBACzCwI,MAAM,CAAC,CAACC,MAAQV,QAAQ1D,IAAI,CAACqE,IAAI,CAAC,CAACtE,MAAQA,IAAIO,MAAM,KAAK8D,MAC1DtE,GAAG,CAAC,CAACsE,KAAK1D,QAAW,CAAA;gBACpB4D,UAAU;gBACVzI,IAAIF,kBAAkB,CAACyI,IAAI,CAACvI,EAAE;gBAC9BoD;gBACAgF;gBACAnI,OAAOH,kBAAkB,CAACyI,IAAI,CAACtI,KAAK;gBACpCyI,MAAMhC,oBACJmB,QAAQ1D,IAAI,CAACmE,MAAM,CAAC,CAACpE,MAAQA,IAAIO,MAAM,KAAK8D,KAAKtE,GAAG,CAAC,CAACC,MAAQA,IAAIQ,IAAI,GACtE5E,kBAAkB,CAACyI,IAAI,CAACrI,KAAK,EAC7B,uDAAuD;gBACvD2E,UAAU,IAAIT,UAAU,EAAE,EAC1ByD,QAAQpC,UAAU;YAEtB,CAAA;QACF,IAAI,CAAC4C,QAAQ3F,MAAM,EAAE,OAAO2E,QAAQ;YAACnI;eAA4BkF;SAAQ,CAACwC,IAAI,CAAC;QAC/E,OAAOlI,iBAAiB0I,OAAO;YAAEiB;QAAQ;IAC3C;AACF;AAEA,OAAO,MAAMM,sBAAsB5B,yBAAwB;AAS3D;;;;CAIC,GACD,OAAO,SAAS6B,4BAA4B1C,OAAkC,CAAC,CAAC;QAC9DA;IAAhB,MAAM2C,WAAU3C,gBAAAA,KAAK2C,OAAO,YAAZ3C,gBAAgB7H;IAChC,OAAO,OAAOyK;YAQGA;QAPf,IAAI,CAACA,QAAQ1F,MAAM,EAAE,OAAO;YAAE2F,QAAQ;YAAKpF,OAAO7E;QAAwB;QAC1E,MAAMkK,QAAQ,MAAMH,QAAQC,QAAQ1F,MAAM;QAC1C,IAAI,CAAC4F,SAASA,UAAUF,QAAQrB,KAAK,EAAE,OAAO;YAAEsB,QAAQ;YAAKpF,OAAO;QAAe;QACnF,MAAM4D,OAAOjG,mBAAmBwH,QAAQvH,MAAM;QAC9C,IAAIsB,sBAAsB0E,KAAK3E,OAAO,GAAG,OAAO;YAAEmG,QAAQ;YAAKpF,OAAO;gBAACzE;mBAA4BqI,KAAK/F,QAAQ;aAAC,CAACoF,IAAI,CAAC;QAAK;QAC5H,MAAMpD,OAAO,MAAMsF,QAAQ/F,SAAS,CAACG,UAAU,CAAC,SAASC,GAAG,CAAC2F,QAAQ1F,MAAM,EAAEhB,GAAG;QAChF,MAAMsF,UAAU,MAAMzB,uBACpB;YAAEzC;YAAMI,GAAG,GAAEkF,eAAAA,QAAQlF,GAAG,YAAXkF,eAAe;YAAIvC,KAAKuC,QAAQvC,GAAG;YAAE3D,SAAS2E,KAAK3E,OAAO;QAAC,GACxE;YAAEyD,UAAUH,KAAKG,QAAQ;QAAC;QAE5B,OAAOqB,UAAU;YAAEqB,QAAQ;YAAKpF,OAAO+D;QAAQ,IAAI;IACrD;AACF;AAEA,OAAO,MAAMuB,wBAAwBL,8BAA6B;AAElE,wDAAwD,GACxD,OAAO,SAASM;IACdvK,kBAAkBC,iBAAiB+J,qBAAqB;QAAEQ,WAAWtK;IAA+B;IACpGN,uBAAuBK,iBAAiBqK;AAC1C"}
|
|
@@ -22,7 +22,7 @@ import { filterEnabledPluginsByReleaseFlags } from "@aglyn/tenant-data-admin/ser
|
|
|
22
22
|
import { AI_PRODUCT_CATEGORY_LIST_MAX, AI_PRODUCT_OPTIONS_MAX, AI_PRODUCT_OPTION_VALUES_MAX, AI_PRODUCT_TEXT_MAX_CHARS, AI_PRODUCT_TYPES, AI_PRODUCTS_BULK_MAX, AI_PRODUCTS_OUTPUT_IDS, aiProductGaps, aiProductsProposalOf, parseAiProductsJobInputs } from "../model/ai-products.js";
|
|
23
23
|
import { AI_STEP_TIERS } from "../providers/catalog.js";
|
|
24
24
|
import { aiModelForStep } from "../providers/routing.js";
|
|
25
|
-
import { AI_CATALOG_MAX_TOKENS, AI_PRODUCT_CATEGORIES_MAX_TOKENS, AI_PRODUCT_COPY_MAX_TOKENS, AI_PRODUCTS_STEP, generateAiCatalog, generateAiCategories, generateAiProductCopy } from "../runtime/ai-products-generation.js";
|
|
25
|
+
import { AI_CATALOG_MAX_TOKENS, AI_PRODUCT_CATEGORIES_MAX_TOKENS, AI_PRODUCT_COPY_MAX_TOKENS, AI_PRODUCTS_STEP, aiCatalogProductsNamed, generateAiCatalog, generateAiCategories, generateAiProductCopy } from "../runtime/ai-products-generation.js";
|
|
26
26
|
import { aiRouteReadsImages } from "../runtime/ai-runtime.js";
|
|
27
27
|
import { registerAiJobAdmission } from "./ai-job-admission.js";
|
|
28
28
|
import { aiJobStepBudget } from "./ai-job-budget.js";
|
|
@@ -353,14 +353,16 @@ function modelOf(context) {
|
|
|
353
353
|
if (generation.status === 'needs_input') return _extends({}, spent, {
|
|
354
354
|
failure: generation.message
|
|
355
355
|
});
|
|
356
|
+
// A product its re-ask still named for a kind, a make or a model is left out (AGL-4065).
|
|
357
|
+
const products = aiCatalogProductsNamed(generation.value, job.brief);
|
|
356
358
|
const proposal = {
|
|
357
359
|
kind: 'catalog',
|
|
358
|
-
products
|
|
360
|
+
products,
|
|
359
361
|
notes: []
|
|
360
362
|
};
|
|
361
363
|
return _extends({}, spent, {
|
|
362
364
|
outputs: [
|
|
363
|
-
output(site, AI_PRODUCTS_OUTPUT_IDS.catalog, `Proposed products · ${
|
|
365
|
+
output(site, AI_PRODUCTS_OUTPUT_IDS.catalog, `Proposed products · ${products.length}`, proposal)
|
|
364
366
|
]
|
|
365
367
|
});
|
|
366
368
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-products-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport {\n pluginRecordIndex,\n type PluginRecordIndex,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-index'\nimport { isHostPluginEnabled } from '@aglyn/aglyn/plugin-manager/enabled-plugins'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport { filterEnabledPluginsByReleaseFlags } from '@aglyn/tenant-data-admin/server/release-flags'\nimport type { AiJob, AiJobOutput } from '../model/ai-jobs.types'\nimport {\n AI_PRODUCT_CATEGORY_LIST_MAX,\n AI_PRODUCT_OPTIONS_MAX,\n AI_PRODUCT_OPTION_VALUES_MAX,\n AI_PRODUCT_TEXT_MAX_CHARS,\n AI_PRODUCT_TYPES,\n AI_PRODUCTS_BULK_MAX,\n AI_PRODUCTS_OUTPUT_IDS,\n aiProductGaps,\n aiProductsProposalOf,\n parseAiProductsJobInputs,\n type AiCatalogProposal,\n type AiCategoriesProposal,\n type AiProductCategory,\n type AiProductCopy,\n type AiProductCopyProposal,\n type AiProductFacts,\n type AiProductPhotoRead,\n type AiProductType,\n} from '../model/ai-products'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { aiModelForStep } from '../providers/routing'\nimport type { AiValidatedGeneration } from '../runtime/ai-doctrine'\nimport {\n AI_CATALOG_MAX_TOKENS,\n AI_PRODUCT_CATEGORIES_MAX_TOKENS,\n AI_PRODUCT_COPY_MAX_TOKENS,\n AI_PRODUCTS_STEP,\n generateAiCatalog,\n generateAiCategories,\n generateAiProductCopy,\n} from '../runtime/ai-products-generation'\nimport { aiRouteReadsImages } from '../runtime/ai-runtime'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiGenerationSpent, aiUnspentOutcome } from './ai-job-generation'\nimport { AI_PRODUCT_IMAGE_READ_MS, readAiProductImage, type AiProductImageSeams } from './ai-product-image'\nimport type { AiJobStepContext, AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep, registerAiJobStepPasses } from './ai-jobs'\n\n/**\n * The `products` step (AGL-2916): commerce by AI, as proposals.\n *\n * `inputs.target` names the work (`model/ai-products.ts`): one product's copy\n * from what its editor holds, the copy of many saved products one a pass, a\n * store's first products from a brief, or its categories and a first set of\n * discounts from a brief.\n *\n * ## It writes nothing\n *\n * Like every runner, this reads, asks and returns. Every output is a\n * `product` proposal, and the commerce plugin's own surfaces write what a\n * person accepts: the product editor's Save, the products card's apply, and\n * the create paths the catalog and discount cards already use — each with the\n * allowance, role and seed checks those paths carry. A proposed product is\n * created as a draft at its suggested price (the store's default where it has\n * none, Zach 2026-10-12), and a proposed discount is created switched off.\n *\n * ## A bulk job, a product at a time\n *\n * A bulk job's pass writes one product's copy and asks the machine to\n * continue while products remain, so each product is one reservation and one\n * provider exchange, and a job that runs out of credits resumes at the\n * product where it stopped. Which product is next is read from the job's own\n * outputs, one a product, so nothing is kept anywhere else. A product that is\n * gone, or whose copy cannot be held to the rules, is reported and passed\n * over rather than failing the products after it.\n *\n * ## What the model is shown\n *\n * The product's name, type, text, tags, options and current search listing,\n * the names of the site's categories, the store's name, and — for a model that\n * reads pictures — the product's first photo, read from the site's own media\n * library as `ai-product-image.ts` describes. A catalog or categories request\n * carries the brief and the store's name, and the second its existing category\n * names. No price, stock, order, customer or other product is read for any of\n * them.\n */\n\nexport const AI_PRODUCTS_NO_SITE_COPY = 'Open the store this job is for before starting it.'\nexport const AI_PRODUCTS_UNKNOWN_SITE_COPY = 'This AI job names a site this workspace does not have.'\nexport const AI_PRODUCTS_NOT_ENTITLED_COPY = 'Your plan does not include selling products. See Billing.'\nexport const AI_PRODUCTS_COMMERCE_OFF_COPY = 'Turn on Commerce for this site before starting the job.'\nexport const AI_PRODUCTS_GONE_COPY = 'This product is no longer in the catalog.'\nexport const AI_PRODUCTS_DECLINED_COPY = 'The model declined to write copy for this product.'\n\n/** The notes a proposal carries about its product's photo. */\nexport const AI_PRODUCT_PHOTO_NOTES: Readonly<Partial<Record<AiProductPhotoRead, string>>> = {\n 'not-in-library':\n 'The photo was not read: it is not in this site’s media library. The copy is written from the product’s words.',\n unreadable: 'The photo could not be read, so the copy is written from the product’s words.',\n model: 'The photo was not read: the model this job ran on does not read pictures.',\n}\n\n/**\n * ASSUMED: how long a catalog or categories pass reads the site before it\n * asks: the host document and, for categories, the category names.\n */\nexport const AI_PRODUCTS_SITE_READ_MS = 1_000\n\nconst TIER = AI_STEP_TIERS[AI_PRODUCTS_STEP]\n\n/**\n * One product's copy (AGL-3035): its generation and re-ask at the copy's own\n * ceiling on the tier `job.products` is served from, with the photo read. The\n * least any pass of the step needs, and so what the step registers.\n */\nexport const AI_JOB_PRODUCT_COPY_BUDGET = aiJobStepBudget({\n tier: TIER,\n maxTokens: AI_PRODUCT_COPY_MAX_TOKENS,\n lookups: 0,\n ownReadsMs: AI_PRODUCT_IMAGE_READ_MS,\n})\n\n/** A catalog: the largest answer the step asks for, at the routing table's ceiling. */\nexport const AI_JOB_CATALOG_BUDGET = aiJobStepBudget({\n tier: TIER,\n maxTokens: AI_CATALOG_MAX_TOKENS,\n lookups: 0,\n ownReadsMs: AI_PRODUCTS_SITE_READ_MS,\n})\n\n/** Categories and discounts. */\nexport const AI_JOB_CATEGORIES_BUDGET = aiJobStepBudget({\n tier: TIER,\n maxTokens: AI_PRODUCT_CATEGORIES_MAX_TOKENS,\n lookups: 0,\n ownReadsMs: AI_PRODUCTS_SITE_READ_MS,\n})\n\n/** What the `products` step registers: the least any of its passes needs. */\nexport const AI_JOB_PRODUCTS_STEP_BUDGET = AI_JOB_PRODUCT_COPY_BUDGET\n\n/** The least time the next pass of a job needs, by what the job asks for. */\nexport function aiProductsRunMinimumMs(job: Pick<AiJob, 'inputs'>): number {\n const inputs = parseAiProductsJobInputs(job.inputs)\n if (typeof inputs === 'string') return AI_JOB_PRODUCT_COPY_BUDGET.minimumMs\n if (inputs.target === 'catalog') return AI_JOB_CATALOG_BUDGET.minimumMs\n if (inputs.target === 'categories') return AI_JOB_CATEGORIES_BUDGET.minimumMs\n return AI_JOB_PRODUCT_COPY_BUDGET.minimumMs\n}\n\ntype Firestore = FirebaseFirestore.Firestore\n\ninterface StoreSite {\n hostId: string\n subdomain: string | null\n /** The store's name, as its visitors read it. */\n name: string\n}\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** Through JSON, so no `undefined` reaches the job document's write. */\nconst plain = <T>(value: T): Record<string, unknown> =>\n JSON.parse(JSON.stringify(value)) as Record<string, unknown>\n\n/** The site a job names, read and checked against the job's org. */\nasync function loadStore(firestore: Firestore, job: AiJob): Promise<StoreSite | { failure: string }> {\n if (!job.hostId) return { failure: AI_PRODUCTS_NO_SITE_COPY }\n const snapshot = await firestore.collection('hosts').doc(job.hostId).get()\n const host = snapshot.exists ? (snapshot.data() as Record<string, unknown>) : null\n // The door checks the caller's membership of the org the job is metered\n // against; a site of another org is refused here, before anything of it\n // reaches a prompt.\n if (!host || host['orgId'] !== job.orgId) return { failure: AI_PRODUCTS_UNKNOWN_SITE_COPY }\n const seo = (host['seo'] ?? {}) as Record<string, unknown>\n const subdomain = str(host['subdomain']) || null\n return {\n hostId: job.hostId,\n subdomain,\n name: str(seo['title']) || str(host['displayName']) || subdomain || '',\n }\n}\n\n/**\n * The site's categories by name, as many as a request lists — from the\n * `productCategory` index the plugin that keeps them publishes (AGL-3080).\n * None when no plugin keeps categories here.\n */\nasync function readCategories(\n hostId: string,\n given?: PluginRecordIndex | null,\n): Promise<AiProductCategory[]> {\n const index = given ?? pluginRecordIndex('productCategory')?.index\n if (!index) return []\n const { records } = await index.list({ hostId, limit: AI_PRODUCT_CATEGORY_LIST_MAX })\n return records\n .map((record) => ({ id: record.id, name: record.name }))\n .sort((a, b) => a.name.localeCompare(b.name))\n}\n\n/**\n * A saved product's facts, as a bulk pass reads them; `null` when it is gone\n * — read through the `product` index the plugin that keeps products publishes\n * (AGL-3080), which answers live, named products only.\n */\nasync function readProductFacts(hostId: string, productId: string): Promise<AiProductFacts | null> {\n const record = await pluginRecordIndex('product')?.index.get({ hostId, id: productId })\n if (!record) return null\n const facts = record.facts\n const type = facts['type']\n const list = (value: unknown): unknown[] => (Array.isArray(value) ? value : [])\n return {\n id: record.id,\n name: record.name,\n type: (AI_PRODUCT_TYPES as readonly unknown[]).includes(type) ? (type as AiProductType) : 'physical',\n text: str(facts['description']).slice(0, AI_PRODUCT_TEXT_MAX_CHARS),\n tags: list(facts['tags']).map(str).filter(Boolean),\n categoryIds: list(facts['categoryIds']).map(str).filter(Boolean),\n options: list(facts['options'])\n .slice(0, AI_PRODUCT_OPTIONS_MAX)\n .map((option) => {\n const record = (option ?? {}) as Record<string, unknown>\n return {\n name: str(record['name']),\n values: list(record['values']).map(str).filter(Boolean).slice(0, AI_PRODUCT_OPTION_VALUES_MAX),\n }\n })\n .filter((option) => option.name),\n imageUrl: str(facts['imageUrl']) || null,\n seoTitle: str(facts['seoTitle']),\n seoDescription: str(facts['seoDescription']),\n }\n}\n\nfunction modelOf(context: AiJobStepContext): string {\n return context.modelFor?.(AI_PRODUCTS_STEP) ?? aiModelForStep(AI_PRODUCTS_STEP)\n}\n\n/** An output addressed to the site, so its link opens the site's products. */\nfunction output(site: StoreSite, id: string, label: string, proposal: object): AiJobOutput {\n return {\n resource: 'product',\n id,\n hostId: site.hostId,\n hostSubdomain: site.subdomain,\n label,\n proposal: plain(proposal),\n }\n}\n\nexport interface AiJobProductsStepDeps {\n image?: AiProductImageSeams\n /** Whether a model reads pictures; the provider's own descriptor otherwise. */\n readsImages?: (model: string) => boolean\n /**\n * Where the site's categories are read; the `productCategory` index the\n * plugin that keeps them publishes otherwise. The eval harness hands its\n * own, over the categories a case names.\n */\n categoryIndex?: PluginRecordIndex | null\n}\n\ninterface CopyRun {\n generation: AiValidatedGeneration<AiProductCopy>\n proposal: AiProductCopyProposal\n}\n\n/** Ask for one product's copy, and build its proposal from the answer. */\nasync function writeProductCopy(\n context: AiJobStepContext & { firestore: Firestore },\n model: string,\n site: StoreSite,\n product: AiProductFacts,\n deps: AiJobProductsStepDeps,\n): Promise<CopyRun> {\n const { job, firestore, signal } = context\n const categories = await readCategories(site.hostId, deps.categoryIndex)\n let photo: AiProductPhotoRead = 'none'\n let image = null\n if (product.imageUrl) {\n if ((deps.readsImages ?? aiRouteReadsImages)(model)) {\n const read = await readAiProductImage(\n firestore,\n { value: product.imageUrl, hostId: site.hostId, orgId: job.orgId },\n deps.image,\n )\n photo = read.status\n image = read.status === 'read' ? read.image : null\n } else {\n photo = 'model'\n }\n }\n const generation = await generateAiProductCopy({\n store: site.name,\n product,\n categories,\n photo: Boolean(image),\n image,\n model,\n maxTokens: AI_JOB_PRODUCT_COPY_BUDGET.maxTokens(model),\n ...(signal ? { signal } : {}),\n })\n const values = generation.status === 'ok' ? generation.value : null\n const names = new Map(categories.map((category) => [category.id, category.name]))\n const note = AI_PRODUCT_PHOTO_NOTES[photo]\n return {\n generation,\n proposal: {\n kind: 'copy',\n product: { id: product.id, name: product.name },\n values,\n categories: (values?.categoryIds ?? []).map((id) => ({ id, name: names.get(id) ?? id })),\n optionNamesBefore: product.options.map((option) => option.name),\n gaps: values ? aiProductGaps(values.description) : [],\n photo,\n skipped: null,\n notes: note ? [note] : [],\n },\n }\n}\n\n/** The step, with its reads replaceable where a spec needs them. */\nexport function createAiJobProductsStep(deps: AiJobProductsStepDeps = {}): AiJobStepRunner {\n return async (context) => {\n const { job, firestore, signal } = context\n const model = modelOf(context)\n const inputs = parseAiProductsJobInputs(job.inputs)\n if (typeof inputs === 'string') return { ...aiUnspentOutcome(model), failure: inputs }\n const site = await loadStore(firestore, job)\n if ('failure' in site) return { ...aiUnspentOutcome(model), failure: site.failure }\n\n if (inputs.target === 'product') {\n const { generation, proposal } = await writeProductCopy(context, model, site, inputs.product, deps)\n const spent = aiGenerationSpent(generation)\n if (generation.status === 'refused') return { ...spent, refused: true }\n if (generation.status === 'needs_input') return { ...spent, failure: generation.message }\n return {\n ...spent,\n outputs: [output(site, AI_PRODUCTS_OUTPUT_IDS.copy(inputs.product.id), `Product copy · ${inputs.product.name}`, proposal)],\n }\n }\n\n if (inputs.target === 'bulk') {\n const done = (job.outputs ?? []).filter((entry) => aiProductsProposalOf(entry)?.kind === 'copy').length\n const productId = inputs.productIds[done]\n if (!productId) return aiUnspentOutcome(model)\n const more = done + 1 < inputs.productIds.length\n const product = await readProductFacts(site.hostId, productId)\n if (!product) {\n const skipped: AiProductCopyProposal = {\n kind: 'copy',\n product: { id: productId, name: productId },\n values: null,\n categories: [],\n optionNamesBefore: [],\n gaps: [],\n photo: 'none',\n skipped: AI_PRODUCTS_GONE_COPY,\n notes: [],\n }\n return {\n ...aiUnspentOutcome(model, {\n outputs: [output(site, AI_PRODUCTS_OUTPUT_IDS.copy(productId), `Product copy · ${productId}`, skipped)],\n }),\n continue: more,\n }\n }\n const { generation, proposal } = await writeProductCopy(context, model, site, product, deps)\n const spent = aiGenerationSpent(generation)\n // One product the rules could not hold, or the model declined, is\n // reported and passed over: its tokens are metered with the pass, and\n // the products after it still get their copy.\n const reported: AiProductCopyProposal =\n generation.status === 'ok'\n ? proposal\n : {\n ...proposal,\n skipped: generation.status === 'needs_input' ? generation.message : AI_PRODUCTS_DECLINED_COPY,\n }\n return {\n ...spent,\n outputs: [output(site, AI_PRODUCTS_OUTPUT_IDS.copy(productId), `Product copy · ${product.name}`, reported)],\n continue: more,\n }\n }\n\n if (inputs.target === 'catalog') {\n const generation = await generateAiCatalog({\n store: site.name,\n brief: job.brief,\n model,\n maxTokens: AI_JOB_CATALOG_BUDGET.maxTokens(model),\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(generation)\n if (generation.status === 'refused') return { ...spent, refused: true }\n if (generation.status === 'needs_input') return { ...spent, failure: generation.message }\n const proposal: AiCatalogProposal = { kind: 'catalog', products: generation.value, notes: [] }\n return {\n ...spent,\n outputs: [\n output(\n site,\n AI_PRODUCTS_OUTPUT_IDS.catalog,\n `Proposed products · ${generation.value.length}`,\n proposal,\n ),\n ],\n }\n }\n\n const existing = await readCategories(site.hostId, deps.categoryIndex)\n const generation = await generateAiCategories({\n store: site.name,\n brief: job.brief,\n existingCategoryNames: existing.map((category) => category.name),\n model,\n maxTokens: AI_JOB_CATEGORIES_BUDGET.maxTokens(model),\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(generation)\n if (generation.status === 'refused') return { ...spent, refused: true }\n if (generation.status === 'needs_input') return { ...spent, failure: generation.message }\n const proposal: AiCategoriesProposal = { kind: 'categories', ...generation.value, notes: [] }\n return {\n ...spent,\n outputs: [output(site, AI_PRODUCTS_OUTPUT_IDS.categories, 'Proposed categories and discounts', proposal)],\n }\n }\n}\n\nexport const runAiJobProductsStep = createAiJobProductsStep()\n\n/**\n * Whether a `products` job may start: inputs it can run, a site of the job's\n * own org, a plan that sells products, and Commerce on for the site and past\n * its release flag. Asked by the create door before the job exists, so a\n * refusal spends nothing.\n */\nexport const aiProductsJobAdmission: AiJobAdmission = async (context) => {\n const inputs = parseAiProductsJobInputs(context.inputs)\n if (typeof inputs === 'string') return { status: 400, error: inputs }\n if (!context.hostId) return { status: 400, error: AI_PRODUCTS_NO_SITE_COPY }\n const owner = await resolveOrgIdForHost(context.hostId)\n if (!owner || owner !== context.orgId) return { status: 404, error: AI_PRODUCTS_UNKNOWN_SITE_COPY }\n const org = context.org as (Partial<AglynOrgBilling> & { enabledPlugins?: string[] }) | null\n if (!checkEntitlement(org, 'commerce')) return { status: 403, error: AI_PRODUCTS_NOT_ENTITLED_COPY }\n // The plugin that keeps products is whichever publishes their index\n // (AGL-3080); none is the same refusal as one switched off.\n const keeper = pluginRecordIndex('product')?.pluginId\n if (!keeper) return { status: 403, error: AI_PRODUCTS_COMMERCE_OFF_COPY }\n const host = (await context.firestore.collection('hosts').doc(context.hostId).get()).data() ?? null\n const released = await filterEnabledPluginsByReleaseFlags([keeper], {\n orgId: context.orgId,\n authorization: null,\n })\n if (!released.includes(keeper) || !isHostPluginEnabled(org, host, keeper)) {\n return { status: 403, error: AI_PRODUCTS_COMMERCE_OFF_COPY }\n }\n return null\n}\n\n/**\n * Registers the `products` step, the least time each of its passes needs, its\n * pass bound and its admission.\n */\nexport function registerAiProductsJob(): void {\n registerAiJobStep('products', runAiJobProductsStep, {\n minimumMs: AI_JOB_PRODUCTS_STEP_BUDGET.minimumMs,\n minimumMsFor: aiProductsRunMinimumMs,\n })\n registerAiJobStepPasses('products', AI_PRODUCTS_BULK_MAX)\n registerAiJobAdmission('products', aiProductsJobAdmission)\n}\n"],"names":["checkEntitlement","pluginRecordIndex","isHostPluginEnabled","resolveOrgIdForHost","filterEnabledPluginsByReleaseFlags","AI_PRODUCT_CATEGORY_LIST_MAX","AI_PRODUCT_OPTIONS_MAX","AI_PRODUCT_OPTION_VALUES_MAX","AI_PRODUCT_TEXT_MAX_CHARS","AI_PRODUCT_TYPES","AI_PRODUCTS_BULK_MAX","AI_PRODUCTS_OUTPUT_IDS","aiProductGaps","aiProductsProposalOf","parseAiProductsJobInputs","AI_STEP_TIERS","aiModelForStep","AI_CATALOG_MAX_TOKENS","AI_PRODUCT_CATEGORIES_MAX_TOKENS","AI_PRODUCT_COPY_MAX_TOKENS","AI_PRODUCTS_STEP","generateAiCatalog","generateAiCategories","generateAiProductCopy","aiRouteReadsImages","registerAiJobAdmission","aiJobStepBudget","aiGenerationSpent","aiUnspentOutcome","AI_PRODUCT_IMAGE_READ_MS","readAiProductImage","registerAiJobStep","registerAiJobStepPasses","AI_PRODUCTS_NO_SITE_COPY","AI_PRODUCTS_UNKNOWN_SITE_COPY","AI_PRODUCTS_NOT_ENTITLED_COPY","AI_PRODUCTS_COMMERCE_OFF_COPY","AI_PRODUCTS_GONE_COPY","AI_PRODUCTS_DECLINED_COPY","AI_PRODUCT_PHOTO_NOTES","unreadable","model","AI_PRODUCTS_SITE_READ_MS","TIER","AI_JOB_PRODUCT_COPY_BUDGET","tier","maxTokens","lookups","ownReadsMs","AI_JOB_CATALOG_BUDGET","AI_JOB_CATEGORIES_BUDGET","AI_JOB_PRODUCTS_STEP_BUDGET","aiProductsRunMinimumMs","job","inputs","minimumMs","target","str","value","trim","plain","JSON","parse","stringify","loadStore","firestore","host","hostId","failure","snapshot","collection","doc","get","exists","data","orgId","seo","subdomain","name","readCategories","given","index","records","list","limit","map","record","id","sort","a","b","localeCompare","readProductFacts","productId","facts","type","Array","isArray","includes","text","slice","tags","filter","Boolean","categoryIds","options","option","values","imageUrl","seoTitle","seoDescription","modelOf","context","modelFor","output","site","label","proposal","resource","hostSubdomain","writeProductCopy","product","deps","signal","categories","categoryIndex","photo","image","readsImages","read","status","generation","store","names","Map","category","note","kind","optionNamesBefore","gaps","description","skipped","notes","createAiJobProductsStep","spent","refused","message","outputs","copy","done","entry","length","productIds","more","continue","reported","brief","products","catalog","existing","existingCategoryNames","runAiJobProductsStep","aiProductsJobAdmission","error","owner","org","keeper","pluginId","released","authorization","registerAiProductsJob","minimumMsFor"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,gBAAgB,QAAQ,2CAA0C;AAE3E,SACEC,iBAAiB,QAEZ,kDAAiD;AACxD,SAASC,mBAAmB,QAAQ,8CAA6C;AACjF,SAASC,mBAAmB,QAAQ,gDAA+C;AACnF,SAASC,kCAAkC,QAAQ,gDAA+C;AAElG,SACEC,4BAA4B,EAC5BC,sBAAsB,EACtBC,4BAA4B,EAC5BC,yBAAyB,EACzBC,gBAAgB,EAChBC,oBAAoB,EACpBC,sBAAsB,EACtBC,aAAa,EACbC,oBAAoB,EACpBC,wBAAwB,QASnB,0BAAsB;AAC7B,SAASC,aAAa,QAAQ,0BAAsB;AACpD,SAASC,cAAc,QAAQ,0BAAsB;AAErD,SACEC,qBAAqB,EACrBC,gCAAgC,EAChCC,0BAA0B,EAC1BC,gBAAgB,EAChBC,iBAAiB,EACjBC,oBAAoB,EACpBC,qBAAqB,QAChB,uCAAmC;AAC1C,SAASC,kBAAkB,QAAQ,2BAAuB;AAC1D,SAASC,sBAAsB,QAA6B,wBAAoB;AAChF,SAASC,eAAe,QAAQ,qBAAiB;AACjD,SAASC,iBAAiB,EAAEC,gBAAgB,QAAQ,yBAAqB;AACzE,SAASC,wBAAwB,EAAEC,kBAAkB,QAAkC,wBAAoB;AAE3G,SAASC,iBAAiB,EAAEC,uBAAuB,QAAQ,eAAW;AAEtE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqCC,GAED,OAAO,MAAMC,2BAA2B,qDAAoD;AAC5F,OAAO,MAAMC,gCAAgC,yDAAwD;AACrG,OAAO,MAAMC,gCAAgC,4DAA2D;AACxG,OAAO,MAAMC,gCAAgC,0DAAyD;AACtG,OAAO,MAAMC,wBAAwB,4CAA2C;AAChF,OAAO,MAAMC,4BAA4B,qDAAoD;AAE7F,4DAA4D,GAC5D,OAAO,MAAMC,yBAAgF;IAC3F,kBACE;IACFC,YAAY;IACZC,OAAO;AACT,EAAC;AAED;;;CAGC,GACD,OAAO,MAAMC,2BAA2B,KAAK;AAE7C,MAAMC,OAAO5B,aAAa,CAACK,iBAAiB;AAE5C;;;;CAIC,GACD,OAAO,MAAMwB,6BAA6BlB,gBAAgB;IACxDmB,MAAMF;IACNG,WAAW3B;IACX4B,SAAS;IACTC,YAAYnB;AACd,GAAE;AAEF,qFAAqF,GACrF,OAAO,MAAMoB,wBAAwBvB,gBAAgB;IACnDmB,MAAMF;IACNG,WAAW7B;IACX8B,SAAS;IACTC,YAAYN;AACd,GAAE;AAEF,8BAA8B,GAC9B,OAAO,MAAMQ,2BAA2BxB,gBAAgB;IACtDmB,MAAMF;IACNG,WAAW5B;IACX6B,SAAS;IACTC,YAAYN;AACd,GAAE;AAEF,2EAA2E,GAC3E,OAAO,MAAMS,8BAA8BP,2BAA0B;AAErE,2EAA2E,GAC3E,OAAO,SAASQ,uBAAuBC,GAA0B;IAC/D,MAAMC,SAASxC,yBAAyBuC,IAAIC,MAAM;IAClD,IAAI,OAAOA,WAAW,UAAU,OAAOV,2BAA2BW,SAAS;IAC3E,IAAID,OAAOE,MAAM,KAAK,WAAW,OAAOP,sBAAsBM,SAAS;IACvE,IAAID,OAAOE,MAAM,KAAK,cAAc,OAAON,yBAAyBK,SAAS;IAC7E,OAAOX,2BAA2BW,SAAS;AAC7C;AAWA,MAAME,MAAM,CAACC,QAA4B,OAAOA,UAAU,WAAWA,MAAMC,IAAI,KAAK;AAEpF,sEAAsE,GACtE,MAAMC,QAAQ,CAAIF,QAChBG,KAAKC,KAAK,CAACD,KAAKE,SAAS,CAACL;AAE5B,kEAAkE,GAClE,eAAeM,UAAUC,SAAoB,EAAEZ,GAAU;QAQ1Ca;IAPb,IAAI,CAACb,IAAIc,MAAM,EAAE,OAAO;QAAEC,SAASnC;IAAyB;IAC5D,MAAMoC,WAAW,MAAMJ,UAAUK,UAAU,CAAC,SAASC,GAAG,CAAClB,IAAIc,MAAM,EAAEK,GAAG;IACxE,MAAMN,OAAOG,SAASI,MAAM,GAAIJ,SAASK,IAAI,KAAiC;IAC9E,wEAAwE;IACxE,wEAAwE;IACxE,oBAAoB;IACpB,IAAI,CAACR,QAAQA,IAAI,CAAC,QAAQ,KAAKb,IAAIsB,KAAK,EAAE,OAAO;QAAEP,SAASlC;IAA8B;IAC1F,MAAM0C,OAAOV,YAAAA,IAAI,CAAC,MAAM,YAAXA,YAAe,CAAC;IAC7B,MAAMW,YAAYpB,IAAIS,IAAI,CAAC,YAAY,KAAK;IAC5C,OAAO;QACLC,QAAQd,IAAIc,MAAM;QAClBU;QACAC,MAAMrB,IAAImB,GAAG,CAAC,QAAQ,KAAKnB,IAAIS,IAAI,CAAC,cAAc,KAAKW,aAAa;IACtE;AACF;AAEA;;;;CAIC,GACD,eAAeE,eACbZ,MAAc,EACda,KAAgC;QAET/E;IAAvB,MAAMgF,QAAQD,gBAAAA,SAAS/E,qBAAAA,kBAAkB,uCAAlBA,mBAAsCgF,KAAK;IAClE,IAAI,CAACA,OAAO,OAAO,EAAE;IACrB,MAAM,EAAEC,OAAO,EAAE,GAAG,MAAMD,MAAME,IAAI,CAAC;QAAEhB;QAAQiB,OAAO/E;IAA6B;IACnF,OAAO6E,QACJG,GAAG,CAAC,CAACC,SAAY,CAAA;YAAEC,IAAID,OAAOC,EAAE;YAAET,MAAMQ,OAAOR,IAAI;QAAC,CAAA,GACpDU,IAAI,CAAC,CAACC,GAAGC,IAAMD,EAAEX,IAAI,CAACa,aAAa,CAACD,EAAEZ,IAAI;AAC/C;AAEA;;;;CAIC,GACD,eAAec,iBAAiBzB,MAAc,EAAE0B,SAAiB;QAC1C5F;IAArB,MAAMqF,SAAS,QAAMrF,qBAAAA,kBAAkB,+BAAlBA,mBAA8BgF,KAAK,CAACT,GAAG,CAAC;QAAEL;QAAQoB,IAAIM;IAAU;IACrF,IAAI,CAACP,QAAQ,OAAO;IACpB,MAAMQ,QAAQR,OAAOQ,KAAK;IAC1B,MAAMC,OAAOD,KAAK,CAAC,OAAO;IAC1B,MAAMX,OAAO,CAACzB,QAA+BsC,MAAMC,OAAO,CAACvC,SAASA,QAAQ,EAAE;IAC9E,OAAO;QACL6B,IAAID,OAAOC,EAAE;QACbT,MAAMQ,OAAOR,IAAI;QACjBiB,MAAM,AAACtF,iBAAwCyF,QAAQ,CAACH,QAASA,OAAyB;QAC1FI,MAAM1C,IAAIqC,KAAK,CAAC,cAAc,EAAEM,KAAK,CAAC,GAAG5F;QACzC6F,MAAMlB,KAAKW,KAAK,CAAC,OAAO,EAAET,GAAG,CAAC5B,KAAK6C,MAAM,CAACC;QAC1CC,aAAarB,KAAKW,KAAK,CAAC,cAAc,EAAET,GAAG,CAAC5B,KAAK6C,MAAM,CAACC;QACxDE,SAAStB,KAAKW,KAAK,CAAC,UAAU,EAC3BM,KAAK,CAAC,GAAG9F,wBACT+E,GAAG,CAAC,CAACqB;YACJ,MAAMpB,SAAUoB,iBAAAA,SAAU,CAAC;YAC3B,OAAO;gBACL5B,MAAMrB,IAAI6B,MAAM,CAAC,OAAO;gBACxBqB,QAAQxB,KAAKG,MAAM,CAAC,SAAS,EAAED,GAAG,CAAC5B,KAAK6C,MAAM,CAACC,SAASH,KAAK,CAAC,GAAG7F;YACnE;QACF,GACC+F,MAAM,CAAC,CAACI,SAAWA,OAAO5B,IAAI;QACjC8B,UAAUnD,IAAIqC,KAAK,CAAC,WAAW,KAAK;QACpCe,UAAUpD,IAAIqC,KAAK,CAAC,WAAW;QAC/BgB,gBAAgBrD,IAAIqC,KAAK,CAAC,iBAAiB;IAC7C;AACF;AAEA,SAASiB,QAAQC,OAAyB;;IACxC,eAAOA,QAAQC,QAAQ,oBAAhBD,QAAQC,QAAQ,MAAhBD,SAAmB5F,oCAAqBJ,eAAeI;AAChE;AAEA,4EAA4E,GAC5E,SAAS8F,OAAOC,IAAe,EAAE5B,EAAU,EAAE6B,KAAa,EAAEC,QAAgB;IAC1E,OAAO;QACLC,UAAU;QACV/B;QACApB,QAAQgD,KAAKhD,MAAM;QACnBoD,eAAeJ,KAAKtC,SAAS;QAC7BuC;QACAC,UAAUzD,MAAMyD;IAClB;AACF;AAmBA,wEAAwE,GACxE,eAAeG,iBACbR,OAAoD,EACpDvE,KAAa,EACb0E,IAAe,EACfM,OAAuB,EACvBC,IAA2B;;IAE3B,MAAM,EAAErE,GAAG,EAAEY,SAAS,EAAE0D,MAAM,EAAE,GAAGX;IACnC,MAAMY,aAAa,MAAM7C,eAAeoC,KAAKhD,MAAM,EAAEuD,KAAKG,aAAa;IACvE,IAAIC,QAA4B;IAChC,IAAIC,QAAQ;IACZ,IAAIN,QAAQb,QAAQ,EAAE;YACfc;QAAL,IAAI,EAACA,oBAAAA,KAAKM,WAAW,YAAhBN,oBAAoBlG,oBAAoBiB,QAAQ;YACnD,MAAMwF,OAAO,MAAMnG,mBACjBmC,WACA;gBAAEP,OAAO+D,QAAQb,QAAQ;gBAAEzC,QAAQgD,KAAKhD,MAAM;gBAAEQ,OAAOtB,IAAIsB,KAAK;YAAC,GACjE+C,KAAKK,KAAK;YAEZD,QAAQG,KAAKC,MAAM;YACnBH,QAAQE,KAAKC,MAAM,KAAK,SAASD,KAAKF,KAAK,GAAG;QAChD,OAAO;YACLD,QAAQ;QACV;IACF;IACA,MAAMK,aAAa,MAAM5G,sBAAsB;QAC7C6G,OAAOjB,KAAKrC,IAAI;QAChB2C;QACAG;QACAE,OAAOvB,QAAQwB;QACfA;QACAtF;QACAK,WAAWF,2BAA2BE,SAAS,CAACL;OAC5CkF,SAAS;QAAEA;IAAO,IAAI,CAAC;IAE7B,MAAMhB,SAASwB,WAAWD,MAAM,KAAK,OAAOC,WAAWzE,KAAK,GAAG;IAC/D,MAAM2E,QAAQ,IAAIC,IAAIV,WAAWvC,GAAG,CAAC,CAACkD,WAAa;YAACA,SAAShD,EAAE;YAAEgD,SAASzD,IAAI;SAAC;IAC/E,MAAM0D,OAAOjG,sBAAsB,CAACuF,MAAM;IAC1C,OAAO;QACLK;QACAd,UAAU;YACRoB,MAAM;YACNhB,SAAS;gBAAElC,IAAIkC,QAAQlC,EAAE;gBAAET,MAAM2C,QAAQ3C,IAAI;YAAC;YAC9C6B;YACAiB,YAAY,SAACjB,0BAAAA,OAAQH,WAAW,mBAAI,EAAE,EAAEnB,GAAG,CAAC,CAACE;oBAAoB8C;uBAAZ;oBAAE9C;oBAAIT,IAAI,GAAEuD,aAAAA,MAAM7D,GAAG,CAACe,eAAV8C,aAAiB9C;gBAAG;;YACrFmD,mBAAmBjB,QAAQhB,OAAO,CAACpB,GAAG,CAAC,CAACqB,SAAWA,OAAO5B,IAAI;YAC9D6D,MAAMhC,SAAS/F,cAAc+F,OAAOiC,WAAW,IAAI,EAAE;YACrDd;YACAe,SAAS;YACTC,OAAON,OAAO;gBAACA;aAAK,GAAG,EAAE;QAC3B;IACF;AACF;AAEA,kEAAkE,GAClE,OAAO,SAASO,wBAAwBrB,OAA8B,CAAC,CAAC;IACtE,OAAO,OAAOV;QACZ,MAAM,EAAE3D,GAAG,EAAEY,SAAS,EAAE0D,MAAM,EAAE,GAAGX;QACnC,MAAMvE,QAAQsE,QAAQC;QACtB,MAAM1D,SAASxC,yBAAyBuC,IAAIC,MAAM;QAClD,IAAI,OAAOA,WAAW,UAAU,OAAO,aAAK1B,iBAAiBa;YAAQ2B,SAASd;;QAC9E,MAAM6D,OAAO,MAAMnD,UAAUC,WAAWZ;QACxC,IAAI,aAAa8D,MAAM,OAAO,aAAKvF,iBAAiBa;YAAQ2B,SAAS+C,KAAK/C,OAAO;;QAEjF,IAAId,OAAOE,MAAM,KAAK,WAAW;YAC/B,MAAM,EAAE2E,UAAU,EAAEd,QAAQ,EAAE,GAAG,MAAMG,iBAAiBR,SAASvE,OAAO0E,MAAM7D,OAAOmE,OAAO,EAAEC;YAC9F,MAAMsB,QAAQrH,kBAAkBwG;YAChC,IAAIA,WAAWD,MAAM,KAAK,WAAW,OAAO,aAAKc;gBAAOC,SAAS;;YACjE,IAAId,WAAWD,MAAM,KAAK,eAAe,OAAO,aAAKc;gBAAO5E,SAAS+D,WAAWe,OAAO;;YACvF,OAAO,aACFF;gBACHG,SAAS;oBAACjC,OAAOC,MAAMxG,uBAAuByI,IAAI,CAAC9F,OAAOmE,OAAO,CAAClC,EAAE,GAAG,CAAC,eAAe,EAAEjC,OAAOmE,OAAO,CAAC3C,IAAI,EAAE,EAAEuC;iBAAU;;QAE9H;QAEA,IAAI/D,OAAOE,MAAM,KAAK,QAAQ;gBACdH;YAAd,MAAMgG,OAAO,EAAChG,eAAAA,IAAI8F,OAAO,YAAX9F,eAAe,EAAE,EAAEiD,MAAM,CAAC,CAACgD;oBAAUzI;uBAAAA,EAAAA,wBAAAA,qBAAqByI,2BAArBzI,sBAA6B4H,IAAI,MAAK;eAAQc,MAAM;YACvG,MAAM1D,YAAYvC,OAAOkG,UAAU,CAACH,KAAK;YACzC,IAAI,CAACxD,WAAW,OAAOjE,iBAAiBa;YACxC,MAAMgH,OAAOJ,OAAO,IAAI/F,OAAOkG,UAAU,CAACD,MAAM;YAChD,MAAM9B,UAAU,MAAM7B,iBAAiBuB,KAAKhD,MAAM,EAAE0B;YACpD,IAAI,CAAC4B,SAAS;gBACZ,MAAMoB,UAAiC;oBACrCJ,MAAM;oBACNhB,SAAS;wBAAElC,IAAIM;wBAAWf,MAAMe;oBAAU;oBAC1Cc,QAAQ;oBACRiB,YAAY,EAAE;oBACdc,mBAAmB,EAAE;oBACrBC,MAAM,EAAE;oBACRb,OAAO;oBACPe,SAASxG;oBACTyG,OAAO,EAAE;gBACX;gBACA,OAAO,aACFlH,iBAAiBa,OAAO;oBACzB0G,SAAS;wBAACjC,OAAOC,MAAMxG,uBAAuByI,IAAI,CAACvD,YAAY,CAAC,eAAe,EAAEA,WAAW,EAAEgD;qBAAS;gBACzG;oBACAa,UAAUD;;YAEd;YACA,MAAM,EAAEtB,UAAU,EAAEd,QAAQ,EAAE,GAAG,MAAMG,iBAAiBR,SAASvE,OAAO0E,MAAMM,SAASC;YACvF,MAAMsB,QAAQrH,kBAAkBwG;YAChC,kEAAkE;YAClE,sEAAsE;YACtE,8CAA8C;YAC9C,MAAMwB,WACJxB,WAAWD,MAAM,KAAK,OAClBb,WACA,aACKA;gBACHwB,SAASV,WAAWD,MAAM,KAAK,gBAAgBC,WAAWe,OAAO,GAAG5G;;YAE5E,OAAO,aACF0G;gBACHG,SAAS;oBAACjC,OAAOC,MAAMxG,uBAAuByI,IAAI,CAACvD,YAAY,CAAC,eAAe,EAAE4B,QAAQ3C,IAAI,EAAE,EAAE6E;iBAAU;gBAC3GD,UAAUD;;QAEd;QAEA,IAAInG,OAAOE,MAAM,KAAK,WAAW;YAC/B,MAAM2E,aAAa,MAAM9G,kBAAkB;gBACzC+G,OAAOjB,KAAKrC,IAAI;gBAChB8E,OAAOvG,IAAIuG,KAAK;gBAChBnH;gBACAK,WAAWG,sBAAsBH,SAAS,CAACL;eACvCkF,SAAS;gBAAEA;YAAO,IAAI,CAAC;YAE7B,MAAMqB,QAAQrH,kBAAkBwG;YAChC,IAAIA,WAAWD,MAAM,KAAK,WAAW,OAAO,aAAKc;gBAAOC,SAAS;;YACjE,IAAId,WAAWD,MAAM,KAAK,eAAe,OAAO,aAAKc;gBAAO5E,SAAS+D,WAAWe,OAAO;;YACvF,MAAM7B,WAA8B;gBAAEoB,MAAM;gBAAWoB,UAAU1B,WAAWzE,KAAK;gBAAEoF,OAAO,EAAE;YAAC;YAC7F,OAAO,aACFE;gBACHG,SAAS;oBACPjC,OACEC,MACAxG,uBAAuBmJ,OAAO,EAC9B,CAAC,oBAAoB,EAAE3B,WAAWzE,KAAK,CAAC6F,MAAM,EAAE,EAChDlC;iBAEH;;QAEL;QAEA,MAAM0C,WAAW,MAAMhF,eAAeoC,KAAKhD,MAAM,EAAEuD,KAAKG,aAAa;QACrE,MAAMM,aAAa,MAAM7G,qBAAqB;YAC5C8G,OAAOjB,KAAKrC,IAAI;YAChB8E,OAAOvG,IAAIuG,KAAK;YAChBI,uBAAuBD,SAAS1E,GAAG,CAAC,CAACkD,WAAaA,SAASzD,IAAI;YAC/DrC;YACAK,WAAWI,yBAAyBJ,SAAS,CAACL;WAC1CkF,SAAS;YAAEA;QAAO,IAAI,CAAC;QAE7B,MAAMqB,QAAQrH,kBAAkBwG;QAChC,IAAIA,WAAWD,MAAM,KAAK,WAAW,OAAO,aAAKc;YAAOC,SAAS;;QACjE,IAAId,WAAWD,MAAM,KAAK,eAAe,OAAO,aAAKc;YAAO5E,SAAS+D,WAAWe,OAAO;;QACvF,MAAM7B,WAAiC;YAAEoB,MAAM;WAAiBN,WAAWzE,KAAK;YAAEoF,OAAO,EAAE;;QAC3F,OAAO,aACFE;YACHG,SAAS;gBAACjC,OAAOC,MAAMxG,uBAAuBiH,UAAU,EAAE,qCAAqCP;aAAU;;IAE7G;AACF;AAEA,OAAO,MAAM4C,uBAAuBlB,0BAAyB;AAE7D;;;;;CAKC,GACD,OAAO,MAAMmB,yBAAyC,OAAOlD;QAY9C;QAFE/G;IATf,MAAMqD,SAASxC,yBAAyBkG,QAAQ1D,MAAM;IACtD,IAAI,OAAOA,WAAW,UAAU,OAAO;QAAE4E,QAAQ;QAAKiC,OAAO7G;IAAO;IACpE,IAAI,CAAC0D,QAAQ7C,MAAM,EAAE,OAAO;QAAE+D,QAAQ;QAAKiC,OAAOlI;IAAyB;IAC3E,MAAMmI,QAAQ,MAAMjK,oBAAoB6G,QAAQ7C,MAAM;IACtD,IAAI,CAACiG,SAASA,UAAUpD,QAAQrC,KAAK,EAAE,OAAO;QAAEuD,QAAQ;QAAKiC,OAAOjI;IAA8B;IAClG,MAAMmI,MAAMrD,QAAQqD,GAAG;IACvB,IAAI,CAACrK,iBAAiBqK,KAAK,aAAa,OAAO;QAAEnC,QAAQ;QAAKiC,OAAOhI;IAA8B;IACnG,oEAAoE;IACpE,4DAA4D;IAC5D,MAAMmI,UAASrK,qBAAAA,kBAAkB,+BAAlBA,mBAA8BsK,QAAQ;IACrD,IAAI,CAACD,QAAQ,OAAO;QAAEpC,QAAQ;QAAKiC,OAAO/H;IAA8B;IACxE,MAAM8B,QAAO,QAAA,AAAC,CAAA,MAAM8C,QAAQ/C,SAAS,CAACK,UAAU,CAAC,SAASC,GAAG,CAACyC,QAAQ7C,MAAM,EAAEK,GAAG,EAAC,EAAGE,IAAI,cAA5E,QAAkF;IAC/F,MAAM8F,WAAW,MAAMpK,mCAAmC;QAACkK;KAAO,EAAE;QAClE3F,OAAOqC,QAAQrC,KAAK;QACpB8F,eAAe;IACjB;IACA,IAAI,CAACD,SAAStE,QAAQ,CAACoE,WAAW,CAACpK,oBAAoBmK,KAAKnG,MAAMoG,SAAS;QACzE,OAAO;YAAEpC,QAAQ;YAAKiC,OAAO/H;QAA8B;IAC7D;IACA,OAAO;AACT,EAAC;AAED;;;CAGC,GACD,OAAO,SAASsI;IACd3I,kBAAkB,YAAYkI,sBAAsB;QAClD1G,WAAWJ,4BAA4BI,SAAS;QAChDoH,cAAcvH;IAChB;IACApB,wBAAwB,YAAYtB;IACpCe,uBAAuB,YAAYyI;AACrC"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-products-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport {\n pluginRecordIndex,\n type PluginRecordIndex,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-index'\nimport { isHostPluginEnabled } from '@aglyn/aglyn/plugin-manager/enabled-plugins'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport { filterEnabledPluginsByReleaseFlags } from '@aglyn/tenant-data-admin/server/release-flags'\nimport type { AiJob, AiJobOutput } from '../model/ai-jobs.types'\nimport {\n AI_PRODUCT_CATEGORY_LIST_MAX,\n AI_PRODUCT_OPTIONS_MAX,\n AI_PRODUCT_OPTION_VALUES_MAX,\n AI_PRODUCT_TEXT_MAX_CHARS,\n AI_PRODUCT_TYPES,\n AI_PRODUCTS_BULK_MAX,\n AI_PRODUCTS_OUTPUT_IDS,\n aiProductGaps,\n aiProductsProposalOf,\n parseAiProductsJobInputs,\n type AiCatalogProposal,\n type AiCategoriesProposal,\n type AiProductCategory,\n type AiProductCopy,\n type AiProductCopyProposal,\n type AiProductFacts,\n type AiProductPhotoRead,\n type AiProductType,\n} from '../model/ai-products'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { aiModelForStep } from '../providers/routing'\nimport type { AiValidatedGeneration } from '../runtime/ai-doctrine'\nimport {\n AI_CATALOG_MAX_TOKENS,\n AI_PRODUCT_CATEGORIES_MAX_TOKENS,\n AI_PRODUCT_COPY_MAX_TOKENS,\n AI_PRODUCTS_STEP,\n aiCatalogProductsNamed,\n generateAiCatalog,\n generateAiCategories,\n generateAiProductCopy,\n} from '../runtime/ai-products-generation'\nimport { aiRouteReadsImages } from '../runtime/ai-runtime'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiGenerationSpent, aiUnspentOutcome } from './ai-job-generation'\nimport { AI_PRODUCT_IMAGE_READ_MS, readAiProductImage, type AiProductImageSeams } from './ai-product-image'\nimport type { AiJobStepContext, AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep, registerAiJobStepPasses } from './ai-jobs'\n\n/**\n * The `products` step (AGL-2916): commerce by AI, as proposals.\n *\n * `inputs.target` names the work (`model/ai-products.ts`): one product's copy\n * from what its editor holds, the copy of many saved products one a pass, a\n * store's first products from a brief, or its categories and a first set of\n * discounts from a brief.\n *\n * ## It writes nothing\n *\n * Like every runner, this reads, asks and returns. Every output is a\n * `product` proposal, and the commerce plugin's own surfaces write what a\n * person accepts: the product editor's Save, the products card's apply, and\n * the create paths the catalog and discount cards already use — each with the\n * allowance, role and seed checks those paths carry. A proposed product is\n * created as a draft at its suggested price (the store's default where it has\n * none, Zach 2026-10-12), and a proposed discount is created switched off.\n *\n * ## A bulk job, a product at a time\n *\n * A bulk job's pass writes one product's copy and asks the machine to\n * continue while products remain, so each product is one reservation and one\n * provider exchange, and a job that runs out of credits resumes at the\n * product where it stopped. Which product is next is read from the job's own\n * outputs, one a product, so nothing is kept anywhere else. A product that is\n * gone, or whose copy cannot be held to the rules, is reported and passed\n * over rather than failing the products after it.\n *\n * ## What the model is shown\n *\n * The product's name, type, text, tags, options and current search listing,\n * the names of the site's categories, the store's name, and — for a model that\n * reads pictures — the product's first photo, read from the site's own media\n * library as `ai-product-image.ts` describes. A catalog or categories request\n * carries the brief and the store's name, and the second its existing category\n * names. No price, stock, order, customer or other product is read for any of\n * them.\n */\n\nexport const AI_PRODUCTS_NO_SITE_COPY = 'Open the store this job is for before starting it.'\nexport const AI_PRODUCTS_UNKNOWN_SITE_COPY = 'This AI job names a site this workspace does not have.'\nexport const AI_PRODUCTS_NOT_ENTITLED_COPY = 'Your plan does not include selling products. See Billing.'\nexport const AI_PRODUCTS_COMMERCE_OFF_COPY = 'Turn on Commerce for this site before starting the job.'\nexport const AI_PRODUCTS_GONE_COPY = 'This product is no longer in the catalog.'\nexport const AI_PRODUCTS_DECLINED_COPY = 'The model declined to write copy for this product.'\n\n/** The notes a proposal carries about its product's photo. */\nexport const AI_PRODUCT_PHOTO_NOTES: Readonly<Partial<Record<AiProductPhotoRead, string>>> = {\n 'not-in-library':\n 'The photo was not read: it is not in this site’s media library. The copy is written from the product’s words.',\n unreadable: 'The photo could not be read, so the copy is written from the product’s words.',\n model: 'The photo was not read: the model this job ran on does not read pictures.',\n}\n\n/**\n * ASSUMED: how long a catalog or categories pass reads the site before it\n * asks: the host document and, for categories, the category names.\n */\nexport const AI_PRODUCTS_SITE_READ_MS = 1_000\n\nconst TIER = AI_STEP_TIERS[AI_PRODUCTS_STEP]\n\n/**\n * One product's copy (AGL-3035): its generation and re-ask at the copy's own\n * ceiling on the tier `job.products` is served from, with the photo read. The\n * least any pass of the step needs, and so what the step registers.\n */\nexport const AI_JOB_PRODUCT_COPY_BUDGET = aiJobStepBudget({\n tier: TIER,\n maxTokens: AI_PRODUCT_COPY_MAX_TOKENS,\n lookups: 0,\n ownReadsMs: AI_PRODUCT_IMAGE_READ_MS,\n})\n\n/** A catalog: the largest answer the step asks for, at the routing table's ceiling. */\nexport const AI_JOB_CATALOG_BUDGET = aiJobStepBudget({\n tier: TIER,\n maxTokens: AI_CATALOG_MAX_TOKENS,\n lookups: 0,\n ownReadsMs: AI_PRODUCTS_SITE_READ_MS,\n})\n\n/** Categories and discounts. */\nexport const AI_JOB_CATEGORIES_BUDGET = aiJobStepBudget({\n tier: TIER,\n maxTokens: AI_PRODUCT_CATEGORIES_MAX_TOKENS,\n lookups: 0,\n ownReadsMs: AI_PRODUCTS_SITE_READ_MS,\n})\n\n/** What the `products` step registers: the least any of its passes needs. */\nexport const AI_JOB_PRODUCTS_STEP_BUDGET = AI_JOB_PRODUCT_COPY_BUDGET\n\n/** The least time the next pass of a job needs, by what the job asks for. */\nexport function aiProductsRunMinimumMs(job: Pick<AiJob, 'inputs'>): number {\n const inputs = parseAiProductsJobInputs(job.inputs)\n if (typeof inputs === 'string') return AI_JOB_PRODUCT_COPY_BUDGET.minimumMs\n if (inputs.target === 'catalog') return AI_JOB_CATALOG_BUDGET.minimumMs\n if (inputs.target === 'categories') return AI_JOB_CATEGORIES_BUDGET.minimumMs\n return AI_JOB_PRODUCT_COPY_BUDGET.minimumMs\n}\n\ntype Firestore = FirebaseFirestore.Firestore\n\ninterface StoreSite {\n hostId: string\n subdomain: string | null\n /** The store's name, as its visitors read it. */\n name: string\n}\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** Through JSON, so no `undefined` reaches the job document's write. */\nconst plain = <T>(value: T): Record<string, unknown> =>\n JSON.parse(JSON.stringify(value)) as Record<string, unknown>\n\n/** The site a job names, read and checked against the job's org. */\nasync function loadStore(firestore: Firestore, job: AiJob): Promise<StoreSite | { failure: string }> {\n if (!job.hostId) return { failure: AI_PRODUCTS_NO_SITE_COPY }\n const snapshot = await firestore.collection('hosts').doc(job.hostId).get()\n const host = snapshot.exists ? (snapshot.data() as Record<string, unknown>) : null\n // The door checks the caller's membership of the org the job is metered\n // against; a site of another org is refused here, before anything of it\n // reaches a prompt.\n if (!host || host['orgId'] !== job.orgId) return { failure: AI_PRODUCTS_UNKNOWN_SITE_COPY }\n const seo = (host['seo'] ?? {}) as Record<string, unknown>\n const subdomain = str(host['subdomain']) || null\n return {\n hostId: job.hostId,\n subdomain,\n name: str(seo['title']) || str(host['displayName']) || subdomain || '',\n }\n}\n\n/**\n * The site's categories by name, as many as a request lists — from the\n * `productCategory` index the plugin that keeps them publishes (AGL-3080).\n * None when no plugin keeps categories here.\n */\nasync function readCategories(\n hostId: string,\n given?: PluginRecordIndex | null,\n): Promise<AiProductCategory[]> {\n const index = given ?? pluginRecordIndex('productCategory')?.index\n if (!index) return []\n const { records } = await index.list({ hostId, limit: AI_PRODUCT_CATEGORY_LIST_MAX })\n return records\n .map((record) => ({ id: record.id, name: record.name }))\n .sort((a, b) => a.name.localeCompare(b.name))\n}\n\n/**\n * A saved product's facts, as a bulk pass reads them; `null` when it is gone\n * — read through the `product` index the plugin that keeps products publishes\n * (AGL-3080), which answers live, named products only.\n */\nasync function readProductFacts(hostId: string, productId: string): Promise<AiProductFacts | null> {\n const record = await pluginRecordIndex('product')?.index.get({ hostId, id: productId })\n if (!record) return null\n const facts = record.facts\n const type = facts['type']\n const list = (value: unknown): unknown[] => (Array.isArray(value) ? value : [])\n return {\n id: record.id,\n name: record.name,\n type: (AI_PRODUCT_TYPES as readonly unknown[]).includes(type) ? (type as AiProductType) : 'physical',\n text: str(facts['description']).slice(0, AI_PRODUCT_TEXT_MAX_CHARS),\n tags: list(facts['tags']).map(str).filter(Boolean),\n categoryIds: list(facts['categoryIds']).map(str).filter(Boolean),\n options: list(facts['options'])\n .slice(0, AI_PRODUCT_OPTIONS_MAX)\n .map((option) => {\n const record = (option ?? {}) as Record<string, unknown>\n return {\n name: str(record['name']),\n values: list(record['values']).map(str).filter(Boolean).slice(0, AI_PRODUCT_OPTION_VALUES_MAX),\n }\n })\n .filter((option) => option.name),\n imageUrl: str(facts['imageUrl']) || null,\n seoTitle: str(facts['seoTitle']),\n seoDescription: str(facts['seoDescription']),\n }\n}\n\nfunction modelOf(context: AiJobStepContext): string {\n return context.modelFor?.(AI_PRODUCTS_STEP) ?? aiModelForStep(AI_PRODUCTS_STEP)\n}\n\n/** An output addressed to the site, so its link opens the site's products. */\nfunction output(site: StoreSite, id: string, label: string, proposal: object): AiJobOutput {\n return {\n resource: 'product',\n id,\n hostId: site.hostId,\n hostSubdomain: site.subdomain,\n label,\n proposal: plain(proposal),\n }\n}\n\nexport interface AiJobProductsStepDeps {\n image?: AiProductImageSeams\n /** Whether a model reads pictures; the provider's own descriptor otherwise. */\n readsImages?: (model: string) => boolean\n /**\n * Where the site's categories are read; the `productCategory` index the\n * plugin that keeps them publishes otherwise. The eval harness hands its\n * own, over the categories a case names.\n */\n categoryIndex?: PluginRecordIndex | null\n}\n\ninterface CopyRun {\n generation: AiValidatedGeneration<AiProductCopy>\n proposal: AiProductCopyProposal\n}\n\n/** Ask for one product's copy, and build its proposal from the answer. */\nasync function writeProductCopy(\n context: AiJobStepContext & { firestore: Firestore },\n model: string,\n site: StoreSite,\n product: AiProductFacts,\n deps: AiJobProductsStepDeps,\n): Promise<CopyRun> {\n const { job, firestore, signal } = context\n const categories = await readCategories(site.hostId, deps.categoryIndex)\n let photo: AiProductPhotoRead = 'none'\n let image = null\n if (product.imageUrl) {\n if ((deps.readsImages ?? aiRouteReadsImages)(model)) {\n const read = await readAiProductImage(\n firestore,\n { value: product.imageUrl, hostId: site.hostId, orgId: job.orgId },\n deps.image,\n )\n photo = read.status\n image = read.status === 'read' ? read.image : null\n } else {\n photo = 'model'\n }\n }\n const generation = await generateAiProductCopy({\n store: site.name,\n product,\n categories,\n photo: Boolean(image),\n image,\n model,\n maxTokens: AI_JOB_PRODUCT_COPY_BUDGET.maxTokens(model),\n ...(signal ? { signal } : {}),\n })\n const values = generation.status === 'ok' ? generation.value : null\n const names = new Map(categories.map((category) => [category.id, category.name]))\n const note = AI_PRODUCT_PHOTO_NOTES[photo]\n return {\n generation,\n proposal: {\n kind: 'copy',\n product: { id: product.id, name: product.name },\n values,\n categories: (values?.categoryIds ?? []).map((id) => ({ id, name: names.get(id) ?? id })),\n optionNamesBefore: product.options.map((option) => option.name),\n gaps: values ? aiProductGaps(values.description) : [],\n photo,\n skipped: null,\n notes: note ? [note] : [],\n },\n }\n}\n\n/** The step, with its reads replaceable where a spec needs them. */\nexport function createAiJobProductsStep(deps: AiJobProductsStepDeps = {}): AiJobStepRunner {\n return async (context) => {\n const { job, firestore, signal } = context\n const model = modelOf(context)\n const inputs = parseAiProductsJobInputs(job.inputs)\n if (typeof inputs === 'string') return { ...aiUnspentOutcome(model), failure: inputs }\n const site = await loadStore(firestore, job)\n if ('failure' in site) return { ...aiUnspentOutcome(model), failure: site.failure }\n\n if (inputs.target === 'product') {\n const { generation, proposal } = await writeProductCopy(context, model, site, inputs.product, deps)\n const spent = aiGenerationSpent(generation)\n if (generation.status === 'refused') return { ...spent, refused: true }\n if (generation.status === 'needs_input') return { ...spent, failure: generation.message }\n return {\n ...spent,\n outputs: [output(site, AI_PRODUCTS_OUTPUT_IDS.copy(inputs.product.id), `Product copy · ${inputs.product.name}`, proposal)],\n }\n }\n\n if (inputs.target === 'bulk') {\n const done = (job.outputs ?? []).filter((entry) => aiProductsProposalOf(entry)?.kind === 'copy').length\n const productId = inputs.productIds[done]\n if (!productId) return aiUnspentOutcome(model)\n const more = done + 1 < inputs.productIds.length\n const product = await readProductFacts(site.hostId, productId)\n if (!product) {\n const skipped: AiProductCopyProposal = {\n kind: 'copy',\n product: { id: productId, name: productId },\n values: null,\n categories: [],\n optionNamesBefore: [],\n gaps: [],\n photo: 'none',\n skipped: AI_PRODUCTS_GONE_COPY,\n notes: [],\n }\n return {\n ...aiUnspentOutcome(model, {\n outputs: [output(site, AI_PRODUCTS_OUTPUT_IDS.copy(productId), `Product copy · ${productId}`, skipped)],\n }),\n continue: more,\n }\n }\n const { generation, proposal } = await writeProductCopy(context, model, site, product, deps)\n const spent = aiGenerationSpent(generation)\n // One product the rules could not hold, or the model declined, is\n // reported and passed over: its tokens are metered with the pass, and\n // the products after it still get their copy.\n const reported: AiProductCopyProposal =\n generation.status === 'ok'\n ? proposal\n : {\n ...proposal,\n skipped: generation.status === 'needs_input' ? generation.message : AI_PRODUCTS_DECLINED_COPY,\n }\n return {\n ...spent,\n outputs: [output(site, AI_PRODUCTS_OUTPUT_IDS.copy(productId), `Product copy · ${product.name}`, reported)],\n continue: more,\n }\n }\n\n if (inputs.target === 'catalog') {\n const generation = await generateAiCatalog({\n store: site.name,\n brief: job.brief,\n model,\n maxTokens: AI_JOB_CATALOG_BUDGET.maxTokens(model),\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(generation)\n if (generation.status === 'refused') return { ...spent, refused: true }\n if (generation.status === 'needs_input') return { ...spent, failure: generation.message }\n // A product its re-ask still named for a kind, a make or a model is left out (AGL-4065).\n const products = aiCatalogProductsNamed(generation.value, job.brief)\n const proposal: AiCatalogProposal = { kind: 'catalog', products, notes: [] }\n return {\n ...spent,\n outputs: [\n output(\n site,\n AI_PRODUCTS_OUTPUT_IDS.catalog,\n `Proposed products · ${products.length}`,\n proposal,\n ),\n ],\n }\n }\n\n const existing = await readCategories(site.hostId, deps.categoryIndex)\n const generation = await generateAiCategories({\n store: site.name,\n brief: job.brief,\n existingCategoryNames: existing.map((category) => category.name),\n model,\n maxTokens: AI_JOB_CATEGORIES_BUDGET.maxTokens(model),\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(generation)\n if (generation.status === 'refused') return { ...spent, refused: true }\n if (generation.status === 'needs_input') return { ...spent, failure: generation.message }\n const proposal: AiCategoriesProposal = { kind: 'categories', ...generation.value, notes: [] }\n return {\n ...spent,\n outputs: [output(site, AI_PRODUCTS_OUTPUT_IDS.categories, 'Proposed categories and discounts', proposal)],\n }\n }\n}\n\nexport const runAiJobProductsStep = createAiJobProductsStep()\n\n/**\n * Whether a `products` job may start: inputs it can run, a site of the job's\n * own org, a plan that sells products, and Commerce on for the site and past\n * its release flag. Asked by the create door before the job exists, so a\n * refusal spends nothing.\n */\nexport const aiProductsJobAdmission: AiJobAdmission = async (context) => {\n const inputs = parseAiProductsJobInputs(context.inputs)\n if (typeof inputs === 'string') return { status: 400, error: inputs }\n if (!context.hostId) return { status: 400, error: AI_PRODUCTS_NO_SITE_COPY }\n const owner = await resolveOrgIdForHost(context.hostId)\n if (!owner || owner !== context.orgId) return { status: 404, error: AI_PRODUCTS_UNKNOWN_SITE_COPY }\n const org = context.org as (Partial<AglynOrgBilling> & { enabledPlugins?: string[] }) | null\n if (!checkEntitlement(org, 'commerce')) return { status: 403, error: AI_PRODUCTS_NOT_ENTITLED_COPY }\n // The plugin that keeps products is whichever publishes their index\n // (AGL-3080); none is the same refusal as one switched off.\n const keeper = pluginRecordIndex('product')?.pluginId\n if (!keeper) return { status: 403, error: AI_PRODUCTS_COMMERCE_OFF_COPY }\n const host = (await context.firestore.collection('hosts').doc(context.hostId).get()).data() ?? null\n const released = await filterEnabledPluginsByReleaseFlags([keeper], {\n orgId: context.orgId,\n authorization: null,\n })\n if (!released.includes(keeper) || !isHostPluginEnabled(org, host, keeper)) {\n return { status: 403, error: AI_PRODUCTS_COMMERCE_OFF_COPY }\n }\n return null\n}\n\n/**\n * Registers the `products` step, the least time each of its passes needs, its\n * pass bound and its admission.\n */\nexport function registerAiProductsJob(): void {\n registerAiJobStep('products', runAiJobProductsStep, {\n minimumMs: AI_JOB_PRODUCTS_STEP_BUDGET.minimumMs,\n minimumMsFor: aiProductsRunMinimumMs,\n })\n registerAiJobStepPasses('products', AI_PRODUCTS_BULK_MAX)\n registerAiJobAdmission('products', aiProductsJobAdmission)\n}\n"],"names":["checkEntitlement","pluginRecordIndex","isHostPluginEnabled","resolveOrgIdForHost","filterEnabledPluginsByReleaseFlags","AI_PRODUCT_CATEGORY_LIST_MAX","AI_PRODUCT_OPTIONS_MAX","AI_PRODUCT_OPTION_VALUES_MAX","AI_PRODUCT_TEXT_MAX_CHARS","AI_PRODUCT_TYPES","AI_PRODUCTS_BULK_MAX","AI_PRODUCTS_OUTPUT_IDS","aiProductGaps","aiProductsProposalOf","parseAiProductsJobInputs","AI_STEP_TIERS","aiModelForStep","AI_CATALOG_MAX_TOKENS","AI_PRODUCT_CATEGORIES_MAX_TOKENS","AI_PRODUCT_COPY_MAX_TOKENS","AI_PRODUCTS_STEP","aiCatalogProductsNamed","generateAiCatalog","generateAiCategories","generateAiProductCopy","aiRouteReadsImages","registerAiJobAdmission","aiJobStepBudget","aiGenerationSpent","aiUnspentOutcome","AI_PRODUCT_IMAGE_READ_MS","readAiProductImage","registerAiJobStep","registerAiJobStepPasses","AI_PRODUCTS_NO_SITE_COPY","AI_PRODUCTS_UNKNOWN_SITE_COPY","AI_PRODUCTS_NOT_ENTITLED_COPY","AI_PRODUCTS_COMMERCE_OFF_COPY","AI_PRODUCTS_GONE_COPY","AI_PRODUCTS_DECLINED_COPY","AI_PRODUCT_PHOTO_NOTES","unreadable","model","AI_PRODUCTS_SITE_READ_MS","TIER","AI_JOB_PRODUCT_COPY_BUDGET","tier","maxTokens","lookups","ownReadsMs","AI_JOB_CATALOG_BUDGET","AI_JOB_CATEGORIES_BUDGET","AI_JOB_PRODUCTS_STEP_BUDGET","aiProductsRunMinimumMs","job","inputs","minimumMs","target","str","value","trim","plain","JSON","parse","stringify","loadStore","firestore","host","hostId","failure","snapshot","collection","doc","get","exists","data","orgId","seo","subdomain","name","readCategories","given","index","records","list","limit","map","record","id","sort","a","b","localeCompare","readProductFacts","productId","facts","type","Array","isArray","includes","text","slice","tags","filter","Boolean","categoryIds","options","option","values","imageUrl","seoTitle","seoDescription","modelOf","context","modelFor","output","site","label","proposal","resource","hostSubdomain","writeProductCopy","product","deps","signal","categories","categoryIndex","photo","image","readsImages","read","status","generation","store","names","Map","category","note","kind","optionNamesBefore","gaps","description","skipped","notes","createAiJobProductsStep","spent","refused","message","outputs","copy","done","entry","length","productIds","more","continue","reported","brief","products","catalog","existing","existingCategoryNames","runAiJobProductsStep","aiProductsJobAdmission","error","owner","org","keeper","pluginId","released","authorization","registerAiProductsJob","minimumMsFor"],"mappings":"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"}
|
|
@@ -82,7 +82,8 @@ export declare const AI_SETTINGS_KIND: "settings";
|
|
|
82
82
|
*/
|
|
83
83
|
export declare const AI_JOB_SETTINGS_STEP_MINIMUM_MS = 5000;
|
|
84
84
|
export declare const AI_SETTINGS_NO_SITE_COPY = "Open the site whose settings should change.";
|
|
85
|
-
|
|
85
|
+
/** Names "Edit site content" (`site.edit`, AGL-4047), never a role. */
|
|
86
|
+
export declare const AI_SETTINGS_ROLE_COPY: string;
|
|
86
87
|
export declare const AI_SETTINGS_LOCKED_COPY = "This site is not accepting changes right now.";
|
|
87
88
|
export declare const AI_SETTINGS_NOTHING_COPY = "The request named no site setting this could change.";
|
|
88
89
|
/** Where a person finds what the op changed, as the build's notes name it. */
|
|
@@ -15,10 +15,11 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/ import { supportedTimeZones } from "@aglyn/aglyn/app-utils/collection-entry-date";
|
|
18
|
+
import { holdsSiteKey } from "@aglyn/aglyn/app-utils/host-permissions";
|
|
19
|
+
import { orgPermissionLabel } from "@aglyn/aglyn/app-utils/org-permissions";
|
|
18
20
|
import { createResourceUid } from "@aglyn/aglyn/app-utils/create-resource-uid";
|
|
19
21
|
import { inheritedMediaAlt } from "@aglyn/aglyn/app-utils/media-metadata";
|
|
20
22
|
import { mediaNodeSrc } from "@aglyn/aglyn/app-utils/media-ref";
|
|
21
|
-
import { hostRoleCanWrite } from "@aglyn/aglyn/app-utils/organizations";
|
|
22
23
|
import { visibleToHost } from "@aglyn/aglyn/app-utils/scope-tokens";
|
|
23
24
|
import { SCREEN_ROOT_PATH } from "@aglyn/aglyn/app-utils/screen-route";
|
|
24
25
|
import { siteLanguageCode } from "@aglyn/aglyn/app-utils/seo-locale";
|
|
@@ -90,7 +91,7 @@ import { registerAiJobStep } from "./ai-jobs.js";
|
|
|
90
91
|
* model, so it fits an inline door.
|
|
91
92
|
*/ export const AI_JOB_SETTINGS_STEP_MINIMUM_MS = 5000;
|
|
92
93
|
export const AI_SETTINGS_NO_SITE_COPY = 'Open the site whose settings should change.';
|
|
93
|
-
export const AI_SETTINGS_ROLE_COPY =
|
|
94
|
+
/** Names "Edit site content" (`site.edit`, AGL-4047), never a role. */ export const AI_SETTINGS_ROLE_COPY = `Changing the site’s settings needs the "${orgPermissionLabel('site.edit')}" permission on this site — ask an organization admin.`;
|
|
94
95
|
export const AI_SETTINGS_LOCKED_COPY = 'This site is not accepting changes right now.';
|
|
95
96
|
export const AI_SETTINGS_NOTHING_COPY = 'The request named no site setting this could change.';
|
|
96
97
|
/** Where a person finds what the op changed, as the build's notes name it. */ export const AI_SETTINGS_WHERE = 'the site’s Settings';
|
|
@@ -454,14 +455,13 @@ const sameValue = (a, b)=>JSON.stringify(a != null ? a : null) === JSON.stringif
|
|
|
454
455
|
*/ export async function writeAiSiteSettings(firestore, input) {
|
|
455
456
|
const hostRef = firestore.collection('hosts').doc(input.hostId);
|
|
456
457
|
return firestore.runTransaction(async (tx)=>{
|
|
457
|
-
var
|
|
458
|
+
var _host_data;
|
|
458
459
|
const host = await tx.get(hostRef);
|
|
459
460
|
if (!host.exists) return {
|
|
460
461
|
ok: false,
|
|
461
462
|
error: 'Unknown site'
|
|
462
463
|
};
|
|
463
|
-
|
|
464
|
-
if (!hostRoleCanWrite(roles[input.uid])) return {
|
|
464
|
+
if (!holdsSiteKey(host, input.uid, 'site.edit')) return {
|
|
465
465
|
ok: false,
|
|
466
466
|
error: AI_SETTINGS_ROLE_COPY
|
|
467
467
|
};
|
|
@@ -506,10 +506,9 @@ const sameValue = (a, b)=>JSON.stringify(a != null ? a : null) === JSON.stringif
|
|
|
506
506
|
}
|
|
507
507
|
// ── The step ──────────────────────────────────────────────────────────────
|
|
508
508
|
/** Why this member may not change this site's settings now, or `null`. */ export async function aiSettingsAccessRefusal(input, deps = {}) {
|
|
509
|
-
var
|
|
509
|
+
var _deps_lockdown;
|
|
510
510
|
if (!input.host.exists) return 'Unknown site';
|
|
511
|
-
|
|
512
|
-
if (!hostRoleCanWrite(roles[input.uid])) return AI_SETTINGS_ROLE_COPY;
|
|
511
|
+
if (!holdsSiteKey(input.host, input.uid, 'site.edit')) return AI_SETTINGS_ROLE_COPY;
|
|
513
512
|
const locked = await ((_deps_lockdown = deps.lockdown) != null ? _deps_lockdown : getLockdownVerdict)({
|
|
514
513
|
staff: false,
|
|
515
514
|
uid: input.uid,
|