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

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 (88) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-job-links.js +8 -0
  3. package/src/lib/components/ai-job-links.js.map +1 -1
  4. package/src/lib/components/ai-job-requests.d.ts +5 -0
  5. package/src/lib/components/ai-job-requests.js +4 -2
  6. package/src/lib/components/ai-job-requests.js.map +1 -1
  7. package/src/lib/components/ai-jobs-store.d.ts +12 -0
  8. package/src/lib/components/ai-jobs-store.js +15 -0
  9. package/src/lib/components/ai-jobs-store.js.map +1 -1
  10. package/src/lib/components/ai-usage-meter-refresh.d.ts +35 -0
  11. package/src/lib/components/ai-usage-meter-refresh.js +63 -0
  12. package/src/lib/components/ai-usage-meter-refresh.js.map +1 -0
  13. package/src/lib/components/assist-panel.component.js +17 -1
  14. package/src/lib/components/assist-panel.component.js.map +1 -1
  15. package/src/lib/components/use-ai-usage-meter.d.ts +2 -0
  16. package/src/lib/components/use-ai-usage-meter.js +8 -3
  17. package/src/lib/components/use-ai-usage-meter.js.map +1 -1
  18. package/src/lib/jobs/ai-build-unit-outcome.d.ts +3 -1
  19. package/src/lib/jobs/ai-build-unit-outcome.js +19 -3
  20. package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -1
  21. package/src/lib/jobs/ai-job-build-step.js +29 -2
  22. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  23. package/src/lib/jobs/ai-job-draft-ids.js +7 -2
  24. package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
  25. package/src/lib/jobs/ai-job-form-dataset.d.ts +100 -0
  26. package/src/lib/jobs/ai-job-form-dataset.js +161 -0
  27. package/src/lib/jobs/ai-job-form-dataset.js.map +1 -0
  28. package/src/lib/jobs/ai-job-form-step.d.ts +15 -3
  29. package/src/lib/jobs/ai-job-form-step.js +37 -9
  30. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  31. package/src/lib/jobs/ai-job-layout-language.js +7 -2
  32. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  33. package/src/lib/jobs/ai-job-page-language.d.ts +9 -0
  34. package/src/lib/jobs/ai-job-page-language.js +44 -4
  35. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  36. package/src/lib/jobs/ai-job-page-sections.js +13 -2
  37. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  38. package/src/lib/jobs/ai-job-page-step.js +32 -2
  39. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  40. package/src/lib/jobs/ai-job-plan-step.d.ts +18 -0
  41. package/src/lib/jobs/ai-job-plan-step.js +41 -4
  42. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  43. package/src/lib/jobs/ai-job-site-datasets.d.ts +183 -0
  44. package/src/lib/jobs/ai-job-site-datasets.js +530 -0
  45. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -0
  46. package/src/lib/jobs/ai-job-site-step.d.ts +15 -8
  47. package/src/lib/jobs/ai-job-site-step.js +148 -29
  48. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  49. package/src/lib/jobs/fixtures/ai-dataset-site-plan-recordings.d.ts +30 -0
  50. package/src/lib/jobs/fixtures/ai-dataset-site-plan-recordings.js +637 -0
  51. package/src/lib/jobs/fixtures/ai-dataset-site-plan-recordings.js.map +1 -0
  52. package/src/lib/layout-language/ai-layout-compiler.d.ts +10 -0
  53. package/src/lib/layout-language/ai-layout-compiler.js +93 -1
  54. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  55. package/src/lib/layout-language/ai-layout-gaps.js +17 -1
  56. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
  57. package/src/lib/layout-language/ai-layout-listings.d.ts +32 -3
  58. package/src/lib/layout-language/ai-layout-listings.js +62 -9
  59. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  60. package/src/lib/layout-language/ai-layout-pictures.js +12 -4
  61. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  62. package/src/lib/model/ai-build-job.d.ts +2 -0
  63. package/src/lib/model/ai-build-job.js +16 -1
  64. package/src/lib/model/ai-build-job.js.map +1 -1
  65. package/src/lib/model/ai-build-plan.d.ts +23 -3
  66. package/src/lib/model/ai-build-plan.js +47 -6
  67. package/src/lib/model/ai-build-plan.js.map +1 -1
  68. package/src/lib/model/ai-plan-capabilities.d.ts +7 -0
  69. package/src/lib/model/ai-plan-capabilities.js.map +1 -1
  70. package/src/lib/model/ai-site-job.d.ts +19 -2
  71. package/src/lib/model/ai-site-job.js +21 -6
  72. package/src/lib/model/ai-site-job.js.map +1 -1
  73. package/src/lib/runtime/ai-dataset-generation.d.ts +77 -0
  74. package/src/lib/runtime/ai-dataset-generation.js +127 -0
  75. package/src/lib/runtime/ai-dataset-generation.js.map +1 -0
  76. package/src/lib/runtime/ai-doctrine-validators.d.ts +84 -0
  77. package/src/lib/runtime/ai-doctrine-validators.js +307 -1
  78. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  79. package/src/lib/runtime/ai-doctrine.js +10 -4
  80. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  81. package/src/lib/runtime/ai-node-tree.d.ts +2 -0
  82. package/src/lib/runtime/ai-node-tree.js +16 -0
  83. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  84. package/src/lib/tools/ai-dataset-tool.d.ts +68 -0
  85. package/src/lib/tools/ai-dataset-tool.js +231 -0
  86. package/src/lib/tools/ai-dataset-tool.js.map +1 -0
  87. package/src/lib/usage/free-ai-credits-left.js +26 -2
  88. package/src/lib/usage/free-ai-credits-left.js.map +1 -1
