@aglyn/plugins-ai 1.0.0-beta.236 → 1.0.0-beta.238
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/components/ai-credits-prompt.component.d.ts +44 -0
- package/src/lib/components/ai-credits-prompt.component.js +117 -0
- package/src/lib/components/ai-credits-prompt.component.js.map +1 -0
- package/src/lib/components/ai-job-cancel.component.d.ts +38 -0
- package/src/lib/components/ai-job-cancel.component.js +156 -0
- package/src/lib/components/ai-job-cancel.component.js.map +1 -0
- package/src/lib/components/ai-job-plan.component.d.ts +5 -4
- package/src/lib/components/ai-job-plan.component.js +48 -12
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-job-requests.d.ts +10 -0
- package/src/lib/components/ai-job-requests.js +12 -4
- package/src/lib/components/ai-job-requests.js.map +1 -1
- package/src/lib/components/ai-jobs-list-page.component.js +141 -72
- package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +48 -9
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-site-start-card.component.js +68 -22
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-usage-strip.component.d.ts +19 -2
- package/src/lib/components/ai-usage-strip.component.js +40 -9
- package/src/lib/components/ai-usage-strip.component.js.map +1 -1
- package/src/lib/components/assist-build-card.component.d.ts +3 -1
- package/src/lib/components/assist-build-card.component.js +58 -7
- package/src/lib/components/assist-build-card.component.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.js +66 -22
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.js +89 -52
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/jobs/ai-job-admission.d.ts +22 -2
- package/src/lib/jobs/ai-job-admission.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.d.ts +24 -11
- package/src/lib/jobs/ai-job-build-step.js +54 -35
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-cancel.d.ts +71 -0
- package/src/lib/jobs/ai-job-cancel.js +102 -0
- package/src/lib/jobs/ai-job-cancel.js.map +1 -0
- package/src/lib/jobs/ai-job-component-step.js +4 -1
- package/src/lib/jobs/ai-job-component-step.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.js +2 -0
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-form-step.js +4 -1
- package/src/lib/jobs/ai-job-form-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +2 -0
- package/src/lib/jobs/ai-job-layout-language.js +13 -1
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +6 -1
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.js +12 -0
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.d.ts +13 -0
- package/src/lib/jobs/ai-job-page-step.js +46 -4
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +42 -0
- package/src/lib/jobs/ai-job-plan-step.js +118 -18
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +49 -3
- package/src/lib/jobs/ai-job-site-content.js +196 -37
- package/src/lib/jobs/ai-job-site-content.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.js +49 -16
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-template-step.js +4 -1
- package/src/lib/jobs/ai-job-template-step.js.map +1 -1
- package/src/lib/jobs/ai-job-text-step.d.ts +8 -0
- package/src/lib/jobs/ai-job-text-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +55 -13
- package/src/lib/jobs/ai-jobs.js +213 -41
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +194 -34
- package/src/lib/jobs/ai-layout-stock-photos.js +861 -146
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.d.ts +30 -0
- package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.js +220 -0
- package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.d.ts +27 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.js +151 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.d.ts +33 -0
- package/src/lib/layout-language/ai-layout-compiler.js +381 -18
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-design.d.ts +6 -3
- package/src/lib/layout-language/ai-layout-design.js +5 -3
- package/src/lib/layout-language/ai-layout-design.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame.d.ts +12 -0
- package/src/lib/layout-language/ai-layout-frame.js +58 -3
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-gaps.js +3 -1
- package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
- package/src/lib/layout-language/ai-layout-language.js +1 -1
- package/src/lib/layout-language/ai-layout-language.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +49 -1
- package/src/lib/layout-language/ai-layout-listings.js +117 -47
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +10 -1
- package/src/lib/layout-language/ai-layout-pictures.js +5 -2
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/layout-language/ai-layout-storefront.d.ts +82 -0
- package/src/lib/layout-language/ai-layout-storefront.js +223 -0
- package/src/lib/layout-language/ai-layout-storefront.js.map +1 -0
- package/src/lib/model/ai-build-job.d.ts +39 -0
- package/src/lib/model/ai-build-job.js +100 -1
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-build-progress.d.ts +8 -0
- package/src/lib/model/ai-build-progress.js +35 -0
- package/src/lib/model/ai-build-progress.js.map +1 -1
- package/src/lib/model/ai-credit-estimate.d.ts +249 -0
- package/src/lib/model/ai-credit-estimate.js +278 -0
- package/src/lib/model/ai-credit-estimate.js.map +1 -0
- package/src/lib/model/ai-job-cancel-copy.d.ts +44 -0
- package/src/lib/model/ai-job-cancel-copy.js +53 -0
- package/src/lib/model/ai-job-cancel-copy.js.map +1 -0
- package/src/lib/model/ai-job-failure-copy.d.ts +4 -1
- package/src/lib/model/ai-job-failure-copy.js +8 -3
- package/src/lib/model/ai-job-failure-copy.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +35 -0
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-site-build-progress.js +7 -4
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +76 -22
- package/src/lib/model/ai-site-job.js +125 -23
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-kinds.d.ts +4 -1
- package/src/lib/model/ai-site-kinds.js +35 -7
- package/src/lib/model/ai-site-kinds.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +37 -1
- package/src/lib/runtime/ai-doctrine-validators.js +161 -7
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +4 -3
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-node-tree.d.ts +11 -0
- package/src/lib/runtime/ai-node-tree.js +26 -0
- package/src/lib/runtime/ai-node-tree.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +592 -14
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/server/ai-jobs-cancel.d.ts +13 -5
- package/src/lib/server/ai-jobs-cancel.js +27 -9
- package/src/lib/server/ai-jobs-cancel.js.map +1 -1
- package/src/lib/server/ai-jobs-gate.d.ts +12 -2
- package/src/lib/server/ai-jobs-gate.js +20 -9
- package/src/lib/server/ai-jobs-gate.js.map +1 -1
- package/src/lib/server/ai-jobs-resume.js +93 -35
- package/src/lib/server/ai-jobs-resume.js.map +1 -1
- package/src/lib/server/ai-jobs-route.js +45 -4
- package/src/lib/server/ai-jobs-route.js.map +1 -1
- package/src/lib/server/assist-chat.js +4 -1
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-edit.js +3 -1
- package/src/lib/server/assist-edit.js.map +1 -1
- package/src/lib/server/assist-music.d.ts +36 -0
- package/src/lib/server/assist-music.js +36 -0
- package/src/lib/server/assist-music.js.map +1 -0
- package/src/lib/server.js +6 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/usage/ai-usage-meter.d.ts +2 -1
- package/src/lib/usage/ai-usage-meter.js +17 -1
- package/src/lib/usage/ai-usage-meter.js.map +1 -1
- package/src/lib/usage/ai-usage-wire.d.ts +6 -0
- package/src/lib/usage/ai-usage-wire.js.map +1 -1
- package/src/lib/usage/assist-usage.d.ts +7 -0
- package/src/lib/usage/assist-usage.js +14 -4
- package/src/lib/usage/assist-usage.js.map +1 -1
- package/src/lib/usage/free-ai-credits-left.d.ts +7 -0
- package/src/lib/usage/free-ai-credits-left.js +4 -2
- package/src/lib/usage/free-ai-credits-left.js.map +1 -1
package/src/lib/jobs/ai-jobs.js
CHANGED
|
@@ -46,6 +46,7 @@ import { aiJobAutoConfirms } from "./ai-job-auto-confirm.js";
|
|
|
46
46
|
import { AI_SITE_GUIDED_BUILD_FAILED_COPY, aiCustomerSafeCopy, aiCustomerSafeItem, aiCustomerSafeReview } from "../model/ai-job-failure-copy.js";
|
|
47
47
|
import { aiSiteStartInputsOf } from "../model/ai-site-start.js";
|
|
48
48
|
import { addAdminAudit } from "@aglyn/tenant-data-admin/server/admin-audit-write";
|
|
49
|
+
import { AI_JOB_CANCELED_ITEM_NOTE, aiJobCancelPending, isAiJobCanceledError, watchAiJobCancel } from "./ai-job-cancel.js";
|
|
49
50
|
/**
|
|
50
51
|
* AI generation jobs — the Firestore state machine (AGL-2904).
|
|
51
52
|
*
|
|
@@ -383,7 +384,7 @@ function jobFrom(snapshot) {
|
|
|
383
384
|
} : {}));
|
|
384
385
|
// A job is a resource the console lists and routes to, named as one.
|
|
385
386
|
const ref = jobsCollection(firestore, input.orgId).doc(createResourceUid());
|
|
386
|
-
const data = {
|
|
387
|
+
const data = _extends({
|
|
387
388
|
orgId: input.orgId,
|
|
388
389
|
hostId: (_input_hostId = input.hostId) != null ? _input_hostId : null,
|
|
389
390
|
kind: input.kind,
|
|
@@ -403,7 +404,9 @@ function jobFrom(snapshot) {
|
|
|
403
404
|
expiresAt: assistExchangeExpiry(now),
|
|
404
405
|
error: null,
|
|
405
406
|
lease: null
|
|
406
|
-
}
|
|
407
|
+
}, input.creditsConfirmed ? {
|
|
408
|
+
creditsConfirmed: input.creditsConfirmed
|
|
409
|
+
} : {});
|
|
407
410
|
await ref.set(data);
|
|
408
411
|
await logAiJobCreated(input.orgId, {
|
|
409
412
|
uid: input.createdBy,
|
|
@@ -522,6 +525,8 @@ export async function getAiJob(firestore, orgId, jobId) {
|
|
|
522
525
|
orchestration: job.orchestration
|
|
523
526
|
} : {}, job.kind === 'build' && ((_job_inputs1 = job.inputs) == null ? void 0 : _job_inputs1['publish']) === true ? {
|
|
524
527
|
publishAsked: true
|
|
528
|
+
} : {}, aiJobCancelPending(job) ? {
|
|
529
|
+
cancelRequested: true
|
|
525
530
|
} : {});
|
|
526
531
|
}
|
|
527
532
|
function leaseIsLive(job, owner, nowMs) {
|
|
@@ -531,6 +536,52 @@ function leaseIsLive(job, owner, nowMs) {
|
|
|
531
536
|
const until = toMillis(lease.until);
|
|
532
537
|
return until !== null && until > nowMs;
|
|
533
538
|
}
|
|
539
|
+
/** Whether ANY owner's lease on the job is still live: a step is in flight. */ function anyLeaseIsLive(job, nowMs) {
|
|
540
|
+
var _job_lease;
|
|
541
|
+
const until = toMillis((_job_lease = job.lease) == null ? void 0 : _job_lease.until);
|
|
542
|
+
return Boolean(job.lease) && until !== null && until > nowMs;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* The write that ends a job `canceled` (AGL-3616): it holds nothing and no
|
|
546
|
+
* lease, a step left running goes back to `pending` (it did not finish), and
|
|
547
|
+
* every item not yet built is skipped with the cancel's note. What the job
|
|
548
|
+
* built, spent and was given back stays as it is: drafts stay drafts, and
|
|
549
|
+
* credits for what ran stay charged.
|
|
550
|
+
*/ function aiJobCanceledPatch(job, now, request) {
|
|
551
|
+
var _job_steps;
|
|
552
|
+
var _job_items;
|
|
553
|
+
return _extends({
|
|
554
|
+
status: 'canceled',
|
|
555
|
+
cancelRequested: request,
|
|
556
|
+
creditsReserved: 0,
|
|
557
|
+
lease: null,
|
|
558
|
+
error: null,
|
|
559
|
+
steps: ((_job_steps = job.steps) != null ? _job_steps : []).map((step)=>step.status === 'running' ? _extends({}, step, {
|
|
560
|
+
status: 'pending',
|
|
561
|
+
endedAt: null
|
|
562
|
+
}) : step)
|
|
563
|
+
}, ((_job_items = job.items) == null ? void 0 : _job_items.length) ? {
|
|
564
|
+
items: aiCanceledItems(job.items)
|
|
565
|
+
} : {}, {
|
|
566
|
+
updatedAt: now
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
function aiCanceledItems(items) {
|
|
570
|
+
return items.map((row)=>row.status === 'pending' || row.status === 'running' ? _extends({}, row, {
|
|
571
|
+
status: 'skipped',
|
|
572
|
+
note: AI_JOB_CANCELED_ITEM_NOTE
|
|
573
|
+
}) : row);
|
|
574
|
+
}
|
|
575
|
+
/** The cancel a pending-cancel job carries, or one made now for a job that has none. */ function cancelRequestOf(job, now) {
|
|
576
|
+
var _job_cancelRequested;
|
|
577
|
+
return (_job_cancelRequested = job.cancelRequested) != null ? _job_cancelRequested : aiJobCancelRequest(now, null);
|
|
578
|
+
}
|
|
579
|
+
/** A cancel asked now by `by`, as the job stores it (the instant is written as a Date). */ function aiJobCancelRequest(now, by) {
|
|
580
|
+
return {
|
|
581
|
+
at: now,
|
|
582
|
+
by
|
|
583
|
+
};
|
|
584
|
+
}
|
|
534
585
|
function nextStepIndex(job) {
|
|
535
586
|
var _job_steps;
|
|
536
587
|
return ((_job_steps = job.steps) != null ? _job_steps : []).findIndex((step)=>step.status === 'pending' || step.status === 'running');
|
|
@@ -559,6 +610,16 @@ function nextStepIndex(job) {
|
|
|
559
610
|
return firestore.runTransaction(async (tx)=>{
|
|
560
611
|
const job = jobFrom(await tx.get(ref));
|
|
561
612
|
if (!job) return null;
|
|
613
|
+
// A cancel asked while a step ran, whose runner never came back to end
|
|
614
|
+
// it (AGL-3616): the lease ran out, so the job ends here, unclaimed.
|
|
615
|
+
if (aiJobCancelPending(job)) {
|
|
616
|
+
if (!leaseIsLive(job, owner, now.getTime())) {
|
|
617
|
+
tx.set(ref, aiJobCanceledPatch(job, now, cancelRequestOf(job, now)), {
|
|
618
|
+
merge: true
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
return null;
|
|
622
|
+
}
|
|
562
623
|
const stepIndex = claimableStepIndex(job, owner, now.getTime());
|
|
563
624
|
if (stepIndex === -1) return null;
|
|
564
625
|
const steps = job.steps.map((step, index)=>{
|
|
@@ -630,7 +691,17 @@ function nextStepIndex(job) {
|
|
|
630
691
|
const ref = jobsCollection(firestore, orgId).doc(jobId);
|
|
631
692
|
return firestore.runTransaction(async (tx)=>{
|
|
632
693
|
const job = jobFrom(await tx.get(ref));
|
|
633
|
-
if (!job
|
|
694
|
+
if (!job) return null;
|
|
695
|
+
// A paused workspace's job a person canceled ends canceled (AGL-3616).
|
|
696
|
+
if (aiJobCancelPending(job)) {
|
|
697
|
+
if (!leaseIsLive(job, owner, now.getTime())) {
|
|
698
|
+
tx.set(ref, aiJobCanceledPatch(job, now, cancelRequestOf(job, now)), {
|
|
699
|
+
merge: true
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
return null;
|
|
703
|
+
}
|
|
704
|
+
if (claimableStepIndex(job, owner, now.getTime()) === -1) return null;
|
|
634
705
|
tx.set(ref, {
|
|
635
706
|
updatedAt: now
|
|
636
707
|
}, {
|
|
@@ -732,8 +803,8 @@ function nextStepIndex(job) {
|
|
|
732
803
|
*/ export async function recordStep(firestore, orgId, jobId, owner, stepIndex, input, now = new Date()) {
|
|
733
804
|
const ref = jobsCollection(firestore, orgId).doc(jobId);
|
|
734
805
|
const recorded = await firestore.runTransaction(async (tx)=>{
|
|
735
|
-
var _ref, _input_items, _job_outputs, _input_outputs, _job_creditsSpent, _job_creditsReserved, _job_creditsReserved1, _job_refundedCredits;
|
|
736
|
-
var _job_lease, _input_item;
|
|
806
|
+
var _ref, _input_items, _job_outputs, _input_outputs, _job_creditsSpent, _job_creditsReserved, _job_creditsReserved1, _job_refundedCredits, _ref1;
|
|
807
|
+
var _job_lease, _input_item, _this;
|
|
737
808
|
const job = jobFrom(await tx.get(ref));
|
|
738
809
|
if (!job) throw new Error(`ai job ${orgId}/${jobId} vanished`);
|
|
739
810
|
const steps = job.steps.map((step, index)=>{
|
|
@@ -756,8 +827,11 @@ function nextStepIndex(job) {
|
|
|
756
827
|
const items = ledger && input.item ? aiApplyJobItemRecord(ledger, input.item, input.creditsSpent, now) : input.items ? ledger : null;
|
|
757
828
|
const settled = input.status !== 'pending';
|
|
758
829
|
const ownsLease = ((_job_lease = job.lease) == null ? void 0 : _job_lease.owner) === owner;
|
|
759
|
-
|
|
760
|
-
|
|
830
|
+
// A cancel asked while this step ran ends the job here (AGL-3616), in the
|
|
831
|
+
// write that records what the step cost and built: nothing after it runs.
|
|
832
|
+
const canceling = aiJobCancelPending(job);
|
|
833
|
+
const parksForReview = Boolean(input.review) && !isAiJobTerminal(job.status) && !canceling;
|
|
834
|
+
const status = isAiJobTerminal(job.status) ? job.status : canceling ? 'canceled' : job.status === 'needs_input' ? job.status : parksForReview ? 'needs_review' : input.status === 'failed' ? 'running' : remaining > 0 ? 'queued' : 'running';
|
|
761
835
|
const patch = _extends({
|
|
762
836
|
status,
|
|
763
837
|
steps,
|
|
@@ -783,7 +857,16 @@ function nextStepIndex(job) {
|
|
|
783
857
|
} : {}, parksForReview && input.review ? {
|
|
784
858
|
review: input.review,
|
|
785
859
|
error: input.review.reason === 'plan' ? null : input.review.message
|
|
786
|
-
} : {}
|
|
860
|
+
} : {}, canceling ? _extends({
|
|
861
|
+
creditsReserved: 0,
|
|
862
|
+
lease: null,
|
|
863
|
+
error: null,
|
|
864
|
+
steps: steps.map((step)=>step.status === 'running' ? _extends({}, step, {
|
|
865
|
+
status: 'pending'
|
|
866
|
+
}) : step)
|
|
867
|
+
}, ((_this = items != null ? items : job.items) == null ? void 0 : _this.length) ? {
|
|
868
|
+
items: aiCanceledItems((_ref1 = items != null ? items : job.items) != null ? _ref1 : [])
|
|
869
|
+
} : {}) : {});
|
|
787
870
|
tx.set(ref, patch, {
|
|
788
871
|
merge: true
|
|
789
872
|
});
|
|
@@ -821,14 +904,14 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
821
904
|
});
|
|
822
905
|
}
|
|
823
906
|
/** Every step done: the job is `done` and holds nothing. */ export async function completeAiJob(firestore, orgId, jobId, now = new Date()) {
|
|
824
|
-
const { job, changed } = await transition(firestore, orgId, jobId, (current)=>isAiJobTerminal(current.status) ? null : {
|
|
907
|
+
const { job, changed } = await transition(firestore, orgId, jobId, (current)=>isAiJobTerminal(current.status) ? null : aiJobCancelPending(current) ? aiJobCanceledPatch(current, now, cancelRequestOf(current, now)) : {
|
|
825
908
|
status: 'done',
|
|
826
909
|
creditsReserved: 0,
|
|
827
910
|
lease: null,
|
|
828
911
|
error: null,
|
|
829
912
|
updatedAt: now
|
|
830
913
|
});
|
|
831
|
-
if (changed) await announceAiJobTransition(job, 'done');
|
|
914
|
+
if (changed && job.status === 'done') await announceAiJobTransition(job, 'done');
|
|
832
915
|
return job;
|
|
833
916
|
}
|
|
834
917
|
/**
|
|
@@ -843,7 +926,7 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
843
926
|
stepIndex: (_ref = detail == null ? void 0 : detail.stepIndex) != null ? _ref : null,
|
|
844
927
|
error: (detail == null ? void 0 : detail.error) instanceof Error ? detail.error.message : (_ref1 = detail == null ? void 0 : detail.error) != null ? _ref1 : null
|
|
845
928
|
});
|
|
846
|
-
const { job, changed } = await transition(firestore, orgId, jobId, (current)=>isAiJobTerminal(current.status) ? null : {
|
|
929
|
+
const { job, changed } = await transition(firestore, orgId, jobId, (current)=>isAiJobTerminal(current.status) ? null : aiJobCancelPending(current) ? aiJobCanceledPatch(current, now, cancelRequestOf(current, now)) : {
|
|
847
930
|
status: 'failed',
|
|
848
931
|
error: message,
|
|
849
932
|
creditsReserved: 0,
|
|
@@ -855,7 +938,7 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
855
938
|
}) : step),
|
|
856
939
|
updatedAt: now
|
|
857
940
|
});
|
|
858
|
-
if (changed) {
|
|
941
|
+
if (changed && job.status === 'failed') {
|
|
859
942
|
var _ref2, _ref3;
|
|
860
943
|
await announceAiJobTransition(job, 'failed', {
|
|
861
944
|
ours: (_ref2 = detail == null ? void 0 : detail.ours) != null ? _ref2 : false,
|
|
@@ -870,7 +953,7 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
870
953
|
* step goes back to `pending` so the same step is what runs when the beat
|
|
871
954
|
* tries again; its attempt is not counted, because nothing was attempted.
|
|
872
955
|
*/ export async function markAiJobNeedsInput(firestore, orgId, jobId, reason, now = new Date()) {
|
|
873
|
-
const { job } = await transition(firestore, orgId, jobId, (current)=>isAiJobTerminal(current.status) ? null : {
|
|
956
|
+
const { job } = await transition(firestore, orgId, jobId, (current)=>isAiJobTerminal(current.status) ? null : aiJobCancelPending(current) ? aiJobCanceledPatch(current, now, cancelRequestOf(current, now)) : {
|
|
874
957
|
status: 'needs_input',
|
|
875
958
|
error: reason,
|
|
876
959
|
lease: null,
|
|
@@ -886,23 +969,46 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
886
969
|
return job;
|
|
887
970
|
}
|
|
888
971
|
/**
|
|
889
|
-
* Cancel
|
|
890
|
-
*
|
|
891
|
-
*
|
|
892
|
-
* goes to complete it.
|
|
972
|
+
* Cancel (AGL-2904, AGL-3616). One transaction decides it against whatever
|
|
973
|
+
* else commits, so a cancel racing a finish resolves to whichever committed
|
|
974
|
+
* first: a job already `done`, `failed` or `canceled` is returned unchanged.
|
|
893
975
|
*
|
|
894
|
-
*
|
|
895
|
-
*
|
|
896
|
-
*
|
|
897
|
-
*
|
|
976
|
+
* - **No step in flight** (queued, parked for a person or the meter, or a
|
|
977
|
+
* lease that ran out): the job ends `canceled` in this write. It holds
|
|
978
|
+
* nothing, its unbuilt items are skipped, and nothing it built is touched.
|
|
979
|
+
* - **A step in flight** (a live lease): the cancel is recorded as
|
|
980
|
+
* `cancelRequested` and the job keeps its status until the runner holding
|
|
981
|
+
* the lease comes back. The runner watches for it and aborts its provider
|
|
982
|
+
* call; whatever the step had spent is metered, and the machine's next
|
|
983
|
+
* write ends the job `canceled` (`recordStep`, or the finish or failure it
|
|
984
|
+
* was about to write, which a pending cancel turns into the cancel).
|
|
985
|
+
*
|
|
986
|
+
* Either way a canceled job is never failed: no `ai.jobFailed` alert, no
|
|
987
|
+
* failure notice, no give-back (what ran stays charged), no publish.
|
|
988
|
+
*
|
|
989
|
+
* The activity row is written once, when the cancel is first asked: a second
|
|
990
|
+
* cancel of the same job is not a second act. `actor` is the member who
|
|
991
|
+
* canceled; a cancel with nobody named is recorded as nobody's rather than
|
|
992
|
+
* as the creator's.
|
|
898
993
|
*/ export async function cancelAiJob(firestore, orgId, jobId, now = new Date(), actor = null) {
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
994
|
+
var _ref;
|
|
995
|
+
const request = aiJobCancelRequest(now, (_ref = actor == null ? void 0 : actor.uid) != null ? _ref : null);
|
|
996
|
+
let firstAsk = false;
|
|
997
|
+
const result = await transition(firestore, orgId, jobId, (current)=>{
|
|
998
|
+
if (isAiJobTerminal(current.status)) return null;
|
|
999
|
+
const inFlight = anyLeaseIsLive(current, now.getTime());
|
|
1000
|
+
if (current.cancelRequested) {
|
|
1001
|
+
// Asked already: nothing new while its step is still in flight; a
|
|
1002
|
+
// step whose runner never came back is ended now.
|
|
1003
|
+
return inFlight ? null : aiJobCanceledPatch(current, now, current.cancelRequested);
|
|
1004
|
+
}
|
|
1005
|
+
firstAsk = true;
|
|
1006
|
+
return inFlight ? {
|
|
1007
|
+
cancelRequested: request,
|
|
903
1008
|
updatedAt: now
|
|
904
|
-
});
|
|
905
|
-
|
|
1009
|
+
} : aiJobCanceledPatch(current, now, request);
|
|
1010
|
+
});
|
|
1011
|
+
if (result.changed && firstAsk) {
|
|
906
1012
|
await logAiJobCanceled(orgId, actor != null ? actor : {
|
|
907
1013
|
uid: null
|
|
908
1014
|
}, {
|
|
@@ -970,8 +1076,16 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
970
1076
|
* ended meanwhile — a person's cancel, which pays for what ran — is left as it
|
|
971
1077
|
* ended and given nothing back.
|
|
972
1078
|
*/ async function failOurFailure(firestore, orgId, jobId, message, detail, refund, now) {
|
|
1079
|
+
var _detail_ours;
|
|
973
1080
|
var _current_items;
|
|
974
1081
|
const current = await getAiJob(firestore, orgId, jobId);
|
|
1082
|
+
// A person's cancel asked while the step ran is the job's end (AGL-3616):
|
|
1083
|
+
// not our failure, so nothing is given back and nobody is told.
|
|
1084
|
+
if (current && aiJobCancelPending(current)) {
|
|
1085
|
+
return failAiJob(firestore, orgId, jobId, message, _extends({}, detail, {
|
|
1086
|
+
ours: false
|
|
1087
|
+
}), now);
|
|
1088
|
+
}
|
|
975
1089
|
// A build that got as far as its items settles item by item (AGL-3616):
|
|
976
1090
|
// what it delivered stands, and what it did not is given back per item.
|
|
977
1091
|
if (current && aiJobSettlesByItem(current.kind) && ((_current_items = current.items) == null ? void 0 : _current_items.length) && !isAiJobTerminal(current.status)) {
|
|
@@ -996,8 +1110,10 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
996
1110
|
now
|
|
997
1111
|
}));
|
|
998
1112
|
}
|
|
1113
|
+
// Given back as ours either way; ANNOUNCED as ours unless the step said the
|
|
1114
|
+
// failure is the customer's own limit (`customerLimit`), which pages no one.
|
|
999
1115
|
return failAiJob(firestore, orgId, jobId, message, _extends({}, detail, {
|
|
1000
|
-
ours: true
|
|
1116
|
+
ours: (_detail_ours = detail.ours) != null ? _detail_ours : true
|
|
1001
1117
|
}), now);
|
|
1002
1118
|
}
|
|
1003
1119
|
// ── A build's settlement (AGL-3616) ──────────────────────────────────────
|
|
@@ -1085,6 +1201,8 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1085
1201
|
const { now } = input;
|
|
1086
1202
|
let job = await getAiJob(firestore, orgId, jobId);
|
|
1087
1203
|
if (!job || isAiJobTerminal(job.status)) return job;
|
|
1204
|
+
// A cancel asked before the build settled is how it ends (AGL-3616).
|
|
1205
|
+
if (aiJobCancelPending(job)) return completeAiJob(firestore, orgId, jobId, now);
|
|
1088
1206
|
const stopped = input.stopped;
|
|
1089
1207
|
if (stopped && ((_job_items = job.items) != null ? _job_items : []).some((row)=>row.status === 'pending' || row.status === 'running')) {
|
|
1090
1208
|
var _job_items4;
|
|
@@ -1222,7 +1340,7 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1222
1340
|
* re-asks the plan. Held at what the retried items are estimated to cost.
|
|
1223
1341
|
* `changed: false` for a job that is not a finished build, or has nothing to
|
|
1224
1342
|
* try again.
|
|
1225
|
-
*/ export async function retryAiBuildJob(firestore, orgId, jobId, now = new Date()) {
|
|
1343
|
+
*/ export async function retryAiBuildJob(firestore, orgId, jobId, now = new Date(), options = {}) {
|
|
1226
1344
|
let retried = [];
|
|
1227
1345
|
const result = await transition(firestore, orgId, jobId, (current)=>{
|
|
1228
1346
|
var _current_plan, _current_items;
|
|
@@ -1232,7 +1350,7 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1232
1350
|
const next = aiBuildRetryLedger(current.items, current.kind === 'site' ? aiLedgerUnits(current.items) : aiBuildUnits(plan));
|
|
1233
1351
|
if (!next.ledger.some((row)=>row.status === 'pending')) return null;
|
|
1234
1352
|
retried = next.retried;
|
|
1235
|
-
return {
|
|
1353
|
+
return _extends({
|
|
1236
1354
|
status: 'queued',
|
|
1237
1355
|
items: next.ledger,
|
|
1238
1356
|
steps: current.steps.map((step)=>step.name === AI_JOB_PLAN_STEP ? step : _extends({}, step, {
|
|
@@ -1247,9 +1365,12 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1247
1365
|
lease: null,
|
|
1248
1366
|
creditsReserved: Math.max(AI_JOB_STEP_RESERVE_CREDITS, current.kind === 'site' ? next.retried.length * AI_JOB_STEP_RESERVE_CREDITS : aiBuildCreditEstimate(plan, {
|
|
1249
1367
|
slots: new Set(next.retried)
|
|
1250
|
-
}))
|
|
1368
|
+
}))
|
|
1369
|
+
}, options.creditsConfirmed ? {
|
|
1370
|
+
creditsConfirmed: options.creditsConfirmed
|
|
1371
|
+
} : {}, {
|
|
1251
1372
|
updatedAt: now
|
|
1252
|
-
};
|
|
1373
|
+
});
|
|
1253
1374
|
});
|
|
1254
1375
|
return _extends({}, result, {
|
|
1255
1376
|
retried: result.changed ? retried : []
|
|
@@ -1268,6 +1389,7 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1268
1389
|
* (AGL-3031), creations included, where that is more.
|
|
1269
1390
|
*/ export async function resumeAiJob(firestore, orgId, jobId, actor, now = new Date(), options = {}) {
|
|
1270
1391
|
return transition(firestore, orgId, jobId, (current)=>{
|
|
1392
|
+
var _options_reducePlan;
|
|
1271
1393
|
var _current_review, _current_inputs;
|
|
1272
1394
|
// A job the meter paused (AGL-3660) carries on from the step it paused
|
|
1273
1395
|
// on: that step is already pending, so it is queued again with nothing
|
|
@@ -1287,7 +1409,8 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1287
1409
|
}) : step);
|
|
1288
1410
|
const outstanding = steps.filter((step)=>step.status === 'pending').length;
|
|
1289
1411
|
const pending = outstanding > 0;
|
|
1290
|
-
const
|
|
1412
|
+
const proposed = ((_current_review = current.review) == null ? void 0 : _current_review.reason) === 'plan' && current.plan ? current.plan : null;
|
|
1413
|
+
const confirming = proposed && options.reducePlan ? (_options_reducePlan = options.reducePlan(proposed)) != null ? _options_reducePlan : proposed : proposed;
|
|
1291
1414
|
const confirmed = confirming ? {
|
|
1292
1415
|
plan: _extends({}, confirming, {
|
|
1293
1416
|
status: 'confirmed',
|
|
@@ -1305,7 +1428,9 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1305
1428
|
steps,
|
|
1306
1429
|
review: null,
|
|
1307
1430
|
error: null
|
|
1308
|
-
}, confirmed, publish, {
|
|
1431
|
+
}, confirmed, publish, options.creditsConfirmed ? {
|
|
1432
|
+
creditsConfirmed: options.creditsConfirmed
|
|
1433
|
+
} : {}, {
|
|
1309
1434
|
creditsReserved: pending ? Math.max(outstanding * AI_JOB_STEP_RESERVE_CREDITS, confirming ? aiJobCreditEstimate(current.kind, confirming) : 0) : 0,
|
|
1310
1435
|
updatedAt: now
|
|
1311
1436
|
});
|
|
@@ -1410,7 +1535,7 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1410
1535
|
* round.
|
|
1411
1536
|
*/ export async function runAiJobStep(firestore, orgId, jobId, options) {
|
|
1412
1537
|
var _options_now, _step_attempts, _options_org, _data, _ref, _ref1, _outcome_effort, _step_passes, _options_actor;
|
|
1413
|
-
var _reservation_allotment, _reservation_allotment1, _outcome_review, _itemRecord_failure, _recorded_job_items;
|
|
1538
|
+
var _reservation_allotment, _reservation_allotment1, _options_signal, _options_signal1, _outcome_review, _itemRecord_failure, _recorded_job_items;
|
|
1414
1539
|
const now = (_options_now = options.now) != null ? _options_now : new Date();
|
|
1415
1540
|
// A reservation the caller took before the job existed is a message
|
|
1416
1541
|
// counted against the org; a step that fails before the provider is
|
|
@@ -1527,6 +1652,11 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1527
1652
|
if (!reservation.allowed) {
|
|
1528
1653
|
const refusal = aiJobRefusalText(org, reservation);
|
|
1529
1654
|
const parked = await markAiJobNeedsInput(firestore, orgId, jobId, refusal, now);
|
|
1655
|
+
// A cancel asked meanwhile ended the job instead of parking it (AGL-3616).
|
|
1656
|
+
if (parked.status === 'canceled') return {
|
|
1657
|
+
outcome: 'canceled',
|
|
1658
|
+
job: parked
|
|
1659
|
+
};
|
|
1530
1660
|
// Nobody parked the job — the meter did — so the row carries no actor
|
|
1531
1661
|
// rather than the creator's name on an act they did not perform. Only
|
|
1532
1662
|
// when the job was not already parked for this: the reason the last
|
|
@@ -1565,6 +1695,21 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1565
1695
|
var _resolveAiModelChoice;
|
|
1566
1696
|
return (_resolveAiModelChoice = resolveAiModelChoice(kind, (_job_model = job.model) != null ? _job_model : null, bounds)) == null ? void 0 : _resolveAiModelChoice.model;
|
|
1567
1697
|
};
|
|
1698
|
+
// A person's cancel stops the step (AGL-3616): the step's signal is the
|
|
1699
|
+
// caller's budget AND a watch on the job, so a cancel aborts the provider
|
|
1700
|
+
// call in flight, stops one about to start, and stops a write the step
|
|
1701
|
+
// asks `throwIfAiJobCanceled` about first.
|
|
1702
|
+
const stepSignal = new AbortController();
|
|
1703
|
+
const forwardBudget = ()=>{
|
|
1704
|
+
var _options_signal;
|
|
1705
|
+
return stepSignal.abort((_options_signal = options.signal) == null ? void 0 : _options_signal.reason);
|
|
1706
|
+
};
|
|
1707
|
+
if ((_options_signal = options.signal) == null ? void 0 : _options_signal.aborted) forwardBudget();
|
|
1708
|
+
else (_options_signal1 = options.signal) == null ? void 0 : _options_signal1.addEventListener('abort', forwardBudget, {
|
|
1709
|
+
once: true
|
|
1710
|
+
});
|
|
1711
|
+
const stopWatch = watchAiJobCancel(()=>getAiJob(firestore, orgId, jobId), stepSignal, options.cancelPollMs);
|
|
1712
|
+
const canceledMidStep = ()=>stepSignal.signal.aborted && isAiJobCanceledError(stepSignal.signal.reason);
|
|
1568
1713
|
let outcome;
|
|
1569
1714
|
// The runner's wall-clock time, recorded on the step beside its tokens
|
|
1570
1715
|
// (AGL-2937): measured here, around the call, rather than asked of it.
|
|
@@ -1574,7 +1719,7 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1574
1719
|
job,
|
|
1575
1720
|
stepIndex,
|
|
1576
1721
|
now,
|
|
1577
|
-
signal:
|
|
1722
|
+
signal: stepSignal.signal,
|
|
1578
1723
|
firestore,
|
|
1579
1724
|
org,
|
|
1580
1725
|
modelFor
|
|
@@ -1590,6 +1735,18 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1590
1735
|
jobId,
|
|
1591
1736
|
releaseError
|
|
1592
1737
|
}));
|
|
1738
|
+
// Canceled mid-step (AGL-3616): the step is handed back unrun and the
|
|
1739
|
+
// record ends the job canceled — never failed, never retried.
|
|
1740
|
+
if (isAiJobCanceledError(error) || canceledMidStep()) {
|
|
1741
|
+
const { job: canceled } = await recordStep(firestore, orgId, jobId, options.owner, stepIndex, {
|
|
1742
|
+
status: 'pending',
|
|
1743
|
+
creditsSpent: 0
|
|
1744
|
+
}, now);
|
|
1745
|
+
return {
|
|
1746
|
+
outcome: 'canceled',
|
|
1747
|
+
job: canceled
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1593
1750
|
const retryable = error instanceof AiUpstreamError && error.retryable || isAbort(error);
|
|
1594
1751
|
if (retryable) {
|
|
1595
1752
|
const { job: requeued } = await recordStep(firestore, orgId, jobId, options.owner, stepIndex, {
|
|
@@ -1612,6 +1769,10 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1612
1769
|
free: (_reservation_free = reservation.free) != null ? _reservation_free : null
|
|
1613
1770
|
}, now)
|
|
1614
1771
|
};
|
|
1772
|
+
} finally{
|
|
1773
|
+
var _options_signal2;
|
|
1774
|
+
stopWatch();
|
|
1775
|
+
(_options_signal2 = options.signal) == null ? void 0 : _options_signal2.removeEventListener('abort', forwardBudget);
|
|
1615
1776
|
}
|
|
1616
1777
|
const latencyMs = Math.max(0, Date.now() - runStarted);
|
|
1617
1778
|
// A step that failed before it reached the provider — the site it builds
|
|
@@ -1632,10 +1793,12 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1632
1793
|
}, now);
|
|
1633
1794
|
return {
|
|
1634
1795
|
outcome: 'failed',
|
|
1635
|
-
job: await failOurFailure(firestore, orgId, jobId, outcome.failure, {
|
|
1796
|
+
job: await failOurFailure(firestore, orgId, jobId, outcome.failure, _extends({
|
|
1636
1797
|
stepIndex,
|
|
1637
1798
|
error: `step failure before the provider: ${outcome.failure}`
|
|
1638
|
-
}, {
|
|
1799
|
+
}, outcome.customerLimit ? {
|
|
1800
|
+
ours: false
|
|
1801
|
+
} : {}), {
|
|
1639
1802
|
reason: 'step-failure',
|
|
1640
1803
|
stepCredits: 0,
|
|
1641
1804
|
free: (_reservation_free1 = reservation.free) != null ? _reservation_free1 : null
|
|
@@ -1703,10 +1866,12 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1703
1866
|
error: message,
|
|
1704
1867
|
tokens
|
|
1705
1868
|
}, now);
|
|
1706
|
-
const detail = {
|
|
1869
|
+
const detail = _extends({
|
|
1707
1870
|
stepIndex,
|
|
1708
1871
|
error: outcome.refused ? 'stop_reason refusal' : `step failure: ${message}`
|
|
1709
|
-
}
|
|
1872
|
+
}, outcome.customerLimit ? {
|
|
1873
|
+
ours: false
|
|
1874
|
+
} : {});
|
|
1710
1875
|
// A model declining the brief is not our failure; a step's own is (AGL-3594).
|
|
1711
1876
|
return {
|
|
1712
1877
|
outcome: 'failed',
|
|
@@ -1758,7 +1923,9 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1758
1923
|
// A build item that failed on our side gives back its own attempt's spend
|
|
1759
1924
|
// before its row is written (AGL-3616), so the row records what was given.
|
|
1760
1925
|
let itemRecord = aiJobSettlesByItem(job.kind) ? outcome.item : undefined;
|
|
1761
|
-
|
|
1926
|
+
// An item a person's cancel cut short is not our failure (AGL-3616): it is
|
|
1927
|
+
// given nothing back, and its row is the cancel's.
|
|
1928
|
+
if ((itemRecord == null ? void 0 : itemRecord.status) === 'failed' && ((_itemRecord_failure = itemRecord.failure) == null ? void 0 : _itemRecord_failure.ours) && !canceledMidStep()) {
|
|
1762
1929
|
var _ref2, _outcome_items, _ref3, _ref4, _reservation_free5;
|
|
1763
1930
|
const slot = itemRecord.slot;
|
|
1764
1931
|
const row = ((_ref2 = (_outcome_items = outcome.items) != null ? _outcome_items : job.items) != null ? _ref2 : []).find((one)=>one.slot === slot);
|
|
@@ -1839,6 +2006,11 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1839
2006
|
}
|
|
1840
2007
|
});
|
|
1841
2008
|
}
|
|
2009
|
+
// A cancel asked while the step ran ended the job in that record (AGL-3616).
|
|
2010
|
+
if (recorded.job.status === 'canceled') return {
|
|
2011
|
+
outcome: 'canceled',
|
|
2012
|
+
job: recorded.job
|
|
2013
|
+
};
|
|
1842
2014
|
if ((review == null ? void 0 : review.reason) === 'plan' && recorded.job.status === 'needs_review' && aiJobAutoConfirms(job)) {
|
|
1843
2015
|
// A guided site start confirms its own plan (AGL-3594): the resume
|
|
1844
2016
|
// door's confirmation, by the job's creator, and the build queued.
|