@aglyn/plugins-ai 1.0.0-beta.240 → 1.0.0-beta.241

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 (87) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-job-links.d.ts +17 -0
  3. package/src/lib/components/ai-job-links.js +63 -0
  4. package/src/lib/components/ai-job-links.js.map +1 -1
  5. package/src/lib/components/ai-job-plan.component.js +8 -1
  6. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  7. package/src/lib/components/ai-site-build-page.component.js +7 -1
  8. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  9. package/src/lib/components/ai-store-finish-card.component.d.ts +28 -0
  10. package/src/lib/components/ai-store-finish-card.component.js +116 -0
  11. package/src/lib/components/ai-store-finish-card.component.js.map +1 -0
  12. package/src/lib/components/assist-build-card.component.js +12 -1
  13. package/src/lib/components/assist-build-card.component.js.map +1 -1
  14. package/src/lib/jobs/ai-job-build-step.d.ts +45 -1
  15. package/src/lib/jobs/ai-job-build-step.js +283 -7
  16. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  17. package/src/lib/jobs/ai-job-drafts.d.ts +9 -1
  18. package/src/lib/jobs/ai-job-drafts.js +4 -1
  19. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  20. package/src/lib/jobs/ai-job-layout-language.d.ts +3 -0
  21. package/src/lib/jobs/ai-job-layout-language.js +4 -2
  22. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  23. package/src/lib/jobs/ai-job-layout-step.js +3 -1
  24. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  25. package/src/lib/jobs/ai-job-page-language.d.ts +2 -1
  26. package/src/lib/jobs/ai-job-page-language.js +19 -7
  27. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  28. package/src/lib/jobs/ai-job-page-sections.d.ts +11 -0
  29. package/src/lib/jobs/ai-job-page-sections.js +40 -3
  30. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  31. package/src/lib/jobs/ai-job-page-step.d.ts +2 -1
  32. package/src/lib/jobs/ai-job-page-step.js +19 -8
  33. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  34. package/src/lib/jobs/ai-job-plan-step.d.ts +12 -0
  35. package/src/lib/jobs/ai-job-plan-step.js +41 -7
  36. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  37. package/src/lib/jobs/ai-job-site-datasets.d.ts +76 -2
  38. package/src/lib/jobs/ai-job-site-datasets.js +206 -13
  39. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  40. package/src/lib/jobs/ai-job-site-step.d.ts +18 -2
  41. package/src/lib/jobs/ai-job-site-step.js +109 -22
  42. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  43. package/src/lib/jobs/ai-job-site-store-pages.d.ts +75 -0
  44. package/src/lib/jobs/ai-job-site-store-pages.js +195 -0
  45. package/src/lib/jobs/ai-job-site-store-pages.js.map +1 -0
  46. package/src/lib/jobs/ai-job-store-links-layout.d.ts +71 -0
  47. package/src/lib/jobs/ai-job-store-links-layout.js +95 -0
  48. package/src/lib/jobs/ai-job-store-links-layout.js.map +1 -0
  49. package/src/lib/jobs/ai-layout-stock-photos.d.ts +69 -3
  50. package/src/lib/jobs/ai-layout-stock-photos.js +603 -42
  51. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  52. package/src/lib/jobs/ai-site-publish.d.ts +18 -0
  53. package/src/lib/jobs/ai-site-publish.js +76 -15
  54. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  55. package/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.d.ts +43 -0
  56. package/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.js +61 -0
  57. package/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.js.map +1 -0
  58. package/src/lib/layout-language/ai-layout-compiler.js +25 -2
  59. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  60. package/src/lib/layout-language/ai-layout-frame.d.ts +10 -0
  61. package/src/lib/layout-language/ai-layout-frame.js +58 -4
  62. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  63. package/src/lib/layout-language/ai-layout-listings.d.ts +6 -0
  64. package/src/lib/layout-language/ai-layout-listings.js +3 -0
  65. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  66. package/src/lib/model/ai-build-job.d.ts +2 -0
  67. package/src/lib/model/ai-build-job.js +23 -3
  68. package/src/lib/model/ai-build-job.js.map +1 -1
  69. package/src/lib/model/ai-build-plan.d.ts +7 -0
  70. package/src/lib/model/ai-build-plan.js.map +1 -1
  71. package/src/lib/model/ai-build-progress.js +3 -1
  72. package/src/lib/model/ai-build-progress.js.map +1 -1
  73. package/src/lib/model/ai-build-store-pages.d.ts +133 -0
  74. package/src/lib/model/ai-build-store-pages.js +209 -0
  75. package/src/lib/model/ai-build-store-pages.js.map +1 -0
  76. package/src/lib/model/ai-layout-store-links.d.ts +64 -0
  77. package/src/lib/model/ai-layout-store-links.js +165 -0
  78. package/src/lib/model/ai-layout-store-links.js.map +1 -0
  79. package/src/lib/model/ai-site-inventory.d.ts +5 -0
  80. package/src/lib/model/ai-site-inventory.js.map +1 -1
  81. package/src/lib/model/ai-site-store-pages.d.ts +192 -0
  82. package/src/lib/model/ai-site-store-pages.js +383 -0
  83. package/src/lib/model/ai-site-store-pages.js.map +1 -0
  84. package/src/lib/runtime/site-inventory.js +8 -3
  85. package/src/lib/runtime/site-inventory.js.map +1 -1
  86. package/src/lib/testing/stand-in-dataset-index.js +8 -4
  87. package/src/lib/testing/stand-in-dataset-index.js.map +1 -1