@@ -24,6 +24,7 @@ import { readAiDraftNodes } from './ai-job-drafts';
24
24
  import { aiPublishGuidedSite } from './ai-site-publish';
25
25
  import { aiPublishSitePosts, aiSiteContentRefusal } from './ai-job-site-content';
26
26
  import { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache';
27
+ import { aiSiteDatasetRefusal } from './ai-job-site-datasets';
27
28
  import { type AiJobStepContext, type AiJobStepOutcome, type AiJobStepRunner } from './ai-job-text-step';
28
29
  import { aiJobStepRunnerFor } from './ai-jobs';
29
30
  /**
@@ -81,11 +82,12 @@ import { aiJobStepRunnerFor } from './ai-jobs';
81
82
  */
82
83
  /**
83
84
  * What a scaffold's units are, in the order it builds them; a component is a
84
- * page job's. A blog's first posts and a store's first products (AGL-3676)
85
- * are built after the layout and the form and before the pages, which are
86
- * told what they are.
85
+ * page job's. The site's datasets (AGL-3616) are built after the layout and
86
+ * before the form, which may write its submissions to one; a blog's first
87
+ * posts and a store's first products (AGL-3676) after the form; all of them
88
+ * before the pages, which are told what they are.
87
89
  */
88
- export type AiSiteUnitKind = 'theme' | 'layout' | 'form' | 'component' | 'posts' | 'products' | 'page' | 'email';
90
+ export type AiSiteUnitKind = 'theme' | 'layout' | 'form' | 'component' | 'dataset' | 'posts' | 'products' | 'page' | 'email';
89
91
  export interface AiSiteUnit {
90
92
  kind: AiSiteUnitKind;
91
93
  /** The job kind whose registered runner builds it. */
@@ -124,9 +126,11 @@ export declare const AI_SITE_MAX_PASSES: number;
124
126
  /**
125
127
  * The units a plan implies, in build order: the palette first, because a
126
128
  * member reads it while the pages are still building; then the layout every
127
- * page renders inside and the form they place, because a page binds both by
128
- * id and so needs them to exist; then the pages; then the welcome email,
129
- * which is about the site rather than part of it.
129
+ * page renders inside, the site's datasets, and the form they place —
130
+ * because a page binds the layout and the form by id, and a form binds the
131
+ * dataset it writes to (AGL-3616), so each needs what it binds to exist;
132
+ * then the pages; then the welcome email, which is about the site rather
133
+ * than part of it.
130
134
  */
131
135
  export declare function aiSiteJobUnits(plan: Pick<AiJobPlan, 'create' | 'screens'>, options?: {
132
136
  welcomeEmail?: boolean;
@@ -155,7 +159,7 @@ export interface AiSiteBuiltRef {
155
159
  id: string;
156
160
  label: string;
157
161
  /** Only what becomes a record a page can name; a palette change is not one. */
158
- kind: 'layout' | 'form' | 'component';
162
+ kind: 'layout' | 'form' | 'component' | 'dataset';
159
163
  }
160
164
  /**
161
165
  * What the scaffold has already created, as the plan's `new:<name>`
@@ -287,6 +291,9 @@ export interface AiJobSiteStepDeps {
287
291
  products?: AiJobStepRunner;
288
292
  /** Whether a part may be built for this member, before its first pass. */
289
293
  contentRefusal?: typeof aiSiteContentRefusal;
294
+ /** A dataset's pass and whether one may be made here (AGL-3616); specs hand in fakes. */
295
+ dataset?: AiJobStepRunner;
296
+ datasetRefusal?: typeof aiSiteDatasetRefusal;
290
297
  /** The posts' publish once the pages are live, and the cache drop after it. */
291
298
  publishPosts?: typeof aiPublishSitePosts;
292
299
  dropCache?: typeof dropPluginSiteCache;
@@ -37,7 +37,9 @@ import { AI_LAYOUT_FORM_PAGE_INPUT, AI_LAYOUT_LANGUAGE_INPUT, aiLayoutFormPageOf
37
37
  import { aiJobPublishesSite, aiPublishGuidedSite } from "./ai-site-publish.js";
38
38
  import { AI_SITE_CONTENT_INPUT, AI_SITE_POST_BUDGET, AI_SITE_POSTS, AI_SITE_POSTS_LABEL, AI_SITE_PRODUCTS_LABEL, AI_SITE_PRODUCTS_PRICE_NOTE, aiPublishSitePosts, aiSiteContentBriefLines, aiSiteContentPart, aiSiteContentRefusal, aiSiteListings, aiSiteProductsBriefLine, createAiSiteProductsRunner, runAiSitePostsUnit } from "./ai-job-site-content.js";
39
39
  import { dropPluginSiteCache } from "@aglyn/aglyn/plugin-manager/plugin-site-cache";
40
+ import { AI_SITE_DATASET_BUDGET, AI_SITE_DATASET_INPUT, AI_SITE_DATASETS_MAX, aiSiteDatasetBriefLines, aiSiteDatasetInputOf, aiSiteDatasetListings, aiSiteDatasetRefusal, aiSitePlanDatasets, aiSiteRecordTemplateOf, runAiSiteDatasetUnit } from "./ai-job-site-datasets.js";
40
41
  import { AI_SITE_LOOK_BUDGET, aiRunSiteLook } from "./ai-job-site-look.js";
42
+ import { AI_FORM_DATASET_MADE_INPUT } from "./ai-job-form-dataset.js";
41
43
  import { aiConfirmedPlan, aiUnspentOutcome } from "./ai-job-generation.js";
42
44
  import { AI_JOB_BRIEF_MAX_CHARS } from "./ai-job-text-step.js";
43
45
  import { AI_JOB_PAGE_STEP_MINIMUM_MS } from "./ai-job-page-budget.js";
@@ -77,6 +79,12 @@ import { aiJobStepRunMinimumMs, aiJobStepRunnerFor, registerAiJobStep, registerA
77
79
  products: {
78
80
  jobKind: 'products',
79
81
  resource: 'product'
82
+ },
83
+ // The scaffold's own runner designs it (`ai-job-site-datasets.ts`), and the
84
+ // data plugin's writer keeps it: a draft of another plugin's (AGL-3616).
85
+ dataset: {
86
+ jobKind: 'text',
87
+ resource: 'draft'
80
88
  }
81
89
  };
82
90
  /** The unit kind a creation is built as, where a unit builds it. */ const CREATION_UNIT_KINDS = {
@@ -123,13 +131,15 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
123
131
  * (one a pass, the largest part) and a welcome email — with nothing to
124
132
  * spare, so a runner that never finishes is still bounded while a real site
125
133
  * is not.
126
- */ export const AI_SITE_MAX_PASSES = AI_SITE_PAGES.max * (AI_SITE_MAX_SECTIONS + 1) + CREATION_UNITS.length + AI_SITE_POSTS + 1;
134
+ */ export const AI_SITE_MAX_PASSES = AI_SITE_PAGES.max * (AI_SITE_MAX_SECTIONS + 1) + CREATION_UNITS.length + AI_SITE_DATASETS_MAX + AI_SITE_POSTS + 1;
127
135
  /**
128
136
  * The units a plan implies, in build order: the palette first, because a
129
137
  * member reads it while the pages are still building; then the layout every
130
- * page renders inside and the form they place, because a page binds both by
131
- * id and so needs them to exist; then the pages; then the welcome email,
132
- * which is about the site rather than part of it.
138
+ * page renders inside, the site's datasets, and the form they place —
139
+ * because a page binds the layout and the form by id, and a form binds the
140
+ * dataset it writes to (AGL-3616), so each needs what it binds to exist;
141
+ * then the pages; then the welcome email, which is about the site rather
142
+ * than part of it.
133
143
  */ export function aiSiteJobUnits(plan, options = {}) {
134
144
  // The site's look is designed first, on every scaffold (AGL-3660): the
135
145
  // header, the footer and every page render in it from their first draft.
@@ -144,6 +154,19 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
144
154
  ];
145
155
  for (const { unit, create } of CREATION_UNITS){
146
156
  if (unit === 'theme') continue;
157
+ // The site's datasets (AGL-3616), before anything that lists them and
158
+ // before the form, which may write its submissions to one of them.
159
+ if (unit === 'form') {
160
+ aiSitePlanDatasets(plan).forEach((creation, index)=>{
161
+ units.push(_extends({
162
+ kind: 'dataset'
163
+ }, UNIT_KINDS.dataset, {
164
+ slot: `d${index}`,
165
+ creation,
166
+ label: creation.name
167
+ }));
168
+ });
169
+ }
147
170
  const creation = plan.create.find((entry)=>entry.kind === create);
148
171
  if (!creation) continue;
149
172
  units.push(_extends({
@@ -254,8 +277,8 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
254
277
  const row = rows == null ? void 0 : rows.shift();
255
278
  if (!row || !unit.creation) continue;
256
279
  // A theme change is a proposal with no record to reference; a layout, a
257
- // form and a component become ids a page can name.
258
- if (unit.creation.kind !== 'layout' && unit.creation.kind !== 'form' && unit.creation.kind !== 'component') {
280
+ // form, a component and a dataset (AGL-3616) become ids a page can name.
281
+ if (unit.creation.kind !== 'layout' && unit.creation.kind !== 'form' && unit.creation.kind !== 'component' && unit.creation.kind !== 'dataset') {
259
282
  continue;
260
283
  }
261
284
  built.set(unit.creation.name.toLowerCase(), {
@@ -359,6 +382,7 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
359
382
  * for leaving it out.
360
383
  */ export function aiSiteUnitJob(job, unit, built) {
361
384
  var _plan_labels;
385
+ var _unitPlan_create_, _unit_creation;
362
386
  const plan = job.plan;
363
387
  const labels = _extends({}, (_plan_labels = plan.labels) != null ? _plan_labels : {});
364
388
  for (const entry of built.values())labels[entry.id] = entry.label;
@@ -380,12 +404,14 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
380
404
  unitPlan.create = [
381
405
  _extends({}, unit.creation, {
382
406
  duplicateOf: aiSiteResolvedRef(unit.creation.duplicateOf, built)
383
- })
407
+ }, unit.creation.writesTo ? {
408
+ writesTo: aiSiteResolvedRef(unit.creation.writesTo, built)
409
+ } : {})
384
410
  ];
385
411
  brief.push(`Build the ${unit.creation.kind} “${unit.creation.name}”: ${unit.creation.why}`);
386
412
  }
387
413
  if (unit.screen) {
388
- var _aiSiteResolvedRef, _aiSiteResolvedRef1, _job_outputs;
414
+ var _aiSiteResolvedRef, _aiSiteResolvedRef1;
389
415
  const screen = unit.screen;
390
416
  // Everything the scaffold has already built is a record the site has, so
391
417
  // the page is planned against it the way a page job is planned against
@@ -419,8 +445,12 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
419
445
  })
420
446
  ];
421
447
  brief.push(`Build the page “${screen.title}” of this site, at ${screen.slug}.`);
422
- // What the blog's posts or the store's products are, once built (AGL-3676).
423
- if (job.kind === 'site') brief.push(...aiSiteContentBriefLines((_job_outputs = job.outputs) != null ? _job_outputs : []));
448
+ // What the blog's posts or the store's products are, once built (AGL-3676),
449
+ // and what the site's datasets hold (AGL-3616).
450
+ if (job.kind === 'site') {
451
+ var _job_outputs, _job_outputs1;
452
+ brief.push(...aiSiteContentBriefLines((_job_outputs = job.outputs) != null ? _job_outputs : []), ...aiSiteDatasetBriefLines((_job_outputs1 = job.outputs) != null ? _job_outputs1 : []));
453
+ }
424
454
  }
425
455
  if (unit.kind === 'products') brief.push(aiSiteProductsBriefLine());
426
456
  if (unit.kind === 'email') {
@@ -434,15 +464,15 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
434
464
  // A page is told the pages built before it, whose photos it does not place
435
465
  // again (AGL-3660): the maker's portrait in About is never a vase in Work.
436
466
  if (unit.screen) {
437
- var _job_outputs1;
438
- const pages = ((_job_outputs1 = job.outputs) != null ? _job_outputs1 : []).filter((output)=>output.resource === 'screen' && output.hostId === job.hostId && output.id).map((output)=>output.id);
467
+ var _job_outputs2;
468
+ const pages = ((_job_outputs2 = job.outputs) != null ? _job_outputs2 : []).filter((output)=>output.resource === 'screen' && output.hostId === job.hostId && output.id).map((output)=>output.id);
439
469
  if (pages.length) unitInputs[AI_STOCK_PHOTO_PAGES_INPUT] = pages;
440
470
  }
441
471
  // The layout is built before the pages, so it is told them (AGL-3596): their
442
472
  // ids are minted on the plan, and the platform writes the header's links.
443
473
  // A page is told them too, so its buttons may go to a page built after it.
444
474
  if (job.kind === 'site' && (unit.kind === 'layout' || unit.kind === 'page')) {
445
- var _job_outputs2;
475
+ var _job_outputs3, _job_outputs4;
446
476
  var _aiSiteKindOfInputs, _aiSiteKindOfInputs1;
447
477
  // A guided start links every page the person asked for (AGL-3660), and
448
478
  // the blog its first posts are written into, by its path, second after
@@ -481,7 +511,7 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
481
511
  // The store's catalog and the blog, listed by the sections that show them,
482
512
  // and the cart in a selling site's header (AGL-3676).
483
513
  const listings = aiSiteListings({
484
- outputs: (_job_outputs2 = job.outputs) != null ? _job_outputs2 : [],
514
+ outputs: (_job_outputs3 = job.outputs) != null ? _job_outputs3 : [],
485
515
  screens: merged.screens,
486
516
  sells: aiSiteSellsProducts(job),
487
517
  // A store lists its catalog on its Shop page even when its first
@@ -490,15 +520,28 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
490
520
  // A music site places an empty player for the artist's own tracks (AGL-3716).
491
521
  music: ((_aiSiteKindOfInputs1 = aiSiteKindOfInputs(job.inputs)) == null ? void 0 : _aiSiteKindOfInputs1.id) === 'music'
492
522
  });
523
+ // The site's datasets, each repeated in the sections that name it (AGL-3616).
524
+ const delivered = new Map([
525
+ ...built.entries()
526
+ ].filter(([, entry])=>entry.kind === 'dataset').map(([name, entry])=>[
527
+ name,
528
+ entry.id
529
+ ]));
530
+ listings.push(...aiSiteDatasetListings({
531
+ outputs: (_job_outputs4 = job.outputs) != null ? _job_outputs4 : [],
532
+ datasets: aiSitePlanDatasets(plan),
533
+ screens: merged.screens,
534
+ delivered
535
+ }));
493
536
  if (listings.length) unitInputs[AI_LAYOUT_LISTINGS_INPUT] = listings;
494
537
  }
495
538
  // A site's pages and its layout are designed in the layout language and
496
539
  // compiled (AGL-3660), and a page is told which page places the site's form.
497
540
  if (unit.kind === 'layout' || unit.kind === 'page') {
498
- var _job_outputs3;
541
+ var _job_outputs5;
499
542
  var _look_proposal;
500
543
  // The look designed first (AGL-3660): its header arrangement and band rhythm.
501
- const look = ((_job_outputs3 = job.outputs) != null ? _job_outputs3 : []).find((output)=>output.resource === 'theme' && output.id === 'look');
544
+ const look = ((_job_outputs5 = job.outputs) != null ? _job_outputs5 : []).find((output)=>output.resource === 'theme' && output.id === 'look');
502
545
  const style = look == null ? void 0 : (_look_proposal = look.proposal) == null ? void 0 : _look_proposal['style'];
503
546
  if (style) unitInputs['siteStyle'] = {
504
547
  headerAlign: style['headerAlign'],
@@ -510,11 +553,11 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
510
553
  if (formPage) unitInputs[AI_LAYOUT_FORM_PAGE_INPUT] = formPage;
511
554
  }
512
555
  if (unit.kind === 'posts') {
513
- var _job_outputs4, _inputs_businessName;
556
+ var _job_outputs6, _inputs_businessName;
514
557
  // The posts already written (one a pass), the addresses the site's own
515
558
  // pages answer at, and the byline the person gave (AGL-3676).
516
559
  const posts = {
517
- written: ((_job_outputs4 = job.outputs) != null ? _job_outputs4 : []).filter((output)=>output.resource === 'entry').map((output)=>({
560
+ written: ((_job_outputs6 = job.outputs) != null ? _job_outputs6 : []).filter((output)=>output.resource === 'entry').map((output)=>({
518
561
  id: output.id,
519
562
  title: output.label
520
563
  })),
@@ -526,6 +569,14 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
526
569
  unitInputs[AI_SITE_CONTENT_INPUT] = posts;
527
570
  }
528
571
  if (unit.kind === 'products') unitInputs['target'] = 'catalog';
572
+ // Where the plan lists a dataset, which its design reads, and the form
573
+ // that writes to it (AGL-3616).
574
+ if (unit.kind === 'dataset' && unit.creation) {
575
+ unitInputs[AI_SITE_DATASET_INPUT] = aiSiteDatasetInputOf(unit.creation, plan.screens, plan.create);
576
+ }
577
+ // A form's dataset this start made, which the form may bind (AGL-3616).
578
+ const writesTo = unit.kind === 'form' ? (_unitPlan_create_ = unitPlan.create[0]) == null ? void 0 : _unitPlan_create_.writesTo : null;
579
+ if (writesTo && isAiPlanNewRef((_unit_creation = unit.creation) == null ? void 0 : _unit_creation.writesTo)) unitInputs[AI_FORM_DATASET_MADE_INPUT] = writesTo;
529
580
  return _extends({}, job, {
530
581
  $id: aiSiteUnitJobId(job, unit),
531
582
  kind: unit.jobKind,
@@ -729,16 +780,31 @@ export const aiSiteJobAdmission = createAiSiteJobAdmission();
729
780
  * Try again read, so a site is settled item by item exactly as a build is.
730
781
  */ export function aiSiteLedgerUnits(units) {
731
782
  const creations = units.filter((unit)=>unit.kind === 'layout' || unit.kind === 'form').map((unit)=>unit.slot);
783
+ const datasets = units.filter((unit)=>unit.kind === 'dataset' && unit.creation);
784
+ /** The dataset a form writes to (AGL-3616): built before it, and it without the binding if it fails. */ const writtenTo = (creation)=>datasets.filter((unit)=>{
785
+ var _ref;
786
+ var _unit_creation;
787
+ return !!(creation == null ? void 0 : creation.writesTo) && creation.writesTo.trim().toLowerCase() === `new:${(_ref = (_unit_creation = unit.creation) == null ? void 0 : _unit_creation.name) != null ? _ref : ''}`.trim().toLowerCase();
788
+ }).map((unit)=>unit.slot);
789
+ /** The datasets a page lists or is the record template of (AGL-3616): built before it, and it without them if they fail. */ const listed = (screen)=>datasets.filter((unit)=>{
790
+ var _ref, _ref1;
791
+ var _unit_creation, _screen_record;
792
+ const named = `new:${(_ref = (_unit_creation = unit.creation) == null ? void 0 : _unit_creation.name) != null ? _ref : ''}`.trim().toLowerCase();
793
+ return (screen == null ? void 0 : (_screen_record = screen.record) == null ? void 0 : _screen_record.dataset.trim().toLowerCase()) === named || ((_ref1 = screen == null ? void 0 : screen.sections) != null ? _ref1 : []).some((section)=>section.uses.some((ref)=>ref.trim().toLowerCase() === named));
794
+ }).map((unit)=>unit.slot);
732
795
  return units.map((unit)=>_extends({
733
796
  slot: unit.slot,
734
- op: unit.kind === 'theme' || unit.kind === 'posts' || unit.kind === 'products' ? unit.kind : unit.jobKind,
797
+ op: unit.kind === 'theme' || unit.kind === 'posts' || unit.kind === 'products' || unit.kind === 'dataset' ? unit.kind : unit.jobKind,
735
798
  label: unit.label
736
799
  }, unit.creation ? {
737
800
  creation: unit.creation
738
801
  } : {}, unit.screen ? {
739
802
  screen: unit.screen
740
803
  } : {}, {
741
- deps: unit.kind === 'page' ? creations : []
804
+ deps: unit.kind === 'page' ? [
805
+ ...creations,
806
+ ...listed(unit.screen)
807
+ ] : unit.kind === 'form' ? writtenTo(unit.creation) : []
742
808
  }));
743
809
  }
744
810
  /**
@@ -770,20 +836,21 @@ export const aiSiteJobAdmission = createAiSiteJobAdmission();
770
836
  return aiSiteJobUnits(plan, {
771
837
  welcomeEmail: aiSiteWelcomeEmail(inputs, freeTaste),
772
838
  content: aiSiteContentPart(job.inputs, freeTaste)
773
- }).filter((unit)=>unit.kind === 'theme' || unit.kind === 'posts' || runnerFor(unit.jobKind));
839
+ }).filter((unit)=>unit.kind === 'theme' || unit.kind === 'posts' || unit.kind === 'dataset' || runnerFor(unit.jobKind));
774
840
  }
775
841
  export function createAiJobSiteStep(deps = {}) {
776
- var _deps_runnerFor, _deps_readNodes, _deps_publish, _deps_look, _deps_contentRefusal, _deps_publishPosts, _deps_dropCache;
842
+ var _deps_runnerFor, _deps_readNodes, _deps_publish, _deps_look, _deps_contentRefusal, _deps_datasetRefusal, _deps_publishPosts, _deps_dropCache;
777
843
  const runnerFor = (_deps_runnerFor = deps.runnerFor) != null ? _deps_runnerFor : aiJobStepRunnerFor;
778
844
  const readNodes = (_deps_readNodes = deps.readNodes) != null ? _deps_readNodes : readAiDraftNodes;
779
845
  const publish = (_deps_publish = deps.publish) != null ? _deps_publish : aiPublishGuidedSite;
780
846
  const look = (_deps_look = deps.look) != null ? _deps_look : aiRunSiteLook;
781
847
  const contentRefusal = (_deps_contentRefusal = deps.contentRefusal) != null ? _deps_contentRefusal : aiSiteContentRefusal;
848
+ const datasetRefusal = (_deps_datasetRefusal = deps.datasetRefusal) != null ? _deps_datasetRefusal : aiSiteDatasetRefusal;
782
849
  const publishPosts = (_deps_publishPosts = deps.publishPosts) != null ? _deps_publishPosts : aiPublishSitePosts;
783
850
  const dropCache = (_deps_dropCache = deps.dropCache) != null ? _deps_dropCache : dropPluginSiteCache;
784
851
  return async (context)=>{
785
- var _ref, _job_outputs, _deps_posts, _deps_products, _job_outputs1;
786
- var _job_items, _rows_get;
852
+ var _ref, _job_outputs, _deps_posts, _deps_products, _deps_dataset, _job_outputs1;
853
+ var _job_items, _rows_get, _rows_get1;
787
854
  const { job } = context;
788
855
  // The scaffold asks no model of its own. What it names where it spends
789
856
  // nothing is the model its plan ran on, which is the one exchange this
@@ -875,11 +942,20 @@ export function createAiJobSiteStep(deps = {}) {
875
942
  error
876
943
  }));
877
944
  };
878
- /** The pages built so far, as the job reported them. */ const builtPages = (extra = [])=>{
945
+ /**
946
+ * The pages built so far, as the job reported them. A dataset's record
947
+ * template is not among them (AGL-3616): it renders once per record only
948
+ * once the member saves its binding, so it is never published at its own
949
+ * address, where its `{{item.*}}` would show.
950
+ */ const templates = new Set(units.filter((unit)=>{
951
+ var _unit_screen;
952
+ return unit.kind === 'page' && ((_unit_screen = unit.screen) == null ? void 0 : _unit_screen.record);
953
+ }).map((unit)=>unit.slot));
954
+ const builtPages = (extra = [])=>{
879
955
  var _job_outputs;
880
956
  const ids = new Set(units.filter((unit)=>{
881
957
  var _rows_get;
882
- return unit.kind === 'page' && aiBuildItemDelivered((_rows_get = rows.get(unit.slot)) != null ? _rows_get : {
958
+ return unit.kind === 'page' && !templates.has(unit.slot) && aiBuildItemDelivered((_rows_get = rows.get(unit.slot)) != null ? _rows_get : {
883
959
  status: 'pending'
884
960
  });
885
961
  }).flatMap((unit)=>{
@@ -898,7 +974,7 @@ export function createAiJobSiteStep(deps = {}) {
898
974
  const catalog = unit.kind === 'products' ? runnerFor('products') : undefined;
899
975
  const runner = unit.kind === 'theme' ? (lookContext)=>look(lookContext, lookContext.job) : unit.kind === 'posts' ? (_deps_posts = deps.posts) != null ? _deps_posts : runAiSitePostsUnit : unit.kind === 'products' ? (_deps_products = deps.products) != null ? _deps_products : catalog ? createAiSiteProductsRunner({
900
976
  catalog
901
- }) : undefined : runnerFor(unit.jobKind);
977
+ }) : undefined : unit.kind === 'dataset' ? (_deps_dataset = deps.dataset) != null ? _deps_dataset : runAiSiteDatasetUnit : runnerFor(unit.jobKind);
902
978
  if (!runner) return _extends({}, aiUnspentOutcome(model), init);
903
979
  const othersOpen = ledgerUnits.some((one)=>{
904
980
  var _rows_get;
@@ -957,6 +1033,48 @@ export function createAiJobSiteStep(deps = {}) {
957
1033
  note: `Not built: ${refusal}`
958
1034
  }));
959
1035
  }
1036
+ // A dataset asks the same before its first pass (AGL-3616): the data
1037
+ // plugin runs here, and its writer admits this member now.
1038
+ if (unit.kind === 'dataset' && ((_rows_get1 = rows.get(unit.slot)) == null ? void 0 : _rows_get1.status) !== 'running') {
1039
+ const refusal = await datasetRefusal(_extends({}, context, {
1040
+ job
1041
+ })).catch((error)=>{
1042
+ console.error('ai site dataset admission failed', {
1043
+ orgId: job.orgId,
1044
+ jobId: job.$id,
1045
+ slot: unit.slot,
1046
+ error
1047
+ });
1048
+ return AI_SITE_UNIT_EMPTY_COPY;
1049
+ });
1050
+ if (refusal) return closing(settle({
1051
+ slot: unit.slot,
1052
+ status: 'skipped',
1053
+ note: `Not built: ${refusal}`
1054
+ }));
1055
+ }
1056
+ // A record template whose dataset was not made has nothing to show
1057
+ // (AGL-3616): it is not built, and the pages that list the dataset are
1058
+ // built without it.
1059
+ const templateOf = unit.kind === 'page' && unit.screen ? aiSiteRecordTemplateOf(unit.screen) : null;
1060
+ if (templateOf) {
1061
+ const dataset = units.find((one)=>{
1062
+ var _one_creation;
1063
+ return one.kind === 'dataset' && ((_one_creation = one.creation) == null ? void 0 : _one_creation.name.trim().toLowerCase()) === templateOf.toLowerCase();
1064
+ });
1065
+ const row = dataset ? rows.get(dataset.slot) : undefined;
1066
+ if (!row || !aiBuildItemDelivered(row) || !row.outputs.length) {
1067
+ return closing(settle(_extends({
1068
+ slot: unit.slot,
1069
+ status: 'skipped',
1070
+ note: `Not built: the dataset “${templateOf}” it shows a page of each record of could not be created.`
1071
+ }, dataset ? {
1072
+ degradedBy: [
1073
+ dataset.slot
1074
+ ]
1075
+ } : {})));
1076
+ }
1077
+ }
960
1078
  // A page whose layout or form failed is built without it, and says so.
961
1079
  const degradation = aiBuildDegradation(next, {
962
1080
  units: ledgerUnits,
@@ -1042,7 +1160,7 @@ export function createAiJobSiteStep(deps = {}) {
1042
1160
  rows.set(unit.slot, _extends({}, rows.get(unit.slot), {
1043
1161
  status: degraded ? 'degraded' : 'succeeded'
1044
1162
  }));
1045
- return finish(done, builtPages(unit.kind === 'page' ? outcome.outputs.filter((output)=>output.resource === 'screen') : []));
1163
+ return finish(done, builtPages(unit.kind === 'page' && !templates.has(unit.slot) ? outcome.outputs.filter((output)=>output.resource === 'screen') : []));
1046
1164
  };
1047
1165
  }
1048
1166
  export const runAiJobSiteStep = createAiJobSiteStep();
@@ -1065,7 +1183,7 @@ export const runAiJobSiteStep = createAiJobSiteStep();
1065
1183
  const units = aiSiteJobUnits(plan, {
1066
1184
  welcomeEmail: inputs.welcomeEmail,
1067
1185
  content: aiSiteContentPart(job.inputs, false)
1068
- }).filter((unit)=>(unit.kind === 'theme' || unit.kind === 'posts' || aiJobStepRunnerFor(unit.jobKind)) && (!owed.size || owed.has(unit.slot)));
1186
+ }).filter((unit)=>(unit.kind === 'theme' || unit.kind === 'posts' || unit.kind === 'dataset' || aiJobStepRunnerFor(unit.jobKind)) && (!owed.size || owed.has(unit.slot)));
1069
1187
  const outputs = (_job_outputs = job.outputs) != null ? _job_outputs : [];
1070
1188
  const ledger = ((_job_items1 = job.items) == null ? void 0 : _job_items1.length) ? job.items : aiSiteInitialLedger(units, outputs);
1071
1189
  const next = aiBuildNextUnit(aiSiteLedgerUnits(units), ledger);
@@ -1073,6 +1191,7 @@ export const runAiJobSiteStep = createAiJobSiteStep();
1073
1191
  if (!unit) return AI_JOB_PAGE_STEP_MINIMUM_MS;
1074
1192
  if (unit.kind === 'theme') return AI_SITE_LOOK_BUDGET.minimumMs;
1075
1193
  if (unit.kind === 'posts') return AI_SITE_POST_BUDGET.minimumMs;
1194
+ if (unit.kind === 'dataset') return AI_SITE_DATASET_BUDGET.minimumMs;
1076
1195
  return aiJobStepRunMinimumMs(aiSiteUnitJob(job, unit, aiBuildBuiltRefs(aiSiteLedgerUnits(units), ledger, outputs)));
1077
1196
  }
1078
1197
  /**