@aglyn/plugins-ai 1.0.0-beta.229 → 1.0.0-beta.230
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/README.md +3 -3
- package/package.json +17 -17
- package/src/lib/activity/ai-activity-actions.js +7 -1
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-automation-zones.d.ts +38 -0
- package/src/lib/components/ai-automation-zones.js +5 -1
- package/src/lib/components/ai-automation-zones.js.map +1 -1
- package/src/lib/components/ai-brief-dialog.component.d.ts +20 -2
- package/src/lib/components/ai-brief-dialog.component.js +87 -8
- package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-campaign-create.component.d.ts +54 -0
- package/src/lib/components/ai-campaign-create.component.js +270 -0
- package/src/lib/components/ai-campaign-create.component.js.map +1 -0
- package/src/lib/components/ai-describe-automation.component.d.ts +8 -3
- package/src/lib/components/ai-describe-automation.component.js +15 -6
- package/src/lib/components/ai-describe-automation.component.js.map +1 -1
- package/src/lib/components/ai-describe-button.component.d.ts +20 -8
- package/src/lib/components/ai-describe-button.component.js +19 -36
- package/src/lib/components/ai-describe-button.component.js.map +1 -1
- package/src/lib/components/ai-describe-email.component.d.ts +31 -0
- package/src/lib/components/ai-describe-email.component.js +22 -0
- package/src/lib/components/ai-describe-email.component.js.map +1 -0
- package/src/lib/components/ai-describe-org-automation.component.d.ts +36 -0
- package/src/lib/components/ai-describe-org-automation.component.js +238 -0
- package/src/lib/components/ai-describe-org-automation.component.js.map +1 -0
- package/src/lib/components/ai-experiment-cards.component.d.ts +9 -1
- package/src/lib/components/ai-experiment-cards.component.js +104 -18
- package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
- package/src/lib/components/ai-explain-automation.component.js +1 -5
- package/src/lib/components/ai-explain-automation.component.js.map +1 -1
- package/src/lib/components/ai-funnel-zones.component.d.ts +61 -0
- package/src/lib/components/ai-funnel-zones.component.js +178 -0
- package/src/lib/components/ai-funnel-zones.component.js.map +1 -0
- package/src/lib/components/ai-insight-card.component.d.ts +42 -0
- package/src/lib/components/ai-insight-card.component.js +137 -0
- package/src/lib/components/ai-insight-card.component.js.map +1 -0
- package/src/lib/components/ai-insight-dialog.component.d.ts +6 -0
- package/src/lib/components/ai-insight-dialog.component.js +9 -3
- package/src/lib/components/ai-insight-dialog.component.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.d.ts +7 -2
- package/src/lib/components/ai-job-plan.component.js +35 -5
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-job-requests.d.ts +17 -1
- package/src/lib/components/ai-job-requests.js +39 -3
- package/src/lib/components/ai-job-requests.js.map +1 -1
- package/src/lib/components/ai-logic-zones.d.ts +64 -0
- package/src/lib/components/ai-logic-zones.js +18 -0
- package/src/lib/components/ai-logic-zones.js.map +1 -0
- package/src/lib/components/ai-logic.component.d.ts +77 -0
- package/src/lib/components/ai-logic.component.js +553 -0
- package/src/lib/components/ai-logic.component.js.map +1 -0
- package/src/lib/components/ai-marketing-insight.component.d.ts +30 -0
- package/src/lib/components/ai-marketing-insight.component.js +80 -0
- package/src/lib/components/ai-marketing-insight.component.js.map +1 -0
- package/src/lib/components/ai-media-create.component.d.ts +55 -0
- package/src/lib/components/ai-media-create.component.js +433 -0
- package/src/lib/components/ai-media-create.component.js.map +1 -0
- package/src/lib/components/ai-overlay-cards.component.d.ts +83 -0
- package/src/lib/components/ai-overlay-cards.component.js +505 -0
- package/src/lib/components/ai-overlay-cards.component.js.map +1 -0
- package/src/lib/components/ai-product-zones.d.ts +5 -0
- package/src/lib/components/ai-product-zones.js +1 -1
- package/src/lib/components/ai-product-zones.js.map +1 -1
- package/src/lib/components/ai-products-create-button.component.d.ts +42 -0
- package/src/lib/components/ai-products-create-button.component.js +67 -0
- package/src/lib/components/ai-products-create-button.component.js.map +1 -0
- package/src/lib/components/ai-products-hub-card.component.js +100 -81
- package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
- package/src/lib/components/ai-revise-automation.component.d.ts +16 -0
- package/src/lib/components/ai-revise-automation.component.js +207 -0
- package/src/lib/components/ai-revise-automation.component.js.map +1 -0
- package/src/lib/components/ai-site-build-page.component.js +140 -16
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-upsell-dialog.component.d.ts +53 -0
- package/src/lib/components/ai-upsell-dialog.component.js +177 -0
- package/src/lib/components/ai-upsell-dialog.component.js.map +1 -0
- package/src/lib/components/assist-build-card.component.d.ts +42 -0
- package/src/lib/components/assist-build-card.component.js +183 -0
- package/src/lib/components/assist-build-card.component.js.map +1 -0
- package/src/lib/components/assist-edit-canvas.d.ts +2 -0
- package/src/lib/components/assist-edit-canvas.js +56 -1
- package/src/lib/components/assist-edit-canvas.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.js +48 -3
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.js +51 -2
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/use-ai-job-run.d.ts +3 -14
- package/src/lib/components/use-ai-job-run.js +0 -50
- package/src/lib/components/use-ai-job-run.js.map +1 -1
- package/src/lib/jobs/ai-build-capabilities.d.ts +58 -0
- package/src/lib/jobs/ai-build-capabilities.js +256 -0
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -0
- package/src/lib/jobs/ai-build-unit-outcome.d.ts +52 -0
- package/src/lib/jobs/ai-build-unit-outcome.js +138 -0
- package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -0
- package/src/lib/jobs/ai-job-build-step.d.ts +144 -0
- package/src/lib/jobs/ai-job-build-step.js +657 -0
- package/src/lib/jobs/ai-job-build-step.js.map +1 -0
- package/src/lib/jobs/ai-job-draft-ids.js +9 -1
- package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.d.ts +14 -4
- package/src/lib/jobs/ai-job-drafts.js +25 -4
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-logic-step.d.ts +107 -0
- package/src/lib/jobs/ai-job-logic-step.js +395 -0
- package/src/lib/jobs/ai-job-logic-step.js.map +1 -0
- package/src/lib/jobs/ai-job-overlay-copy.d.ts +70 -0
- package/src/lib/jobs/ai-job-overlay-copy.js +178 -0
- package/src/lib/jobs/ai-job-overlay-copy.js.map +1 -0
- package/src/lib/jobs/ai-job-page-step.js +6 -6
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +26 -1
- package/src/lib/jobs/ai-job-plan-step.js +79 -11
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +15 -1
- package/src/lib/jobs/ai-job-site-step.js +200 -58
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-text-step.d.ts +22 -1
- package/src/lib/jobs/ai-job-text-step.js +12 -0
- package/src/lib/jobs/ai-job-text-step.js.map +1 -1
- package/src/lib/jobs/ai-job-workflow-step.d.ts +54 -4
- package/src/lib/jobs/ai-job-workflow-step.js +318 -13
- package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +80 -2
- package/src/lib/jobs/ai-jobs.js +377 -13
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-logic-records.d.ts +40 -0
- package/src/lib/jobs/ai-logic-records.js +55 -0
- package/src/lib/jobs/ai-logic-records.js.map +1 -0
- package/src/lib/jobs/ai-site-publish.d.ts +7 -0
- package/src/lib/jobs/ai-site-publish.js +9 -0
- package/src/lib/jobs/ai-site-publish.js.map +1 -1
- package/src/lib/jobs/ai-workflow-records.d.ts +4 -0
- package/src/lib/jobs/ai-workflow-records.js +40 -1
- package/src/lib/jobs/ai-workflow-records.js.map +1 -1
- package/src/lib/model/ai-automation-draft.d.ts +18 -0
- package/src/lib/model/ai-automation-draft.js +86 -0
- package/src/lib/model/ai-automation-draft.js.map +1 -1
- package/src/lib/model/ai-build-job.d.ts +201 -0
- package/src/lib/model/ai-build-job.js +471 -0
- package/src/lib/model/ai-build-job.js.map +1 -0
- package/src/lib/model/ai-build-plan.d.ts +61 -0
- package/src/lib/model/ai-build-plan.js +154 -9
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-build-progress.d.ts +56 -0
- package/src/lib/model/ai-build-progress.js +128 -0
- package/src/lib/model/ai-build-progress.js.map +1 -0
- package/src/lib/model/ai-insight.d.ts +20 -10
- package/src/lib/model/ai-insight.js +72 -9
- package/src/lib/model/ai-insight.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 +80 -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-logic-job.d.ts +108 -0
- package/src/lib/model/ai-logic-job.js +327 -0
- package/src/lib/model/ai-logic-job.js.map +1 -0
- package/src/lib/model/ai-media-credits.d.ts +52 -0
- package/src/lib/model/ai-media-credits.js +61 -0
- package/src/lib/model/ai-media-credits.js.map +1 -0
- package/src/lib/model/ai-overlay-copy.d.ts +114 -0
- package/src/lib/model/ai-overlay-copy.js +175 -0
- package/src/lib/model/ai-overlay-copy.js.map +1 -0
- package/src/lib/model/ai-site-build-progress.d.ts +20 -3
- package/src/lib/model/ai-site-build-progress.js +170 -10
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-svg.d.ts +69 -0
- package/src/lib/model/ai-svg.js +265 -0
- package/src/lib/model/ai-svg.js.map +1 -0
- package/src/lib/model/ai-usage-by-user.d.ts +3 -2
- package/src/lib/model/ai-usage-by-user.js.map +1 -1
- package/src/lib/model/ai-workflow-job.d.ts +92 -4
- package/src/lib/model/ai-workflow-job.js +124 -7
- package/src/lib/model/ai-workflow-job.js.map +1 -1
- package/src/lib/model/assist-build.d.ts +58 -0
- package/src/lib/model/assist-build.js +52 -0
- package/src/lib/model/assist-build.js.map +1 -0
- package/src/lib/model/assist-edit.d.ts +48 -3
- package/src/lib/model/assist-edit.js +48 -4
- package/src/lib/model/assist-edit.js.map +1 -1
- package/src/lib/plugin-config.js +2 -0
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/plugin.js +240 -11
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/catalog.d.ts +81 -4
- package/src/lib/providers/catalog.js +80 -9
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/providers/image-contract.d.ts +116 -0
- package/src/lib/providers/image-contract.js +74 -0
- package/src/lib/providers/image-contract.js.map +1 -0
- package/src/lib/providers/model-choice.js +11 -0
- package/src/lib/providers/model-choice.js.map +1 -1
- package/src/lib/providers/routing.js +18 -0
- package/src/lib/providers/routing.js.map +1 -1
- package/src/lib/providers/vertex-image.d.ts +121 -0
- package/src/lib/providers/vertex-image.js +312 -0
- package/src/lib/providers/vertex-image.js.map +1 -0
- package/src/lib/runtime/ai-doctrine-validators.d.ts +19 -0
- package/src/lib/runtime/ai-doctrine-validators.js +89 -0
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +11 -5
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.js +4 -0
- package/src/lib/runtime/ai-eval-live.js.map +1 -1
- package/src/lib/runtime/ai-eval.d.ts +15 -1
- package/src/lib/runtime/ai-eval.js +52 -1
- package/src/lib/runtime/ai-eval.js.map +1 -1
- package/src/lib/runtime/experiment-variant-copy.d.ts +34 -0
- package/src/lib/runtime/experiment-variant-copy.js +102 -0
- package/src/lib/runtime/experiment-variant-copy.js.map +1 -0
- package/src/lib/server/ai-experiment-versions.d.ts +51 -0
- package/src/lib/server/ai-experiment-versions.js +270 -0
- package/src/lib/server/ai-experiment-versions.js.map +1 -0
- package/src/lib/server/ai-jobs-resume.js +74 -4
- package/src/lib/server/ai-jobs-resume.js.map +1 -1
- package/src/lib/server/ai-media-image.d.ts +66 -0
- package/src/lib/server/ai-media-image.js +521 -0
- package/src/lib/server/ai-media-image.js.map +1 -0
- package/src/lib/server/ai-media-svg.d.ts +91 -0
- package/src/lib/server/ai-media-svg.js +186 -0
- package/src/lib/server/ai-media-svg.js.map +1 -0
- package/src/lib/server/assist-build.d.ts +54 -0
- package/src/lib/server/assist-build.js +121 -0
- package/src/lib/server/assist-build.js.map +1 -0
- package/src/lib/server/assist-chat.js +108 -12
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-edit.js +213 -3
- package/src/lib/server/assist-edit.js.map +1 -1
- package/src/lib/server/assist-view-context.js +242 -4
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/server.js +26 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/tools/ai-logic-tool.d.ts +27 -0
- package/src/lib/tools/ai-logic-tool.js +207 -0
- package/src/lib/tools/ai-logic-tool.js.map +1 -0
- package/src/lib/tools/ai-overlay-tool.d.ts +35 -0
- package/src/lib/tools/ai-overlay-tool.js +107 -0
- package/src/lib/tools/ai-overlay-tool.js.map +1 -0
- package/src/lib/tools/ai-workflow-tool.d.ts +11 -0
- package/src/lib/tools/ai-workflow-tool.js +31 -0
- package/src/lib/tools/ai-workflow-tool.js.map +1 -1
- package/src/lib/usage/assist-job-refund.d.ts +28 -1
- package/src/lib/usage/assist-job-refund.js +44 -9
- package/src/lib/usage/assist-job-refund.js.map +1 -1
- package/src/lib/usage/assist-usage.d.ts +5 -0
- package/src/lib/usage/assist-usage.js +2 -0
- package/src/lib/usage/assist-usage.js.map +1 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { HostFunction } from '@aglyn/aglyn/app-utils/functions';
|
|
18
|
+
import type { AiLogicSiteVariable } from '../model/ai-logic-job';
|
|
19
|
+
/**
|
|
20
|
+
* What a `logic` job reads (AGL-3603): the site's variables and function
|
|
21
|
+
* names, in the window the Functions & Variables page itself reads, and one
|
|
22
|
+
* saved function by id. Functions and variables are core documents
|
|
23
|
+
* (`hosts/{hostId}/functions`, `hosts/{hostId}/variables`), read through the
|
|
24
|
+
* Admin SDK as projections of the fields named here.
|
|
25
|
+
*/
|
|
26
|
+
type Firestore = FirebaseFirestore.Firestore;
|
|
27
|
+
/** Records of one kind a job reads: the logic page's own ceiling. */
|
|
28
|
+
export declare const AI_LOGIC_RECORDS_WINDOW = 100;
|
|
29
|
+
export interface AiLogicRecords {
|
|
30
|
+
variables: AiLogicSiteVariable[];
|
|
31
|
+
functions: string[];
|
|
32
|
+
}
|
|
33
|
+
/** The site's live variables (name, type, value) and function names. */
|
|
34
|
+
export declare function readAiLogicRecords(firestore: Firestore, hostId: string): Promise<AiLogicRecords>;
|
|
35
|
+
/** One saved function of the site, or `null` for one that does not exist or was deleted. */
|
|
36
|
+
export declare function readAiLogicFunction(firestore: Firestore, input: {
|
|
37
|
+
hostId: string;
|
|
38
|
+
id: string;
|
|
39
|
+
}): Promise<HostFunction | null>;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
17
|
+
/** Records of one kind a job reads: the logic page's own ceiling. */ export const AI_LOGIC_RECORDS_WINDOW = 100;
|
|
18
|
+
const live = (data)=>data['deletedAt'] == null;
|
|
19
|
+
/** The site's live variables (name, type, value) and function names. */ export async function readAiLogicRecords(firestore, hostId) {
|
|
20
|
+
const host = firestore.collection('hosts').doc(hostId);
|
|
21
|
+
const [variables, functions] = await Promise.all([
|
|
22
|
+
host.collection('variables').select('name', 'type', 'value', 'deletedAt').limit(AI_LOGIC_RECORDS_WINDOW).get(),
|
|
23
|
+
host.collection('functions').select('name', 'deletedAt').limit(AI_LOGIC_RECORDS_WINDOW).get()
|
|
24
|
+
]);
|
|
25
|
+
return {
|
|
26
|
+
variables: variables.docs.map((doc)=>{
|
|
27
|
+
var _doc_data;
|
|
28
|
+
return (_doc_data = doc.data()) != null ? _doc_data : {};
|
|
29
|
+
}).filter(live).filter((data)=>typeof data['name'] === 'string' && data['name']).map((data)=>({
|
|
30
|
+
name: String(data['name']),
|
|
31
|
+
type: typeof data['type'] === 'string' ? data['type'] : 'text',
|
|
32
|
+
value: typeof data['value'] === 'string' ? data['value'] : ''
|
|
33
|
+
})),
|
|
34
|
+
functions: functions.docs.map((doc)=>{
|
|
35
|
+
var _doc_data;
|
|
36
|
+
return (_doc_data = doc.data()) != null ? _doc_data : {};
|
|
37
|
+
}).filter(live).map((data)=>typeof data['name'] === 'string' ? data['name'].trim() : '').filter(Boolean)
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** One saved function of the site, or `null` for one that does not exist or was deleted. */ export async function readAiLogicFunction(firestore, input) {
|
|
41
|
+
var _snapshot_data;
|
|
42
|
+
const snapshot = await firestore.collection('hosts').doc(input.hostId).collection('functions').doc(input.id).get();
|
|
43
|
+
const data = snapshot.exists ? (_snapshot_data = snapshot.data()) != null ? _snapshot_data : {} : null;
|
|
44
|
+
if (!data || !live(data) || typeof data['name'] !== 'string') return null;
|
|
45
|
+
return _extends({
|
|
46
|
+
name: data['name'],
|
|
47
|
+
parameters: Array.isArray(data['parameters']) ? data['parameters'] : [],
|
|
48
|
+
variables: Array.isArray(data['variables']) ? data['variables'] : [],
|
|
49
|
+
operations: Array.isArray(data['operations']) ? data['operations'] : []
|
|
50
|
+
}, typeof data['returnValue'] === 'string' ? {
|
|
51
|
+
returnValue: data['returnValue']
|
|
52
|
+
} : {});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=ai-logic-records.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-logic-records.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { HostFunction } from '@aglyn/aglyn/app-utils/functions'\nimport type { AiLogicSiteVariable } from '../model/ai-logic-job'\n\n/**\n * What a `logic` job reads (AGL-3603): the site's variables and function\n * names, in the window the Functions & Variables page itself reads, and one\n * saved function by id. Functions and variables are core documents\n * (`hosts/{hostId}/functions`, `hosts/{hostId}/variables`), read through the\n * Admin SDK as projections of the fields named here.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\ntype Data = Record<string, unknown>\n\n/** Records of one kind a job reads: the logic page's own ceiling. */\nexport const AI_LOGIC_RECORDS_WINDOW = 100\n\nconst live = (data: Data) => data['deletedAt'] == null\n\nexport interface AiLogicRecords {\n variables: AiLogicSiteVariable[]\n functions: string[]\n}\n\n/** The site's live variables (name, type, value) and function names. */\nexport async function readAiLogicRecords(firestore: Firestore, hostId: string): Promise<AiLogicRecords> {\n const host = firestore.collection('hosts').doc(hostId)\n const [variables, functions] = await Promise.all([\n host.collection('variables').select('name', 'type', 'value', 'deletedAt').limit(AI_LOGIC_RECORDS_WINDOW).get(),\n host.collection('functions').select('name', 'deletedAt').limit(AI_LOGIC_RECORDS_WINDOW).get(),\n ])\n return {\n variables: variables.docs\n .map((doc) => (doc.data() ?? {}) as Data)\n .filter(live)\n .filter((data) => typeof data['name'] === 'string' && data['name'])\n .map((data) => ({\n name: String(data['name']),\n type: (typeof data['type'] === 'string' ? data['type'] : 'text') as AiLogicSiteVariable['type'],\n value: typeof data['value'] === 'string' ? data['value'] : '',\n })),\n functions: functions.docs\n .map((doc) => (doc.data() ?? {}) as Data)\n .filter(live)\n .map((data) => (typeof data['name'] === 'string' ? data['name'].trim() : ''))\n .filter(Boolean),\n }\n}\n\n/** One saved function of the site, or `null` for one that does not exist or was deleted. */\nexport async function readAiLogicFunction(\n firestore: Firestore,\n input: { hostId: string; id: string },\n): Promise<HostFunction | null> {\n const snapshot = await firestore.collection('hosts').doc(input.hostId).collection('functions').doc(input.id).get()\n const data = snapshot.exists ? ((snapshot.data() ?? {}) as Data) : null\n if (!data || !live(data) || typeof data['name'] !== 'string') return null\n return {\n name: data['name'],\n parameters: Array.isArray(data['parameters']) ? (data['parameters'] as HostFunction['parameters']) : [],\n variables: Array.isArray(data['variables']) ? (data['variables'] as HostFunction['variables']) : [],\n operations: Array.isArray(data['operations']) ? (data['operations'] as HostFunction['operations']) : [],\n ...(typeof data['returnValue'] === 'string' ? { returnValue: data['returnValue'] } : {}),\n }\n}\n"],"names":["AI_LOGIC_RECORDS_WINDOW","live","data","readAiLogicRecords","firestore","hostId","host","collection","doc","variables","functions","Promise","all","select","limit","get","docs","map","filter","name","String","type","value","trim","Boolean","readAiLogicFunction","input","snapshot","id","exists","parameters","Array","isArray","operations","returnValue"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAgBD,mEAAmE,GACnE,OAAO,MAAMA,0BAA0B,IAAG;AAE1C,MAAMC,OAAO,CAACC,OAAeA,IAAI,CAAC,YAAY,IAAI;AAOlD,sEAAsE,GACtE,OAAO,eAAeC,mBAAmBC,SAAoB,EAAEC,MAAc;IAC3E,MAAMC,OAAOF,UAAUG,UAAU,CAAC,SAASC,GAAG,CAACH;IAC/C,MAAM,CAACI,WAAWC,UAAU,GAAG,MAAMC,QAAQC,GAAG,CAAC;QAC/CN,KAAKC,UAAU,CAAC,aAAaM,MAAM,CAAC,QAAQ,QAAQ,SAAS,aAAaC,KAAK,CAACd,yBAAyBe,GAAG;QAC5GT,KAAKC,UAAU,CAAC,aAAaM,MAAM,CAAC,QAAQ,aAAaC,KAAK,CAACd,yBAAyBe,GAAG;KAC5F;IACD,OAAO;QACLN,WAAWA,UAAUO,IAAI,CACtBC,GAAG,CAAC,CAACT;gBAASA;oBAAAA,YAAAA,IAAIN,IAAI,cAARM,YAAc,CAAC;WAC7BU,MAAM,CAACjB,MACPiB,MAAM,CAAC,CAAChB,OAAS,OAAOA,IAAI,CAAC,OAAO,KAAK,YAAYA,IAAI,CAAC,OAAO,EACjEe,GAAG,CAAC,CAACf,OAAU,CAAA;gBACdiB,MAAMC,OAAOlB,IAAI,CAAC,OAAO;gBACzBmB,MAAO,OAAOnB,IAAI,CAAC,OAAO,KAAK,WAAWA,IAAI,CAAC,OAAO,GAAG;gBACzDoB,OAAO,OAAOpB,IAAI,CAAC,QAAQ,KAAK,WAAWA,IAAI,CAAC,QAAQ,GAAG;YAC7D,CAAA;QACFQ,WAAWA,UAAUM,IAAI,CACtBC,GAAG,CAAC,CAACT;gBAASA;oBAAAA,YAAAA,IAAIN,IAAI,cAARM,YAAc,CAAC;WAC7BU,MAAM,CAACjB,MACPgB,GAAG,CAAC,CAACf,OAAU,OAAOA,IAAI,CAAC,OAAO,KAAK,WAAWA,IAAI,CAAC,OAAO,CAACqB,IAAI,KAAK,IACxEL,MAAM,CAACM;IACZ;AACF;AAEA,0FAA0F,GAC1F,OAAO,eAAeC,oBACpBrB,SAAoB,EACpBsB,KAAqC;QAGJC;IADjC,MAAMA,WAAW,MAAMvB,UAAUG,UAAU,CAAC,SAASC,GAAG,CAACkB,MAAMrB,MAAM,EAAEE,UAAU,CAAC,aAAaC,GAAG,CAACkB,MAAME,EAAE,EAAEb,GAAG;IAChH,MAAMb,OAAOyB,SAASE,MAAM,IAAKF,iBAAAA,SAASzB,IAAI,cAAbyB,iBAAmB,CAAC,IAAc;IACnE,IAAI,CAACzB,QAAQ,CAACD,KAAKC,SAAS,OAAOA,IAAI,CAAC,OAAO,KAAK,UAAU,OAAO;IACrE,OAAO;QACLiB,MAAMjB,IAAI,CAAC,OAAO;QAClB4B,YAAYC,MAAMC,OAAO,CAAC9B,IAAI,CAAC,aAAa,IAAKA,IAAI,CAAC,aAAa,GAAkC,EAAE;QACvGO,WAAWsB,MAAMC,OAAO,CAAC9B,IAAI,CAAC,YAAY,IAAKA,IAAI,CAAC,YAAY,GAAiC,EAAE;QACnG+B,YAAYF,MAAMC,OAAO,CAAC9B,IAAI,CAAC,aAAa,IAAKA,IAAI,CAAC,aAAa,GAAkC,EAAE;OACnG,OAAOA,IAAI,CAAC,cAAc,KAAK,WAAW;QAAEgC,aAAahC,IAAI,CAAC,cAAc;IAAC,IAAI,CAAC;AAE1F"}
|
|
@@ -52,6 +52,13 @@ export interface AiSitePublishDeps {
|
|
|
52
52
|
sendSitePublished?: typeof sendGa4SitePublished;
|
|
53
53
|
dropCache?: typeof dropPluginSiteCache;
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Whether a build puts its pages live (AGL-3616): only where its request
|
|
57
|
+
* asked to (`publish`, set by Assist's proposal) AND the member ticked the
|
|
58
|
+
* plan card's box when confirming (`publishConfirmed`, set by the resume
|
|
59
|
+
* door). Every other build leaves drafts.
|
|
60
|
+
*/
|
|
61
|
+
export declare function aiJobPublishesBuild(job: Pick<AiJob, 'kind' | 'inputs'>): boolean;
|
|
55
62
|
/** Whether a finished job of this shape publishes what it built. */
|
|
56
63
|
export declare function aiJobPublishesSite(job: Pick<AiJob, 'kind' | 'inputs'>): boolean;
|
|
57
64
|
/**
|
|
@@ -22,6 +22,15 @@ import { decodeStoredNodes, encodeStoredNodes } from "@aglyn/aglyn/app-utils/sto
|
|
|
22
22
|
import { dropPluginSiteCache } from "@aglyn/aglyn/plugin-manager/plugin-site-cache";
|
|
23
23
|
import { sendGa4SitePublished } from "@aglyn/tenant-data-admin/server/ga4-measurement-protocol";
|
|
24
24
|
import { FieldValue } from "firebase-admin/firestore";
|
|
25
|
+
/**
|
|
26
|
+
* Whether a build puts its pages live (AGL-3616): only where its request
|
|
27
|
+
* asked to (`publish`, set by Assist's proposal) AND the member ticked the
|
|
28
|
+
* plan card's box when confirming (`publishConfirmed`, set by the resume
|
|
29
|
+
* door). Every other build leaves drafts.
|
|
30
|
+
*/ export function aiJobPublishesBuild(job) {
|
|
31
|
+
var _job_inputs, _job_inputs1;
|
|
32
|
+
return job.kind === 'build' && ((_job_inputs = job.inputs) == null ? void 0 : _job_inputs['publish']) === true && ((_job_inputs1 = job.inputs) == null ? void 0 : _job_inputs1['publishConfirmed']) === true;
|
|
33
|
+
}
|
|
25
34
|
/** Whether a finished job of this shape publishes what it built. */ export function aiJobPublishesSite(job) {
|
|
26
35
|
var _job_inputs;
|
|
27
36
|
return job.kind === 'site' && ((_job_inputs = job.inputs) == null ? void 0 : _job_inputs['autoConfirm']) === true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-site-publish.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 { isFirstPublishedRoute } from '@aglyn/aglyn/app-utils/analytics-events'\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { hostPublicOrigin } from '@aglyn/aglyn/app-utils/host-naming'\nimport {\n blockingRouteOwner,\n normalizeScreenSlug,\n SCREEN_ROOT_PATH,\n screenRoutePathToUrl,\n} from '@aglyn/aglyn/app-utils/screen-route'\nimport { decodeStoredNodes, encodeStoredNodes } from '@aglyn/aglyn/app-utils/stored-nodes'\nimport { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { sendGa4SitePublished } from '@aglyn/tenant-data-admin/server/ga4-measurement-protocol'\nimport { FieldValue, type Firestore } from 'firebase-admin/firestore'\nimport type { AiJob, AiJobOutput, AiJobSitePublish } from '../model/ai-jobs.types'\n\n/**\n * A guided site start publishes what it built (AGL-3596).\n *\n * A person who asked for \"a website\" in the guided start and watched it build\n * was then handed drafts: a site whose address still answered with the\n * starter, or with nothing. A `site` job started from the guided start (its\n * plan confirmed on the person's behalf, `inputs.autoConfirm`) therefore ends\n * by putting every page it built on the site, through the same writes a\n * person's Publish makes:\n *\n * - each page's address in the host's routing map, `publishedAt` on the page,\n * in ONE batch with the publish outbox entry that drops the live site's\n * cached pages (AGL-2575), the drop itself attempted at once;\n * - the home page takes `/` from the starter home page the site was created\n * with (AGL-3408), and an untouched starter goes to the trash rather than\n * staying beside it as a second \"Home\";\n * - the navigation entries the pages proposed are added to the header of\n * the layout they render inside, as a new live version of that layout;\n * - `site_published` once, server-side, as the scheduled publish sends it.\n *\n * A page that cannot be published — deleted meanwhile, no address, an address\n * another page holds — stays a draft and is reported with the plain sentence\n * for why. It never fails the job: the site is still built, and the person\n * fixes that one page in Pages. Every other job kind keeps producing drafts.\n *\n * The allowance is respected by construction: a draft is counted against the\n * plan's pages when it is written (`aiDraftBandRefusal`), so publishing\n * drafts adds none, and retiring the starter frees the one it held.\n */\n\nexport interface AiSitePublishDeps {\n sendSitePublished?: typeof sendGa4SitePublished\n dropCache?: typeof dropPluginSiteCache\n}\n\n/** Whether a finished job of this shape publishes what it built. */\nexport function aiJobPublishesSite(job: Pick<AiJob, 'kind' | 'inputs'>): boolean {\n return job.kind === 'site' && job.inputs?.['autoConfirm'] === true\n}\n\n/**\n * Where a site answers: its custom domain, else its subdomain under the\n * deployment's tenant apex — never a hard-coded platform host, so a\n * self-hosted deployment links to its own sites.\n */\nexport function aiSiteLiveUrl(host: { cname?: unknown; subdomain?: unknown }): string | null {\n const text = (value: unknown) => (typeof value === 'string' ? value.trim() : '')\n const origin = hostPublicOrigin({ cname: text(host.cname) || null, subdomain: text(host.subdomain) || null })\n return origin ? `${origin}/` : null\n}\n\n/** The sentences a page that stayed a draft is reported with. */\nexport const AI_SITE_PUBLISH_DELETED = 'It was deleted before it could be published.'\nexport const AI_SITE_PUBLISH_NO_ADDRESS = 'It has no address yet. Give it one in Pages and publish it.'\nexport const aiSitePublishTakenCopy = (path: string): string =>\n `Another page is already published at ${screenRoutePathToUrl(path)}. Give this one another address in Pages and publish it.`\nexport const AI_SITE_PUBLISH_FAILED = 'It could not be published just now. Publish it from Pages.'\n\n/** A navigation entry a page proposed, as the page step reports it. */\ninterface NavEntry {\n screenId: string\n label: string\n}\n\nfunction navEntriesOf(outputs: readonly AiJobOutput[]): NavEntry[] {\n const entries: NavEntry[] = []\n for (const output of outputs) {\n if (output.resource !== 'screen') continue\n const navigation = output.proposal?.['navigation'] as { label?: unknown } | undefined\n const label = typeof navigation?.label === 'string' ? navigation.label.trim() : ''\n if (label && !entries.some((entry) => entry.screenId === output.id)) {\n entries.push({ screenId: output.id, label })\n }\n }\n return entries\n}\n\ntype MutableNode = {\n $id: string\n componentId?: string\n parentId?: string\n props?: Record<string, unknown>\n nodes?: string[]\n [key: string]: unknown\n}\n\n/**\n * The layout's nodes with its links to the retired starter home pointed at\n * the new home, and a link for each proposed entry the header does not\n * already carry, placed in the header's toolbar after its existing links.\n * `null` when nothing changes, or when the layout has no toolbar to hold an\n * entry and no link to repoint.\n */\nexport function aiLayoutWithNavigation(\n nodes: NodesMap,\n input: { entries: readonly NavEntry[]; retiredHomeId?: string | null; homeId?: string | null },\n): NodesMap | null {\n const map: Record<string, MutableNode> = {}\n for (const [id, node] of Object.entries(nodes as unknown as Record<string, MutableNode>)) {\n map[id] = { ...node, ...(node.props ? { props: { ...node.props } } : {}), ...(node.nodes ? { nodes: [...node.nodes] } : {}) }\n }\n let changed = false\n const links = Object.values(map).filter((node) => node.componentId === 'muiScreenLink')\n if (input.retiredHomeId && input.homeId) {\n for (const link of links) {\n if (link.props?.['screenId'] === input.retiredHomeId) {\n link.props = { ...link.props, screenId: input.homeId }\n changed = true\n }\n }\n }\n const linked = new Set(links.map((link) => link.props?.['screenId']).filter(Boolean))\n const toolbar = Object.values(map).find((node) => node.componentId === 'muiToolbar')\n const missing = input.entries.filter((entry) => !linked.has(entry.screenId))\n if (toolbar && missing.length) {\n const children = toolbar.nodes ?? []\n // After the last link already in the toolbar (the brand), before the\n // search, the mode switch and the call to action.\n const lastLink = children.reduce(\n (at, id, index) => (map[id]?.componentId === 'muiScreenLink' ? index : at),\n -1,\n )\n const added = missing.map((entry) => {\n const id = `ai_nav_${createResourceUid()}`\n map[id] = {\n $id: id,\n componentId: 'muiScreenLink',\n pluginId: 'mui',\n parentId: toolbar.$id,\n props: { children: entry.label, screenId: entry.screenId, renderAs: 'link', color: 'inherit' },\n sx: { textDecoration: 'none', whiteSpace: 'nowrap' },\n nodes: [],\n }\n return id\n })\n toolbar.nodes = [...children.slice(0, lastLink + 1), ...added, ...children.slice(lastLink + 1)]\n changed = true\n }\n return changed ? (map as unknown as NodesMap) : null\n}\n\n/**\n * Publishes the pages a guided site start built. Never throws for a page; a\n * Firestore failure of the publish write itself reports every page as a\n * draft, for the same reason, and is logged.\n */\nexport async function aiPublishGuidedSite(\n firestore: Firestore,\n input: { job: Pick<AiJob, '$id' | 'orgId' | 'hostId'>; outputs: readonly AiJobOutput[]; now: Date },\n deps: AiSitePublishDeps = {},\n): Promise<AiJobSitePublish> {\n const sendSitePublished = deps.sendSitePublished ?? sendGa4SitePublished\n const dropCache = deps.dropCache ?? dropPluginSiteCache\n const { job, now } = input\n const hostId = job.hostId ?? ''\n const pages = input.outputs.filter(\n (output, index, all) =>\n output.resource === 'screen' &&\n (output.hostId ?? hostId) === hostId &&\n all.findIndex((other) => other.resource === 'screen' && other.id === output.id) === index,\n )\n const hostRef = firestore.collection('hosts').doc(hostId)\n const host = await hostRef.get()\n const result: AiJobSitePublish = {\n liveUrl: aiSiteLiveUrl({ cname: host.get('cname'), subdomain: host.get('subdomain') }),\n published: [],\n drafts: [],\n }\n if (!host.exists || !pages.length) return result\n\n const before = { ...((host.get('screens') ?? {}) as Record<string, string>) }\n const placeholder = (host.get('defaultHomeScreenId') as string | undefined) || null\n const routing = { ...before }\n const entries: Record<string, string> = {}\n const accepted: Array<{ id: string; label: string; path: string; slug: string }> = []\n for (const page of pages) {\n const snapshot = await hostRef.collection('screens').doc(page.id).get()\n const label = String(snapshot.get('displayName') ?? page.label ?? '') || page.label\n if (!snapshot.exists || snapshot.get('deletedAt') != null) {\n result.drafts.push({ id: page.id, label, reason: AI_SITE_PUBLISH_DELETED })\n continue\n }\n const slug = normalizeScreenSlug(String(snapshot.get('slug') ?? ''))\n if (!slug) {\n result.drafts.push({ id: page.id, label, reason: AI_SITE_PUBLISH_NO_ADDRESS })\n continue\n }\n const owner = blockingRouteOwner(routing, slug, placeholder)\n if (owner && owner !== page.id) {\n result.drafts.push({ id: page.id, label, reason: aiSitePublishTakenCopy(slug) })\n continue\n }\n routing[page.id] = slug\n entries[page.id] = slug\n accepted.push({ id: page.id, label, path: slug, slug })\n }\n if (!accepted.length) return result\n\n // The first real home page replaces the placeholder in the same write.\n const home = accepted.find((page) => page.path === SCREEN_ROOT_PATH) ?? null\n const releases =\n home && placeholder && placeholder !== home.id && before[placeholder] === SCREEN_ROOT_PATH\n ? placeholder\n : null\n // An untouched starter home (still its one first version) is retired to the\n // trash; one its owner edited stays in Pages as their draft.\n const retire = releases\n ? (await hostRef.collection('screens').doc(releases).collection('versions').limit(2).get()).size <= 1\n : false\n\n // The header of the layout the pages render inside learns their entries.\n const layoutWrite = await aiNavigationLayoutWrite(firestore, {\n hostId,\n pageIds: accepted.map((page) => page.id),\n entries: navEntriesOf(pages).filter((entry) => entries[entry.screenId]),\n retiredHomeId: releases,\n homeId: home?.id ?? null,\n jobId: job.$id,\n now,\n }).catch((error: unknown) => {\n console.error('ai site publish: navigation not applied', { hostId, jobId: job.$id, error })\n return null\n })\n\n const paths = [\n ...new Set(\n [\n ...Object.values(entries),\n ...(releases ? [SCREEN_ROOT_PATH] : []),\n ].map((path) => screenRoutePathToUrl(path)),\n ),\n ]\n const batch = firestore.batch()\n const hostUpdates: Record<string, unknown> = { updatedAt: now }\n for (const [id, path] of Object.entries(entries)) hostUpdates[`screens.${id}`] = path\n if (releases) {\n hostUpdates[`screens.${releases}`] = FieldValue.delete()\n hostUpdates['defaultHomeScreenId'] = FieldValue.delete()\n batch.set(\n hostRef.collection('screens').doc(releases),\n { publishedAt: FieldValue.delete(), ...(retire ? { deletedAt: now } : {}) },\n { merge: true },\n )\n } else if (placeholder && entries[placeholder]) {\n hostUpdates['defaultHomeScreenId'] = FieldValue.delete()\n }\n batch.update(hostRef, hostUpdates)\n for (const page of accepted) {\n batch.set(hostRef.collection('screens').doc(page.id), { slug: page.slug, publishedAt: now }, { merge: true })\n }\n if (layoutWrite) {\n batch.create(layoutWrite.versionRef, layoutWrite.version)\n batch.set(layoutWrite.layoutRef, { versionId: layoutWrite.versionRef.id, updatedAt: now }, { merge: true })\n }\n // The announce, written down in the publish's own batch (AGL-2575): a\n // layout change reaches every page, so it asks for the whole site.\n const outboxRef = firestore.collection('publishOutbox').doc(createResourceUid())\n batch.set(outboxRef, {\n hostId,\n paths: paths.length ? paths : [SCREEN_ROOT_PATH],\n createdAt: FieldValue.serverTimestamp(),\n attempts: 0,\n ...(layoutWrite ? { entireHost: true } : {}),\n })\n try {\n await batch.commit()\n } catch (error) {\n console.error('ai site publish failed', { hostId, jobId: job.$id, error })\n for (const page of accepted) result.drafts.push({ id: page.id, label: page.label, reason: AI_SITE_PUBLISH_FAILED })\n return result\n }\n result.published = accepted.map(({ id, label, path }) => ({ id, label, path: screenRoutePathToUrl(path) }))\n\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({\n hostIds: [hostId],\n reason: 'guided AI site start published',\n ...(layoutWrite ? {} : { paths: { [hostId]: paths } }),\n })\n if (dropped.complete) await outboxRef.delete().catch(() => undefined)\n // A site that came alive with nobody's browser on the publish (AGL-1589).\n await sendSitePublished({ hostId, firstPublish: isFirstPublishedRoute(before, placeholder) })\n return result\n}\n\ninterface LayoutWrite {\n layoutRef: FirebaseFirestore.DocumentReference\n versionRef: FirebaseFirestore.DocumentReference\n version: Record<string, unknown>\n}\n\n/**\n * The new live version of the layout the published pages render inside, with\n * their navigation entries and the home link moved off a retired starter;\n * `null` when there is nothing to change or no one layout to change.\n */\nasync function aiNavigationLayoutWrite(\n firestore: Firestore,\n input: {\n hostId: string\n pageIds: readonly string[]\n entries: readonly NavEntry[]\n retiredHomeId: string | null\n homeId: string | null\n jobId: string\n now: Date\n },\n): Promise<LayoutWrite | null> {\n if (!input.entries.length && !input.retiredHomeId) return null\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n const layoutIds = new Set<string>()\n for (const id of input.pageIds) {\n const screen = await hostRef.collection('screens').doc(id).get()\n const versionId = screen.get('versionId')\n const version =\n typeof versionId === 'string' && versionId\n ? await hostRef.collection('screens').doc(id).collection('versions').doc(versionId).get()\n : null\n const layoutId = version?.get('layoutId') ?? screen.get('layoutId')\n if (typeof layoutId === 'string' && layoutId) layoutIds.add(layoutId)\n }\n if (layoutIds.size !== 1) return null\n const [layoutId] = [...layoutIds]\n const layoutRef = hostRef.collection('layouts').doc(layoutId)\n const layout = await layoutRef.get()\n const currentVersionId = layout.get('versionId')\n if (!layout.exists || layout.get('deletedAt') != null || typeof currentVersionId !== 'string') return null\n const current = await layoutRef.collection('versions').doc(currentVersionId).get()\n const nodes = current.exists ? decodeStoredNodes<NodesMap>(current.get('nodes')) : null\n if (!nodes) return null\n const next = aiLayoutWithNavigation(nodes, input)\n const packed = next ? encodeStoredNodes(next) : null\n if (!next || !packed) return null\n const versionRef = layoutRef.collection('versions').doc(createResourceUid())\n return {\n layoutRef,\n versionRef,\n version: {\n layoutId,\n hostId: input.hostId,\n displayName: 'Navigation for the new pages',\n nodes: Buffer.from(packed),\n createdAt: input.now,\n updatedAt: input.now,\n aiJobId: input.jobId,\n },\n }\n}\n"],"names":["isFirstPublishedRoute","createResourceUid","hostPublicOrigin","blockingRouteOwner","normalizeScreenSlug","SCREEN_ROOT_PATH","screenRoutePathToUrl","decodeStoredNodes","encodeStoredNodes","dropPluginSiteCache","sendGa4SitePublished","FieldValue","aiJobPublishesSite","job","kind","inputs","aiSiteLiveUrl","host","text","value","trim","origin","cname","subdomain","AI_SITE_PUBLISH_DELETED","AI_SITE_PUBLISH_NO_ADDRESS","aiSitePublishTakenCopy","path","AI_SITE_PUBLISH_FAILED","navEntriesOf","outputs","entries","output","resource","navigation","proposal","label","some","entry","screenId","id","push","aiLayoutWithNavigation","nodes","input","map","node","Object","props","changed","links","values","filter","componentId","retiredHomeId","homeId","link","linked","Set","Boolean","toolbar","find","missing","has","length","children","lastLink","reduce","at","index","added","$id","pluginId","parentId","renderAs","color","sx","textDecoration","whiteSpace","slice","aiPublishGuidedSite","firestore","deps","accepted","sendSitePublished","dropCache","now","hostId","pages","all","findIndex","other","hostRef","collection","doc","get","result","liveUrl","published","drafts","exists","before","placeholder","routing","page","snapshot","String","reason","slug","owner","home","releases","retire","limit","size","layoutWrite","aiNavigationLayoutWrite","pageIds","jobId","catch","error","console","paths","batch","hostUpdates","updatedAt","delete","set","publishedAt","deletedAt","merge","update","create","versionRef","version","layoutRef","versionId","outboxRef","createdAt","serverTimestamp","attempts","entireHost","commit","dropped","hostIds","complete","undefined","firstPublish","layoutIds","screen","layoutId","add","layout","currentVersionId","current","next","packed","displayName","Buffer","from","aiJobId"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-site-publish.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 { isFirstPublishedRoute } from '@aglyn/aglyn/app-utils/analytics-events'\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { hostPublicOrigin } from '@aglyn/aglyn/app-utils/host-naming'\nimport {\n blockingRouteOwner,\n normalizeScreenSlug,\n SCREEN_ROOT_PATH,\n screenRoutePathToUrl,\n} from '@aglyn/aglyn/app-utils/screen-route'\nimport { decodeStoredNodes, encodeStoredNodes } from '@aglyn/aglyn/app-utils/stored-nodes'\nimport { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { sendGa4SitePublished } from '@aglyn/tenant-data-admin/server/ga4-measurement-protocol'\nimport { FieldValue, type Firestore } from 'firebase-admin/firestore'\nimport type { AiJob, AiJobOutput, AiJobSitePublish } from '../model/ai-jobs.types'\n\n/**\n * A guided site start publishes what it built (AGL-3596).\n *\n * A person who asked for \"a website\" in the guided start and watched it build\n * was then handed drafts: a site whose address still answered with the\n * starter, or with nothing. A `site` job started from the guided start (its\n * plan confirmed on the person's behalf, `inputs.autoConfirm`) therefore ends\n * by putting every page it built on the site, through the same writes a\n * person's Publish makes:\n *\n * - each page's address in the host's routing map, `publishedAt` on the page,\n * in ONE batch with the publish outbox entry that drops the live site's\n * cached pages (AGL-2575), the drop itself attempted at once;\n * - the home page takes `/` from the starter home page the site was created\n * with (AGL-3408), and an untouched starter goes to the trash rather than\n * staying beside it as a second \"Home\";\n * - the navigation entries the pages proposed are added to the header of\n * the layout they render inside, as a new live version of that layout;\n * - `site_published` once, server-side, as the scheduled publish sends it.\n *\n * A page that cannot be published — deleted meanwhile, no address, an address\n * another page holds — stays a draft and is reported with the plain sentence\n * for why. It never fails the job: the site is still built, and the person\n * fixes that one page in Pages. Every other job kind keeps producing drafts.\n *\n * The allowance is respected by construction: a draft is counted against the\n * plan's pages when it is written (`aiDraftBandRefusal`), so publishing\n * drafts adds none, and retiring the starter frees the one it held.\n */\n\nexport interface AiSitePublishDeps {\n sendSitePublished?: typeof sendGa4SitePublished\n dropCache?: typeof dropPluginSiteCache\n}\n\n/**\n * Whether a build puts its pages live (AGL-3616): only where its request\n * asked to (`publish`, set by Assist's proposal) AND the member ticked the\n * plan card's box when confirming (`publishConfirmed`, set by the resume\n * door). Every other build leaves drafts.\n */\nexport function aiJobPublishesBuild(job: Pick<AiJob, 'kind' | 'inputs'>): boolean {\n return job.kind === 'build' && job.inputs?.['publish'] === true && job.inputs?.['publishConfirmed'] === true\n}\n\n/** Whether a finished job of this shape publishes what it built. */\nexport function aiJobPublishesSite(job: Pick<AiJob, 'kind' | 'inputs'>): boolean {\n return job.kind === 'site' && job.inputs?.['autoConfirm'] === true\n}\n\n/**\n * Where a site answers: its custom domain, else its subdomain under the\n * deployment's tenant apex — never a hard-coded platform host, so a\n * self-hosted deployment links to its own sites.\n */\nexport function aiSiteLiveUrl(host: { cname?: unknown; subdomain?: unknown }): string | null {\n const text = (value: unknown) => (typeof value === 'string' ? value.trim() : '')\n const origin = hostPublicOrigin({ cname: text(host.cname) || null, subdomain: text(host.subdomain) || null })\n return origin ? `${origin}/` : null\n}\n\n/** The sentences a page that stayed a draft is reported with. */\nexport const AI_SITE_PUBLISH_DELETED = 'It was deleted before it could be published.'\nexport const AI_SITE_PUBLISH_NO_ADDRESS = 'It has no address yet. Give it one in Pages and publish it.'\nexport const aiSitePublishTakenCopy = (path: string): string =>\n `Another page is already published at ${screenRoutePathToUrl(path)}. Give this one another address in Pages and publish it.`\nexport const AI_SITE_PUBLISH_FAILED = 'It could not be published just now. Publish it from Pages.'\n\n/** A navigation entry a page proposed, as the page step reports it. */\ninterface NavEntry {\n screenId: string\n label: string\n}\n\nfunction navEntriesOf(outputs: readonly AiJobOutput[]): NavEntry[] {\n const entries: NavEntry[] = []\n for (const output of outputs) {\n if (output.resource !== 'screen') continue\n const navigation = output.proposal?.['navigation'] as { label?: unknown } | undefined\n const label = typeof navigation?.label === 'string' ? navigation.label.trim() : ''\n if (label && !entries.some((entry) => entry.screenId === output.id)) {\n entries.push({ screenId: output.id, label })\n }\n }\n return entries\n}\n\ntype MutableNode = {\n $id: string\n componentId?: string\n parentId?: string\n props?: Record<string, unknown>\n nodes?: string[]\n [key: string]: unknown\n}\n\n/**\n * The layout's nodes with its links to the retired starter home pointed at\n * the new home, and a link for each proposed entry the header does not\n * already carry, placed in the header's toolbar after its existing links.\n * `null` when nothing changes, or when the layout has no toolbar to hold an\n * entry and no link to repoint.\n */\nexport function aiLayoutWithNavigation(\n nodes: NodesMap,\n input: { entries: readonly NavEntry[]; retiredHomeId?: string | null; homeId?: string | null },\n): NodesMap | null {\n const map: Record<string, MutableNode> = {}\n for (const [id, node] of Object.entries(nodes as unknown as Record<string, MutableNode>)) {\n map[id] = { ...node, ...(node.props ? { props: { ...node.props } } : {}), ...(node.nodes ? { nodes: [...node.nodes] } : {}) }\n }\n let changed = false\n const links = Object.values(map).filter((node) => node.componentId === 'muiScreenLink')\n if (input.retiredHomeId && input.homeId) {\n for (const link of links) {\n if (link.props?.['screenId'] === input.retiredHomeId) {\n link.props = { ...link.props, screenId: input.homeId }\n changed = true\n }\n }\n }\n const linked = new Set(links.map((link) => link.props?.['screenId']).filter(Boolean))\n const toolbar = Object.values(map).find((node) => node.componentId === 'muiToolbar')\n const missing = input.entries.filter((entry) => !linked.has(entry.screenId))\n if (toolbar && missing.length) {\n const children = toolbar.nodes ?? []\n // After the last link already in the toolbar (the brand), before the\n // search, the mode switch and the call to action.\n const lastLink = children.reduce(\n (at, id, index) => (map[id]?.componentId === 'muiScreenLink' ? index : at),\n -1,\n )\n const added = missing.map((entry) => {\n const id = `ai_nav_${createResourceUid()}`\n map[id] = {\n $id: id,\n componentId: 'muiScreenLink',\n pluginId: 'mui',\n parentId: toolbar.$id,\n props: { children: entry.label, screenId: entry.screenId, renderAs: 'link', color: 'inherit' },\n sx: { textDecoration: 'none', whiteSpace: 'nowrap' },\n nodes: [],\n }\n return id\n })\n toolbar.nodes = [...children.slice(0, lastLink + 1), ...added, ...children.slice(lastLink + 1)]\n changed = true\n }\n return changed ? (map as unknown as NodesMap) : null\n}\n\n/**\n * Publishes the pages a guided site start built. Never throws for a page; a\n * Firestore failure of the publish write itself reports every page as a\n * draft, for the same reason, and is logged.\n */\nexport async function aiPublishGuidedSite(\n firestore: Firestore,\n input: { job: Pick<AiJob, '$id' | 'orgId' | 'hostId'>; outputs: readonly AiJobOutput[]; now: Date },\n deps: AiSitePublishDeps = {},\n): Promise<AiJobSitePublish> {\n const sendSitePublished = deps.sendSitePublished ?? sendGa4SitePublished\n const dropCache = deps.dropCache ?? dropPluginSiteCache\n const { job, now } = input\n const hostId = job.hostId ?? ''\n const pages = input.outputs.filter(\n (output, index, all) =>\n output.resource === 'screen' &&\n (output.hostId ?? hostId) === hostId &&\n all.findIndex((other) => other.resource === 'screen' && other.id === output.id) === index,\n )\n const hostRef = firestore.collection('hosts').doc(hostId)\n const host = await hostRef.get()\n const result: AiJobSitePublish = {\n liveUrl: aiSiteLiveUrl({ cname: host.get('cname'), subdomain: host.get('subdomain') }),\n published: [],\n drafts: [],\n }\n if (!host.exists || !pages.length) return result\n\n const before = { ...((host.get('screens') ?? {}) as Record<string, string>) }\n const placeholder = (host.get('defaultHomeScreenId') as string | undefined) || null\n const routing = { ...before }\n const entries: Record<string, string> = {}\n const accepted: Array<{ id: string; label: string; path: string; slug: string }> = []\n for (const page of pages) {\n const snapshot = await hostRef.collection('screens').doc(page.id).get()\n const label = String(snapshot.get('displayName') ?? page.label ?? '') || page.label\n if (!snapshot.exists || snapshot.get('deletedAt') != null) {\n result.drafts.push({ id: page.id, label, reason: AI_SITE_PUBLISH_DELETED })\n continue\n }\n const slug = normalizeScreenSlug(String(snapshot.get('slug') ?? ''))\n if (!slug) {\n result.drafts.push({ id: page.id, label, reason: AI_SITE_PUBLISH_NO_ADDRESS })\n continue\n }\n const owner = blockingRouteOwner(routing, slug, placeholder)\n if (owner && owner !== page.id) {\n result.drafts.push({ id: page.id, label, reason: aiSitePublishTakenCopy(slug) })\n continue\n }\n routing[page.id] = slug\n entries[page.id] = slug\n accepted.push({ id: page.id, label, path: slug, slug })\n }\n if (!accepted.length) return result\n\n // The first real home page replaces the placeholder in the same write.\n const home = accepted.find((page) => page.path === SCREEN_ROOT_PATH) ?? null\n const releases =\n home && placeholder && placeholder !== home.id && before[placeholder] === SCREEN_ROOT_PATH\n ? placeholder\n : null\n // An untouched starter home (still its one first version) is retired to the\n // trash; one its owner edited stays in Pages as their draft.\n const retire = releases\n ? (await hostRef.collection('screens').doc(releases).collection('versions').limit(2).get()).size <= 1\n : false\n\n // The header of the layout the pages render inside learns their entries.\n const layoutWrite = await aiNavigationLayoutWrite(firestore, {\n hostId,\n pageIds: accepted.map((page) => page.id),\n entries: navEntriesOf(pages).filter((entry) => entries[entry.screenId]),\n retiredHomeId: releases,\n homeId: home?.id ?? null,\n jobId: job.$id,\n now,\n }).catch((error: unknown) => {\n console.error('ai site publish: navigation not applied', { hostId, jobId: job.$id, error })\n return null\n })\n\n const paths = [\n ...new Set(\n [\n ...Object.values(entries),\n ...(releases ? [SCREEN_ROOT_PATH] : []),\n ].map((path) => screenRoutePathToUrl(path)),\n ),\n ]\n const batch = firestore.batch()\n const hostUpdates: Record<string, unknown> = { updatedAt: now }\n for (const [id, path] of Object.entries(entries)) hostUpdates[`screens.${id}`] = path\n if (releases) {\n hostUpdates[`screens.${releases}`] = FieldValue.delete()\n hostUpdates['defaultHomeScreenId'] = FieldValue.delete()\n batch.set(\n hostRef.collection('screens').doc(releases),\n { publishedAt: FieldValue.delete(), ...(retire ? { deletedAt: now } : {}) },\n { merge: true },\n )\n } else if (placeholder && entries[placeholder]) {\n hostUpdates['defaultHomeScreenId'] = FieldValue.delete()\n }\n batch.update(hostRef, hostUpdates)\n for (const page of accepted) {\n batch.set(hostRef.collection('screens').doc(page.id), { slug: page.slug, publishedAt: now }, { merge: true })\n }\n if (layoutWrite) {\n batch.create(layoutWrite.versionRef, layoutWrite.version)\n batch.set(layoutWrite.layoutRef, { versionId: layoutWrite.versionRef.id, updatedAt: now }, { merge: true })\n }\n // The announce, written down in the publish's own batch (AGL-2575): a\n // layout change reaches every page, so it asks for the whole site.\n const outboxRef = firestore.collection('publishOutbox').doc(createResourceUid())\n batch.set(outboxRef, {\n hostId,\n paths: paths.length ? paths : [SCREEN_ROOT_PATH],\n createdAt: FieldValue.serverTimestamp(),\n attempts: 0,\n ...(layoutWrite ? { entireHost: true } : {}),\n })\n try {\n await batch.commit()\n } catch (error) {\n console.error('ai site publish failed', { hostId, jobId: job.$id, error })\n for (const page of accepted) result.drafts.push({ id: page.id, label: page.label, reason: AI_SITE_PUBLISH_FAILED })\n return result\n }\n result.published = accepted.map(({ id, label, path }) => ({ id, label, path: screenRoutePathToUrl(path) }))\n\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({\n hostIds: [hostId],\n reason: 'guided AI site start published',\n ...(layoutWrite ? {} : { paths: { [hostId]: paths } }),\n })\n if (dropped.complete) await outboxRef.delete().catch(() => undefined)\n // A site that came alive with nobody's browser on the publish (AGL-1589).\n await sendSitePublished({ hostId, firstPublish: isFirstPublishedRoute(before, placeholder) })\n return result\n}\n\ninterface LayoutWrite {\n layoutRef: FirebaseFirestore.DocumentReference\n versionRef: FirebaseFirestore.DocumentReference\n version: Record<string, unknown>\n}\n\n/**\n * The new live version of the layout the published pages render inside, with\n * their navigation entries and the home link moved off a retired starter;\n * `null` when there is nothing to change or no one layout to change.\n */\nasync function aiNavigationLayoutWrite(\n firestore: Firestore,\n input: {\n hostId: string\n pageIds: readonly string[]\n entries: readonly NavEntry[]\n retiredHomeId: string | null\n homeId: string | null\n jobId: string\n now: Date\n },\n): Promise<LayoutWrite | null> {\n if (!input.entries.length && !input.retiredHomeId) return null\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n const layoutIds = new Set<string>()\n for (const id of input.pageIds) {\n const screen = await hostRef.collection('screens').doc(id).get()\n const versionId = screen.get('versionId')\n const version =\n typeof versionId === 'string' && versionId\n ? await hostRef.collection('screens').doc(id).collection('versions').doc(versionId).get()\n : null\n const layoutId = version?.get('layoutId') ?? screen.get('layoutId')\n if (typeof layoutId === 'string' && layoutId) layoutIds.add(layoutId)\n }\n if (layoutIds.size !== 1) return null\n const [layoutId] = [...layoutIds]\n const layoutRef = hostRef.collection('layouts').doc(layoutId)\n const layout = await layoutRef.get()\n const currentVersionId = layout.get('versionId')\n if (!layout.exists || layout.get('deletedAt') != null || typeof currentVersionId !== 'string') return null\n const current = await layoutRef.collection('versions').doc(currentVersionId).get()\n const nodes = current.exists ? decodeStoredNodes<NodesMap>(current.get('nodes')) : null\n if (!nodes) return null\n const next = aiLayoutWithNavigation(nodes, input)\n const packed = next ? encodeStoredNodes(next) : null\n if (!next || !packed) return null\n const versionRef = layoutRef.collection('versions').doc(createResourceUid())\n return {\n layoutRef,\n versionRef,\n version: {\n layoutId,\n hostId: input.hostId,\n displayName: 'Navigation for the new pages',\n nodes: Buffer.from(packed),\n createdAt: input.now,\n updatedAt: input.now,\n aiJobId: input.jobId,\n },\n }\n}\n"],"names":["isFirstPublishedRoute","createResourceUid","hostPublicOrigin","blockingRouteOwner","normalizeScreenSlug","SCREEN_ROOT_PATH","screenRoutePathToUrl","decodeStoredNodes","encodeStoredNodes","dropPluginSiteCache","sendGa4SitePublished","FieldValue","aiJobPublishesBuild","job","kind","inputs","aiJobPublishesSite","aiSiteLiveUrl","host","text","value","trim","origin","cname","subdomain","AI_SITE_PUBLISH_DELETED","AI_SITE_PUBLISH_NO_ADDRESS","aiSitePublishTakenCopy","path","AI_SITE_PUBLISH_FAILED","navEntriesOf","outputs","entries","output","resource","navigation","proposal","label","some","entry","screenId","id","push","aiLayoutWithNavigation","nodes","input","map","node","Object","props","changed","links","values","filter","componentId","retiredHomeId","homeId","link","linked","Set","Boolean","toolbar","find","missing","has","length","children","lastLink","reduce","at","index","added","$id","pluginId","parentId","renderAs","color","sx","textDecoration","whiteSpace","slice","aiPublishGuidedSite","firestore","deps","accepted","sendSitePublished","dropCache","now","hostId","pages","all","findIndex","other","hostRef","collection","doc","get","result","liveUrl","published","drafts","exists","before","placeholder","routing","page","snapshot","String","reason","slug","owner","home","releases","retire","limit","size","layoutWrite","aiNavigationLayoutWrite","pageIds","jobId","catch","error","console","paths","batch","hostUpdates","updatedAt","delete","set","publishedAt","deletedAt","merge","update","create","versionRef","version","layoutRef","versionId","outboxRef","createdAt","serverTimestamp","attempts","entireHost","commit","dropped","hostIds","complete","undefined","firstPublish","layoutIds","screen","layoutId","add","layout","currentVersionId","current","next","packed","displayName","Buffer","from","aiJobId"],"mappings":"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"}
|
|
@@ -49,6 +49,10 @@ export declare function readAiAutomationRecords(firestore: Firestore, input: {
|
|
|
49
49
|
crm: boolean;
|
|
50
50
|
only?: ReadonlyArray<keyof AiAutomationRecords>;
|
|
51
51
|
}): Promise<AiAutomationRecords>;
|
|
52
|
+
export declare function readAiOrgAutomationRecords(firestore: Firestore, input: {
|
|
53
|
+
orgId: string;
|
|
54
|
+
crm: boolean;
|
|
55
|
+
}): Promise<AiAutomationRecords>;
|
|
52
56
|
/** The site's functions, for a workflow outline that says which of its calls still resolve. */
|
|
53
57
|
export declare function readAiWorkflowFunctions(firestore: Firestore, hostId: string): Promise<AiAutomationNamedRecord[]>;
|
|
54
58
|
export type AiWorkflowTarget = {
|
|
@@ -17,7 +17,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
17
17
|
*/ import { actionRunResult } from "@aglyn/aglyn/app-utils/activity-presenter";
|
|
18
18
|
import { isFormArchived } from "@aglyn/aglyn/app-utils/forms";
|
|
19
19
|
import { pluginRecordIndex } from "@aglyn/aglyn/plugin-manager/plugin-record-index";
|
|
20
|
-
import { visibleToHost } from "@aglyn/aglyn/app-utils/scope-tokens";
|
|
20
|
+
import { ORG_SCOPE_TOKEN, scopeCovers, visibleToHost } from "@aglyn/aglyn/app-utils/scope-tokens";
|
|
21
21
|
import { listOrgContainers } from "@aglyn/tenant-data-admin/server/org-containers";
|
|
22
22
|
/** Records of one kind a lookup reads: the Actions editor's picker window. */ export const AI_WORKFLOW_RECORDS_WINDOW = 100;
|
|
23
23
|
/** Pipelines a stage lookup reads. */ export const AI_WORKFLOW_PIPELINES_WINDOW = 20;
|
|
@@ -119,6 +119,45 @@ const live = (data)=>data['deletedAt'] == null;
|
|
|
119
119
|
stages
|
|
120
120
|
};
|
|
121
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* The WORKSPACE's records an org automation may name (AGL-3603): its lists,
|
|
124
|
+
* and, with the CRM, its pipelines' stages; and the live campaigns and the
|
|
125
|
+
* datasets shared with every site. The draft opens placed on every site, so
|
|
126
|
+
* it names only what every site can use — the rule the Org automations
|
|
127
|
+
* editor's own pickers keep (`scopeCovers`); anything else stays a
|
|
128
|
+
* placeholder the person picks once the sites are chosen. Forms, workflows
|
|
129
|
+
* and webhooks are one site's each, and an org automation names none of
|
|
130
|
+
* them, so none are read.
|
|
131
|
+
*/ /** The placement an org automation's draft opens on: every site. */ const EVERY_SITE = [
|
|
132
|
+
ORG_SCOPE_TOKEN
|
|
133
|
+
];
|
|
134
|
+
export async function readAiOrgAutomationRecords(firestore, input) {
|
|
135
|
+
const org = firestore.collection('orgs').doc(input.orgId);
|
|
136
|
+
const [datasets, lists, campaigns, stages] = await Promise.all([
|
|
137
|
+
indexed('dataset', {
|
|
138
|
+
orgId: input.orgId,
|
|
139
|
+
hostId: null
|
|
140
|
+
}, (record)=>scopeCovers(record.facts['visibleTo'], EVERY_SITE)),
|
|
141
|
+
named(org.collection('lists'), [
|
|
142
|
+
'name',
|
|
143
|
+
'deletedAt'
|
|
144
|
+
], live, (data)=>text(data['name'])),
|
|
145
|
+
listOrgContainers(firestore, 'campaign', input.orgId, AI_WORKFLOW_RECORDS_WINDOW).then((containers)=>containers.filter((container)=>container.live && container.name && scopeCovers(container.visibleTo, EVERY_SITE)).map((container)=>({
|
|
146
|
+
id: container.id,
|
|
147
|
+
name: container.name
|
|
148
|
+
}))),
|
|
149
|
+
input.crm ? readStages(input.orgId, null) : Promise.resolve([])
|
|
150
|
+
]);
|
|
151
|
+
return {
|
|
152
|
+
forms: [],
|
|
153
|
+
datasets,
|
|
154
|
+
lists,
|
|
155
|
+
campaigns,
|
|
156
|
+
workflows: [],
|
|
157
|
+
webhooks: [],
|
|
158
|
+
stages
|
|
159
|
+
};
|
|
160
|
+
}
|
|
122
161
|
/**
|
|
123
162
|
* The stages of the pipelines the site can see, asked of the plugin that keeps
|
|
124
163
|
* pipelines (its `pipeline` record index), never read from its collection. No
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-workflow-records.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 { actionRunResult } from '@aglyn/aglyn/app-utils/activity-presenter'\nimport { isFormArchived } from '@aglyn/aglyn/app-utils/forms'\nimport {\n pluginRecordIndex,\n type PluginIndexedRecord,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-index'\nimport { visibleToHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport { listOrgContainers } from '@aglyn/tenant-data-admin/server/org-containers'\nimport type {\n AiAutomationForm,\n AiAutomationNamedRecord,\n AiAutomationRecords,\n} from '../model/ai-automation-draft'\nimport type { AiIndexedWorkflow, AiRunRecord } from '../model/ai-automation-outline'\nimport type { AiWorkflowTargetType } from '../model/ai-workflow-job'\nimport type { AiAutomation } from '../model/ai-automation-format'\n\n/**\n * What a `workflow` job reads (AGL-2919), scoped to the job's own site and\n * org: the records a drafted automation's words are looked up among, the\n * automation an explanation reads, and the run it explains. Every read is\n * windowed the way the Actions editor's pickers are, so a lookup never reads\n * more than the editor would offer.\n *\n * The site's workflows, webhooks and saved actions are the workflows plugin's\n * records, and the org's datasets the data plugin's, so they are read through\n * the indexes those plugins publish (`workflow`, `webhook`, `action`,\n * `dataset` — AGL-3080), never from their collections: where no plugin keeps a\n * kind, there are none to name. The rest are read through the Admin SDK as\n * projections of the fields named here.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\ntype Data = Record<string, unknown>\n\n/** Records of one kind a lookup reads: the Actions editor's picker window. */\nexport const AI_WORKFLOW_RECORDS_WINDOW = 100\n\n/** Pipelines a stage lookup reads. */\nexport const AI_WORKFLOW_PIPELINES_WINDOW = 20\n\nfunction text(value: unknown): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\nasync function named(\n query: FirebaseFirestore.Query,\n fields: string[],\n keep: (data: Data) => boolean,\n nameOf: (data: Data, id: string) => string,\n): Promise<AiAutomationNamedRecord[]> {\n const snapshot = await query\n .select(...fields)\n .limit(AI_WORKFLOW_RECORDS_WINDOW)\n .get()\n return snapshot.docs\n .map((doc) => ({ data: (doc.data() ?? {}) as Data, id: doc.id }))\n .filter(({ data }) => keep(data))\n .map(({ data, id }) => ({ id, name: nameOf(data, id) }))\n .filter((record) => record.name)\n}\n\nconst live = (data: Data) => data['deletedAt'] == null\n\n/** The org's live campaigns placed on the site, named, in the editor's window. */\nasync function placedCampaigns(\n firestore: Firestore,\n orgId: string,\n hostId: string,\n): Promise<AiAutomationNamedRecord[]> {\n const containers = await listOrgContainers(firestore, 'campaign', orgId, AI_WORKFLOW_RECORDS_WINDOW)\n return containers\n .filter((container) => container.live && container.name && visibleToHost(container.visibleTo, hostId))\n .map((container) => ({ id: container.id, name: container.name }))\n}\n\n/**\n * The live records of a kind another plugin keeps, through its index, in the\n * editor's window; none where no plugin keeps the kind here.\n */\nasync function indexed(\n kind: string,\n scope: { orgId?: string; hostId: string },\n keep: (record: PluginIndexedRecord) => boolean = () => true,\n): Promise<AiAutomationNamedRecord[]> {\n const owner = pluginRecordIndex(kind)\n if (!owner) return []\n const { records } = await owner.index.list({ ...scope, limit: AI_WORKFLOW_RECORDS_WINDOW })\n return records.filter(keep).map(({ id, name }) => ({ id, name }))\n}\n\n/** The site's forms that are not archived, with the field names their submissions carry. */\nasync function readForms(host: FirebaseFirestore.DocumentReference): Promise<AiAutomationForm[]> {\n const snapshot = await host\n .collection('forms')\n .select('displayName', 'slug', 'fields', 'archivedAt')\n .limit(AI_WORKFLOW_RECORDS_WINDOW)\n .get()\n return snapshot.docs\n .filter((doc) => !isFormArchived((doc.data() ?? {}) as { archivedAt?: unknown }))\n .map((doc) => {\n const data = (doc.data() ?? {}) as Data\n return {\n id: doc.id,\n name: text(data['displayName']) || text(data['slug']) || doc.id,\n fields: (Array.isArray(data['fields']) ? (data['fields'] as Data[]) : [])\n .map((field) => text(field?.['fieldName']))\n .filter(Boolean),\n }\n })\n}\n\n/**\n * The site's records a drafted automation may name. Stages are read only for\n * a workspace with the CRM, whose pipelines they are; a site without it has\n * no stage a condition could name. `only`, when given, names the kinds to\n * read, and every other kind is answered empty without a read.\n */\nexport async function readAiAutomationRecords(\n firestore: Firestore,\n input: { orgId: string; hostId: string; crm: boolean; only?: ReadonlyArray<keyof AiAutomationRecords> },\n): Promise<AiAutomationRecords> {\n const host = firestore.collection('hosts').doc(input.hostId)\n const org = firestore.collection('orgs').doc(input.orgId)\n const wanted = (kind: keyof AiAutomationRecords) => !input.only || input.only.includes(kind)\n const none = Promise.resolve([] as AiAutomationNamedRecord[])\n const [forms, datasets, lists, campaigns, workflows, webhooks, stages] = await Promise.all([\n !wanted('forms') ? Promise.resolve([] as AiAutomationForm[]) : readForms(host),\n // The org's datasets shared with this site, as the data plugin indexes them.\n !wanted('datasets') ? none : indexed('dataset', { orgId: input.orgId, hostId: input.hostId }),\n !wanted('lists') ? none : named(org.collection('lists'), ['name', 'deletedAt'], live, (data) => text(data['name'])),\n // The org's campaigns placed on this site: the set the automation's\n // `assignCampaign` step accepts when it runs here. Read as the `campaign`\n // container kind, where its declaration says the containers are stored.\n !wanted('campaigns') ? none : placedCampaigns(firestore, input.orgId, input.hostId),\n !wanted('workflows') ? none : indexed('workflow', { hostId: input.hostId }),\n // Only a webhook that posts OUT is a step's target; an inbound one is an\n // endpoint that runs a workflow.\n !wanted('webhooks')\n ? none\n : indexed('webhook', { hostId: input.hostId }, (record) => record.facts['direction'] === 'outbound'),\n input.crm && wanted('stages') ? readStages(input.orgId, input.hostId) : none,\n ])\n return { forms, datasets, lists, campaigns, workflows, webhooks, stages }\n}\n\n/**\n * The stages of the pipelines the site can see, asked of the plugin that keeps\n * pipelines (its `pipeline` record index), never read from its collection. No\n * index means no plugin keeps pipelines in this process: no stage to name.\n */\nasync function readStages(orgId: string, hostId: string): Promise<AiAutomationNamedRecord[]> {\n const pipelines = pluginRecordIndex('pipeline')\n if (!pipelines) return []\n const { records } = await pipelines.index.list({ orgId, hostId, limit: AI_WORKFLOW_PIPELINES_WINDOW })\n return records\n .flatMap((record) => (Array.isArray(record.facts['stages']) ? (record.facts['stages'] as Data[]) : []))\n .map((stage) => ({ id: text(stage?.['id']), name: text(stage?.['name']) }))\n .filter((stage) => stage.id && stage.name)\n}\n\n/** The site's functions, for a workflow outline that says which of its calls still resolve. */\nexport async function readAiWorkflowFunctions(\n firestore: Firestore,\n hostId: string,\n): Promise<AiAutomationNamedRecord[]> {\n return named(\n firestore.collection('hosts').doc(hostId).collection('functions'),\n ['name', 'deletedAt'],\n live,\n (data) => text(data['name']),\n )\n}\n\nexport type AiWorkflowTarget =\n | { type: 'action'; id: string; name: string; action: AiAutomation }\n | { type: 'workflow'; id: string; name: string; workflow: AiIndexedWorkflow }\n\n/**\n * A saved automation of the site, read through the index of the plugin that\n * keeps it, or `null` for one that does not exist, was deleted, or that no\n * plugin here keeps.\n */\nexport async function readAiWorkflowTarget(\n input: { hostId: string; type: AiWorkflowTargetType; id: string },\n): Promise<AiWorkflowTarget | null> {\n const owner = pluginRecordIndex(input.type)\n const record = owner ? await owner.index.get({ hostId: input.hostId, id: input.id }) : null\n if (!record) return null\n const stored = { name: record.name, ...record.facts }\n return input.type === 'action'\n ? { type: 'action', id: record.id, name: record.name, action: stored as unknown as AiAutomation }\n : { type: 'workflow', id: record.id, name: record.name, workflow: stored as unknown as AiIndexedWorkflow }\n}\n\nexport type AiWorkflowRunRead =\n | { ok: true; run: AiRunRecord }\n | { ok: false; reason: 'gone' | 'not-failed' }\n\n/**\n * One run of an automation, as the site's activity log recorded it: gone when\n * there is no such entry or it belongs to another automation, and refused\n * when it did not fail. Only what the run history shows is read — never the\n * event's payload.\n */\nexport async function readAiWorkflowRun(\n firestore: Firestore,\n input: { hostId: string; targetId: string; runId: string },\n): Promise<AiWorkflowRunRead> {\n const snapshot = await firestore\n .collection('hosts')\n .doc(input.hostId)\n .collection('activity')\n .doc(input.runId)\n .get()\n const data = snapshot.exists ? ((snapshot.data() ?? {}) as Data) : null\n const target = data?.['target'] as { id?: unknown } | undefined\n if (!data || target?.id !== input.targetId) return { ok: false, reason: 'gone' }\n const run: AiRunRecord = {\n result: data['result'],\n trigger: data['trigger'],\n summary: data['summary'],\n action: data['action'],\n createdAt: data['createdAt'],\n }\n return actionRunResult(run as never) === 'failed' ? { ok: true, run } : { ok: false, reason: 'not-failed' }\n}\n"],"names":["actionRunResult","isFormArchived","pluginRecordIndex","visibleToHost","listOrgContainers","AI_WORKFLOW_RECORDS_WINDOW","AI_WORKFLOW_PIPELINES_WINDOW","text","value","trim","named","query","fields","keep","nameOf","snapshot","select","limit","get","docs","map","doc","data","id","filter","name","record","live","placedCampaigns","firestore","orgId","hostId","containers","container","visibleTo","indexed","kind","scope","owner","records","index","list","readForms","host","collection","Array","isArray","field","Boolean","readAiAutomationRecords","input","org","wanted","only","includes","none","Promise","resolve","forms","datasets","lists","campaigns","workflows","webhooks","stages","all","facts","crm","readStages","pipelines","flatMap","stage","readAiWorkflowFunctions","readAiWorkflowTarget","type","stored","action","workflow","readAiWorkflowRun","runId","exists","target","targetId","ok","reason","run","result","trigger","summary","createdAt"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-workflow-records.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 { actionRunResult } from '@aglyn/aglyn/app-utils/activity-presenter'\nimport { isFormArchived } from '@aglyn/aglyn/app-utils/forms'\nimport {\n pluginRecordIndex,\n type PluginIndexedRecord,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-index'\nimport { ORG_SCOPE_TOKEN, scopeCovers, visibleToHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport { listOrgContainers } from '@aglyn/tenant-data-admin/server/org-containers'\nimport type {\n AiAutomationForm,\n AiAutomationNamedRecord,\n AiAutomationRecords,\n} from '../model/ai-automation-draft'\nimport type { AiIndexedWorkflow, AiRunRecord } from '../model/ai-automation-outline'\nimport type { AiWorkflowTargetType } from '../model/ai-workflow-job'\nimport type { AiAutomation } from '../model/ai-automation-format'\n\n/**\n * What a `workflow` job reads (AGL-2919), scoped to the job's own site and\n * org: the records a drafted automation's words are looked up among, the\n * automation an explanation reads, and the run it explains. Every read is\n * windowed the way the Actions editor's pickers are, so a lookup never reads\n * more than the editor would offer.\n *\n * The site's workflows, webhooks and saved actions are the workflows plugin's\n * records, and the org's datasets the data plugin's, so they are read through\n * the indexes those plugins publish (`workflow`, `webhook`, `action`,\n * `dataset` — AGL-3080), never from their collections: where no plugin keeps a\n * kind, there are none to name. The rest are read through the Admin SDK as\n * projections of the fields named here.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\ntype Data = Record<string, unknown>\n\n/** Records of one kind a lookup reads: the Actions editor's picker window. */\nexport const AI_WORKFLOW_RECORDS_WINDOW = 100\n\n/** Pipelines a stage lookup reads. */\nexport const AI_WORKFLOW_PIPELINES_WINDOW = 20\n\nfunction text(value: unknown): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\nasync function named(\n query: FirebaseFirestore.Query,\n fields: string[],\n keep: (data: Data) => boolean,\n nameOf: (data: Data, id: string) => string,\n): Promise<AiAutomationNamedRecord[]> {\n const snapshot = await query\n .select(...fields)\n .limit(AI_WORKFLOW_RECORDS_WINDOW)\n .get()\n return snapshot.docs\n .map((doc) => ({ data: (doc.data() ?? {}) as Data, id: doc.id }))\n .filter(({ data }) => keep(data))\n .map(({ data, id }) => ({ id, name: nameOf(data, id) }))\n .filter((record) => record.name)\n}\n\nconst live = (data: Data) => data['deletedAt'] == null\n\n/** The org's live campaigns placed on the site, named, in the editor's window. */\nasync function placedCampaigns(\n firestore: Firestore,\n orgId: string,\n hostId: string,\n): Promise<AiAutomationNamedRecord[]> {\n const containers = await listOrgContainers(firestore, 'campaign', orgId, AI_WORKFLOW_RECORDS_WINDOW)\n return containers\n .filter((container) => container.live && container.name && visibleToHost(container.visibleTo, hostId))\n .map((container) => ({ id: container.id, name: container.name }))\n}\n\n/**\n * The live records of a kind another plugin keeps, through its index, in the\n * editor's window; none where no plugin keeps the kind here.\n */\nasync function indexed(\n kind: string,\n scope: { orgId?: string; hostId: string | null },\n keep: (record: PluginIndexedRecord) => boolean = () => true,\n): Promise<AiAutomationNamedRecord[]> {\n const owner = pluginRecordIndex(kind)\n if (!owner) return []\n const { records } = await owner.index.list({ ...scope, limit: AI_WORKFLOW_RECORDS_WINDOW })\n return records.filter(keep).map(({ id, name }) => ({ id, name }))\n}\n\n/** The site's forms that are not archived, with the field names their submissions carry. */\nasync function readForms(host: FirebaseFirestore.DocumentReference): Promise<AiAutomationForm[]> {\n const snapshot = await host\n .collection('forms')\n .select('displayName', 'slug', 'fields', 'archivedAt')\n .limit(AI_WORKFLOW_RECORDS_WINDOW)\n .get()\n return snapshot.docs\n .filter((doc) => !isFormArchived((doc.data() ?? {}) as { archivedAt?: unknown }))\n .map((doc) => {\n const data = (doc.data() ?? {}) as Data\n return {\n id: doc.id,\n name: text(data['displayName']) || text(data['slug']) || doc.id,\n fields: (Array.isArray(data['fields']) ? (data['fields'] as Data[]) : [])\n .map((field) => text(field?.['fieldName']))\n .filter(Boolean),\n }\n })\n}\n\n/**\n * The site's records a drafted automation may name. Stages are read only for\n * a workspace with the CRM, whose pipelines they are; a site without it has\n * no stage a condition could name. `only`, when given, names the kinds to\n * read, and every other kind is answered empty without a read.\n */\nexport async function readAiAutomationRecords(\n firestore: Firestore,\n input: { orgId: string; hostId: string; crm: boolean; only?: ReadonlyArray<keyof AiAutomationRecords> },\n): Promise<AiAutomationRecords> {\n const host = firestore.collection('hosts').doc(input.hostId)\n const org = firestore.collection('orgs').doc(input.orgId)\n const wanted = (kind: keyof AiAutomationRecords) => !input.only || input.only.includes(kind)\n const none = Promise.resolve([] as AiAutomationNamedRecord[])\n const [forms, datasets, lists, campaigns, workflows, webhooks, stages] = await Promise.all([\n !wanted('forms') ? Promise.resolve([] as AiAutomationForm[]) : readForms(host),\n // The org's datasets shared with this site, as the data plugin indexes them.\n !wanted('datasets') ? none : indexed('dataset', { orgId: input.orgId, hostId: input.hostId }),\n !wanted('lists') ? none : named(org.collection('lists'), ['name', 'deletedAt'], live, (data) => text(data['name'])),\n // The org's campaigns placed on this site: the set the automation's\n // `assignCampaign` step accepts when it runs here. Read as the `campaign`\n // container kind, where its declaration says the containers are stored.\n !wanted('campaigns') ? none : placedCampaigns(firestore, input.orgId, input.hostId),\n !wanted('workflows') ? none : indexed('workflow', { hostId: input.hostId }),\n // Only a webhook that posts OUT is a step's target; an inbound one is an\n // endpoint that runs a workflow.\n !wanted('webhooks')\n ? none\n : indexed('webhook', { hostId: input.hostId }, (record) => record.facts['direction'] === 'outbound'),\n input.crm && wanted('stages') ? readStages(input.orgId, input.hostId) : none,\n ])\n return { forms, datasets, lists, campaigns, workflows, webhooks, stages }\n}\n\n/**\n * The WORKSPACE's records an org automation may name (AGL-3603): its lists,\n * and, with the CRM, its pipelines' stages; and the live campaigns and the\n * datasets shared with every site. The draft opens placed on every site, so\n * it names only what every site can use — the rule the Org automations\n * editor's own pickers keep (`scopeCovers`); anything else stays a\n * placeholder the person picks once the sites are chosen. Forms, workflows\n * and webhooks are one site's each, and an org automation names none of\n * them, so none are read.\n */\n/** The placement an org automation's draft opens on: every site. */\nconst EVERY_SITE: readonly string[] = [ORG_SCOPE_TOKEN]\n\nexport async function readAiOrgAutomationRecords(\n firestore: Firestore,\n input: { orgId: string; crm: boolean },\n): Promise<AiAutomationRecords> {\n const org = firestore.collection('orgs').doc(input.orgId)\n const [datasets, lists, campaigns, stages] = await Promise.all([\n indexed('dataset', { orgId: input.orgId, hostId: null }, (record) =>\n scopeCovers(record.facts['visibleTo'] as string[] | undefined, EVERY_SITE),\n ),\n named(org.collection('lists'), ['name', 'deletedAt'], live, (data) => text(data['name'])),\n listOrgContainers(firestore, 'campaign', input.orgId, AI_WORKFLOW_RECORDS_WINDOW).then((containers) =>\n containers\n .filter((container) => container.live && container.name && scopeCovers(container.visibleTo, EVERY_SITE))\n .map((container) => ({ id: container.id, name: container.name })),\n ),\n input.crm ? readStages(input.orgId, null) : Promise.resolve([] as AiAutomationNamedRecord[]),\n ])\n return { forms: [], datasets, lists, campaigns, workflows: [], webhooks: [], stages }\n}\n\n/**\n * The stages of the pipelines the site can see, asked of the plugin that keeps\n * pipelines (its `pipeline` record index), never read from its collection. No\n * index means no plugin keeps pipelines in this process: no stage to name.\n */\nasync function readStages(orgId: string, hostId: string | null): Promise<AiAutomationNamedRecord[]> {\n const pipelines = pluginRecordIndex('pipeline')\n if (!pipelines) return []\n const { records } = await pipelines.index.list({ orgId, hostId, limit: AI_WORKFLOW_PIPELINES_WINDOW })\n return records\n .flatMap((record) => (Array.isArray(record.facts['stages']) ? (record.facts['stages'] as Data[]) : []))\n .map((stage) => ({ id: text(stage?.['id']), name: text(stage?.['name']) }))\n .filter((stage) => stage.id && stage.name)\n}\n\n/** The site's functions, for a workflow outline that says which of its calls still resolve. */\nexport async function readAiWorkflowFunctions(\n firestore: Firestore,\n hostId: string,\n): Promise<AiAutomationNamedRecord[]> {\n return named(\n firestore.collection('hosts').doc(hostId).collection('functions'),\n ['name', 'deletedAt'],\n live,\n (data) => text(data['name']),\n )\n}\n\nexport type AiWorkflowTarget =\n | { type: 'action'; id: string; name: string; action: AiAutomation }\n | { type: 'workflow'; id: string; name: string; workflow: AiIndexedWorkflow }\n\n/**\n * A saved automation of the site, read through the index of the plugin that\n * keeps it, or `null` for one that does not exist, was deleted, or that no\n * plugin here keeps.\n */\nexport async function readAiWorkflowTarget(\n input: { hostId: string; type: AiWorkflowTargetType; id: string },\n): Promise<AiWorkflowTarget | null> {\n const owner = pluginRecordIndex(input.type)\n const record = owner ? await owner.index.get({ hostId: input.hostId, id: input.id }) : null\n if (!record) return null\n const stored = { name: record.name, ...record.facts }\n return input.type === 'action'\n ? { type: 'action', id: record.id, name: record.name, action: stored as unknown as AiAutomation }\n : { type: 'workflow', id: record.id, name: record.name, workflow: stored as unknown as AiIndexedWorkflow }\n}\n\nexport type AiWorkflowRunRead =\n | { ok: true; run: AiRunRecord }\n | { ok: false; reason: 'gone' | 'not-failed' }\n\n/**\n * One run of an automation, as the site's activity log recorded it: gone when\n * there is no such entry or it belongs to another automation, and refused\n * when it did not fail. Only what the run history shows is read — never the\n * event's payload.\n */\nexport async function readAiWorkflowRun(\n firestore: Firestore,\n input: { hostId: string; targetId: string; runId: string },\n): Promise<AiWorkflowRunRead> {\n const snapshot = await firestore\n .collection('hosts')\n .doc(input.hostId)\n .collection('activity')\n .doc(input.runId)\n .get()\n const data = snapshot.exists ? ((snapshot.data() ?? {}) as Data) : null\n const target = data?.['target'] as { id?: unknown } | undefined\n if (!data || target?.id !== input.targetId) return { ok: false, reason: 'gone' }\n const run: AiRunRecord = {\n result: data['result'],\n trigger: data['trigger'],\n summary: data['summary'],\n action: data['action'],\n createdAt: data['createdAt'],\n }\n return actionRunResult(run as never) === 'failed' ? { ok: true, run } : { ok: false, reason: 'not-failed' }\n}\n"],"names":["actionRunResult","isFormArchived","pluginRecordIndex","ORG_SCOPE_TOKEN","scopeCovers","visibleToHost","listOrgContainers","AI_WORKFLOW_RECORDS_WINDOW","AI_WORKFLOW_PIPELINES_WINDOW","text","value","trim","named","query","fields","keep","nameOf","snapshot","select","limit","get","docs","map","doc","data","id","filter","name","record","live","placedCampaigns","firestore","orgId","hostId","containers","container","visibleTo","indexed","kind","scope","owner","records","index","list","readForms","host","collection","Array","isArray","field","Boolean","readAiAutomationRecords","input","org","wanted","only","includes","none","Promise","resolve","forms","datasets","lists","campaigns","workflows","webhooks","stages","all","facts","crm","readStages","EVERY_SITE","readAiOrgAutomationRecords","then","pipelines","flatMap","stage","readAiWorkflowFunctions","readAiWorkflowTarget","type","stored","action","workflow","readAiWorkflowRun","runId","exists","target","targetId","ok","reason","run","result","trigger","summary","createdAt"],"mappings":"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"}
|
|
@@ -72,3 +72,21 @@ export declare function aiAutomationDraft(answer: AiAutomationAnswer, records: A
|
|
|
72
72
|
* fill in before switching it on, and what the answer said needs deciding.
|
|
73
73
|
*/
|
|
74
74
|
export declare function aiAutomationDraftNote(draft: AiAutomationDraft, notes: readonly string[]): string;
|
|
75
|
+
/**
|
|
76
|
+
* What a revised draft changed from the saved action it was drawn from
|
|
77
|
+
* (AGL-3603), worked out in code from the two stored shapes — never the
|
|
78
|
+
* model's own account of what it did. A step whose kind is both removed and
|
|
79
|
+
* added — edited, or moved — is reported as changed.
|
|
80
|
+
*/
|
|
81
|
+
export declare function aiAutomationRevisionChanges(before: Pick<AiAutomation, 'trigger' | 'steps'>, after: Pick<AiAutomation, 'trigger' | 'steps'>, label: (type: string) => string): string[];
|
|
82
|
+
/**
|
|
83
|
+
* What a revised draft's output tells a person: which saved action it was
|
|
84
|
+
* drawn from, what changed, that the saved one is untouched, and the draft's
|
|
85
|
+
* own note.
|
|
86
|
+
*/
|
|
87
|
+
export declare function aiAutomationRevisionNote(input: {
|
|
88
|
+
fromName: string;
|
|
89
|
+
changes: readonly string[];
|
|
90
|
+
draft: AiAutomationDraft;
|
|
91
|
+
notes: readonly string[];
|
|
92
|
+
}): string;
|
|
@@ -311,5 +311,91 @@ function stepOf(step, records) {
|
|
|
311
311
|
parts.push(...notes);
|
|
312
312
|
return parts.join(' ');
|
|
313
313
|
}
|
|
314
|
+
/** A step as compared across a revision: its stored fields, in a fixed key order. */ function stepSignature(step) {
|
|
315
|
+
const record = step;
|
|
316
|
+
return JSON.stringify(Object.keys(record).sort().map((key)=>[
|
|
317
|
+
key,
|
|
318
|
+
record[key]
|
|
319
|
+
]));
|
|
320
|
+
}
|
|
321
|
+
/** The longest run of `after` that `before` holds in order, as a pair of kept flags. */ function keptSteps(before, after) {
|
|
322
|
+
const table = Array.from({
|
|
323
|
+
length: before.length + 1
|
|
324
|
+
}, ()=>new Array(after.length + 1).fill(0));
|
|
325
|
+
for(let i = before.length - 1; i >= 0; i -= 1){
|
|
326
|
+
for(let j = after.length - 1; j >= 0; j -= 1){
|
|
327
|
+
table[i][j] = before[i] === after[j] ? table[i + 1][j + 1] + 1 : Math.max(table[i + 1][j], table[i][j + 1]);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
const keptBefore = before.map(()=>false);
|
|
331
|
+
const keptAfter = after.map(()=>false);
|
|
332
|
+
let i = 0;
|
|
333
|
+
let j = 0;
|
|
334
|
+
while(i < before.length && j < after.length){
|
|
335
|
+
if (before[i] === after[j]) {
|
|
336
|
+
keptBefore[i] = true;
|
|
337
|
+
keptAfter[j] = true;
|
|
338
|
+
i += 1;
|
|
339
|
+
j += 1;
|
|
340
|
+
} else if (table[i + 1][j] >= table[i][j + 1]) i += 1;
|
|
341
|
+
else j += 1;
|
|
342
|
+
}
|
|
343
|
+
return [
|
|
344
|
+
keptBefore,
|
|
345
|
+
keptAfter
|
|
346
|
+
];
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* What a revised draft changed from the saved action it was drawn from
|
|
350
|
+
* (AGL-3603), worked out in code from the two stored shapes — never the
|
|
351
|
+
* model's own account of what it did. A step whose kind is both removed and
|
|
352
|
+
* added — edited, or moved — is reported as changed.
|
|
353
|
+
*/ export function aiAutomationRevisionChanges(before, after, label) {
|
|
354
|
+
var _before_steps, _after_steps;
|
|
355
|
+
var _before_trigger, _after_trigger;
|
|
356
|
+
const changes = [];
|
|
357
|
+
if (((_before_trigger = before.trigger) == null ? void 0 : _before_trigger.event) !== ((_after_trigger = after.trigger) == null ? void 0 : _after_trigger.event)) changes.push('It starts on a different event.');
|
|
358
|
+
else {
|
|
359
|
+
const conditions = (trigger)=>{
|
|
360
|
+
var _ref, _trigger_combinator;
|
|
361
|
+
var _trigger_conditions;
|
|
362
|
+
return JSON.stringify({
|
|
363
|
+
conditions: (_ref = trigger == null ? void 0 : trigger.conditions) != null ? _ref : [],
|
|
364
|
+
combinator: (trigger == null ? void 0 : (_trigger_conditions = trigger.conditions) == null ? void 0 : _trigger_conditions.length) ? (_trigger_combinator = trigger.combinator) != null ? _trigger_combinator : 'and' : null
|
|
365
|
+
});
|
|
366
|
+
};
|
|
367
|
+
if (conditions(before.trigger) !== conditions(after.trigger)) changes.push('Its conditions changed.');
|
|
368
|
+
}
|
|
369
|
+
const beforeSteps = (_before_steps = before.steps) != null ? _before_steps : [];
|
|
370
|
+
const afterSteps = (_after_steps = after.steps) != null ? _after_steps : [];
|
|
371
|
+
const [keptBefore, keptAfter] = keptSteps(beforeSteps.map(stepSignature), afterSteps.map(stepSignature));
|
|
372
|
+
const removed = beforeSteps.filter((_, index)=>!keptBefore[index]).map((step)=>label(step.type));
|
|
373
|
+
const added = afterSteps.filter((_, index)=>!keptAfter[index]).map((step)=>label(step.type));
|
|
374
|
+
const changed = [
|
|
375
|
+
...new Set(removed.filter((name)=>added.includes(name)))
|
|
376
|
+
];
|
|
377
|
+
const only = (names)=>[
|
|
378
|
+
...new Set(names.filter((name)=>!changed.includes(name)))
|
|
379
|
+
];
|
|
380
|
+
if (changed.length) changes.push(`Changed: ${changed.join(', ')}.`);
|
|
381
|
+
if (only(added).length) changes.push(`Added: ${only(added).join(', ')}.`);
|
|
382
|
+
if (only(removed).length) changes.push(`Removed: ${only(removed).join(', ')}.`);
|
|
383
|
+
return changes;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* What a revised draft's output tells a person: which saved action it was
|
|
387
|
+
* drawn from, what changed, that the saved one is untouched, and the draft's
|
|
388
|
+
* own note.
|
|
389
|
+
*/ export function aiAutomationRevisionNote(input) {
|
|
390
|
+
const changes = input.changes.length ? input.changes : [
|
|
391
|
+
'Nothing changed from the saved action.'
|
|
392
|
+
];
|
|
393
|
+
return [
|
|
394
|
+
`A changed copy of “${input.fromName}”, which is left as it is.`,
|
|
395
|
+
...changes,
|
|
396
|
+
aiAutomationDraftNote(input.draft, input.notes),
|
|
397
|
+
'Switch the old one off when you switch this one on.'
|
|
398
|
+
].join(' ');
|
|
399
|
+
}
|
|
314
400
|
|
|
315
401
|
//# sourceMappingURL=ai-automation-draft.js.map
|