@aglyn/plugins-ai 1.0.0-beta.228 → 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-jobs-list-page.component.js +12 -1
- package/src/lib/components/ai-jobs-list-page.component.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.d.ts +1 -1
- package/src/lib/components/ai-site-build-page.component.js +177 -20
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-site-start-card.component.d.ts +10 -1
- package/src/lib/components/ai-site-start-card.component.js +7 -4
- package/src/lib/components/ai-site-start-card.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.d.ts +11 -0
- package/src/lib/components/assist-jobs-drawer.component.js +85 -4
- 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-layout-site-pages.d.ts +70 -0
- package/src/lib/jobs/ai-job-layout-site-pages.js +216 -0
- package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -0
- package/src/lib/jobs/ai-job-layout-step.d.ts +2 -2
- package/src/lib/jobs/ai-job-layout-step.js +19 -1
- package/src/lib/jobs/ai-job-layout-step.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 +207 -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 +90 -2
- package/src/lib/jobs/ai-jobs.js +495 -61
- 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-job-failure-copy.d.ts +20 -1
- package/src/lib/model/ai-job-failure-copy.js +28 -1
- package/src/lib/model/ai-job-failure-copy.js.map +1 -1
- package/src/lib/model/ai-job-notice.d.ts +3 -0
- package/src/lib/model/ai-job-notice.js +11 -2
- package/src/lib/model/ai-job-notice.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +87 -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 +28 -3
- package/src/lib/model/ai-site-build-progress.js +231 -17
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-site-start-seo.d.ts +16 -0
- package/src/lib/model/ai-site-start-seo.js +54 -4
- package/src/lib/model/ai-site-start-seo.js.map +1 -1
- package/src/lib/model/ai-site-start.d.ts +14 -0
- package/src/lib/model/ai-site-start.js +43 -0
- package/src/lib/model/ai-site-start.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.d.ts +6 -0
- package/src/lib/runtime/ai-doctrine.js +16 -6
- 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 +31 -3
- package/src/lib/usage/assist-job-refund.js +46 -10
- 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
package/src/lib/jobs/ai-jobs.js
CHANGED
|
@@ -27,7 +27,6 @@ import { AI_JOB_PLAN_STEP, AI_JOB_TERMINAL_STATUSES } from "../model/ai-jobs.typ
|
|
|
27
27
|
import { resolveEffectivePlan } from "@aglyn/aglyn/app-utils/plan-entitlements";
|
|
28
28
|
import { aiOverageReservationRefusal } from "../billing/ai-overage-gate.js";
|
|
29
29
|
import { aiAllotmentRefusalText } from "../model/ai-allotments.js";
|
|
30
|
-
import { aiJobPlanCreditEstimate } from "../model/ai-site-job.js";
|
|
31
30
|
import { AI_OFF_FOR_SITE_COPY, isAiOffForSite } from "../model/ai-site-switch.js";
|
|
32
31
|
import { resolveAiModelChoice } from "../providers/model-choice.js";
|
|
33
32
|
import { aiOutputTargetType } from "../activity/ai-activity-actions.js";
|
|
@@ -40,9 +39,12 @@ import { AI_JOB_TEXT_STEP_MINIMUM_MS, runAiJobTextStep } from "./ai-job-text-ste
|
|
|
40
39
|
import { AI_JOB_SEO_STEP_MINIMUM_MS } from "./ai-job-seo-budget.js";
|
|
41
40
|
import { AI_JOB_THEME_STEP_MINIMUM_MS } from "./ai-job-theme-budget.js";
|
|
42
41
|
import { assistExchangeExpiry, assistUsageDay, assistUsageMonth, recordAssistCost, releaseAssistMessage, reserveAssistMessage } from "../usage/assist-usage.js";
|
|
43
|
-
import { aiJobRefundCredits, refundJobCredits } from "../usage/assist-job-refund.js";
|
|
42
|
+
import { aiJobItemRefundKey, aiJobRefundCredits, refundJobCredits } from "../usage/assist-job-refund.js";
|
|
43
|
+
import { aiBuildSettlement, aiBuildUnits, aiBuildRetryLedger, aiBuildCreditEstimate, aiLedgerUnits, aiJobCreditEstimate } from "../model/ai-build-job.js";
|
|
44
44
|
import { freeAssistAccount } from "../usage/assist-free-taste.js";
|
|
45
45
|
import { aiJobAutoConfirms } from "./ai-job-auto-confirm.js";
|
|
46
|
+
import { AI_SITE_GUIDED_BUILD_FAILED_COPY } from "../model/ai-job-failure-copy.js";
|
|
47
|
+
import { aiSiteStartInputsOf } from "../model/ai-site-start.js";
|
|
46
48
|
import { addAdminAudit } from "@aglyn/tenant-data-admin/server/admin-audit-write";
|
|
47
49
|
/**
|
|
48
50
|
* AI generation jobs — the Firestore state machine (AGL-2904).
|
|
@@ -151,7 +153,8 @@ export { AI_JOB_PLAN_STEP };
|
|
|
151
153
|
'layout',
|
|
152
154
|
'template',
|
|
153
155
|
'form',
|
|
154
|
-
'email'
|
|
156
|
+
'email',
|
|
157
|
+
'build'
|
|
155
158
|
];
|
|
156
159
|
const stepRunners = new Map();
|
|
157
160
|
const stepMinimums = new Map();
|
|
@@ -426,12 +429,35 @@ export async function getAiJob(firestore, orgId, jobId) {
|
|
|
426
429
|
...options.statuses
|
|
427
430
|
]);
|
|
428
431
|
else if (options.hostId) query = query.where('hostId', '==', options.hostId);
|
|
429
|
-
const
|
|
430
|
-
|
|
432
|
+
const read = async (from, count)=>(await from.orderBy('createdAt', 'desc').limit(count).get()).docs.map((doc)=>jobFrom(doc)).filter((job)=>job !== null);
|
|
433
|
+
try {
|
|
434
|
+
return await read(query, limit);
|
|
435
|
+
} catch (error) {
|
|
436
|
+
var _options_statuses1;
|
|
437
|
+
// A site's list while its (hostId, createdAt) index is still building
|
|
438
|
+
// (AGL-3596): Firestore refuses the query with FAILED_PRECONDITION until
|
|
439
|
+
// the index is ready, which answered the site's AI jobs page with a 500.
|
|
440
|
+
// The org's own newest jobs — the index every org list already uses —
|
|
441
|
+
// filtered to the site in memory stand in, bounded to
|
|
442
|
+
// `AI_JOBS_HOST_FALLBACK_SCAN` jobs, so a site whose jobs are older than
|
|
443
|
+
// that lists fewer until the index is ready rather than none.
|
|
444
|
+
if (!options.hostId || options.status || ((_options_statuses1 = options.statuses) == null ? void 0 : _options_statuses1.length) || !isFailedPrecondition(error)) throw error;
|
|
445
|
+
console.warn('ai jobs: host index not ready; listing from the org', {
|
|
446
|
+
orgId,
|
|
447
|
+
hostId: options.hostId
|
|
448
|
+
});
|
|
449
|
+
const recent = await read(jobsCollection(firestore, orgId), AI_JOBS_HOST_FALLBACK_SCAN);
|
|
450
|
+
return recent.filter((job)=>job.hostId === options.hostId).slice(0, limit);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
/** The most of an org's newest jobs a site's list scans while its index builds (AGL-3596). */ export const AI_JOBS_HOST_FALLBACK_SCAN = 300;
|
|
454
|
+
/** Firestore's "this query needs an index that is not ready" (gRPC 9). */ function isFailedPrecondition(error) {
|
|
455
|
+
const code = error == null ? void 0 : error.code;
|
|
456
|
+
return code === 9 || code === 'failed-precondition' || code === 'FAILED_PRECONDITION';
|
|
431
457
|
}
|
|
432
458
|
/** The wire form. `running` is the lease, read against `now`. */ export function aiJobSummary(job, now = new Date()) {
|
|
433
459
|
var _job_hostId, _job_steps, _job_outputs, _job_creditsReserved, _job_creditsSpent, _job_refundedCredits, _toIso, _toIso1, _job_error, _job_plan_labels, _job_plan_confirmedBy, _job_review, _job_applied_versions, _job_applied_staged;
|
|
434
|
-
var _job_lease, _job_inputs;
|
|
460
|
+
var _job_lease, _job_inputs, _job_items, _job_inputs1;
|
|
435
461
|
const leaseUntil = toMillis((_job_lease = job.lease) == null ? void 0 : _job_lease.until);
|
|
436
462
|
return _extends({
|
|
437
463
|
id: job.$id,
|
|
@@ -476,6 +502,14 @@ export async function getAiJob(firestore, orgId, jobId) {
|
|
|
476
502
|
} : null
|
|
477
503
|
}, job.sitePublish ? {
|
|
478
504
|
sitePublish: job.sitePublish
|
|
505
|
+
} : {}, job.kind === 'site' ? {
|
|
506
|
+
siteInputs: aiSiteStartInputsOf(job.inputs)
|
|
507
|
+
} : {}, ((_job_items = job.items) == null ? void 0 : _job_items.length) ? {
|
|
508
|
+
items: job.items
|
|
509
|
+
} : {}, job.orchestration ? {
|
|
510
|
+
orchestration: job.orchestration
|
|
511
|
+
} : {}, job.kind === 'build' && ((_job_inputs1 = job.inputs) == null ? void 0 : _job_inputs1['publish']) === true ? {
|
|
512
|
+
publishAsked: true
|
|
479
513
|
} : {});
|
|
480
514
|
}
|
|
481
515
|
function leaseIsLive(job, owner, nowMs) {
|
|
@@ -595,6 +629,38 @@ function nextStepIndex(job) {
|
|
|
595
629
|
});
|
|
596
630
|
});
|
|
597
631
|
}
|
|
632
|
+
/**
|
|
633
|
+
* A build's ledger with one pass applied (AGL-3616): the pass's credits on
|
|
634
|
+
* the item's row, its status, failure, note and outputs, and what was given
|
|
635
|
+
* back for it. Pure; the machine applies it inside `recordStep`.
|
|
636
|
+
*/ export function aiApplyJobItemRecord(ledger, record, credits) {
|
|
637
|
+
return ledger.map((row)=>{
|
|
638
|
+
var _row_creditsSpent, _row_attemptCredits, _row_creditsRefunded, _record_creditsRefunded, _row_outputs, _record_outputs;
|
|
639
|
+
var _record_degradedBy;
|
|
640
|
+
return row.slot !== record.slot ? row : _extends({}, row, {
|
|
641
|
+
status: record.status,
|
|
642
|
+
creditsSpent: ((_row_creditsSpent = row.creditsSpent) != null ? _row_creditsSpent : 0) + credits,
|
|
643
|
+
attemptCredits: ((_row_attemptCredits = row.attemptCredits) != null ? _row_attemptCredits : 0) + credits,
|
|
644
|
+
creditsRefunded: ((_row_creditsRefunded = row.creditsRefunded) != null ? _row_creditsRefunded : 0) + ((_record_creditsRefunded = record.creditsRefunded) != null ? _record_creditsRefunded : 0)
|
|
645
|
+
}, record.refundKey ? {
|
|
646
|
+
refundKey: record.refundKey
|
|
647
|
+
} : {}, record.failure !== undefined ? {
|
|
648
|
+
failure: record.failure
|
|
649
|
+
} : {}, record.note !== undefined ? {
|
|
650
|
+
note: record.note
|
|
651
|
+
} : {}, ((_record_degradedBy = record.degradedBy) == null ? void 0 : _record_degradedBy.length) ? {
|
|
652
|
+
degradedBy: record.degradedBy
|
|
653
|
+
} : {}, {
|
|
654
|
+
outputs: [
|
|
655
|
+
...(_row_outputs = row.outputs) != null ? _row_outputs : [],
|
|
656
|
+
...((_record_outputs = record.outputs) != null ? _record_outputs : []).filter((id)=>{
|
|
657
|
+
var _row_outputs;
|
|
658
|
+
return !((_row_outputs = row.outputs) != null ? _row_outputs : []).includes(id);
|
|
659
|
+
})
|
|
660
|
+
]
|
|
661
|
+
});
|
|
662
|
+
});
|
|
663
|
+
}
|
|
598
664
|
/**
|
|
599
665
|
* The runs whose stop reasons a step's record keeps (AGL-3042), newest last.
|
|
600
666
|
* A page job that builds a layout, a form and a component, then five sections
|
|
@@ -652,8 +718,8 @@ function nextStepIndex(job) {
|
|
|
652
718
|
*/ export async function recordStep(firestore, orgId, jobId, owner, stepIndex, input, now = new Date()) {
|
|
653
719
|
const ref = jobsCollection(firestore, orgId).doc(jobId);
|
|
654
720
|
const recorded = await firestore.runTransaction(async (tx)=>{
|
|
655
|
-
var _job_outputs, _input_outputs, _job_creditsSpent, _job_creditsReserved, _job_creditsReserved1;
|
|
656
|
-
var _job_lease;
|
|
721
|
+
var _ref, _input_items, _job_outputs, _input_outputs, _job_creditsSpent, _job_creditsReserved, _job_creditsReserved1, _job_refundedCredits;
|
|
722
|
+
var _job_lease, _input_item;
|
|
657
723
|
const job = jobFrom(await tx.get(ref));
|
|
658
724
|
if (!job) throw new Error(`ai job ${orgId}/${jobId} vanished`);
|
|
659
725
|
const steps = job.steps.map((step, index)=>{
|
|
@@ -671,6 +737,9 @@ function nextStepIndex(job) {
|
|
|
671
737
|
} : {}) : step;
|
|
672
738
|
});
|
|
673
739
|
const remaining = steps.filter((step)=>step.status === 'pending' || step.status === 'running').length;
|
|
740
|
+
// A build's item ledger moves with its spend, in this one write (AGL-3616).
|
|
741
|
+
const ledger = (_ref = (_input_items = input.items) != null ? _input_items : job.items) != null ? _ref : null;
|
|
742
|
+
const items = ledger && input.item ? aiApplyJobItemRecord(ledger, input.item, input.creditsSpent) : input.items ? ledger : null;
|
|
674
743
|
const settled = input.status !== 'pending';
|
|
675
744
|
const ownsLease = ((_job_lease = job.lease) == null ? void 0 : _job_lease.owner) === owner;
|
|
676
745
|
const parksForReview = Boolean(input.review) && !isAiJobTerminal(job.status);
|
|
@@ -693,6 +762,10 @@ function nextStepIndex(job) {
|
|
|
693
762
|
plan: input.plan
|
|
694
763
|
} : {}, input.sitePublish ? {
|
|
695
764
|
sitePublish: input.sitePublish
|
|
765
|
+
} : {}, items ? {
|
|
766
|
+
items
|
|
767
|
+
} : {}, ((_input_item = input.item) == null ? void 0 : _input_item.creditsRefunded) ? {
|
|
768
|
+
refundedCredits: ((_job_refundedCredits = job.refundedCredits) != null ? _job_refundedCredits : 0) + input.item.creditsRefunded
|
|
696
769
|
} : {}, parksForReview && input.review ? {
|
|
697
770
|
review: input.review,
|
|
698
771
|
error: input.review.reason === 'plan' ? null : input.review.message
|
|
@@ -707,10 +780,11 @@ function nextStepIndex(job) {
|
|
|
707
780
|
parked: parksForReview && job.status !== 'needs_review'
|
|
708
781
|
};
|
|
709
782
|
});
|
|
710
|
-
if (recorded.parked) await announceAiJobTransition(recorded.job, 'needs-review');
|
|
783
|
+
if (recorded.parked && !input.quiet) await announceAiJobTransition(recorded.job, 'needs-review');
|
|
711
784
|
return {
|
|
712
785
|
job: recorded.job,
|
|
713
|
-
remaining: recorded.remaining
|
|
786
|
+
remaining: recorded.remaining,
|
|
787
|
+
parked: recorded.parked
|
|
714
788
|
};
|
|
715
789
|
}
|
|
716
790
|
async function transition(firestore, orgId, jobId, apply) {
|
|
@@ -868,6 +942,291 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
868
942
|
return job;
|
|
869
943
|
}
|
|
870
944
|
}
|
|
945
|
+
/**
|
|
946
|
+
* A failure on our side, given back BEFORE the job is failed (AGL-3596): the
|
|
947
|
+
* failed state is the last one a follower of the job reads and the one its
|
|
948
|
+
* notice is written from, so it already carries `refundedCredits`. A job that
|
|
949
|
+
* ended meanwhile — a person's cancel, which pays for what ran — is left as it
|
|
950
|
+
* ended and given nothing back.
|
|
951
|
+
*/ async function failOurFailure(firestore, orgId, jobId, message, detail, refund, now) {
|
|
952
|
+
var _current_items;
|
|
953
|
+
const current = await getAiJob(firestore, orgId, jobId);
|
|
954
|
+
// A build that got as far as its items settles item by item (AGL-3616):
|
|
955
|
+
// what it delivered stands, and what it did not is given back per item.
|
|
956
|
+
if (current && aiJobSettlesByItem(current.kind) && ((_current_items = current.items) == null ? void 0 : _current_items.length) && !isAiJobTerminal(current.status)) {
|
|
957
|
+
var _detail_stepIndex;
|
|
958
|
+
console.error('ai build stopped', {
|
|
959
|
+
orgId,
|
|
960
|
+
jobId,
|
|
961
|
+
stepIndex: (_detail_stepIndex = detail.stepIndex) != null ? _detail_stepIndex : null,
|
|
962
|
+
error: detail.error
|
|
963
|
+
});
|
|
964
|
+
return settleAiBuildJob(firestore, orgId, jobId, {
|
|
965
|
+
now,
|
|
966
|
+
free: refund.free,
|
|
967
|
+
stopped: {
|
|
968
|
+
message,
|
|
969
|
+
reason: refund.reason
|
|
970
|
+
}
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
if (current && !isAiJobTerminal(current.status)) {
|
|
974
|
+
await refundOurFailure(firestore, orgId, current, _extends({}, refund, {
|
|
975
|
+
now
|
|
976
|
+
}));
|
|
977
|
+
}
|
|
978
|
+
return failAiJob(firestore, orgId, jobId, message, detail, now);
|
|
979
|
+
}
|
|
980
|
+
// ── A build's settlement (AGL-3616) ──────────────────────────────────────
|
|
981
|
+
/**
|
|
982
|
+
* The kinds settled item by item (AGL-3616): a build, and a site scaffold,
|
|
983
|
+
* which is the build's preset. Their step writes an item ledger; a failed
|
|
984
|
+
* item gives back its own spend and the rest goes on.
|
|
985
|
+
*/ export function aiJobSettlesByItem(kind) {
|
|
986
|
+
return kind === 'build' || kind === 'site';
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Whether Try again may run a job's failed items: a finished build, done or
|
|
990
|
+
* failed; a site scaffold that delivered part of its site — one that built
|
|
991
|
+
* nothing starts over from its own answers instead.
|
|
992
|
+
*/ export function aiBuildRetryable(job) {
|
|
993
|
+
if (job.kind === 'build') return job.status === 'done' || job.status === 'failed';
|
|
994
|
+
return job.kind === 'site' && job.status === 'done';
|
|
995
|
+
}
|
|
996
|
+
/** What a build that delivered nothing says; its planning was given back too. */ export const AI_BUILD_NOTHING_BUILT_COPY = 'Nothing in this request could be built. Try again, or ask for less at once.';
|
|
997
|
+
/** What an item the build never reached says when the build stopped early. */ export const AI_BUILD_NOT_REACHED_COPY = 'Not built: the build stopped before it reached this.';
|
|
998
|
+
/**
|
|
999
|
+
* Gives back one build item's spend, or its planning's (`slot: 'plan'`),
|
|
1000
|
+
* under the item key (job, slot, attempt), so a replayed pass gives back once
|
|
1001
|
+
* and a later attempt is a give-back of its own. The credits given back and
|
|
1002
|
+
* the key, or `null` when nothing was. Never throws.
|
|
1003
|
+
*/ async function refundBuildCredits(firestore, orgId, job, input) {
|
|
1004
|
+
const credits = Math.max(0, Math.floor(input.credits));
|
|
1005
|
+
if (credits <= 0) return null;
|
|
1006
|
+
try {
|
|
1007
|
+
var _data;
|
|
1008
|
+
const free = input.free !== undefined ? input.free : freeAssistAccount((_data = (await firestore.collection('orgs').doc(orgId).get()).data()) != null ? _data : {});
|
|
1009
|
+
const created = toMillis(job.createdAt);
|
|
1010
|
+
const jobDay = assistUsageDay(created === null ? input.now : new Date(created));
|
|
1011
|
+
const refund = await refundJobCredits(firestore, {
|
|
1012
|
+
orgId,
|
|
1013
|
+
free,
|
|
1014
|
+
jobId: job.$id,
|
|
1015
|
+
ordinal: 0,
|
|
1016
|
+
credits,
|
|
1017
|
+
month: assistUsageMonth(input.now),
|
|
1018
|
+
day: assistUsageDay(input.now),
|
|
1019
|
+
reason: input.reason,
|
|
1020
|
+
item: {
|
|
1021
|
+
slot: input.slot,
|
|
1022
|
+
attempt: input.attempt,
|
|
1023
|
+
jobDay
|
|
1024
|
+
}
|
|
1025
|
+
});
|
|
1026
|
+
// A key the month already holds was given back by an earlier run of this pass.
|
|
1027
|
+
if (refund.status !== 'returned' && refund.status !== 'duplicate') return null;
|
|
1028
|
+
return {
|
|
1029
|
+
credits,
|
|
1030
|
+
key: aiJobItemRefundKey({
|
|
1031
|
+
day: jobDay,
|
|
1032
|
+
jobId: job.$id,
|
|
1033
|
+
slot: input.slot,
|
|
1034
|
+
attempt: input.attempt
|
|
1035
|
+
})
|
|
1036
|
+
};
|
|
1037
|
+
} catch (error) {
|
|
1038
|
+
console.error('ai build refund failed', {
|
|
1039
|
+
orgId,
|
|
1040
|
+
jobId: job.$id,
|
|
1041
|
+
slot: input.slot,
|
|
1042
|
+
error
|
|
1043
|
+
});
|
|
1044
|
+
return null;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
/** What the build's plan step spent: its orchestration. */ function aiBuildPlanCredits(job) {
|
|
1048
|
+
var _ref;
|
|
1049
|
+
var _job_steps_find;
|
|
1050
|
+
return (_ref = (_job_steps_find = job.steps.find((step)=>step.name === AI_JOB_PLAN_STEP)) == null ? void 0 : _job_steps_find.creditsSpent) != null ? _ref : 0;
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* Ends a build (AGL-3616), once nothing is left to build or the machine
|
|
1054
|
+
* stopped it. A build that STOPPED — a provider failure, its attempts used up
|
|
1055
|
+
* — fails the item it was on and gives back that attempt's spend, and leaves
|
|
1056
|
+
* the items it never reached `skipped`. Then: anything delivered, and its
|
|
1057
|
+
* planning is charged and the job is `done`, failed items and all; nothing
|
|
1058
|
+
* delivered, and its planning is given back too and the job is `failed`.
|
|
1059
|
+
*/ export async function settleAiBuildJob(firestore, orgId, jobId, input) {
|
|
1060
|
+
var _job_items, _job_items1, _job_items2, _ref, _ref1;
|
|
1061
|
+
var _job_orchestration;
|
|
1062
|
+
const { now } = input;
|
|
1063
|
+
let job = await getAiJob(firestore, orgId, jobId);
|
|
1064
|
+
if (!job || isAiJobTerminal(job.status)) return job;
|
|
1065
|
+
const stopped = input.stopped;
|
|
1066
|
+
if (stopped && ((_job_items = job.items) != null ? _job_items : []).some((row)=>row.status === 'pending' || row.status === 'running')) {
|
|
1067
|
+
var _job_items3;
|
|
1068
|
+
const refunds = new Map();
|
|
1069
|
+
for (const row of (_job_items3 = job.items) != null ? _job_items3 : []){
|
|
1070
|
+
var _row_attemptCredits;
|
|
1071
|
+
if (row.status !== 'running') continue;
|
|
1072
|
+
const refund = await refundBuildCredits(firestore, orgId, job, {
|
|
1073
|
+
slot: row.slot,
|
|
1074
|
+
attempt: row.attempt,
|
|
1075
|
+
credits: (_row_attemptCredits = row.attemptCredits) != null ? _row_attemptCredits : 0,
|
|
1076
|
+
reason: stopped.reason,
|
|
1077
|
+
free: input.free,
|
|
1078
|
+
now
|
|
1079
|
+
});
|
|
1080
|
+
if (refund) refunds.set(row.slot, refund);
|
|
1081
|
+
}
|
|
1082
|
+
job = (await transition(firestore, orgId, jobId, (current)=>{
|
|
1083
|
+
var _current_items, _current_refundedCredits;
|
|
1084
|
+
if (isAiJobTerminal(current.status)) return null;
|
|
1085
|
+
let given = 0;
|
|
1086
|
+
const items = ((_current_items = current.items) != null ? _current_items : []).map((row)=>{
|
|
1087
|
+
if (row.status === 'running') {
|
|
1088
|
+
var _ref, _row_creditsRefunded, _ref1;
|
|
1089
|
+
const refund = refunds.get(row.slot);
|
|
1090
|
+
given += (_ref = refund == null ? void 0 : refund.credits) != null ? _ref : 0;
|
|
1091
|
+
return _extends({}, row, {
|
|
1092
|
+
status: 'failed',
|
|
1093
|
+
failure: {
|
|
1094
|
+
ours: true,
|
|
1095
|
+
reason: stopped.reason,
|
|
1096
|
+
message: stopped.message
|
|
1097
|
+
},
|
|
1098
|
+
creditsRefunded: ((_row_creditsRefunded = row.creditsRefunded) != null ? _row_creditsRefunded : 0) + ((_ref1 = refund == null ? void 0 : refund.credits) != null ? _ref1 : 0)
|
|
1099
|
+
}, refund ? {
|
|
1100
|
+
refundKey: refund.key
|
|
1101
|
+
} : {});
|
|
1102
|
+
}
|
|
1103
|
+
return row.status === 'pending' ? _extends({}, row, {
|
|
1104
|
+
status: 'skipped',
|
|
1105
|
+
note: AI_BUILD_NOT_REACHED_COPY
|
|
1106
|
+
}) : row;
|
|
1107
|
+
});
|
|
1108
|
+
return {
|
|
1109
|
+
items,
|
|
1110
|
+
refundedCredits: ((_current_refundedCredits = current.refundedCredits) != null ? _current_refundedCredits : 0) + given,
|
|
1111
|
+
updatedAt: now
|
|
1112
|
+
};
|
|
1113
|
+
})).job;
|
|
1114
|
+
}
|
|
1115
|
+
// A site is delivered when a page is (AGL-3616): a form alone is not a
|
|
1116
|
+
// site. A scaffold that owed no page (a deployment without the page kind)
|
|
1117
|
+
// is delivered by whatever it did build.
|
|
1118
|
+
const sitePages = job.kind === 'site' ? ((_job_items1 = job.items) != null ? _job_items1 : []).filter((row)=>row.op === 'page') : [];
|
|
1119
|
+
const delivered = sitePages.length ? sitePages.some((row)=>row.status === 'succeeded' || row.status === 'degraded') : aiBuildSettlement((_job_items2 = job.items) != null ? _job_items2 : []).delivered;
|
|
1120
|
+
const planCredits = aiBuildPlanCredits(job);
|
|
1121
|
+
if (delivered) {
|
|
1122
|
+
await transition(firestore, orgId, jobId, (current)=>isAiJobTerminal(current.status) ? null : {
|
|
1123
|
+
orchestration: {
|
|
1124
|
+
creditsSpent: planCredits,
|
|
1125
|
+
settled: 'charged'
|
|
1126
|
+
},
|
|
1127
|
+
updatedAt: now
|
|
1128
|
+
});
|
|
1129
|
+
return completeAiJob(firestore, orgId, jobId, now);
|
|
1130
|
+
}
|
|
1131
|
+
// A site that built no page gives back what its other parts cost too: it
|
|
1132
|
+
// was not built, and its Try again starts over from its answers.
|
|
1133
|
+
const parts = new Map();
|
|
1134
|
+
if (job.kind === 'site') {
|
|
1135
|
+
var _job_items4;
|
|
1136
|
+
for (const row of (_job_items4 = job.items) != null ? _job_items4 : []){
|
|
1137
|
+
var _row_creditsSpent, _row_creditsRefunded, _ref2;
|
|
1138
|
+
const owed = Math.max(0, ((_row_creditsSpent = row.creditsSpent) != null ? _row_creditsSpent : 0) - ((_row_creditsRefunded = row.creditsRefunded) != null ? _row_creditsRefunded : 0));
|
|
1139
|
+
if (owed <= 0 || row.status === 'failed') continue;
|
|
1140
|
+
const given = await refundBuildCredits(firestore, orgId, job, {
|
|
1141
|
+
slot: row.slot,
|
|
1142
|
+
attempt: row.attempt,
|
|
1143
|
+
credits: owed,
|
|
1144
|
+
reason: (_ref2 = stopped == null ? void 0 : stopped.reason) != null ? _ref2 : 'step-failure',
|
|
1145
|
+
free: input.free,
|
|
1146
|
+
now
|
|
1147
|
+
});
|
|
1148
|
+
if (given) parts.set(row.slot, given);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
const refund = ((_job_orchestration = job.orchestration) == null ? void 0 : _job_orchestration.settled) === 'refunded' ? null : await refundBuildCredits(firestore, orgId, job, {
|
|
1152
|
+
slot: 'plan',
|
|
1153
|
+
attempt: 1,
|
|
1154
|
+
credits: planCredits,
|
|
1155
|
+
reason: (_ref = stopped == null ? void 0 : stopped.reason) != null ? _ref : 'step-failure',
|
|
1156
|
+
free: input.free,
|
|
1157
|
+
now
|
|
1158
|
+
});
|
|
1159
|
+
await transition(firestore, orgId, jobId, (current)=>{
|
|
1160
|
+
var _current_refundedCredits, _ref, _ref1, _current_items;
|
|
1161
|
+
var _current_orchestration;
|
|
1162
|
+
return isAiJobTerminal(current.status) ? null : _extends({
|
|
1163
|
+
orchestration: {
|
|
1164
|
+
creditsSpent: planCredits,
|
|
1165
|
+
settled: refund || ((_current_orchestration = current.orchestration) == null ? void 0 : _current_orchestration.settled) === 'refunded' ? 'refunded' : 'charged'
|
|
1166
|
+
}
|
|
1167
|
+
}, refund || parts.size ? {
|
|
1168
|
+
refundedCredits: ((_current_refundedCredits = current.refundedCredits) != null ? _current_refundedCredits : 0) + ((_ref = refund == null ? void 0 : refund.credits) != null ? _ref : 0) + [
|
|
1169
|
+
...parts.values()
|
|
1170
|
+
].reduce((total, part)=>total + part.credits, 0),
|
|
1171
|
+
refundReason: (_ref1 = stopped == null ? void 0 : stopped.reason) != null ? _ref1 : 'step-failure'
|
|
1172
|
+
} : {}, parts.size ? {
|
|
1173
|
+
items: ((_current_items = current.items) != null ? _current_items : []).map((row)=>{
|
|
1174
|
+
var _row_creditsRefunded;
|
|
1175
|
+
const part = parts.get(row.slot);
|
|
1176
|
+
return part ? _extends({}, row, {
|
|
1177
|
+
creditsRefunded: ((_row_creditsRefunded = row.creditsRefunded) != null ? _row_creditsRefunded : 0) + part.credits,
|
|
1178
|
+
refundKey: part.key
|
|
1179
|
+
}) : row;
|
|
1180
|
+
})
|
|
1181
|
+
} : {}, {
|
|
1182
|
+
updatedAt: now
|
|
1183
|
+
});
|
|
1184
|
+
});
|
|
1185
|
+
const nothing = job.kind === 'site' ? AI_SITE_GUIDED_BUILD_FAILED_COPY : AI_BUILD_NOTHING_BUILT_COPY;
|
|
1186
|
+
return failAiJob(firestore, orgId, jobId, (_ref1 = stopped == null ? void 0 : stopped.message) != null ? _ref1 : nothing, {
|
|
1187
|
+
error: 'build delivered nothing'
|
|
1188
|
+
}, now);
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* Try again on a build (AGL-3616): its failed items back to pending on their
|
|
1192
|
+
* next attempt, with what they left unbuilt (`aiBuildRetryLedger`), and its
|
|
1193
|
+
* generation step queued again. Never re-runs an item that succeeded, never
|
|
1194
|
+
* re-asks the plan. Held at what the retried items are estimated to cost.
|
|
1195
|
+
* `changed: false` for a job that is not a finished build, or has nothing to
|
|
1196
|
+
* try again.
|
|
1197
|
+
*/ export async function retryAiBuildJob(firestore, orgId, jobId, now = new Date()) {
|
|
1198
|
+
let retried = [];
|
|
1199
|
+
const result = await transition(firestore, orgId, jobId, (current)=>{
|
|
1200
|
+
var _current_plan, _current_items;
|
|
1201
|
+
if (!aiBuildRetryable(current)) return null;
|
|
1202
|
+
const plan = ((_current_plan = current.plan) == null ? void 0 : _current_plan.status) === 'confirmed' ? current.plan : null;
|
|
1203
|
+
if (!plan || !((_current_items = current.items) == null ? void 0 : _current_items.length)) return null;
|
|
1204
|
+
const next = aiBuildRetryLedger(current.items, current.kind === 'site' ? aiLedgerUnits(current.items) : aiBuildUnits(plan));
|
|
1205
|
+
if (!next.ledger.some((row)=>row.status === 'pending')) return null;
|
|
1206
|
+
retried = next.retried;
|
|
1207
|
+
return {
|
|
1208
|
+
status: 'queued',
|
|
1209
|
+
items: next.ledger,
|
|
1210
|
+
steps: current.steps.map((step)=>step.name === AI_JOB_PLAN_STEP ? step : _extends({}, step, {
|
|
1211
|
+
status: 'pending',
|
|
1212
|
+
attempts: 0,
|
|
1213
|
+
passes: 0,
|
|
1214
|
+
endedAt: null,
|
|
1215
|
+
error: null
|
|
1216
|
+
})),
|
|
1217
|
+
review: null,
|
|
1218
|
+
error: null,
|
|
1219
|
+
lease: null,
|
|
1220
|
+
creditsReserved: Math.max(AI_JOB_STEP_RESERVE_CREDITS, current.kind === 'site' ? next.retried.length * AI_JOB_STEP_RESERVE_CREDITS : aiBuildCreditEstimate(plan, {
|
|
1221
|
+
slots: new Set(next.retried)
|
|
1222
|
+
})),
|
|
1223
|
+
updatedAt: now
|
|
1224
|
+
};
|
|
1225
|
+
});
|
|
1226
|
+
return _extends({}, result, {
|
|
1227
|
+
retried: result.changed ? retried : []
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
871
1230
|
/**
|
|
872
1231
|
* Resume a job that stopped for a person (AGL-2935): confirm its plan, or
|
|
873
1232
|
* try again the step whose answer broke a building rule. One transaction,
|
|
@@ -878,9 +1237,9 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
878
1237
|
* it queues are held at the nominal figure again, which the park released —
|
|
879
1238
|
* and a confirmed plan is held at what the whole job is estimated to cost
|
|
880
1239
|
* (AGL-3031), creations included, where that is more.
|
|
881
|
-
*/ export async function resumeAiJob(firestore, orgId, jobId, actor, now = new Date()) {
|
|
1240
|
+
*/ export async function resumeAiJob(firestore, orgId, jobId, actor, now = new Date(), options = {}) {
|
|
882
1241
|
return transition(firestore, orgId, jobId, (current)=>{
|
|
883
|
-
var _current_review;
|
|
1242
|
+
var _current_review, _current_inputs;
|
|
884
1243
|
if (current.status !== 'needs_review') return null;
|
|
885
1244
|
const steps = current.steps.map((step)=>step.status === 'pending' ? _extends({}, step, {
|
|
886
1245
|
attempts: 0
|
|
@@ -895,13 +1254,18 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
895
1254
|
confirmedBy: actor.uid
|
|
896
1255
|
})
|
|
897
1256
|
} : {};
|
|
1257
|
+
const publish = confirming && current.kind === 'build' && options.publishConfirmed && ((_current_inputs = current.inputs) == null ? void 0 : _current_inputs['publish']) === true ? {
|
|
1258
|
+
inputs: _extends({}, current.inputs, {
|
|
1259
|
+
publishConfirmed: true
|
|
1260
|
+
})
|
|
1261
|
+
} : {};
|
|
898
1262
|
return _extends({
|
|
899
1263
|
status: pending ? 'queued' : 'done',
|
|
900
1264
|
steps,
|
|
901
1265
|
review: null,
|
|
902
1266
|
error: null
|
|
903
|
-
}, confirmed, {
|
|
904
|
-
creditsReserved: pending ? Math.max(outstanding * AI_JOB_STEP_RESERVE_CREDITS, confirming ?
|
|
1267
|
+
}, confirmed, publish, {
|
|
1268
|
+
creditsReserved: pending ? Math.max(outstanding * AI_JOB_STEP_RESERVE_CREDITS, confirming ? aiJobCreditEstimate(current.kind, confirming) : 0) : 0,
|
|
905
1269
|
updatedAt: now
|
|
906
1270
|
});
|
|
907
1271
|
});
|
|
@@ -1005,7 +1369,7 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1005
1369
|
* round.
|
|
1006
1370
|
*/ export async function runAiJobStep(firestore, orgId, jobId, options) {
|
|
1007
1371
|
var _options_now, _step_attempts, _options_org, _data, _ref, _ref1, _outcome_effort, _step_passes, _options_actor;
|
|
1008
|
-
var _reservation_allotment, _reservation_allotment1;
|
|
1372
|
+
var _reservation_allotment, _reservation_allotment1, _outcome_review, _itemRecord_failure, _recorded_job_items;
|
|
1009
1373
|
const now = (_options_now = options.now) != null ? _options_now : new Date();
|
|
1010
1374
|
// A reservation the caller took before the job existed is a message
|
|
1011
1375
|
// counted against the org; a step that fails before the provider is
|
|
@@ -1046,32 +1410,28 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1046
1410
|
const runner = aiJobRunnerForStep(job.kind, step.name);
|
|
1047
1411
|
if (!runner) {
|
|
1048
1412
|
await releaseHeld();
|
|
1049
|
-
const failed = await failAiJob(firestore, orgId, jobId, AI_JOB_NOT_AVAILABLE_COPY, {
|
|
1050
|
-
stepIndex,
|
|
1051
|
-
error: `no runner registered for kind ${job.kind}`
|
|
1052
|
-
}, now);
|
|
1053
1413
|
return {
|
|
1054
1414
|
outcome: 'failed',
|
|
1055
|
-
job: await
|
|
1415
|
+
job: await failOurFailure(firestore, orgId, jobId, AI_JOB_NOT_AVAILABLE_COPY, {
|
|
1416
|
+
stepIndex,
|
|
1417
|
+
error: `no runner registered for kind ${job.kind}`
|
|
1418
|
+
}, {
|
|
1056
1419
|
reason: 'unavailable',
|
|
1057
|
-
stepCredits: 0
|
|
1058
|
-
|
|
1059
|
-
})
|
|
1420
|
+
stepCredits: 0
|
|
1421
|
+
}, now)
|
|
1060
1422
|
};
|
|
1061
1423
|
}
|
|
1062
1424
|
if (((_step_attempts = step.attempts) != null ? _step_attempts : 0) > AI_JOB_STEP_MAX_ATTEMPTS) {
|
|
1063
1425
|
await releaseHeld();
|
|
1064
|
-
const failed = await failAiJob(firestore, orgId, jobId, AI_UPSTREAM_FAILURE_COPY, {
|
|
1065
|
-
stepIndex,
|
|
1066
|
-
error: `step ${step.name} exhausted ${AI_JOB_STEP_MAX_ATTEMPTS} attempts`
|
|
1067
|
-
}, now);
|
|
1068
1426
|
return {
|
|
1069
1427
|
outcome: 'failed',
|
|
1070
|
-
job: await
|
|
1428
|
+
job: await failOurFailure(firestore, orgId, jobId, AI_UPSTREAM_FAILURE_COPY, {
|
|
1429
|
+
stepIndex,
|
|
1430
|
+
error: `step ${step.name} exhausted ${AI_JOB_STEP_MAX_ATTEMPTS} attempts`
|
|
1431
|
+
}, {
|
|
1071
1432
|
reason: 'timeout',
|
|
1072
|
-
stepCredits: 0
|
|
1073
|
-
|
|
1074
|
-
})
|
|
1433
|
+
stepCredits: 0
|
|
1434
|
+
}, now)
|
|
1075
1435
|
};
|
|
1076
1436
|
}
|
|
1077
1437
|
const org = (_options_org = options.org) != null ? _options_org : (_data = (await firestore.collection('orgs').doc(orgId).get()).data()) != null ? _data : {};
|
|
@@ -1197,18 +1557,16 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1197
1557
|
job: requeued
|
|
1198
1558
|
};
|
|
1199
1559
|
}
|
|
1200
|
-
const failed = await failAiJob(firestore, orgId, jobId, AI_UPSTREAM_FAILURE_COPY, {
|
|
1201
|
-
stepIndex,
|
|
1202
|
-
error
|
|
1203
|
-
}, now);
|
|
1204
1560
|
return {
|
|
1205
1561
|
outcome: 'failed',
|
|
1206
|
-
job: await
|
|
1562
|
+
job: await failOurFailure(firestore, orgId, jobId, AI_UPSTREAM_FAILURE_COPY, {
|
|
1563
|
+
stepIndex,
|
|
1564
|
+
error
|
|
1565
|
+
}, {
|
|
1207
1566
|
reason: 'provider',
|
|
1208
1567
|
stepCredits: 0,
|
|
1209
|
-
free: (_reservation_free = reservation.free) != null ? _reservation_free : null
|
|
1210
|
-
|
|
1211
|
-
})
|
|
1568
|
+
free: (_reservation_free = reservation.free) != null ? _reservation_free : null
|
|
1569
|
+
}, now)
|
|
1212
1570
|
};
|
|
1213
1571
|
}
|
|
1214
1572
|
const latencyMs = Math.max(0, Date.now() - runStarted);
|
|
@@ -1228,18 +1586,16 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1228
1586
|
creditsSpent: 0,
|
|
1229
1587
|
error: outcome.failure
|
|
1230
1588
|
}, now);
|
|
1231
|
-
const failed = await failAiJob(firestore, orgId, jobId, outcome.failure, {
|
|
1232
|
-
stepIndex,
|
|
1233
|
-
error: `step failure before the provider: ${outcome.failure}`
|
|
1234
|
-
}, now);
|
|
1235
1589
|
return {
|
|
1236
1590
|
outcome: 'failed',
|
|
1237
|
-
job: await
|
|
1591
|
+
job: await failOurFailure(firestore, orgId, jobId, outcome.failure, {
|
|
1592
|
+
stepIndex,
|
|
1593
|
+
error: `step failure before the provider: ${outcome.failure}`
|
|
1594
|
+
}, {
|
|
1238
1595
|
reason: 'step-failure',
|
|
1239
1596
|
stepCredits: 0,
|
|
1240
|
-
free: (_reservation_free1 = reservation.free) != null ? _reservation_free1 : null
|
|
1241
|
-
|
|
1242
|
-
})
|
|
1597
|
+
free: (_reservation_free1 = reservation.free) != null ? _reservation_free1 : null
|
|
1598
|
+
}, now)
|
|
1243
1599
|
};
|
|
1244
1600
|
}
|
|
1245
1601
|
const tokens = {
|
|
@@ -1303,19 +1659,45 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1303
1659
|
error: message,
|
|
1304
1660
|
tokens
|
|
1305
1661
|
}, now);
|
|
1306
|
-
const
|
|
1662
|
+
const detail = {
|
|
1307
1663
|
stepIndex,
|
|
1308
1664
|
error: outcome.refused ? 'stop_reason refusal' : `step failure: ${message}`
|
|
1309
|
-
}
|
|
1665
|
+
};
|
|
1310
1666
|
// A model declining the brief is not our failure; a step's own is (AGL-3594).
|
|
1311
1667
|
return {
|
|
1312
1668
|
outcome: 'failed',
|
|
1313
|
-
job: outcome.refused ?
|
|
1669
|
+
job: outcome.refused ? await failAiJob(firestore, orgId, jobId, message, detail, now) : await failOurFailure(firestore, orgId, jobId, message, detail, {
|
|
1314
1670
|
reason: 'step-failure',
|
|
1315
1671
|
stepCredits: credits,
|
|
1316
|
-
free: (_reservation_free3 = reservation.free) != null ? _reservation_free3 : null
|
|
1317
|
-
|
|
1318
|
-
|
|
1672
|
+
free: (_reservation_free3 = reservation.free) != null ? _reservation_free3 : null
|
|
1673
|
+
}, now)
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
// A guided site start whose step broke a building rule after its re-ask
|
|
1677
|
+
// fails, given back whole (AGL-3596): the person never saw the plan, so a
|
|
1678
|
+
// review asking them to look at it again is a review of nothing. The rule
|
|
1679
|
+
// and its findings go to the log for staff; the person reads one sentence,
|
|
1680
|
+
// and Try again starts a fresh job.
|
|
1681
|
+
if (((_outcome_review = outcome.review) == null ? void 0 : _outcome_review.reason) === 'doctrine' && aiJobAutoConfirms(job) && step.name !== AI_JOB_PLAN_STEP) {
|
|
1682
|
+
var _outcome_review_findings, _outcome_review_message, _reservation_free4;
|
|
1683
|
+
await recordStep(firestore, orgId, jobId, options.owner, stepIndex, _extends({
|
|
1684
|
+
status: 'failed',
|
|
1685
|
+
creditsSpent: credits,
|
|
1686
|
+
error: AI_SITE_GUIDED_BUILD_FAILED_COPY,
|
|
1687
|
+
outputs: outcome.outputs
|
|
1688
|
+
}, spentNothing ? {} : {
|
|
1689
|
+
tokens
|
|
1690
|
+
}), now);
|
|
1691
|
+
return {
|
|
1692
|
+
outcome: 'failed',
|
|
1693
|
+
job: await failOurFailure(firestore, orgId, jobId, AI_SITE_GUIDED_BUILD_FAILED_COPY, {
|
|
1694
|
+
stepIndex,
|
|
1695
|
+
error: `doctrine refusal after the re-ask: ${JSON.stringify((_outcome_review_findings = outcome.review.findings) != null ? _outcome_review_findings : [])} ${(_outcome_review_message = outcome.review.message) != null ? _outcome_review_message : ''}`
|
|
1696
|
+
}, {
|
|
1697
|
+
reason: 'doctrine-refused',
|
|
1698
|
+
stepCredits: credits,
|
|
1699
|
+
free: (_reservation_free4 = reservation.free) != null ? _reservation_free4 : null
|
|
1700
|
+
}, now)
|
|
1319
1701
|
};
|
|
1320
1702
|
}
|
|
1321
1703
|
// A step that stopped for a person parks the job in the same write that
|
|
@@ -1329,6 +1711,26 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1329
1711
|
// for a person never continues: the review is what it waits on. The pass
|
|
1330
1712
|
// cap is what stops a runner that never finishes: past it the pass is
|
|
1331
1713
|
// recorded as the step's last, and what it produced stands.
|
|
1714
|
+
// A build item that failed on our side gives back its own attempt's spend
|
|
1715
|
+
// before its row is written (AGL-3616), so the row records what was given.
|
|
1716
|
+
let itemRecord = aiJobSettlesByItem(job.kind) ? outcome.item : undefined;
|
|
1717
|
+
if ((itemRecord == null ? void 0 : itemRecord.status) === 'failed' && ((_itemRecord_failure = itemRecord.failure) == null ? void 0 : _itemRecord_failure.ours)) {
|
|
1718
|
+
var _ref2, _outcome_items, _ref3, _ref4, _reservation_free5;
|
|
1719
|
+
const slot = itemRecord.slot;
|
|
1720
|
+
const row = ((_ref2 = (_outcome_items = outcome.items) != null ? _outcome_items : job.items) != null ? _ref2 : []).find((one)=>one.slot === slot);
|
|
1721
|
+
const refund = await refundBuildCredits(firestore, orgId, job, {
|
|
1722
|
+
slot,
|
|
1723
|
+
attempt: (_ref3 = row == null ? void 0 : row.attempt) != null ? _ref3 : 1,
|
|
1724
|
+
credits: ((_ref4 = row == null ? void 0 : row.attemptCredits) != null ? _ref4 : 0) + credits,
|
|
1725
|
+
reason: itemRecord.failure.reason,
|
|
1726
|
+
free: (_reservation_free5 = reservation.free) != null ? _reservation_free5 : null,
|
|
1727
|
+
now
|
|
1728
|
+
});
|
|
1729
|
+
if (refund) itemRecord = _extends({}, itemRecord, {
|
|
1730
|
+
creditsRefunded: refund.credits,
|
|
1731
|
+
refundKey: refund.key
|
|
1732
|
+
});
|
|
1733
|
+
}
|
|
1332
1734
|
const review = outcome.review;
|
|
1333
1735
|
const continuing = !review && Boolean(outcome.continue) && ((_step_passes = step.passes) != null ? _step_passes : 0) + 1 < aiJobStepMaxPasses(job.kind);
|
|
1334
1736
|
if (!review && outcome.continue && !continuing) {
|
|
@@ -1354,6 +1756,12 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1354
1756
|
review
|
|
1355
1757
|
} : {}, outcome.sitePublish ? {
|
|
1356
1758
|
sitePublish: outcome.sitePublish
|
|
1759
|
+
} : {}, aiJobSettlesByItem(job.kind) && outcome.items ? {
|
|
1760
|
+
items: outcome.items
|
|
1761
|
+
} : {}, itemRecord ? {
|
|
1762
|
+
item: itemRecord
|
|
1763
|
+
} : {}, review ? {
|
|
1764
|
+
quiet: true
|
|
1357
1765
|
} : {}), now);
|
|
1358
1766
|
const actor = (_options_actor = options.actor) != null ? _options_actor : {
|
|
1359
1767
|
uid: job.createdBy
|
|
@@ -1410,7 +1818,7 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1410
1818
|
};
|
|
1411
1819
|
}
|
|
1412
1820
|
if (review && recorded.job.status === 'needs_review') {
|
|
1413
|
-
var
|
|
1821
|
+
var _reservation_free6;
|
|
1414
1822
|
// Nobody parked the job but its own step, so the row names no actor, as
|
|
1415
1823
|
// the meter's park does.
|
|
1416
1824
|
await logAiJobNeedsInput(orgId, {
|
|
@@ -1420,15 +1828,22 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1420
1828
|
kind: job.kind,
|
|
1421
1829
|
reason: review.reason
|
|
1422
1830
|
});
|
|
1423
|
-
// A plan the plan rules still refused
|
|
1831
|
+
// A plan the plan rules still refused, and a step that still broke a
|
|
1832
|
+
// building rule after its re-ask, are our failures (AGL-3594, AGL-3596):
|
|
1833
|
+
// given back before the person is told, so the notice can say so. Trying
|
|
1834
|
+
// again spends anew, and a second refusal gives back again, within the
|
|
1835
|
+
// day's bound.
|
|
1836
|
+
const ours = outcome.uncredited ? 'plan-refused' : review.reason === 'doctrine' ? 'doctrine-refused' : null;
|
|
1837
|
+
const parked = ours ? await refundOurFailure(firestore, orgId, recorded.job, {
|
|
1838
|
+
reason: ours,
|
|
1839
|
+
stepCredits: credits,
|
|
1840
|
+
free: (_reservation_free6 = reservation.free) != null ? _reservation_free6 : null,
|
|
1841
|
+
now
|
|
1842
|
+
}) : recorded.job;
|
|
1843
|
+
if (recorded.parked) await announceAiJobTransition(parked, 'needs-review');
|
|
1424
1844
|
return {
|
|
1425
1845
|
outcome: 'needs_review',
|
|
1426
|
-
job:
|
|
1427
|
-
reason: 'plan-refused',
|
|
1428
|
-
stepCredits: credits,
|
|
1429
|
-
free: (_reservation_free4 = reservation.free) != null ? _reservation_free4 : null,
|
|
1430
|
-
now
|
|
1431
|
-
}) : recorded.job
|
|
1846
|
+
job: parked
|
|
1432
1847
|
};
|
|
1433
1848
|
}
|
|
1434
1849
|
if (recorded.remaining > 0 || isAiJobTerminal(recorded.job.status)) {
|
|
@@ -1437,6 +1852,25 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1437
1852
|
job: recorded.job
|
|
1438
1853
|
};
|
|
1439
1854
|
}
|
|
1855
|
+
// A build ends by its items (AGL-3616): done when anything was delivered.
|
|
1856
|
+
if (aiJobSettlesByItem(job.kind) && step.name !== AI_JOB_PLAN_STEP && ((_recorded_job_items = recorded.job.items) == null ? void 0 : _recorded_job_items.length)) {
|
|
1857
|
+
var _recorded_job_items1, _reservation_free7;
|
|
1858
|
+
// Items still open here are ones the pass cap cut off: the build stopped.
|
|
1859
|
+
const open = aiBuildSettlement((_recorded_job_items1 = recorded.job.items) != null ? _recorded_job_items1 : []).open;
|
|
1860
|
+
const settled = await settleAiBuildJob(firestore, orgId, jobId, _extends({
|
|
1861
|
+
now,
|
|
1862
|
+
free: (_reservation_free7 = reservation.free) != null ? _reservation_free7 : null
|
|
1863
|
+
}, open ? {
|
|
1864
|
+
stopped: {
|
|
1865
|
+
message: AI_UPSTREAM_FAILURE_COPY,
|
|
1866
|
+
reason: 'timeout'
|
|
1867
|
+
}
|
|
1868
|
+
} : {}));
|
|
1869
|
+
return {
|
|
1870
|
+
outcome: settled.status === 'failed' ? 'failed' : 'done',
|
|
1871
|
+
job: settled
|
|
1872
|
+
};
|
|
1873
|
+
}
|
|
1440
1874
|
return {
|
|
1441
1875
|
outcome: 'done',
|
|
1442
1876
|
job: await completeAiJob(firestore, orgId, jobId, now)
|