@@ -38,6 +38,10 @@ import { AI_LAYOUT_FORM_PAGE_INPUT, AI_LAYOUT_LANGUAGE_INPUT, aiLayoutFormPageOf
38
38
  import { aiDatasetListingsOf } from "./ai-job-site-datasets.js";
39
39
  import { AI_FORM_DATASET_MADE_INPUT } from "./ai-job-form-dataset.js";
40
40
  import { AI_LAYOUT_LISTINGS_INPUT } from "../layout-language/ai-layout-listings.js";
41
+ import { AI_BUILD_STORE_OP, AI_BUILD_STORE_PAGES_PRESENT_NOTE, aiBuildStoreLayoutLinks, aiBuildStoreLayoutOf, aiBuildStorePagesOf, aiBuildStorePagesStillMissing } from "../model/ai-build-store-pages.js";
42
+ import { writeAiStoreLinksLayoutDraft } from "./ai-job-store-links-layout.js";
43
+ import { AI_SITE_STORE_LINKS_INPUT, AI_SITE_STORE_PAGES_NOTE, aiStoreFrameLinks, aiStorePagesToWrite } from "../model/ai-site-store-pages.js";
44
+ import { AI_SITE_STORE_PAGES_INPUT, aiSiteStorePageId, aiSiteStorePagesRefusal, runAiSiteStorePagesUnit } from "./ai-job-site-store-pages.js";
41
45
  import { AI_JOB_BRIEF_MAX_CHARS } from "./ai-job-text-step.js";
42
46
  import { aiJobPublishesBuild, aiPublishGuidedSite } from "./ai-site-publish.js";
43
47
  import { AI_PLANNED_JOB_KINDS, aiJobStepRunMinimumMs, aiJobStepRunnerFor, registerAiJobStep, registerAiJobStepPasses } from "./ai-jobs.js";
@@ -78,7 +82,7 @@ import { AI_PLANNED_JOB_KINDS, aiJobStepRunMinimumMs, aiJobStepRunnerFor, regist
78
82
  * The most passes a build's step may take: every unit at a page's worst — its
79
83
  * sections and its listing — so a runner that never finishes is still
80
84
  * bounded while the largest plan is not. Try again starts the count over.
81
- */ export const AI_BUILD_MAX_PASSES = AI_BUILD_LIMITS.units * (AI_SITE_MAX_SECTIONS + 1);
85
+ */ export const AI_BUILD_MAX_PASSES = AI_BUILD_LIMITS.units * (AI_SITE_MAX_SECTIONS + 1) + 1;
82
86
  /** The job kind a creation of the build is built by. */ const CREATION_JOB_KINDS = {
83
87
  layout: 'layout',
84
88
  form: 'form',
@@ -221,6 +225,10 @@ import { AI_PLANNED_JOB_KINDS, aiJobStepRunMinimumMs, aiJobStepRunnerFor, regist
221
225
  const listings = aiDatasetListingsOf(datasets, plan.screens);
222
226
  if (listings.length) inputs[AI_LAYOUT_LISTINGS_INPUT] = listings;
223
227
  }
228
+ // A build that makes the site a store links its account and policies from
229
+ // the layout it makes (AGL-3676), by path: they are written after it.
230
+ const storePages = aiBuildStorePagesOf(plan);
231
+ if (context.kind === 'layout' && storePages) inputs[AI_SITE_STORE_LINKS_INPUT] = aiStoreFrameLinks(storePages.pages);
224
232
  return _extends({}, job, {
225
233
  $id: aiBuildUnitJobId(job, unit),
226
234
  kind: context.kind,
@@ -239,6 +247,139 @@ import { AI_PLANNED_JOB_KINDS, aiJobStepRunMinimumMs, aiJobStepRunnerFor, regist
239
247
  failure
240
248
  }, extra);
241
249
  }
250
+ /** The site's pages as a store's own pages are checked against them (AGL-3676): every page that is not deleted. */ export async function readAiStoreSitePages(firestore, hostId) {
251
+ const snapshot = await firestore.collection('hosts').doc(hostId).collection('screens').select('displayName', 'slug', 'kind', 'deletedAt', 'layoutId').get();
252
+ return snapshot.docs.flatMap((doc)=>{
253
+ var _doc_get, _doc_get1;
254
+ const kind = doc.get('kind');
255
+ if (doc.get('deletedAt') || kind && kind !== 'template') return [];
256
+ const slug = String((_doc_get = doc.get('slug')) != null ? _doc_get : '');
257
+ const layoutId = doc.get('layoutId');
258
+ return slug ? [
259
+ {
260
+ id: doc.id,
261
+ slug,
262
+ title: String((_doc_get1 = doc.get('displayName')) != null ? _doc_get1 : ''),
263
+ template: kind === 'template',
264
+ layoutId: typeof layoutId === 'string' && layoutId ? layoutId : null
265
+ }
266
+ ] : [];
267
+ });
268
+ }
269
+ /**
270
+ * What a build's store pages unit is told (AGL-3676): the pages its plan kept,
271
+ * less any the site has gained since — never counting the pages this unit
272
+ * wrote itself on an earlier pass, which it finds again by their ids — what
273
+ * their words link, and the layout they render inside: the plan's Shop
274
+ * page's, else the site's own (`aiBuildStoreLayoutOf`).
275
+ */ export function aiBuildStorePagesInput(job, context) {
276
+ var _job_outputs;
277
+ const plan = job.plan;
278
+ const kept = aiBuildStorePagesOf(plan);
279
+ if (!kept) return null;
280
+ const own = new Set(kept.pages.map((page)=>aiSiteStorePageId(context.unitJobId, page.key)));
281
+ const pages = aiBuildStorePagesStillMissing(kept.pages, context.sitePages.filter((page)=>!page.id || !own.has(page.id)));
282
+ const built = aiBuildBuiltRefs(context.units, context.ledger, (_job_outputs = job.outputs) != null ? _job_outputs : []);
283
+ const shop = plan.screens.find((screen)=>!screen.record && screen.slug.replace(/^\/+/, '').split('/')[0].toLowerCase() === kept.facts.shopPath.slice(1));
284
+ const layout = shop ? aiSiteResolvedRef(shop.layout, built) : null;
285
+ return {
286
+ pages,
287
+ facts: kept.facts,
288
+ layoutId: aiBuildStoreLayoutOf(layout && !isAiPlanNewRef(layout) ? layout : null, context.sitePages)
289
+ };
290
+ }
291
+ /**
292
+ * Adds a store's links to the site's own layout (AGL-3676), as a draft
293
+ * version, where the build made no layout of its own for its store pages: the
294
+ * outputs that record it — the layout version written (`storeLinks: draft`),
295
+ * and on the first page `storeLinks: missing` where the layout had no header
296
+ * or footer list to add them to, or there is no layout at all.
297
+ */ export async function aiBuildStoreLinksOutputs(firestore, input, write = writeAiStoreLinksLayoutDraft) {
298
+ var _outputs_find;
299
+ const outputs = input.outputs.map((output)=>_extends({}, output));
300
+ const layoutId = input.store.layoutId;
301
+ // A layout this build made was told the store's links as it was compiled.
302
+ if (layoutId && input.builtLayouts.has(layoutId)) return outputs;
303
+ const written = new Set(outputs.map((output)=>{
304
+ var _output_proposal;
305
+ return (_output_proposal = output.proposal) == null ? void 0 : _output_proposal['storePage'];
306
+ }).filter((key)=>typeof key === 'string'));
307
+ const links = aiBuildStoreLayoutLinks(input.store.pages, written);
308
+ const missing = ()=>{
309
+ var _first_proposal;
310
+ const [first, ...rest] = outputs;
311
+ const state = 'missing';
312
+ return first ? [
313
+ _extends({}, first, {
314
+ proposal: _extends({}, (_first_proposal = first.proposal) != null ? _first_proposal : {}, {
315
+ storeLinks: state
316
+ })
317
+ }),
318
+ ...rest
319
+ ] : outputs;
320
+ };
321
+ if (!layoutId || !input.job.hostId) return missing();
322
+ const hrefs = new Set([
323
+ ...links.header,
324
+ ...links.cart ? [
325
+ links.cart
326
+ ] : [],
327
+ ...links.footer
328
+ ].map((link)=>link.href.slice(1)));
329
+ const screenPaths = {};
330
+ for (const page of input.sitePages){
331
+ const slug = page.slug.replace(/^\/+/, '').split('/')[0].toLowerCase();
332
+ if (page.id && hrefs.has(slug)) screenPaths[page.id] = slug;
333
+ }
334
+ const draft = await write(firestore, {
335
+ hostId: input.job.hostId,
336
+ layoutId,
337
+ versionId: `${input.unitJobId}-links`.slice(0, 64),
338
+ links,
339
+ screenPaths,
340
+ uid: input.job.createdBy,
341
+ aiJobId: aiOriginJobId(input.job),
342
+ now: input.now
343
+ });
344
+ if (draft.status === 'present') return outputs;
345
+ if (draft.status === 'gone') return missing();
346
+ const versionId = draft.versionId;
347
+ const layout = versionId ? [
348
+ {
349
+ resource: 'layout',
350
+ id: draft.layoutId,
351
+ versionId,
352
+ hostId: input.job.hostId,
353
+ hostSubdomain: (_outputs_find = outputs.find((output)=>output.hostSubdomain)) == null ? void 0 : _outputs_find.hostSubdomain,
354
+ label: draft.name,
355
+ note: 'A new draft version with Account and policy links. Visitors see it once it is published.',
356
+ proposal: {
357
+ storeLinks: 'draft',
358
+ links,
359
+ screenPaths
360
+ }
361
+ }
362
+ ] : [];
363
+ return [
364
+ ...draft.status === 'unrecognized' ? missing() : outputs,
365
+ ...layout
366
+ ];
367
+ }
368
+ /** The store's links a build drafted into the site's layout (AGL-3676), as its publish puts them live; `null` where it drafted none. */ export function aiBuildStoreLinksToPublish(outputs) {
369
+ var _ref;
370
+ var _draft_proposal, _draft_proposal1;
371
+ const draft = outputs.find((output)=>{
372
+ var _output_proposal;
373
+ return output.resource === 'layout' && ((_output_proposal = output.proposal) == null ? void 0 : _output_proposal['storeLinks']) === 'draft' && output.versionId;
374
+ });
375
+ if (!(draft == null ? void 0 : draft.versionId)) return null;
376
+ return {
377
+ layoutId: draft.id,
378
+ versionId: draft.versionId,
379
+ links: (_draft_proposal = draft.proposal) == null ? void 0 : _draft_proposal['links'],
380
+ screenPaths: (_ref = (_draft_proposal1 = draft.proposal) == null ? void 0 : _draft_proposal1['screenPaths']) != null ? _ref : {}
381
+ };
382
+ }
242
383
  /** Whether a build's workspace spends the Free taste. */ export function aiBuildFreeTaste(org) {
243
384
  return Boolean(org) && resolveEffectivePlan(org) === 'free';
244
385
  }
@@ -248,7 +389,7 @@ import { AI_PLANNED_JOB_KINDS, aiJobStepRunMinimumMs, aiJobStepRunnerFor, regist
248
389
  return (_data = (await firestore.collection('hosts').doc(hostId).get()).data()) != null ? _data : null;
249
390
  }
250
391
  export function createAiJobBuildStep(deps = {}) {
251
- var _deps_runnerFor, _deps_writerFor, _deps_opsFor, _deps_admissionFor, _deps_pluginAdmission, _deps_readNodes, _deps_publish;
392
+ var _deps_runnerFor, _deps_writerFor, _deps_opsFor, _deps_admissionFor, _deps_pluginAdmission, _deps_readNodes, _deps_publish, _deps_storePages, _deps_storePagesRefusal, _deps_readSitePages, _deps_writeStoreLinks;
252
393
  const runnerFor = (_deps_runnerFor = deps.runnerFor) != null ? _deps_runnerFor : aiJobStepRunnerFor;
253
394
  const writerFor = (_deps_writerFor = deps.writerFor) != null ? _deps_writerFor : aiPluginDraftWriter;
254
395
  const opsFor = (_deps_opsFor = deps.opsFor) != null ? _deps_opsFor : (context)=>aiBuildOps(context);
@@ -256,6 +397,10 @@ export function createAiJobBuildStep(deps = {}) {
256
397
  const pluginAdmission = (_deps_pluginAdmission = deps.pluginAdmission) != null ? _deps_pluginAdmission : aiPluginDraftAdmissionRefusal;
257
398
  const readNodes = (_deps_readNodes = deps.readNodes) != null ? _deps_readNodes : readAiDraftNodes;
258
399
  const publish = (_deps_publish = deps.publish) != null ? _deps_publish : aiPublishGuidedSite;
400
+ const storePagesRunner = (_deps_storePages = deps.storePages) != null ? _deps_storePages : runAiSiteStorePagesUnit;
401
+ const storePagesRefusal = (_deps_storePagesRefusal = deps.storePagesRefusal) != null ? _deps_storePagesRefusal : aiSiteStorePagesRefusal;
402
+ const readSitePages = (_deps_readSitePages = deps.readSitePages) != null ? _deps_readSitePages : readAiStoreSitePages;
403
+ const writeStoreLinks = (_deps_writeStoreLinks = deps.writeStoreLinks) != null ? _deps_writeStoreLinks : writeAiStoreLinksLayoutDraft;
259
404
  return async (context)=>{
260
405
  var _ref, _context_org, _aiBuildOrder, _job_hostId, _job_hostId1, _ref1;
261
406
  var _job_items, _rows_get;
@@ -292,14 +437,15 @@ export function createAiJobBuildStep(deps = {}) {
292
437
  status: 'pending'
293
438
  });
294
439
  });
295
- /** The build's last pass puts its pages live, where it was asked and confirmed. */ const finish = async (outcome, extraPages = [])=>{
296
- var _job_outputs;
440
+ /** The build's last pass puts its pages live, where it was asked and confirmed. */ const finish = async (outcome, extraPages = [], extraOutputs = [])=>{
441
+ var _job_outputs, _job_outputs1;
297
442
  if (!aiJobPublishesBuild(job) || job.sitePublish || !job.hostId) return outcome;
298
443
  // A record template renders once per record only once its binding is
299
444
  // saved, so it is never published at its own address (AGL-3616).
445
+ // A store's own pages (AGL-3676) go live with the pages the build planned.
300
446
  const pageIds = new Set(units.filter((one)=>{
301
447
  var _rows_get;
302
- return one.screen && !one.screen.record && aiBuildItemDelivered((_rows_get = rows.get(one.slot)) != null ? _rows_get : {
448
+ return (one.screen && !one.screen.record || one.op === AI_BUILD_STORE_OP) && aiBuildItemDelivered((_rows_get = rows.get(one.slot)) != null ? _rows_get : {
303
449
  status: 'pending'
304
450
  });
305
451
  }).flatMap((one)=>{
@@ -314,11 +460,26 @@ export function createAiJobBuildStep(deps = {}) {
314
460
  if (!pages.length) return outcome;
315
461
  // A canceled build never puts anything live (AGL-3616): its pages stay drafts.
316
462
  if (await aiJobCancelAsked(firestore, job, context.signal)) return outcome;
317
- const sitePublish = await publish(firestore, {
463
+ // The store pages a layout this build made links and that were not written come out of it.
464
+ const storePages = aiBuildStorePagesOf(plan);
465
+ const live = new Set(pages.map((page)=>{
466
+ var _page_proposal;
467
+ return (_page_proposal = page.proposal) == null ? void 0 : _page_proposal['storePage'];
468
+ }).filter(Boolean));
469
+ const unwrittenHrefs = storePages ? aiStorePagesToWrite(storePages.pages).filter((page)=>!live.has(page.key)).map((page)=>page.href) : [];
470
+ const storeLinks = aiBuildStoreLinksToPublish([
471
+ ...(_job_outputs1 = job.outputs) != null ? _job_outputs1 : [],
472
+ ...extraOutputs
473
+ ]);
474
+ const sitePublish = await publish(firestore, _extends({
318
475
  job,
319
476
  outputs: pages,
320
477
  now: context.now
321
- }).catch((error)=>{
478
+ }, unwrittenHrefs.length ? {
479
+ unwrittenHrefs
480
+ } : {}, storeLinks ? {
481
+ storeLinks
482
+ } : {})).catch((error)=>{
322
483
  console.error('ai build publish threw', {
323
484
  orgId: job.orgId,
324
485
  jobId: job.$id,
@@ -350,6 +511,121 @@ export function createAiJobBuildStep(deps = {}) {
350
511
  degradedBy: degradation.degradedBy
351
512
  });
352
513
  }
514
+ // A store's own pages (AGL-3676): written by code, behind the site start's gate.
515
+ if (unit.op === AI_BUILD_STORE_OP) {
516
+ var _job_outputs;
517
+ var _rows_get1;
518
+ if (!job.hostId) return settle({
519
+ slot: unit.slot,
520
+ status: 'skipped',
521
+ note: 'Not built: open the site first.'
522
+ });
523
+ if (((_rows_get1 = rows.get(unit.slot)) == null ? void 0 : _rows_get1.status) !== 'running') {
524
+ const refusal = await storePagesRefusal(_extends({}, context, {
525
+ job
526
+ })).catch((error)=>{
527
+ console.error('ai build store pages admission failed', {
528
+ orgId: job.orgId,
529
+ jobId: job.$id,
530
+ error
531
+ });
532
+ return AI_BUILD_UNIT_EMPTY_COPY;
533
+ });
534
+ if (refusal) return settle({
535
+ slot: unit.slot,
536
+ status: 'skipped',
537
+ note: `Not built: ${refusal}`
538
+ });
539
+ }
540
+ const unitJobId = aiBuildUnitJobId(job, unit);
541
+ const sitePages = await readSitePages(firestore, job.hostId);
542
+ const input = aiBuildStorePagesInput(job, {
543
+ unitJobId,
544
+ sitePages,
545
+ units,
546
+ ledger
547
+ });
548
+ if (!input) return settle({
549
+ slot: unit.slot,
550
+ status: 'skipped',
551
+ note: aiBuildUnavailableCopy('store page')
552
+ });
553
+ if (!aiStorePagesToWrite(input.pages).length) {
554
+ return settle({
555
+ slot: unit.slot,
556
+ status: 'skipped',
557
+ note: AI_BUILD_STORE_PAGES_PRESENT_NOTE
558
+ });
559
+ }
560
+ const derived = _extends({}, job, {
561
+ $id: unitJobId,
562
+ kind: 'text',
563
+ steps: [],
564
+ outputs: [],
565
+ items: null,
566
+ plan: null,
567
+ inputs: _extends({}, job.inputs, {
568
+ originJobId: aiOriginJobId(job),
569
+ [AI_SITE_STORE_PAGES_INPUT]: input
570
+ })
571
+ });
572
+ let outcome;
573
+ try {
574
+ outcome = await storePagesRunner(_extends({}, context, {
575
+ job: derived
576
+ }));
577
+ } catch (error) {
578
+ if (aiUnitErrorRetryable(error)) throw error;
579
+ console.error('ai build store pages threw', {
580
+ orgId: job.orgId,
581
+ jobId: job.$id,
582
+ error
583
+ });
584
+ return settle(failed(unit.slot, {
585
+ ours: true,
586
+ reason: 'step-failure',
587
+ message: AI_BUILD_UNIT_EMPTY_COPY
588
+ }));
589
+ }
590
+ const stopped = aiUnitFailure(unit.slot, outcome);
591
+ if (stopped) return settle(stopped, aiUnitSpend(outcome));
592
+ // The site's own header and footer link them (AGL-3676), in a draft
593
+ // version of its layout; a layout this build made already does.
594
+ const builtLayouts = new Set([
595
+ ...aiBuildBuiltRefs(units, ledger, (_job_outputs = job.outputs) != null ? _job_outputs : []).values()
596
+ ].filter((entry)=>entry.kind === 'layout').map((entry)=>entry.id));
597
+ const outputs = await aiBuildStoreLinksOutputs(firestore, {
598
+ job,
599
+ unitJobId,
600
+ store: input,
601
+ outputs: outcome.outputs,
602
+ sitePages,
603
+ builtLayouts,
604
+ now: context.now
605
+ }, writeStoreLinks).catch((error)=>{
606
+ console.error('ai build store links not written', {
607
+ orgId: job.orgId,
608
+ jobId: job.$id,
609
+ error
610
+ });
611
+ return outcome.outputs;
612
+ });
613
+ const ids = outputs.map((output)=>output.id);
614
+ const done = settle({
615
+ slot: unit.slot,
616
+ status: 'succeeded',
617
+ outputs: ids,
618
+ note: AI_SITE_STORE_PAGES_NOTE
619
+ }, _extends({}, aiUnitSpend(outcome), {
620
+ outputs
621
+ }));
622
+ if (done.continue) return done;
623
+ rows.set(unit.slot, _extends({}, rows.get(unit.slot), {
624
+ status: 'succeeded',
625
+ outputs: ids
626
+ }));
627
+ return finish(done, outputs.filter((output)=>output.resource === 'screen'), outputs);
628
+ }
353
629
  const capability = ops.get(unit.op);
354
630
  const noun = (_ref1 = capability == null ? void 0 : capability.noun) != null ? _ref1 : unit.op;
355
631
  if (!capability) return settle({