@aglyn/plugins-ai 1.0.0-beta.246 → 1.0.0-beta.249
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/activity/ai-activity-actions.js +5 -1
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-job-links.js +14 -0
- package/src/lib/components/ai-job-links.js.map +1 -1
- package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
- package/src/lib/components/ai-site-start-card.component.js +92 -19
- package/src/lib/components/ai-site-start-card.component.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.js +56 -2
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.d.ts +3 -8
- package/src/lib/jobs/ai-job-build-step.js +49 -12
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-form-dataset.d.ts +8 -5
- package/src/lib/jobs/ai-job-form-dataset.js +28 -10
- package/src/lib/jobs/ai-job-form-dataset.js.map +1 -1
- package/src/lib/jobs/ai-job-form-step.d.ts +20 -4
- package/src/lib/jobs/ai-job-form-step.js +98 -28
- package/src/lib/jobs/ai-job-form-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +6 -2
- package/src/lib/jobs/ai-job-layout-language.js +16 -5
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +4 -1
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-media-step.d.ts +239 -0
- package/src/lib/jobs/ai-job-media-step.js +610 -0
- package/src/lib/jobs/ai-job-media-step.js.map +1 -0
- package/src/lib/jobs/ai-job-page-language.d.ts +2 -0
- package/src/lib/jobs/ai-job-page-language.js +9 -0
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.js +4 -3
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +3 -2
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +10 -1
- package/src/lib/jobs/ai-job-plan-step.js +73 -15
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-settings-step.d.ts +0 -4
- package/src/lib/jobs/ai-job-settings-step.js +2 -7
- package/src/lib/jobs/ai-job-settings-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +2 -2
- package/src/lib/jobs/ai-job-site-content.js +5 -3
- package/src/lib/jobs/ai-job-site-content.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +1 -1
- package/src/lib/jobs/ai-job-site-datasets.js +3 -2
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-job-site-events.d.ts +148 -0
- package/src/lib/jobs/ai-job-site-events.js +313 -0
- package/src/lib/jobs/ai-job-site-events.js.map +1 -0
- package/src/lib/jobs/ai-job-site-step.d.ts +9 -3
- package/src/lib/jobs/ai-job-site-step.js +63 -17
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-tracking-step.d.ts +184 -0
- package/src/lib/jobs/ai-job-tracking-step.js +501 -0
- package/src/lib/jobs/ai-job-tracking-step.js.map +1 -0
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +41 -0
- package/src/lib/jobs/ai-layout-stock-photos.js +71 -3
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/ai-site-publish.js +2 -3
- package/src/lib/jobs/ai-site-publish.js.map +1 -1
- package/src/lib/jobs/ai-stock-photo-picker.d.ts +24 -0
- package/src/lib/jobs/ai-stock-photo-picker.js +108 -0
- package/src/lib/jobs/ai-stock-photo-picker.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-jazz-events-build-plan-recording.d.ts +37 -0
- package/src/lib/jobs/fixtures/ai-jazz-events-build-plan-recording.js +47 -0
- package/src/lib/jobs/fixtures/ai-jazz-events-build-plan-recording.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3760-recordings.d.ts +21 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3760-recordings.js +128 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3760-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3769-recordings.d.ts +21 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3769-recordings.js +126 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3769-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.d.ts +10 -4
- package/src/lib/layout-language/ai-layout-compiler.js +38 -19
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame.d.ts +7 -5
- package/src/lib/layout-language/ai-layout-frame.js +12 -9
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-gaps.js +3 -1
- package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
- package/src/lib/layout-language/ai-layout-labels.d.ts +70 -0
- package/src/lib/layout-language/ai-layout-labels.js +162 -0
- package/src/lib/layout-language/ai-layout-labels.js.map +1 -0
- package/src/lib/layout-language/ai-layout-links.js +5 -2
- package/src/lib/layout-language/ai-layout-links.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +14 -1
- package/src/lib/layout-language/ai-layout-listings.js +32 -5
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
- package/src/lib/model/ai-build-event-page.d.ts +70 -0
- package/src/lib/model/ai-build-event-page.js +145 -0
- package/src/lib/model/ai-build-event-page.js.map +1 -0
- package/src/lib/model/ai-build-job.d.ts +21 -0
- package/src/lib/model/ai-build-job.js +39 -0
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-build-plan.d.ts +25 -14
- package/src/lib/model/ai-build-plan.js +33 -23
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-job-activity.js +3 -1
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +10 -2
- package/src/lib/model/ai-jobs.types.js +3 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-plan-capabilities.d.ts +8 -0
- package/src/lib/model/ai-plan-capabilities.js.map +1 -1
- package/src/lib/model/ai-site-inventory.d.ts +2 -0
- package/src/lib/model/ai-site-inventory.js +5 -0
- package/src/lib/model/ai-site-inventory.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +2 -2
- package/src/lib/model/ai-site-job.js +3 -3
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +25 -3
- package/src/lib/runtime/ai-doctrine-validators.js +53 -6
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.d.ts +6 -1
- package/src/lib/runtime/ai-doctrine.js +13 -9
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.d.ts +1 -1
- package/src/lib/runtime/ai-eval-live.js +1 -1
- package/src/lib/runtime/ai-eval-live.js.map +1 -1
- package/src/lib/runtime/ai-palette.d.ts +3 -1
- package/src/lib/runtime/ai-palette.generated.js +498 -10
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/ai-palette.js +5 -3
- package/src/lib/runtime/ai-palette.js.map +1 -1
- package/src/lib/runtime/ai-template-examples.js +8 -2
- package/src/lib/runtime/ai-template-examples.js.map +1 -1
- package/src/lib/server.js +8 -0
- package/src/lib/server.js.map +1 -1
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { createResourceUid } from "@aglyn/aglyn/app-utils/create-resource-uid";
|
|
18
|
+
import { hostRoleCanWrite } from "@aglyn/aglyn/app-utils/organizations";
|
|
19
|
+
import { SCREEN_ROOT_PATH } from "@aglyn/aglyn/app-utils/screen-route";
|
|
20
|
+
import { GA_MEASUREMENT_ID_PATTERN, GOOGLE_ADS_ID_PATTERN, GTM_CONTAINER_ID_PATTERN, LINKEDIN_PARTNER_ID_PATTERN, META_PIXEL_ID_PATTERN, PINTEREST_TAG_ID_PATTERN, TIKTOK_PIXEL_ID_PATTERN } from "@aglyn/aglyn/app-utils/visitor-consent";
|
|
21
|
+
import { dropPluginSiteCache } from "@aglyn/aglyn/plugin-manager/plugin-site-cache";
|
|
22
|
+
import { getLockdownVerdict } from "@aglyn/tenant-data-admin/server/lockdown";
|
|
23
|
+
import { resolveOrgIdForHost } from "@aglyn/tenant-data-admin/server/organizations";
|
|
24
|
+
import { FieldValue } from "firebase-admin/firestore";
|
|
25
|
+
import { registerAiJobAdmission } from "./ai-job-admission.js";
|
|
26
|
+
import { aiJobEditModel } from "./ai-job-edit-step.js";
|
|
27
|
+
import { aiLimitReview, aiUnspentOutcome } from "./ai-job-generation.js";
|
|
28
|
+
import { registerAiJobStep } from "./ai-jobs.js";
|
|
29
|
+
/**
|
|
30
|
+
* The `tracking` step (AGL-3751): the site's analytics and advertising tags,
|
|
31
|
+
* as the console's Setup → Tracking card sets them — Google Analytics, Google
|
|
32
|
+
* Tag Manager, and the Meta, Google Ads, LinkedIn, TikTok and Pinterest tags —
|
|
33
|
+
* as a first-class build operation, so "connect Google Analytics G-4R7KX2PQ9Z"
|
|
34
|
+
* is one item on Assist's plan card.
|
|
35
|
+
*
|
|
36
|
+
* NO MODEL. The planner already wrote the ids as the item's arguments; this
|
|
37
|
+
* holds each to the pattern the card's field (and the tenant's loader) holds
|
|
38
|
+
* it to and writes it, so it costs no credits.
|
|
39
|
+
*
|
|
40
|
+
* ── Only the card's own fields, and only public ids ──────────────────────
|
|
41
|
+
*
|
|
42
|
+
* Every field written is one the Tracking card writes, under the same name:
|
|
43
|
+
* `analytics.gaMeasurementId`, `analytics.gtmContainerId` and
|
|
44
|
+
* `analytics.adTags.{meta,google-ads,linkedin,tiktok,pinterest}`, each tested
|
|
45
|
+
* by the same expression (`GA_MEASUREMENT_ID_PATTERN` and its siblings in
|
|
46
|
+
* `visitor-consent.ts`). Each is an id that is printed in every page's HTML
|
|
47
|
+
* anyway, never a credential. Removing one deletes the field, as the card's
|
|
48
|
+
* cleared box does (`CLEARABLE_TRACKING_PATHS`).
|
|
49
|
+
*
|
|
50
|
+
* NEVER: the consent banner (`consent.*` — turning it off, its mode, or the
|
|
51
|
+
* advertising question), search indexing, verification tokens, and anything
|
|
52
|
+
* that needs a secret the person pastes — the Ad conversions card's access
|
|
53
|
+
* tokens, Zapier, order webhooks' signing secrets. Those are not arguments.
|
|
54
|
+
* The tags load only where the consent banner lets them, exactly as a tag the
|
|
55
|
+
* card saved does: the tenant gates on the stored ids, not on who wrote them.
|
|
56
|
+
*
|
|
57
|
+
* ── Live on confirm, who may, idempotent ─────────────────────────────────
|
|
58
|
+
*
|
|
59
|
+
* As the `settings` step (AGL-3748): a tag has no draft, so the plan card's
|
|
60
|
+
* confirm is the agreement and the write is live. One transaction re-reads
|
|
61
|
+
* the host, re-checks the role, writes only what differs with a site-wide
|
|
62
|
+
* publish outbox entry, and the site's cached pages are dropped after. The
|
|
63
|
+
* gate is the Firestore rules' for the host document (`canWriteHostContent`,
|
|
64
|
+
* `hostWritesFrozen`): an admin, editor or author, while no lockdown freezes
|
|
65
|
+
* the site, its org or the platform.
|
|
66
|
+
*/ export const AI_TRACKING_KIND = 'tracking';
|
|
67
|
+
/** ASSUMED: the least time the step needs — the site, the org and one transaction. */ export const AI_JOB_TRACKING_STEP_MINIMUM_MS = 5000;
|
|
68
|
+
export const AI_TRACKING_NO_SITE_COPY = 'Open the site whose tracking should change.';
|
|
69
|
+
export const AI_TRACKING_ROLE_COPY = 'Changing the site’s tracking needs the admin, editor or author role on this site.';
|
|
70
|
+
export const AI_TRACKING_LOCKED_COPY = 'This site is not accepting changes right now.';
|
|
71
|
+
export const AI_TRACKING_NOTHING_COPY = 'The request named no tracking id this could set or remove.';
|
|
72
|
+
/** Where a person finds what the op changed: the card that edits it. */ export const AI_TRACKING_WHERE = 'Setup → Tracking';
|
|
73
|
+
/** The output's fixed id: one site's tracking, whatever changed. */ export const AI_TRACKING_OUTPUT_ID = 'tracking';
|
|
74
|
+
/** The Tracking card's fields, in its order (`trackingSchema`, host-settings-scope.tsx). */ export const AI_TRACKING_TAGS = [
|
|
75
|
+
{
|
|
76
|
+
arg: 'googleAnalytics',
|
|
77
|
+
path: 'analytics.gaMeasurementId',
|
|
78
|
+
label: 'Google Analytics',
|
|
79
|
+
pattern: GA_MEASUREMENT_ID_PATTERN,
|
|
80
|
+
shape: 'G-XXXXXXXXXX',
|
|
81
|
+
ask: 'What’s your Google Analytics measurement ID? It looks like G-XXXXXXXXXX.',
|
|
82
|
+
named: /google analytics|\bga4?\b|\banalytics\b/i
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
arg: 'googleTagManager',
|
|
86
|
+
path: 'analytics.gtmContainerId',
|
|
87
|
+
label: 'Google Tag Manager',
|
|
88
|
+
pattern: GTM_CONTAINER_ID_PATTERN,
|
|
89
|
+
shape: 'GTM-XXXXXXX',
|
|
90
|
+
ask: 'What’s your Google Tag Manager container ID? It looks like GTM-XXXXXXX.',
|
|
91
|
+
named: /tag manager|\bgtm\b/i
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
arg: 'metaPixel',
|
|
95
|
+
path: 'analytics.adTags.meta',
|
|
96
|
+
label: 'the Meta pixel',
|
|
97
|
+
pattern: META_PIXEL_ID_PATTERN,
|
|
98
|
+
shape: '8–20 digits',
|
|
99
|
+
ask: 'What’s your Meta pixel ID? It’s the number (8–20 digits) shown for your pixel in Meta Events Manager.',
|
|
100
|
+
named: /\bmeta\b|facebook|instagram/i
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
arg: 'googleAds',
|
|
104
|
+
path: 'analytics.adTags.google-ads',
|
|
105
|
+
label: 'the Google Ads tag',
|
|
106
|
+
pattern: GOOGLE_ADS_ID_PATTERN,
|
|
107
|
+
shape: 'AW-123456789',
|
|
108
|
+
ask: 'What’s your Google Ads conversion ID? It looks like AW-123456789, from Google Ads.',
|
|
109
|
+
named: /google ads|adwords/i
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
arg: 'linkedinInsight',
|
|
113
|
+
path: 'analytics.adTags.linkedin',
|
|
114
|
+
label: 'the LinkedIn Insight Tag',
|
|
115
|
+
pattern: LINKEDIN_PARTNER_ID_PATTERN,
|
|
116
|
+
shape: '4–10 digits',
|
|
117
|
+
ask: 'What’s your LinkedIn partner ID? It’s the number (4–10 digits) in LinkedIn Campaign Manager.',
|
|
118
|
+
named: /linkedin/i
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
arg: 'tiktokPixel',
|
|
122
|
+
path: 'analytics.adTags.tiktok',
|
|
123
|
+
label: 'the TikTok pixel',
|
|
124
|
+
pattern: TIKTOK_PIXEL_ID_PATTERN,
|
|
125
|
+
shape: '16–24 capital letters and digits',
|
|
126
|
+
ask: 'What’s your TikTok pixel code? It’s in TikTok Ads Manager → Events, like C4ABCDEFGH1234567890.',
|
|
127
|
+
named: /tiktok/i
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
arg: 'pinterestTag',
|
|
131
|
+
path: 'analytics.adTags.pinterest',
|
|
132
|
+
label: 'the Pinterest tag',
|
|
133
|
+
pattern: PINTEREST_TAG_ID_PATTERN,
|
|
134
|
+
shape: '10–16 digits',
|
|
135
|
+
ask: 'What’s your Pinterest tag ID? It’s the number (10–16 digits) in Pinterest Ads → Conversions.',
|
|
136
|
+
named: /pinterest/i
|
|
137
|
+
}
|
|
138
|
+
];
|
|
139
|
+
/** Asked when an item names no tag at all. */ export const AI_TRACKING_WHICH_COPY = 'Which tag should be connected, and what is its ID? Google Analytics, Google Tag Manager, the Meta pixel, Google Ads, LinkedIn, TikTok or Pinterest.';
|
|
140
|
+
const TAG_ARGS = AI_TRACKING_TAGS.map((tag)=>tag.arg);
|
|
141
|
+
const ID_RULE = 'Exactly as the person gave it; leave it out unless the request states the id — never make one up.';
|
|
142
|
+
/** The item's arguments, as the planner fills them in. Every one optional. */ export const AI_TRACKING_ARGS_SCHEMA = {
|
|
143
|
+
type: 'object',
|
|
144
|
+
properties: {
|
|
145
|
+
googleAnalytics: {
|
|
146
|
+
type: 'string',
|
|
147
|
+
description: `The Google Analytics 4 measurement ID, like G-XXXXXXXXXX. ${ID_RULE}`,
|
|
148
|
+
maxLength: 24
|
|
149
|
+
},
|
|
150
|
+
googleTagManager: {
|
|
151
|
+
type: 'string',
|
|
152
|
+
description: `The Google Tag Manager container ID, like GTM-XXXXXXX. ${ID_RULE}`,
|
|
153
|
+
maxLength: 24
|
|
154
|
+
},
|
|
155
|
+
metaPixel: {
|
|
156
|
+
type: 'string',
|
|
157
|
+
description: `The Meta (Facebook) pixel ID, 8–20 digits. ${ID_RULE}`,
|
|
158
|
+
maxLength: 24
|
|
159
|
+
},
|
|
160
|
+
googleAds: {
|
|
161
|
+
type: 'string',
|
|
162
|
+
description: `The Google Ads conversion ID, like AW-123456789. ${ID_RULE}`,
|
|
163
|
+
maxLength: 24
|
|
164
|
+
},
|
|
165
|
+
linkedinInsight: {
|
|
166
|
+
type: 'string',
|
|
167
|
+
description: `The LinkedIn Insight Tag partner ID, 4–10 digits. ${ID_RULE}`,
|
|
168
|
+
maxLength: 24
|
|
169
|
+
},
|
|
170
|
+
tiktokPixel: {
|
|
171
|
+
type: 'string',
|
|
172
|
+
description: `The TikTok pixel code, like C4ABCDEFGH1234567890. ${ID_RULE}`,
|
|
173
|
+
maxLength: 32
|
|
174
|
+
},
|
|
175
|
+
pinterestTag: {
|
|
176
|
+
type: 'string',
|
|
177
|
+
description: `The Pinterest tag ID, 10–16 digits. ${ID_RULE}`,
|
|
178
|
+
maxLength: 24
|
|
179
|
+
},
|
|
180
|
+
remove: {
|
|
181
|
+
type: 'array',
|
|
182
|
+
description: 'The tags the person asked to take off the site.',
|
|
183
|
+
items: {
|
|
184
|
+
type: 'string',
|
|
185
|
+
enum: TAG_ARGS
|
|
186
|
+
},
|
|
187
|
+
maxItems: AI_TRACKING_TAGS.length
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
additionalProperties: false
|
|
191
|
+
};
|
|
192
|
+
const text = (value)=>typeof value === 'string' ? value.trim() : '';
|
|
193
|
+
/** The item's arguments read: what may be written, and a sentence for each that may not. */ export function aiTrackingRequestOf(inputs) {
|
|
194
|
+
const set = {};
|
|
195
|
+
const problems = [];
|
|
196
|
+
const removing = new Set(Array.isArray(inputs == null ? void 0 : inputs['remove']) ? (inputs == null ? void 0 : inputs['remove']).map((one)=>String(one)) : []);
|
|
197
|
+
const remove = [];
|
|
198
|
+
for (const tag of AI_TRACKING_TAGS){
|
|
199
|
+
const value = text(inputs == null ? void 0 : inputs[tag.arg]);
|
|
200
|
+
if (value && removing.has(tag.arg)) {
|
|
201
|
+
problems.push(`${capitalized(tag.label)} was both set and removed, so it was left as it is.`);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (removing.has(tag.arg)) {
|
|
205
|
+
remove.push(tag.path);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (!value) continue;
|
|
209
|
+
// The card's own test, and the loader's: an id of any other shape is
|
|
210
|
+
// saved by nobody, since it would never load.
|
|
211
|
+
if (tag.pattern.test(value)) set[tag.path] = value;
|
|
212
|
+
else problems.push(`“${value.slice(0, 40)}” is not an ID for ${tag.label}, which looks like ${tag.shape}.`);
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
request: {
|
|
216
|
+
set,
|
|
217
|
+
remove
|
|
218
|
+
},
|
|
219
|
+
problems
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function capitalized(label) {
|
|
223
|
+
return label.charAt(0).toUpperCase() + label.slice(1);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* The question a planned tracking change asks the person, or `null` when it
|
|
227
|
+
* sets or removes something (AGL-3751). An item with no usable id — the
|
|
228
|
+
* planner kept "add my Meta pixel" with no id, or an id of the wrong shape —
|
|
229
|
+
* is never built: it asks for the id of the tag its arguments or its name
|
|
230
|
+
* name, in the card's own words, and says why an id given was not one.
|
|
231
|
+
*/ export function aiTrackingMissingInput(item) {
|
|
232
|
+
const read = aiTrackingRequestOf(item.args);
|
|
233
|
+
if (!aiTrackingRequestEmpty(read.request)) return null;
|
|
234
|
+
// The tags its arguments tried to set, else the ones its name or reason names.
|
|
235
|
+
const tried = AI_TRACKING_TAGS.filter((tag)=>text(item.args[tag.arg]));
|
|
236
|
+
const naming = (words)=>AI_TRACKING_TAGS.filter((tag)=>tag.named.test(words != null ? words : ''));
|
|
237
|
+
const byName = naming(item.name);
|
|
238
|
+
const named = tried.length ? tried : byName.length ? byName : naming(item.why);
|
|
239
|
+
const asks = named.length ? named.map((tag)=>tag.ask) : [
|
|
240
|
+
AI_TRACKING_WHICH_COPY
|
|
241
|
+
];
|
|
242
|
+
return [
|
|
243
|
+
...read.problems,
|
|
244
|
+
...asks
|
|
245
|
+
].join(' ');
|
|
246
|
+
}
|
|
247
|
+
/** Whether a read request asks for anything at all. */ export function aiTrackingRequestEmpty(request) {
|
|
248
|
+
return Object.keys(request.set).length === 0 && request.remove.length === 0;
|
|
249
|
+
}
|
|
250
|
+
// ── The write ─────────────────────────────────────────────────────────────
|
|
251
|
+
const getPath = (data, path)=>path.split('.').reduce((at, key)=>at && typeof at === 'object' ? at[key] : undefined, data);
|
|
252
|
+
/** Every change the request asks, in the card's order, before comparing with what is stored. */ export function aiTrackingChanges(request) {
|
|
253
|
+
const changes = [];
|
|
254
|
+
for (const tag of AI_TRACKING_TAGS){
|
|
255
|
+
const value = request.set[tag.path];
|
|
256
|
+
if (value) changes.push({
|
|
257
|
+
path: tag.path,
|
|
258
|
+
value,
|
|
259
|
+
said: `${capitalized(tag.label)} is connected (${value}).`
|
|
260
|
+
});
|
|
261
|
+
else if (request.remove.includes(tag.path)) changes.push({
|
|
262
|
+
path: tag.path,
|
|
263
|
+
value: null,
|
|
264
|
+
said: `${capitalized(tag.label)} is removed.`
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
return changes;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* What the person should know about the tags the site now carries, in the
|
|
271
|
+
* card's own words: where the consent banner is off, nothing of ours gates
|
|
272
|
+
* them; and a container beside a direct Google tag counts everything twice.
|
|
273
|
+
*/ export function aiTrackingWarnings(host) {
|
|
274
|
+
const warnings = [];
|
|
275
|
+
const has = (path)=>{
|
|
276
|
+
const value = getPath(host, path);
|
|
277
|
+
return typeof value === 'string' && value !== '';
|
|
278
|
+
};
|
|
279
|
+
const any = AI_TRACKING_TAGS.some((tag)=>has(tag.path));
|
|
280
|
+
if (any && getPath(host, 'consent.disabled') === true) {
|
|
281
|
+
warnings.push('This site’s cookie banner is turned off because it uses its own consent tool, so that tool decides when these load.');
|
|
282
|
+
}
|
|
283
|
+
if (has('analytics.gtmContainerId') && (has('analytics.gaMeasurementId') || has('analytics.adTags.google-ads'))) {
|
|
284
|
+
warnings.push('Do not also put an Analytics or Google Ads tag in the Tag Manager container: that loads the same measurement twice and counts every visit and conversion twice.');
|
|
285
|
+
}
|
|
286
|
+
return warnings;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* The tags written, in one transaction that reads the host as it is stored
|
|
290
|
+
* NOW: the member still holds a write role, and only the values that differ
|
|
291
|
+
* from what is stored are written, with a site-wide publish outbox entry
|
|
292
|
+
* beside them so the live site's cached pages are dropped.
|
|
293
|
+
*/ export async function writeAiSiteTracking(firestore, input) {
|
|
294
|
+
const hostRef = firestore.collection('hosts').doc(input.hostId);
|
|
295
|
+
return firestore.runTransaction(async (tx)=>{
|
|
296
|
+
var _host_get, _host_data, _data_analytics, _data_consent;
|
|
297
|
+
const host = await tx.get(hostRef);
|
|
298
|
+
if (!host.exists) return {
|
|
299
|
+
ok: false,
|
|
300
|
+
error: 'Unknown site'
|
|
301
|
+
};
|
|
302
|
+
const roles = (_host_get = host.get('memberRoles')) != null ? _host_get : {};
|
|
303
|
+
if (!hostRoleCanWrite(roles[input.uid])) return {
|
|
304
|
+
ok: false,
|
|
305
|
+
error: AI_TRACKING_ROLE_COPY
|
|
306
|
+
};
|
|
307
|
+
const data = (_host_data = host.data()) != null ? _host_data : {};
|
|
308
|
+
const changes = aiTrackingChanges(input.request);
|
|
309
|
+
const changed = changes.filter((change)=>{
|
|
310
|
+
const stored = getPath(data, change.path);
|
|
311
|
+
return change.value === null ? stored != null && stored !== '' : stored !== change.value;
|
|
312
|
+
});
|
|
313
|
+
// What the site carries once this lands, for the warnings.
|
|
314
|
+
// Only the two maps the warnings read; both are plain strings and flags.
|
|
315
|
+
const after = JSON.parse(JSON.stringify({
|
|
316
|
+
analytics: (_data_analytics = data['analytics']) != null ? _data_analytics : {},
|
|
317
|
+
consent: (_data_consent = data['consent']) != null ? _data_consent : {}
|
|
318
|
+
}));
|
|
319
|
+
for (const change of changed){
|
|
320
|
+
const keys = change.path.split('.');
|
|
321
|
+
const leaf = keys.pop();
|
|
322
|
+
let node = after;
|
|
323
|
+
for (const key of keys){
|
|
324
|
+
if (!node[key] || typeof node[key] !== 'object') node[key] = {};
|
|
325
|
+
node = node[key];
|
|
326
|
+
}
|
|
327
|
+
if (change.value === null) delete node[leaf];
|
|
328
|
+
else node[leaf] = change.value;
|
|
329
|
+
}
|
|
330
|
+
const done = changes.map((change)=>change.said);
|
|
331
|
+
const warnings = aiTrackingWarnings(after);
|
|
332
|
+
if (!changed.length) return {
|
|
333
|
+
ok: true,
|
|
334
|
+
done,
|
|
335
|
+
changed: [],
|
|
336
|
+
warnings,
|
|
337
|
+
outboxId: null
|
|
338
|
+
};
|
|
339
|
+
const updates = {
|
|
340
|
+
updatedAt: input.now
|
|
341
|
+
};
|
|
342
|
+
for (const change of changed)updates[change.path] = change.value === null ? FieldValue.delete() : change.value;
|
|
343
|
+
tx.update(hostRef, updates);
|
|
344
|
+
// The tags are in every page's head, so every cached page is stale.
|
|
345
|
+
const outboxRef = firestore.collection('publishOutbox').doc(createResourceUid());
|
|
346
|
+
tx.set(outboxRef, {
|
|
347
|
+
hostId: input.hostId,
|
|
348
|
+
paths: [
|
|
349
|
+
SCREEN_ROOT_PATH
|
|
350
|
+
],
|
|
351
|
+
entireHost: true,
|
|
352
|
+
createdAt: FieldValue.serverTimestamp(),
|
|
353
|
+
attempts: 0
|
|
354
|
+
});
|
|
355
|
+
return {
|
|
356
|
+
ok: true,
|
|
357
|
+
done,
|
|
358
|
+
changed: changed.map((change)=>change.path),
|
|
359
|
+
warnings,
|
|
360
|
+
outboxId: outboxRef.id
|
|
361
|
+
};
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
// ── The step ──────────────────────────────────────────────────────────────
|
|
365
|
+
/** Why this member may not change this site's tracking now, or `null`. */ export async function aiTrackingAccessRefusal(input, deps = {}) {
|
|
366
|
+
var _input_host_get, _deps_lockdown;
|
|
367
|
+
if (!input.host.exists) return 'Unknown site';
|
|
368
|
+
const roles = (_input_host_get = input.host.get('memberRoles')) != null ? _input_host_get : {};
|
|
369
|
+
if (!hostRoleCanWrite(roles[input.uid])) return AI_TRACKING_ROLE_COPY;
|
|
370
|
+
const locked = await ((_deps_lockdown = deps.lockdown) != null ? _deps_lockdown : getLockdownVerdict)({
|
|
371
|
+
staff: false,
|
|
372
|
+
uid: input.uid,
|
|
373
|
+
org: input.org,
|
|
374
|
+
host: input.host.data(),
|
|
375
|
+
intent: 'write'
|
|
376
|
+
});
|
|
377
|
+
return locked ? AI_TRACKING_LOCKED_COPY : null;
|
|
378
|
+
}
|
|
379
|
+
/** The output's note: what changed, that it is live, how consent gates it, and what was not done. */ export function aiTrackingOutputNote(done, warnings, notDone, changedAny) {
|
|
380
|
+
const did = done.length ? `${done.join(' ')} Live now, in ${AI_TRACKING_WHERE}.${changedAny ? '' : ' It already was, so nothing was written.'}` : 'Nothing needed changing.';
|
|
381
|
+
// The card's own promise, true of a tag whoever saved it.
|
|
382
|
+
const consent = done.length ? ' Tags load only where a visitor’s cookie consent allows them.' : '';
|
|
383
|
+
const warn = warnings.length ? ` ${warnings.join(' ')}` : '';
|
|
384
|
+
const not = notDone.length ? ` Not done: ${notDone.join(' ')}` : '';
|
|
385
|
+
return `${did}${consent}${warn}${not}`.slice(0, 1000);
|
|
386
|
+
}
|
|
387
|
+
export function createAiJobTrackingStep(deps = {}) {
|
|
388
|
+
var _deps_write, _deps_dropCache;
|
|
389
|
+
const write = (_deps_write = deps.write) != null ? _deps_write : writeAiSiteTracking;
|
|
390
|
+
const dropCache = (_deps_dropCache = deps.dropCache) != null ? _deps_dropCache : dropPluginSiteCache;
|
|
391
|
+
return async ({ job, now, firestore, org, modelFor })=>{
|
|
392
|
+
var _ref, _data;
|
|
393
|
+
// No model is asked: the outcome records the edit step's at no cost.
|
|
394
|
+
const model = (_ref = modelFor == null ? void 0 : modelFor('job.edit')) != null ? _ref : aiJobEditModel();
|
|
395
|
+
const unspent = (failure)=>_extends({}, aiUnspentOutcome(model), {
|
|
396
|
+
failure
|
|
397
|
+
});
|
|
398
|
+
const hostId = job.hostId;
|
|
399
|
+
if (!hostId) return unspent(AI_TRACKING_NO_SITE_COPY);
|
|
400
|
+
const read = aiTrackingRequestOf(job.inputs);
|
|
401
|
+
if (aiTrackingRequestEmpty(read.request)) return unspent([
|
|
402
|
+
AI_TRACKING_NOTHING_COPY,
|
|
403
|
+
...read.problems
|
|
404
|
+
].join(' '));
|
|
405
|
+
const orgData = org != null ? org : (_data = (await firestore.collection('orgs').doc(job.orgId).get()).data()) != null ? _data : null;
|
|
406
|
+
const host = await firestore.collection('hosts').doc(hostId).get();
|
|
407
|
+
const refused = await aiTrackingAccessRefusal({
|
|
408
|
+
host,
|
|
409
|
+
uid: job.createdBy,
|
|
410
|
+
org: orgData
|
|
411
|
+
}, {
|
|
412
|
+
lockdown: deps.lockdown
|
|
413
|
+
});
|
|
414
|
+
if (refused) return aiUnspentOutcome(model, {
|
|
415
|
+
review: aiLimitReview(refused)
|
|
416
|
+
});
|
|
417
|
+
const written = await write(firestore, {
|
|
418
|
+
hostId,
|
|
419
|
+
uid: job.createdBy,
|
|
420
|
+
request: read.request,
|
|
421
|
+
now
|
|
422
|
+
});
|
|
423
|
+
if (written.ok === false) return unspent(written.error);
|
|
424
|
+
if (written.outboxId) {
|
|
425
|
+
// The live site's cached pages go now; the outbox entry stays for the
|
|
426
|
+
// drain unless the drop certainly happened.
|
|
427
|
+
const dropped = await dropCache({
|
|
428
|
+
hostIds: [
|
|
429
|
+
hostId
|
|
430
|
+
],
|
|
431
|
+
reason: 'AI build changed site tracking'
|
|
432
|
+
}).catch(()=>({
|
|
433
|
+
complete: false
|
|
434
|
+
}));
|
|
435
|
+
if (dropped.complete) await firestore.collection('publishOutbox').doc(written.outboxId).delete().catch(()=>undefined);
|
|
436
|
+
}
|
|
437
|
+
const output = {
|
|
438
|
+
resource: 'tracking',
|
|
439
|
+
id: AI_TRACKING_OUTPUT_ID,
|
|
440
|
+
hostId,
|
|
441
|
+
hostSubdomain: typeof host.get('subdomain') === 'string' && host.get('subdomain') || null,
|
|
442
|
+
label: 'Site tracking',
|
|
443
|
+
note: aiTrackingOutputNote(written.done, written.warnings, read.problems, written.changed.length > 0)
|
|
444
|
+
};
|
|
445
|
+
return aiUnspentOutcome(model, {
|
|
446
|
+
outputs: [
|
|
447
|
+
output
|
|
448
|
+
]
|
|
449
|
+
});
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
export const runAiJobTrackingStep = createAiJobTrackingStep();
|
|
453
|
+
/**
|
|
454
|
+
* A tracking change is admitted on a site of the job's own org, for a member
|
|
455
|
+
* who may write its settings now, where the request names at least one tag
|
|
456
|
+
* this can set or remove. Refused in those words before anything is written.
|
|
457
|
+
*/ export function createAiTrackingJobAdmission(deps = {}) {
|
|
458
|
+
var _deps_ownerOf;
|
|
459
|
+
const ownerOf = (_deps_ownerOf = deps.ownerOf) != null ? _deps_ownerOf : resolveOrgIdForHost;
|
|
460
|
+
return async (context)=>{
|
|
461
|
+
var _context_uid;
|
|
462
|
+
if (!context.hostId) return {
|
|
463
|
+
status: 400,
|
|
464
|
+
error: AI_TRACKING_NO_SITE_COPY
|
|
465
|
+
};
|
|
466
|
+
const owner = await ownerOf(context.hostId);
|
|
467
|
+
if (!owner || owner !== context.orgId) return {
|
|
468
|
+
status: 404,
|
|
469
|
+
error: 'Unknown site'
|
|
470
|
+
};
|
|
471
|
+
const host = await context.firestore.collection('hosts').doc(context.hostId).get();
|
|
472
|
+
const refused = await aiTrackingAccessRefusal({
|
|
473
|
+
host,
|
|
474
|
+
uid: (_context_uid = context.uid) != null ? _context_uid : '',
|
|
475
|
+
org: context.org
|
|
476
|
+
}, {
|
|
477
|
+
lockdown: deps.lockdown
|
|
478
|
+
});
|
|
479
|
+
if (refused) return {
|
|
480
|
+
status: 403,
|
|
481
|
+
error: refused
|
|
482
|
+
};
|
|
483
|
+
const read = aiTrackingRequestOf(context.inputs);
|
|
484
|
+
return aiTrackingRequestEmpty(read.request) ? {
|
|
485
|
+
status: 400,
|
|
486
|
+
error: [
|
|
487
|
+
AI_TRACKING_NOTHING_COPY,
|
|
488
|
+
...read.problems
|
|
489
|
+
].join(' ')
|
|
490
|
+
} : null;
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
export const aiTrackingJobAdmission = createAiTrackingJobAdmission();
|
|
494
|
+
/** Registers the tracking step and its door (AGL-3751). */ export function registerAiTrackingJob() {
|
|
495
|
+
registerAiJobStep(AI_TRACKING_KIND, runAiJobTrackingStep, {
|
|
496
|
+
minimumMs: AI_JOB_TRACKING_STEP_MINIMUM_MS
|
|
497
|
+
});
|
|
498
|
+
registerAiJobAdmission(AI_TRACKING_KIND, aiTrackingJobAdmission);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
//# sourceMappingURL=ai-job-tracking-step.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-tracking-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 { SCREEN_ROOT_PATH } from '@aglyn/aglyn/app-utils/screen-route'\nimport {\n GA_MEASUREMENT_ID_PATTERN,\n GOOGLE_ADS_ID_PATTERN,\n GTM_CONTAINER_ID_PATTERN,\n LINKEDIN_PARTNER_ID_PATTERN,\n META_PIXEL_ID_PATTERN,\n PINTEREST_TAG_ID_PATTERN,\n TIKTOK_PIXEL_ID_PATTERN,\n} from '@aglyn/aglyn/app-utils/visitor-consent'\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 `tracking` step (AGL-3751): the site's analytics and advertising tags,\n * as the console's Setup → Tracking card sets them — Google Analytics, Google\n * Tag Manager, and the Meta, Google Ads, LinkedIn, TikTok and Pinterest tags —\n * as a first-class build operation, so \"connect Google Analytics G-4R7KX2PQ9Z\"\n * is one item on Assist's plan card.\n *\n * NO MODEL. The planner already wrote the ids as the item's arguments; this\n * holds each to the pattern the card's field (and the tenant's loader) holds\n * it to and writes it, so it costs no credits.\n *\n * ── Only the card's own fields, and only public ids ──────────────────────\n *\n * Every field written is one the Tracking card writes, under the same name:\n * `analytics.gaMeasurementId`, `analytics.gtmContainerId` and\n * `analytics.adTags.{meta,google-ads,linkedin,tiktok,pinterest}`, each tested\n * by the same expression (`GA_MEASUREMENT_ID_PATTERN` and its siblings in\n * `visitor-consent.ts`). Each is an id that is printed in every page's HTML\n * anyway, never a credential. Removing one deletes the field, as the card's\n * cleared box does (`CLEARABLE_TRACKING_PATHS`).\n *\n * NEVER: the consent banner (`consent.*` — turning it off, its mode, or the\n * advertising question), search indexing, verification tokens, and anything\n * that needs a secret the person pastes — the Ad conversions card's access\n * tokens, Zapier, order webhooks' signing secrets. Those are not arguments.\n * The tags load only where the consent banner lets them, exactly as a tag the\n * card saved does: the tenant gates on the stored ids, not on who wrote them.\n *\n * ── Live on confirm, who may, idempotent ─────────────────────────────────\n *\n * As the `settings` step (AGL-3748): a tag has no draft, so the plan card's\n * confirm is the agreement and the write is live. One transaction re-reads\n * the host, re-checks the role, writes only what differs with a site-wide\n * publish outbox entry, and the site's cached pages are dropped after. The\n * gate is the Firestore rules' for the host document (`canWriteHostContent`,\n * `hostWritesFrozen`): an admin, editor or author, while no lockdown freezes\n * the site, its org or the platform.\n */\n\nexport const AI_TRACKING_KIND = 'tracking' as const\n\n/** ASSUMED: the least time the step needs — the site, the org and one transaction. */\nexport const AI_JOB_TRACKING_STEP_MINIMUM_MS = 5_000\n\nexport const AI_TRACKING_NO_SITE_COPY = 'Open the site whose tracking should change.'\nexport const AI_TRACKING_ROLE_COPY = 'Changing the site’s tracking needs the admin, editor or author role on this site.'\nexport const AI_TRACKING_LOCKED_COPY = 'This site is not accepting changes right now.'\nexport const AI_TRACKING_NOTHING_COPY = 'The request named no tracking id this could set or remove.'\n\n/** Where a person finds what the op changed: the card that edits it. */\nexport const AI_TRACKING_WHERE = 'Setup → Tracking'\n\n/** The output's fixed id: one site's tracking, whatever changed. */\nexport const AI_TRACKING_OUTPUT_ID = 'tracking'\n\n/** One tag the card sets: its argument, its stored field, its name, and its pattern. */\nexport interface AiTrackingTag {\n arg: string\n path: string\n label: string\n pattern: RegExp\n /** What the card's field says the id looks like. */\n shape: string\n /** Asked when a request names this tag and no usable id, from the card's helper text. */\n ask: string\n /** Words that name this tag in an item's name. */\n named: RegExp\n}\n\n/** The Tracking card's fields, in its order (`trackingSchema`, host-settings-scope.tsx). */\nexport const AI_TRACKING_TAGS: readonly AiTrackingTag[] = [\n {\n arg: 'googleAnalytics',\n path: 'analytics.gaMeasurementId',\n label: 'Google Analytics',\n pattern: GA_MEASUREMENT_ID_PATTERN,\n shape: 'G-XXXXXXXXXX',\n ask: 'What’s your Google Analytics measurement ID? It looks like G-XXXXXXXXXX.',\n named: /google analytics|\\bga4?\\b|\\banalytics\\b/i,\n },\n {\n arg: 'googleTagManager',\n path: 'analytics.gtmContainerId',\n label: 'Google Tag Manager',\n pattern: GTM_CONTAINER_ID_PATTERN,\n shape: 'GTM-XXXXXXX',\n ask: 'What’s your Google Tag Manager container ID? It looks like GTM-XXXXXXX.',\n named: /tag manager|\\bgtm\\b/i,\n },\n {\n arg: 'metaPixel',\n path: 'analytics.adTags.meta',\n label: 'the Meta pixel',\n pattern: META_PIXEL_ID_PATTERN,\n shape: '8–20 digits',\n ask: 'What’s your Meta pixel ID? It’s the number (8–20 digits) shown for your pixel in Meta Events Manager.',\n named: /\\bmeta\\b|facebook|instagram/i,\n },\n {\n arg: 'googleAds',\n path: 'analytics.adTags.google-ads',\n label: 'the Google Ads tag',\n pattern: GOOGLE_ADS_ID_PATTERN,\n shape: 'AW-123456789',\n ask: 'What’s your Google Ads conversion ID? It looks like AW-123456789, from Google Ads.',\n named: /google ads|adwords/i,\n },\n {\n arg: 'linkedinInsight',\n path: 'analytics.adTags.linkedin',\n label: 'the LinkedIn Insight Tag',\n pattern: LINKEDIN_PARTNER_ID_PATTERN,\n shape: '4–10 digits',\n ask: 'What’s your LinkedIn partner ID? It’s the number (4–10 digits) in LinkedIn Campaign Manager.',\n named: /linkedin/i,\n },\n {\n arg: 'tiktokPixel',\n path: 'analytics.adTags.tiktok',\n label: 'the TikTok pixel',\n pattern: TIKTOK_PIXEL_ID_PATTERN,\n shape: '16–24 capital letters and digits',\n ask: 'What’s your TikTok pixel code? It’s in TikTok Ads Manager → Events, like C4ABCDEFGH1234567890.',\n named: /tiktok/i,\n },\n {\n arg: 'pinterestTag',\n path: 'analytics.adTags.pinterest',\n label: 'the Pinterest tag',\n pattern: PINTEREST_TAG_ID_PATTERN,\n shape: '10–16 digits',\n ask: 'What’s your Pinterest tag ID? It’s the number (10–16 digits) in Pinterest Ads → Conversions.',\n named: /pinterest/i,\n },\n]\n\n/** Asked when an item names no tag at all. */\nexport const AI_TRACKING_WHICH_COPY =\n 'Which tag should be connected, and what is its ID? Google Analytics, Google Tag Manager, the Meta pixel, Google Ads, LinkedIn, TikTok or Pinterest.'\n\nconst TAG_ARGS = AI_TRACKING_TAGS.map((tag) => tag.arg)\n\nconst ID_RULE = 'Exactly as the person gave it; leave it out unless the request states the id — never make one up.'\n\n/** The item's arguments, as the planner fills them in. Every one optional. */\nexport const AI_TRACKING_ARGS_SCHEMA: PluginAiCapabilityArgsSchema = {\n type: 'object',\n properties: {\n googleAnalytics: { type: 'string', description: `The Google Analytics 4 measurement ID, like G-XXXXXXXXXX. ${ID_RULE}`, maxLength: 24 },\n googleTagManager: { type: 'string', description: `The Google Tag Manager container ID, like GTM-XXXXXXX. ${ID_RULE}`, maxLength: 24 },\n metaPixel: { type: 'string', description: `The Meta (Facebook) pixel ID, 8–20 digits. ${ID_RULE}`, maxLength: 24 },\n googleAds: { type: 'string', description: `The Google Ads conversion ID, like AW-123456789. ${ID_RULE}`, maxLength: 24 },\n linkedinInsight: { type: 'string', description: `The LinkedIn Insight Tag partner ID, 4–10 digits. ${ID_RULE}`, maxLength: 24 },\n tiktokPixel: { type: 'string', description: `The TikTok pixel code, like C4ABCDEFGH1234567890. ${ID_RULE}`, maxLength: 32 },\n pinterestTag: { type: 'string', description: `The Pinterest tag ID, 10–16 digits. ${ID_RULE}`, maxLength: 24 },\n remove: {\n type: 'array',\n description: 'The tags the person asked to take off the site.',\n items: { type: 'string', enum: TAG_ARGS },\n maxItems: AI_TRACKING_TAGS.length,\n },\n },\n additionalProperties: false,\n}\n\n/** What an item asks: ids to set by field, and fields to clear. */\nexport interface AiTrackingRequest {\n set: Record<string, string>\n remove: string[]\n}\n\nexport interface AiTrackingRead {\n request: AiTrackingRequest\n /** A sentence per value that could not be used, so the rest still are. */\n problems: string[]\n}\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** The item's arguments read: what may be written, and a sentence for each that may not. */\nexport function aiTrackingRequestOf(inputs: Readonly<Record<string, unknown>> | null | undefined): AiTrackingRead {\n const set: Record<string, string> = {}\n const problems: string[] = []\n const removing = new Set(Array.isArray(inputs?.['remove']) ? (inputs?.['remove'] as unknown[]).map((one) => String(one)) : [])\n const remove: string[] = []\n for (const tag of AI_TRACKING_TAGS) {\n const value = text(inputs?.[tag.arg])\n if (value && removing.has(tag.arg)) {\n problems.push(`${capitalized(tag.label)} was both set and removed, so it was left as it is.`)\n continue\n }\n if (removing.has(tag.arg)) {\n remove.push(tag.path)\n continue\n }\n if (!value) continue\n // The card's own test, and the loader's: an id of any other shape is\n // saved by nobody, since it would never load.\n if (tag.pattern.test(value)) set[tag.path] = value\n else problems.push(`“${value.slice(0, 40)}” is not an ID for ${tag.label}, which looks like ${tag.shape}.`)\n }\n return { request: { set, remove }, problems }\n}\n\nfunction capitalized(label: string): string {\n return label.charAt(0).toUpperCase() + label.slice(1)\n}\n\n/**\n * The question a planned tracking change asks the person, or `null` when it\n * sets or removes something (AGL-3751). An item with no usable id — the\n * planner kept \"add my Meta pixel\" with no id, or an id of the wrong shape —\n * is never built: it asks for the id of the tag its arguments or its name\n * name, in the card's own words, and says why an id given was not one.\n */\nexport function aiTrackingMissingInput(item: { name?: string; why?: string; args: Readonly<Record<string, unknown>> }): string | null {\n const read = aiTrackingRequestOf(item.args)\n if (!aiTrackingRequestEmpty(read.request)) return null\n // The tags its arguments tried to set, else the ones its name or reason names.\n const tried = AI_TRACKING_TAGS.filter((tag) => text(item.args[tag.arg]))\n const naming = (words: string | undefined) => AI_TRACKING_TAGS.filter((tag) => tag.named.test(words ?? ''))\n const byName = naming(item.name)\n const named = tried.length ? tried : byName.length ? byName : naming(item.why)\n const asks = named.length ? named.map((tag) => tag.ask) : [AI_TRACKING_WHICH_COPY]\n return [...read.problems, ...asks].join(' ')\n}\n\n/** Whether a read request asks for anything at all. */\nexport function aiTrackingRequestEmpty(request: AiTrackingRequest): boolean {\n return Object.keys(request.set).length === 0 && request.remove.length === 0\n}\n\n// ── The write ─────────────────────────────────────────────────────────────\n\nconst getPath = (data: Record<string, unknown>, path: string): unknown =>\n path.split('.').reduce<unknown>((at, key) => (at && typeof at === 'object' ? (at as Record<string, unknown>)[key] : undefined), data)\n\n/** One change to the host document, with the sentence that says it was made. */\nexport interface AiTrackingChange {\n path: string\n /** The id to store, or `null` to delete the field. */\n value: string | null\n said: string\n}\n\n/** Every change the request asks, in the card's order, before comparing with what is stored. */\nexport function aiTrackingChanges(request: AiTrackingRequest): AiTrackingChange[] {\n const changes: AiTrackingChange[] = []\n for (const tag of AI_TRACKING_TAGS) {\n const value = request.set[tag.path]\n if (value) changes.push({ path: tag.path, value, said: `${capitalized(tag.label)} is connected (${value}).` })\n else if (request.remove.includes(tag.path)) changes.push({ path: tag.path, value: null, said: `${capitalized(tag.label)} is removed.` })\n }\n return changes\n}\n\n/**\n * What the person should know about the tags the site now carries, in the\n * card's own words: where the consent banner is off, nothing of ours gates\n * them; and a container beside a direct Google tag counts everything twice.\n */\nexport function aiTrackingWarnings(host: Record<string, unknown>): string[] {\n const warnings: string[] = []\n const has = (path: string) => {\n const value = getPath(host, path)\n return typeof value === 'string' && value !== ''\n }\n const any = AI_TRACKING_TAGS.some((tag) => has(tag.path))\n if (any && getPath(host, 'consent.disabled') === true) {\n warnings.push('This site’s cookie banner is turned off because it uses its own consent tool, so that tool decides when these load.')\n }\n if (has('analytics.gtmContainerId') && (has('analytics.gaMeasurementId') || has('analytics.adTags.google-ads'))) {\n warnings.push('Do not also put an Analytics or Google Ads tag in the Tag Manager container: that loads the same measurement twice and counts every visit and conversion twice.')\n }\n return warnings\n}\n\nexport type AiTrackingWrite =\n | { ok: true; done: string[]; changed: string[]; warnings: string[]; outboxId: string | null }\n | { ok: false; error: string }\n\n/**\n * The tags written, in one transaction that reads the host as it is stored\n * NOW: the member still holds a write role, and only the values that differ\n * from what is stored are written, with a site-wide publish outbox entry\n * beside them so the live site's cached pages are dropped.\n */\nexport async function writeAiSiteTracking(\n firestore: Firestore,\n input: { hostId: string; uid: string; request: AiTrackingRequest; now: Date },\n): Promise<AiTrackingWrite> {\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n return firestore.runTransaction(async (tx): Promise<AiTrackingWrite> => {\n const host = await tx.get(hostRef)\n if (!host.exists) return { ok: false, error: 'Unknown site' }\n const roles = (host.get('memberRoles') ?? {}) as Record<string, unknown>\n if (!hostRoleCanWrite(roles[input.uid])) return { ok: false, error: AI_TRACKING_ROLE_COPY }\n const data = (host.data() ?? {}) as Record<string, unknown>\n const changes = aiTrackingChanges(input.request)\n const changed = changes.filter((change) => {\n const stored = getPath(data, change.path)\n return change.value === null ? stored != null && stored !== '' : stored !== change.value\n })\n // What the site carries once this lands, for the warnings.\n // Only the two maps the warnings read; both are plain strings and flags.\n const after: Record<string, unknown> = JSON.parse(JSON.stringify({ analytics: data['analytics'] ?? {}, consent: data['consent'] ?? {} }))\n for (const change of changed) {\n const keys = change.path.split('.')\n const leaf = keys.pop() as string\n let node = after as Record<string, unknown>\n for (const key of keys) {\n if (!node[key] || typeof node[key] !== 'object') node[key] = {}\n node = node[key] as Record<string, unknown>\n }\n if (change.value === null) delete node[leaf]\n else node[leaf] = change.value\n }\n const done = changes.map((change) => change.said)\n const warnings = aiTrackingWarnings(after)\n if (!changed.length) return { ok: true, done, changed: [], warnings, outboxId: null }\n const updates: Record<string, unknown> = { updatedAt: input.now }\n for (const change of changed) updates[change.path] = change.value === null ? FieldValue.delete() : change.value\n tx.update(hostRef, updates)\n // The tags are in every page's head, so every cached page is stale.\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, changed: changed.map((change) => change.path), warnings, outboxId: outboxRef.id }\n })\n}\n\n// ── The step ──────────────────────────────────────────────────────────────\n\n/** Why this member may not change this site's tracking now, or `null`. */\nexport async function aiTrackingAccessRefusal(\n input: { host: FirebaseFirestore.DocumentSnapshot; uid: string; org: object | null },\n deps: { lockdown?: typeof getLockdownVerdict } = {},\n): Promise<string | null> {\n if (!input.host.exists) return 'Unknown site'\n const roles = (input.host.get('memberRoles') ?? {}) as Record<string, unknown>\n if (!hostRoleCanWrite(roles[input.uid])) return AI_TRACKING_ROLE_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_TRACKING_LOCKED_COPY : null\n}\n\n/** The output's note: what changed, that it is live, how consent gates it, and what was not done. */\nexport function aiTrackingOutputNote(done: readonly string[], warnings: readonly string[], notDone: readonly string[], changedAny: boolean): string {\n const did = done.length\n ? `${done.join(' ')} Live now, in ${AI_TRACKING_WHERE}.${changedAny ? '' : ' It already was, so nothing was written.'}`\n : 'Nothing needed changing.'\n // The card's own promise, true of a tag whoever saved it.\n const consent = done.length ? ' Tags load only where a visitor’s cookie consent allows them.' : ''\n const warn = warnings.length ? ` ${warnings.join(' ')}` : ''\n const not = notDone.length ? ` Not done: ${notDone.join(' ')}` : ''\n return `${did}${consent}${warn}${not}`.slice(0, 1_000)\n}\n\nexport interface AiJobTrackingStepDeps {\n write?: typeof writeAiSiteTracking\n lockdown?: typeof getLockdownVerdict\n dropCache?: typeof dropPluginSiteCache\n}\n\nexport function createAiJobTrackingStep(deps: AiJobTrackingStepDeps = {}): AiJobStepRunner {\n const write = deps.write ?? writeAiSiteTracking\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_TRACKING_NO_SITE_COPY)\n const read = aiTrackingRequestOf(job.inputs)\n if (aiTrackingRequestEmpty(read.request)) return unspent([AI_TRACKING_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 aiTrackingAccessRefusal({ host, uid: job.createdBy, org: orgData }, { lockdown: deps.lockdown })\n if (refused) return aiUnspentOutcome(model, { review: aiLimitReview(refused) })\n const written = await write(firestore, { hostId, uid: job.createdBy, request: read.request, now })\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 tracking' }).catch(() => ({ complete: false }))\n if (dropped.complete) await firestore.collection('publishOutbox').doc(written.outboxId).delete().catch(() => undefined)\n }\n const output: AiJobOutput = {\n resource: 'tracking',\n id: AI_TRACKING_OUTPUT_ID,\n hostId,\n hostSubdomain: (typeof host.get('subdomain') === 'string' && host.get('subdomain')) || null,\n label: 'Site tracking',\n note: aiTrackingOutputNote(written.done, written.warnings, read.problems, written.changed.length > 0),\n }\n return aiUnspentOutcome(model, { outputs: [output] })\n }\n}\n\nexport const runAiJobTrackingStep = createAiJobTrackingStep()\n\n// ── Admission ─────────────────────────────────────────────────────────────\n\nexport interface AiTrackingJobAdmissionDeps {\n ownerOf?: typeof resolveOrgIdForHost\n lockdown?: typeof getLockdownVerdict\n}\n\n/**\n * A tracking change is admitted on a site of the job's own org, for a member\n * who may write its settings now, where the request names at least one tag\n * this can set or remove. Refused in those words before anything is written.\n */\nexport function createAiTrackingJobAdmission(deps: AiTrackingJobAdmissionDeps = {}): AiJobAdmission {\n const ownerOf = deps.ownerOf ?? resolveOrgIdForHost\n return async (context): Promise<AiJobAdmissionRefusal | null> => {\n if (!context.hostId) return { status: 400, error: AI_TRACKING_NO_SITE_COPY }\n const owner = await ownerOf(context.hostId)\n if (!owner || owner !== context.orgId) return { status: 404, error: 'Unknown site' }\n const host = await context.firestore.collection('hosts').doc(context.hostId).get()\n const refused = await aiTrackingAccessRefusal({ host, uid: context.uid ?? '', org: context.org }, { lockdown: deps.lockdown })\n if (refused) return { status: 403, error: refused }\n const read = aiTrackingRequestOf(context.inputs as Record<string, unknown>)\n return aiTrackingRequestEmpty(read.request) ? { status: 400, error: [AI_TRACKING_NOTHING_COPY, ...read.problems].join(' ') } : null\n }\n}\n\nexport const aiTrackingJobAdmission = createAiTrackingJobAdmission()\n\n/** Registers the tracking step and its door (AGL-3751). */\nexport function registerAiTrackingJob(): void {\n registerAiJobStep(AI_TRACKING_KIND, runAiJobTrackingStep, { minimumMs: AI_JOB_TRACKING_STEP_MINIMUM_MS })\n registerAiJobAdmission(AI_TRACKING_KIND, aiTrackingJobAdmission)\n}\n"],"names":["createResourceUid","hostRoleCanWrite","SCREEN_ROOT_PATH","GA_MEASUREMENT_ID_PATTERN","GOOGLE_ADS_ID_PATTERN","GTM_CONTAINER_ID_PATTERN","LINKEDIN_PARTNER_ID_PATTERN","META_PIXEL_ID_PATTERN","PINTEREST_TAG_ID_PATTERN","TIKTOK_PIXEL_ID_PATTERN","dropPluginSiteCache","getLockdownVerdict","resolveOrgIdForHost","FieldValue","registerAiJobAdmission","aiJobEditModel","aiLimitReview","aiUnspentOutcome","registerAiJobStep","AI_TRACKING_KIND","AI_JOB_TRACKING_STEP_MINIMUM_MS","AI_TRACKING_NO_SITE_COPY","AI_TRACKING_ROLE_COPY","AI_TRACKING_LOCKED_COPY","AI_TRACKING_NOTHING_COPY","AI_TRACKING_WHERE","AI_TRACKING_OUTPUT_ID","AI_TRACKING_TAGS","arg","path","label","pattern","shape","ask","named","AI_TRACKING_WHICH_COPY","TAG_ARGS","map","tag","ID_RULE","AI_TRACKING_ARGS_SCHEMA","type","properties","googleAnalytics","description","maxLength","googleTagManager","metaPixel","googleAds","linkedinInsight","tiktokPixel","pinterestTag","remove","items","enum","maxItems","length","additionalProperties","text","value","trim","aiTrackingRequestOf","inputs","set","problems","removing","Set","Array","isArray","one","String","has","push","capitalized","test","slice","request","charAt","toUpperCase","aiTrackingMissingInput","item","read","args","aiTrackingRequestEmpty","tried","filter","naming","words","byName","name","why","asks","join","Object","keys","getPath","data","split","reduce","at","key","undefined","aiTrackingChanges","changes","said","includes","aiTrackingWarnings","host","warnings","any","some","writeAiSiteTracking","firestore","input","hostRef","collection","doc","hostId","runTransaction","tx","get","exists","ok","error","roles","uid","changed","change","stored","after","JSON","parse","stringify","analytics","consent","leaf","pop","node","done","outboxId","updates","updatedAt","now","delete","update","outboxRef","paths","entireHost","createdAt","serverTimestamp","attempts","id","aiTrackingAccessRefusal","deps","locked","lockdown","staff","org","intent","aiTrackingOutputNote","notDone","changedAny","did","warn","not","createAiJobTrackingStep","write","dropCache","job","modelFor","model","unspent","failure","orgData","orgId","refused","createdBy","review","written","dropped","hostIds","reason","catch","complete","output","resource","hostSubdomain","note","outputs","runAiJobTrackingStep","createAiTrackingJobAdmission","ownerOf","context","status","owner","aiTrackingJobAdmission","registerAiTrackingJob","minimumMs"],"mappings":"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"}
|