@aglyn/plugins-ai 1.0.0-beta.266 → 1.0.0-beta.268

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.
Files changed (40) hide show
  1. package/package.json +18 -18
  2. package/src/lib/components/ai-job-site.d.ts +8 -1
  3. package/src/lib/components/ai-job-site.js +7 -0
  4. package/src/lib/components/ai-job-site.js.map +1 -1
  5. package/src/lib/components/ai-site-build-page.component.js +37 -0
  6. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  7. package/src/lib/components/ai-site-start-card.component.js +25 -6
  8. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  9. package/src/lib/components/ai-site-start-steps.component.d.ts +8 -2
  10. package/src/lib/components/ai-site-start-steps.component.js +55 -26
  11. package/src/lib/components/ai-site-start-steps.component.js.map +1 -1
  12. package/src/lib/components/ai-site-start-tracking.d.ts +11 -3
  13. package/src/lib/components/ai-site-start-tracking.js +28 -9
  14. package/src/lib/components/ai-site-start-tracking.js.map +1 -1
  15. package/src/lib/components/assist-jobs-drawer.component.js +24 -2
  16. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  17. package/src/lib/jobs/ai-empty-site-sweep.d.ts +154 -0
  18. package/src/lib/jobs/ai-empty-site-sweep.js +346 -0
  19. package/src/lib/jobs/ai-empty-site-sweep.js.map +1 -0
  20. package/src/lib/jobs/ai-jobs-beat.d.ts +11 -3
  21. package/src/lib/jobs/ai-jobs-beat.js +23 -6
  22. package/src/lib/jobs/ai-jobs-beat.js.map +1 -1
  23. package/src/lib/jobs/ai-jobs.js +46 -7
  24. package/src/lib/jobs/ai-jobs.js.map +1 -1
  25. package/src/lib/jobs/ai-layout-stock-photos.d.ts +35 -0
  26. package/src/lib/jobs/ai-layout-stock-photos.js +321 -19
  27. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  28. package/src/lib/jobs/fixtures/ai-stock-photo-agl3923-recordings.d.ts +35 -0
  29. package/src/lib/jobs/fixtures/ai-stock-photo-agl3923-recordings.js +379 -0
  30. package/src/lib/jobs/fixtures/ai-stock-photo-agl3923-recordings.js.map +1 -0
  31. package/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.d.ts +31 -0
  32. package/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.js +208 -0
  33. package/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.js.map +1 -0
  34. package/src/lib/layout-language/ai-layout-design.js +5 -0
  35. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  36. package/src/lib/model/ai-jobs.types.d.ts +20 -0
  37. package/src/lib/model/ai-jobs.types.js.map +1 -1
  38. package/src/lib/model/ai-site-start-handoff.d.ts +49 -0
  39. package/src/lib/model/ai-site-start-handoff.js +88 -0
  40. package/src/lib/model/ai-site-start-handoff.js.map +1 -0
@@ -47,6 +47,7 @@ import { AI_SITE_GUIDED_BUILD_FAILED_COPY, aiCustomerSafeCopy, aiCustomerSafeIte
47
47
  import { aiSiteStartInputsOf } from "../model/ai-site-start.js";
48
48
  import { addAdminAudit } from "@aglyn/tenant-data-admin/server/admin-audit-write";
49
49
  import { AI_JOB_CANCELED_ITEM_NOTE, aiJobCancelPending, isAiJobCanceledError, watchAiJobCancel } from "./ai-job-cancel.js";
50
+ import { clearAiEmptySiteSweep, scheduleAiEmptySiteSweep } from "./ai-empty-site-sweep.js";
50
51
  /**
51
52
  * AI generation jobs — the Firestore state machine (AGL-2904).
52
53
  *
@@ -256,6 +257,16 @@ let transitionListener = null;
256
257
  /** Whether a transition listener is registered: what a surface that runs jobs must have. */ export function aiJobTransitionListenerRegistered() {
257
258
  return transitionListener !== null;
258
259
  }
260
+ /**
261
+ * A job just ENDED `canceled` or `failed`, in the write the caller made
262
+ * (AGL-3936): a site start that built nothing schedules the removal of the
263
+ * empty site it was for, an hour out (`ai-empty-site-sweep.ts`). Every write
264
+ * that ends a job calls this once, after its transaction commits. Never
265
+ * throws.
266
+ */ async function noteAiJobEnded(firestore, job, now) {
267
+ if (!job || job.status !== 'canceled' && job.status !== 'failed') return;
268
+ await scheduleAiEmptySiteSweep(firestore, job, now);
269
+ }
259
270
  /** Tells the listener; a listener that throws never fails the write it follows. */ async function announceAiJobTransition(job, to, failure) {
260
271
  if (!transitionListener) return;
261
272
  try {
@@ -408,6 +419,8 @@ function jobFrom(snapshot) {
408
419
  creditsConfirmed: input.creditsConfirmed
409
420
  } : {});
410
421
  await ref.set(data);
422
+ // A new job on the site — a guided start begun again — keeps it (AGL-3936).
423
+ await clearAiEmptySiteSweep(firestore, input.hostId);
411
424
  await logAiJobCreated(input.orgId, {
412
425
  uid: input.createdBy,
413
426
  email: (_input_createdByEmail = input.createdByEmail) != null ? _input_createdByEmail : null
@@ -527,6 +540,8 @@ export async function getAiJob(firestore, orgId, jobId) {
527
540
  publishAsked: true
528
541
  } : {}, aiJobCancelPending(job) ? {
529
542
  cancelRequested: true
543
+ } : {}, job.siteRemoved ? {
544
+ siteRemoved: true
530
545
  } : {});
531
546
  }
532
547
  function leaseIsLive(job, owner, nowMs) {
@@ -607,16 +622,20 @@ function nextStepIndex(job) {
607
622
  * stale lease is released by being taken, not by a sweep of its own.
608
623
  */ export async function claimNextStep(firestore, orgId, jobId, owner, now = new Date()) {
609
624
  const ref = jobsCollection(firestore, orgId).doc(jobId);
610
- return firestore.runTransaction(async (tx)=>{
625
+ let ended = null;
626
+ const claimed = await firestore.runTransaction(async (tx)=>{
627
+ ended = null;
611
628
  const job = jobFrom(await tx.get(ref));
612
629
  if (!job) return null;
613
630
  // A cancel asked while a step ran, whose runner never came back to end
614
631
  // it (AGL-3616): the lease ran out, so the job ends here, unclaimed.
615
632
  if (aiJobCancelPending(job)) {
616
633
  if (!leaseIsLive(job, owner, now.getTime())) {
617
- tx.set(ref, aiJobCanceledPatch(job, now, cancelRequestOf(job, now)), {
634
+ const patch = aiJobCanceledPatch(job, now, cancelRequestOf(job, now));
635
+ tx.set(ref, patch, {
618
636
  merge: true
619
637
  });
638
+ ended = _extends({}, job, patch);
620
639
  }
621
640
  return null;
622
641
  }
@@ -652,6 +671,8 @@ function nextStepIndex(job) {
652
671
  stepIndex
653
672
  };
654
673
  });
674
+ await noteAiJobEnded(firestore, ended, now);
675
+ return claimed;
655
676
  }
656
677
  /**
657
678
  * Extend the lease while a long step is still running. `false` when the
@@ -689,15 +710,19 @@ function nextStepIndex(job) {
689
710
  * parked, no step left, or another owner's live lease.
690
711
  */ export async function holdPausedAiJob(firestore, orgId, jobId, owner, now = new Date()) {
691
712
  const ref = jobsCollection(firestore, orgId).doc(jobId);
692
- return firestore.runTransaction(async (tx)=>{
713
+ let ended = null;
714
+ const held = await firestore.runTransaction(async (tx)=>{
715
+ ended = null;
693
716
  const job = jobFrom(await tx.get(ref));
694
717
  if (!job) return null;
695
718
  // A paused workspace's job a person canceled ends canceled (AGL-3616).
696
719
  if (aiJobCancelPending(job)) {
697
720
  if (!leaseIsLive(job, owner, now.getTime())) {
698
- tx.set(ref, aiJobCanceledPatch(job, now, cancelRequestOf(job, now)), {
721
+ const patch = aiJobCanceledPatch(job, now, cancelRequestOf(job, now));
722
+ tx.set(ref, patch, {
699
723
  merge: true
700
724
  });
725
+ ended = _extends({}, job, patch);
701
726
  }
702
727
  return null;
703
728
  }
@@ -711,6 +736,8 @@ function nextStepIndex(job) {
711
736
  updatedAt: now
712
737
  });
713
738
  });
739
+ await noteAiJobEnded(firestore, ended, now);
740
+ return held;
714
741
  }
715
742
  /**
716
743
  * A build's ledger with one pass applied (AGL-3616): the pass's credits on
@@ -874,9 +901,12 @@ function nextStepIndex(job) {
874
901
  job: _extends({}, job, patch),
875
902
  remaining,
876
903
  // Entered `needs_review` in this write, rather than parked again.
877
- parked: parksForReview && job.status !== 'needs_review'
904
+ parked: parksForReview && job.status !== 'needs_review',
905
+ // Ended `canceled` in this write (AGL-3936).
906
+ ended: canceling && !isAiJobTerminal(job.status)
878
907
  };
879
908
  });
909
+ if (recorded.ended) await noteAiJobEnded(firestore, recorded.job, now);
880
910
  if (recorded.parked && !input.quiet) await announceAiJobTransition(recorded.job, 'needs-review');
881
911
  return {
882
912
  job: recorded.job,
@@ -912,6 +942,7 @@ async function transition(firestore, orgId, jobId, apply) {
912
942
  updatedAt: now
913
943
  });
914
944
  if (changed && job.status === 'done') await announceAiJobTransition(job, 'done');
945
+ if (changed) await noteAiJobEnded(firestore, job, now);
915
946
  return job;
916
947
  }
917
948
  /**
@@ -946,6 +977,7 @@ async function transition(firestore, orgId, jobId, apply) {
946
977
  error: (detail == null ? void 0 : detail.error) instanceof Error ? detail.error.message : (detail == null ? void 0 : detail.error) == null ? null : String(detail.error)
947
978
  });
948
979
  }
980
+ if (changed) await noteAiJobEnded(firestore, job, now);
949
981
  return job;
950
982
  }
951
983
  /**
@@ -953,7 +985,7 @@ async function transition(firestore, orgId, jobId, apply) {
953
985
  * step goes back to `pending` so the same step is what runs when the beat
954
986
  * tries again; its attempt is not counted, because nothing was attempted.
955
987
  */ export async function markAiJobNeedsInput(firestore, orgId, jobId, reason, now = new Date()) {
956
- const { job } = await transition(firestore, orgId, jobId, (current)=>isAiJobTerminal(current.status) ? null : aiJobCancelPending(current) ? aiJobCanceledPatch(current, now, cancelRequestOf(current, now)) : {
988
+ const { job, changed } = await transition(firestore, orgId, jobId, (current)=>isAiJobTerminal(current.status) ? null : aiJobCancelPending(current) ? aiJobCanceledPatch(current, now, cancelRequestOf(current, now)) : {
957
989
  status: 'needs_input',
958
990
  error: reason,
959
991
  lease: null,
@@ -966,6 +998,7 @@ async function transition(firestore, orgId, jobId, apply) {
966
998
  }),
967
999
  updatedAt: now
968
1000
  });
1001
+ if (changed) await noteAiJobEnded(firestore, job, now);
969
1002
  return job;
970
1003
  }
971
1004
  /**
@@ -1016,6 +1049,7 @@ async function transition(firestore, orgId, jobId, apply) {
1016
1049
  kind: result.job.kind
1017
1050
  });
1018
1051
  }
1052
+ if (result.changed) await noteAiJobEnded(firestore, result.job, now);
1019
1053
  return result;
1020
1054
  }
1021
1055
  /**
@@ -1372,6 +1406,8 @@ async function transition(firestore, orgId, jobId, apply) {
1372
1406
  updatedAt: now
1373
1407
  });
1374
1408
  });
1409
+ // A site start tried again keeps its site (AGL-3936).
1410
+ if (result.changed) await clearAiEmptySiteSweep(firestore, result.job.hostId);
1375
1411
  return _extends({}, result, {
1376
1412
  retried: result.changed ? retried : []
1377
1413
  });
@@ -1388,7 +1424,7 @@ async function transition(firestore, orgId, jobId, apply) {
1388
1424
  * and a confirmed plan is held at what the whole job is estimated to cost
1389
1425
  * (AGL-3031), creations included, where that is more.
1390
1426
  */ export async function resumeAiJob(firestore, orgId, jobId, actor, now = new Date(), options = {}) {
1391
- return transition(firestore, orgId, jobId, (current)=>{
1427
+ const resumed = await transition(firestore, orgId, jobId, (current)=>{
1392
1428
  var _options_reducePlan;
1393
1429
  var _current_review, _current_inputs;
1394
1430
  // A job the meter paused (AGL-3660) carries on from the step it paused
@@ -1435,6 +1471,9 @@ async function transition(firestore, orgId, jobId, apply) {
1435
1471
  updatedAt: now
1436
1472
  });
1437
1473
  });
1474
+ // A site start resumed keeps its site (AGL-3936).
1475
+ if (resumed.changed) await clearAiEmptySiteSweep(firestore, resumed.job.hostId);
1476
+ return resumed;
1438
1477
  }
1439
1478
  /**
1440
1479
  * Record what a person applied from a finished job's outputs (AGL-2910): the