@aglyn/plugins-ai 1.0.0-beta.244 → 1.0.0-beta.246

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 (116) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.js +3 -1
  3. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  4. package/src/lib/components/ai-job-links.js +7 -0
  5. package/src/lib/components/ai-job-links.js.map +1 -1
  6. package/src/lib/components/ai-product-zones.d.ts +3 -1
  7. package/src/lib/components/ai-product-zones.js.map +1 -1
  8. package/src/lib/components/ai-products-hub-card.component.js +1 -1
  9. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  10. package/src/lib/components/ai-products-proposals.component.js +17 -9
  11. package/src/lib/components/ai-products-proposals.component.js.map +1 -1
  12. package/src/lib/components/ai-site-start-card.component.js +6 -4
  13. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  14. package/src/lib/components/ai-upsell-dialog.component.js +1 -1
  15. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  16. package/src/lib/jobs/ai-build-capabilities.js +162 -2
  17. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  18. package/src/lib/jobs/ai-job-build-step.d.ts +7 -0
  19. package/src/lib/jobs/ai-job-build-step.js +85 -16
  20. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  21. package/src/lib/jobs/ai-job-duplicate-step.d.ts +123 -0
  22. package/src/lib/jobs/ai-job-duplicate-step.js +269 -0
  23. package/src/lib/jobs/ai-job-duplicate-step.js.map +1 -0
  24. package/src/lib/jobs/ai-job-layout-language.js +13 -0
  25. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  26. package/src/lib/jobs/ai-job-navigation-step.d.ts +157 -0
  27. package/src/lib/jobs/ai-job-navigation-step.js +439 -0
  28. package/src/lib/jobs/ai-job-navigation-step.js.map +1 -0
  29. package/src/lib/jobs/ai-job-page-language.d.ts +2 -1
  30. package/src/lib/jobs/ai-job-page-language.js +21 -5
  31. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  32. package/src/lib/jobs/ai-job-page-sections.js +1 -1
  33. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  34. package/src/lib/jobs/ai-job-plan-step.d.ts +4 -2
  35. package/src/lib/jobs/ai-job-plan-step.js +16 -7
  36. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  37. package/src/lib/jobs/ai-job-products-step.d.ts +2 -2
  38. package/src/lib/jobs/ai-job-products-step.js +2 -2
  39. package/src/lib/jobs/ai-job-products-step.js.map +1 -1
  40. package/src/lib/jobs/ai-job-settings-step.d.ts +272 -0
  41. package/src/lib/jobs/ai-job-settings-step.js +695 -0
  42. package/src/lib/jobs/ai-job-settings-step.js.map +1 -0
  43. package/src/lib/jobs/ai-job-site-content.d.ts +20 -6
  44. package/src/lib/jobs/ai-job-site-content.js +28 -12
  45. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  46. package/src/lib/jobs/ai-job-site-datasets.d.ts +2 -0
  47. package/src/lib/jobs/ai-job-site-datasets.js +10 -3
  48. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  49. package/src/lib/jobs/ai-job-site-step.d.ts +3 -1
  50. package/src/lib/jobs/ai-job-site-step.js +70 -10
  51. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  52. package/src/lib/jobs/ai-site-publish.d.ts +62 -4
  53. package/src/lib/jobs/ai-site-publish.js +203 -56
  54. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  55. package/src/lib/layout-language/ai-layout-compiler.d.ts +7 -4
  56. package/src/lib/layout-language/ai-layout-compiler.js +26 -25
  57. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  58. package/src/lib/layout-language/ai-layout-frame-hollows.d.ts +41 -0
  59. package/src/lib/layout-language/ai-layout-frame-hollows.js +330 -0
  60. package/src/lib/layout-language/ai-layout-frame-hollows.js.map +1 -0
  61. package/src/lib/layout-language/ai-layout-frame.d.ts +18 -1
  62. package/src/lib/layout-language/ai-layout-frame.js +83 -13
  63. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  64. package/src/lib/layout-language/ai-layout-listings.d.ts +35 -0
  65. package/src/lib/layout-language/ai-layout-listings.js +34 -0
  66. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  67. package/src/lib/layout-language/ai-layout-tree.d.ts +2 -0
  68. package/src/lib/layout-language/ai-layout-tree.js +17 -0
  69. package/src/lib/layout-language/ai-layout-tree.js.map +1 -1
  70. package/src/lib/model/ai-build-job.d.ts +23 -0
  71. package/src/lib/model/ai-build-job.js +102 -13
  72. package/src/lib/model/ai-build-job.js.map +1 -1
  73. package/src/lib/model/ai-build-plan.d.ts +25 -0
  74. package/src/lib/model/ai-build-plan.js +45 -1
  75. package/src/lib/model/ai-build-plan.js.map +1 -1
  76. package/src/lib/model/ai-credit-estimate.d.ts +52 -0
  77. package/src/lib/model/ai-credit-estimate.js +51 -0
  78. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  79. package/src/lib/model/ai-job-activity.js +4 -1
  80. package/src/lib/model/ai-job-activity.js.map +1 -1
  81. package/src/lib/model/ai-jobs.types.d.ts +10 -2
  82. package/src/lib/model/ai-jobs.types.js +4 -1
  83. package/src/lib/model/ai-jobs.types.js.map +1 -1
  84. package/src/lib/model/ai-layout-menus.d.ts +104 -0
  85. package/src/lib/model/ai-layout-menus.js +518 -0
  86. package/src/lib/model/ai-layout-menus.js.map +1 -0
  87. package/src/lib/model/ai-products.d.ts +31 -1
  88. package/src/lib/model/ai-products.js +34 -1
  89. package/src/lib/model/ai-products.js.map +1 -1
  90. package/src/lib/model/ai-site-inventory.d.ts +15 -0
  91. package/src/lib/model/ai-site-inventory.js.map +1 -1
  92. package/src/lib/model/ai-site-job.d.ts +10 -0
  93. package/src/lib/model/ai-site-job.js +36 -9
  94. package/src/lib/model/ai-site-job.js.map +1 -1
  95. package/src/lib/model/ai-site-store-pages.js +1 -1
  96. package/src/lib/model/ai-site-store-pages.js.map +1 -1
  97. package/src/lib/runtime/ai-doctrine-validators.d.ts +53 -0
  98. package/src/lib/runtime/ai-doctrine-validators.js +166 -25
  99. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  100. package/src/lib/runtime/ai-doctrine.js +7 -2
  101. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  102. package/src/lib/runtime/ai-palette.generated.js +1 -1
  103. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  104. package/src/lib/runtime/ai-products-generation.d.ts +4 -2
  105. package/src/lib/runtime/ai-products-generation.js +12 -6
  106. package/src/lib/runtime/ai-products-generation.js.map +1 -1
  107. package/src/lib/runtime/site-inventory.d.ts +12 -0
  108. package/src/lib/runtime/site-inventory.js +29 -1
  109. package/src/lib/runtime/site-inventory.js.map +1 -1
  110. package/src/lib/server.js +12 -0
  111. package/src/lib/server.js.map +1 -1
  112. package/src/lib/testing/stand-in-dataset-index.js +37 -3
  113. package/src/lib/testing/stand-in-dataset-index.js.map +1 -1
  114. package/src/lib/tools/ai-products-tool.d.ts +3 -1
  115. package/src/lib/tools/ai-products-tool.js +15 -5
  116. package/src/lib/tools/ai-products-tool.js.map +1 -1
@@ -26,6 +26,8 @@ import { renderEmailHtml } from "@aglyn/shared-util-email/email-render";
26
26
  import { AI_BUILD_PLAN_CREATION_NOUNS, AI_BUILD_PLAN_EMBED_HOST_NAMES, AI_BUILD_PLAN_LIMITS, AI_PLAN_ASKS_FOR_VIDEO, aiEmbedHostOf, aiEmbedVideoKey, AI_PLAN_DATASET_ITEM_OP, aiPlanCreateFor, aiPlanFormDatasetNames, aiPlanItemFor, aiPlanSlugKey, aiPlanUndeclaredRefs, isAiPlanNewRef } from "../model/ai-build-plan.js";
27
27
  import { aiCreationNoun, aiPlanCanPlaceForm, aiPlanUncreatable, aiPlanUncreatableKind } from "../model/ai-plan-capabilities.js";
28
28
  import { aiFreeSiteSectionsWithin, aiSiteHomeMinSections, aiSitePlanIsHome } from "../model/ai-site-job.js";
29
+ import { aiLayoutIsShopPage, aiLayoutShopGridSection } from "../layout-language/ai-layout-listings.js";
30
+ import { aiStorefrontSays } from "../layout-language/ai-layout-storefront.js";
29
31
  import { AI_INSTANCE_REF_PROP, validateAiNodeTree } from "./ai-node-tree.js";
30
32
  import { AI_PAGE_SCROLL_TO_PROP, aiPageLinkTarget, aiStoredScrollTargets } from "./ai-page-links.js";
31
33
  import { AI_EMAIL_CLIP_BYTES, AI_OUTPUT_BUDGETS, AI_OUTPUT_SURFACE, AI_TEXT_LIMITS, isHeadlineVariant } from "./ai-palette.js";
@@ -3769,6 +3771,37 @@ const FORM_SECTION_NAME = /\bform\b|\bsign[- ]?up\b|\bsubscribe\b|\bnewsletter\b
3769
3771
  }))
3770
3772
  });
3771
3773
  }
3774
+ /**
3775
+ * A page section that is a layout region — a header, navigation or footer,
3776
+ * read by the name rule 2's `plan-layout-region-section` reads — is taken off
3777
+ * the page before the rules read the plan (AGL-3750). The region is already
3778
+ * the layout's, so the rule's own answer, "take the section off the page", is
3779
+ * the only one: a live Free start of 2026-10-12 (a bakery that sells cakes to
3780
+ * order) planned a Header and a Footer on its pages three times and stopped.
3781
+ *
3782
+ * Only a section that places nothing, or nothing but a layout (which rule 2
3783
+ * refuses placing as well), is taken off: one that places a form or a
3784
+ * component may be a band misnamed, so the rule still asks. A page whose
3785
+ * every section is a region keeps them all, and is re-asked as before, since
3786
+ * a page of no sections has no answer here. A home this leaves under its
3787
+ * fewest sections, or a Free plan still past its wall, is the existing
3788
+ * checks' to settle or refuse.
3789
+ */ export function aiSettlePlanLayoutRegionSections(plan, inventory) {
3790
+ const kinds = inventoryKinds(inventory);
3791
+ const removable = (section)=>LAYOUT_REGION_NAME.test(section.name) && section.uses.every((ref)=>refKind(ref, plan, kinds) === 'layout');
3792
+ let changed = false;
3793
+ const screens = plan.screens.map((screen)=>{
3794
+ const kept = screen.sections.filter((section)=>!removable(section));
3795
+ if (kept.length === screen.sections.length || !kept.length) return screen;
3796
+ changed = true;
3797
+ return _extends({}, screen, {
3798
+ sections: kept
3799
+ });
3800
+ });
3801
+ return changed ? _extends({}, plan, {
3802
+ screens
3803
+ }) : plan;
3804
+ }
3772
3805
  /** Every saved form a plan could place, the plan's own creations first. */ function planFormChoices(plan, inventory) {
3773
3806
  var _ref;
3774
3807
  return [
@@ -4419,6 +4452,135 @@ function slugKey(slug) {
4419
4452
  const needs = !((_ref = inventory == null ? void 0 : inventory.layouts.length) != null ? _ref : 0) && capabilities.create.layout.allowed ? 1 : 0;
4420
4453
  return Math.max(creates, needs);
4421
4454
  }
4455
+ /** The Free wall a plan is held to, or null where the job is not on the Free taste. */ function freePlanWall(plan, inventory, capabilities) {
4456
+ if (!(capabilities == null ? void 0 : capabilities.freeTaste)) return null;
4457
+ const asked = plan.screens.reduce((sum, screen)=>sum + screen.sections.length, 0);
4458
+ const layouts = freePlanLayouts(plan, inventory, capabilities);
4459
+ const pages = Math.max(plan.screens.length, 1);
4460
+ // A Free site scaffold (AGL-3594) is held to its page cap first, and then to
4461
+ // what a SITE plan's worst case leaves for sections, with room for a retry.
4462
+ const sitePages = capabilities.freeSitePages;
4463
+ const overPages = sitePages !== undefined && plan.screens.length > sitePages;
4464
+ // A site start's saved form is a step of its own (AGL-3596), counted on the wall since AGL-3660.
4465
+ const forms = plan.create.filter((entry)=>entry.kind === 'form').length - aiPlanUncreatable(plan, capabilities).filter((entry)=>entry.kind === 'form').length;
4466
+ const fits = sitePages !== undefined ? aiFreeSiteSectionsWithin({
4467
+ layouts,
4468
+ pages,
4469
+ forms: Math.max(0, forms)
4470
+ }, FREE_AI_TASTE_CREDITS_PER_MONTH) : aiFreePageSectionsWithin({
4471
+ layouts,
4472
+ pages
4473
+ });
4474
+ // A site start's home keeps its full five (AGL-3660): told only "plan at
4475
+ // most 8", the live yoga start of 2026-10-08 cut a home of 6 to 4 and kept
4476
+ // its other page, so the redo names where the sections come from.
4477
+ const homeMin = sitePages !== undefined && plan.screens.some(aiSitePlanIsHome) ? aiSiteHomeMinSections({
4478
+ pages,
4479
+ across: fits
4480
+ }) : 0;
4481
+ return {
4482
+ asked,
4483
+ fits,
4484
+ layouts,
4485
+ pages,
4486
+ sitePages,
4487
+ overPages,
4488
+ homeMin
4489
+ };
4490
+ }
4491
+ /**
4492
+ * The most sections past the Free wall a plan is trimmed by rather than
4493
+ * re-asked (AGL-3737). The live candle-shop start of 2026-10-12 planned 9
4494
+ * sections against a wall of 8 on all three answers, each with a home of 7:
4495
+ * the model reaching for the budget the site redo names ("plan 7 to 8") and
4496
+ * landing one band past it, which no re-ask cured. Within two, the plan's
4497
+ * shape is right and only a band or two the brief needs least is extra, so
4498
+ * taking those from the end loses nothing the plan depends on. Further over,
4499
+ * the shape itself is off — a list planned as sections, a page the wall
4500
+ * cannot pay for — and cutting more would drop content the model meant, so
4501
+ * that plan is still re-asked.
4502
+ */ export const AI_FREE_WALL_SETTLE_MAX_OVER = 2;
4503
+ /** A section through which a visitor reaches the owner: contact, a visit, a booking. */ const REACH_SECTION_NAME = /\b(?:contact|get in touch|visit(?: us)?|find us|locations?|hours|book(?:ing)?|reserve|reservations?)\b/i;
4504
+ /**
4505
+ * Whether the Free wall's trim keeps a section of a page (AGL-3737): the
4506
+ * page's opening, any section that places something (a dataset it lists, a
4507
+ * form, a component — dropping it would leave a creation unplaced), one that
4508
+ * collects answers or says how to reach the owner, and a storefront's
4509
+ * listing parts — the bestsellers, the collections, the sign-up, and a Shop
4510
+ * page's product grid.
4511
+ */ function freeWallKeeps(screen, index) {
4512
+ const section = screen.sections[index];
4513
+ if (index === 0 || section.uses.length > 0) return true;
4514
+ if (FORM_SECTION_NAME.test(section.name) || REACH_SECTION_NAME.test(section.name)) return true;
4515
+ if (aiStorefrontSays('featured', section.name) || aiStorefrontSays('collections', section.name) || aiStorefrontSays('signup', section.name)) {
4516
+ return true;
4517
+ }
4518
+ return aiLayoutIsShopPage(screen) && aiLayoutShopGridSection(screen.sections) === index;
4519
+ }
4520
+ /**
4521
+ * A Free plan a section or two past the wall is trimmed to fit before the
4522
+ * rules read the plan (AGL-3737), rather than re-asked: which sections go has
4523
+ * one answer, so the re-ask only costs the member a stopped start. The live
4524
+ * candle-shop start of 2026-10-12 asked for a home of 7 and a second page of
4525
+ * 2 against a wall of 8, three answers running, and stopped needing input.
4526
+ *
4527
+ * One section goes at a time, the last of its page that `freeWallKeeps`
4528
+ * does not hold, from the page furthest past its share: the home at / past
4529
+ * its fewest (`aiSiteHomeMinSections`, five on a site start whose wall pays
4530
+ * for a full home), any other page past two; on a tie, the longer page, then
4531
+ * the home. No home goes under its fewest and no page under one section. The
4532
+ * shares are what a site start is told to plan (a home of five or six, the
4533
+ * other page two or three): the candle shop's 7 + 2 becomes 6 + 2, never
4534
+ * 7 + 1, and the recorded yoga answer's 6 + 4 becomes 5 + 3, the shape its
4535
+ * own redo chose.
4536
+ *
4537
+ * A plan more than `AI_FREE_WALL_SETTLE_MAX_OVER` past the wall, a site
4538
+ * start past its page cap, a plan with a list split into sections, and a
4539
+ * plan the trim cannot fit without a section it keeps are returned
4540
+ * unchanged, for the wall to re-ask.
4541
+ */ export function aiSettlePlanFreeWall(plan, inventory, capabilities = null) {
4542
+ const wall = freePlanWall(plan, inventory, capabilities);
4543
+ if (!wall || wall.overPages) return plan;
4544
+ const over = wall.asked - wall.fits;
4545
+ if (over <= 0 || over > AI_FREE_WALL_SETTLE_MAX_OVER) return plan;
4546
+ // A list split into sections is joined by its re-ask, which frees the room;
4547
+ // a trim would cut one of its items and hide the split from the rule.
4548
+ if (detectPlanSplitLists(plan, inventory, capabilities).length) return plan;
4549
+ const kept = plan.screens.map((screen)=>screen.sections.map((section, index)=>({
4550
+ section,
4551
+ index
4552
+ })));
4553
+ const home = plan.screens.findIndex(aiSitePlanIsHome);
4554
+ const floor = (screenIndex)=>screenIndex === home ? Math.max(wall.homeMin, 1) : 1;
4555
+ const target = (screenIndex)=>screenIndex === home ? Math.max(wall.homeMin, 1) : 2;
4556
+ // The last section of a page the trim may take, or -1.
4557
+ const last = (screenIndex)=>{
4558
+ const entries = kept[screenIndex];
4559
+ if (entries.length <= floor(screenIndex)) return -1;
4560
+ for(let at = entries.length - 1; at > 0; at -= 1){
4561
+ if (!freeWallKeeps(plan.screens[screenIndex], entries[at].index)) return at;
4562
+ }
4563
+ return -1;
4564
+ };
4565
+ for(let left = over; left > 0; left -= 1){
4566
+ let pick = -1;
4567
+ for(let index = 0; index < kept.length; index += 1){
4568
+ if (last(index) === -1) continue;
4569
+ const room = kept[index].length - target(index);
4570
+ const best = pick === -1 ? null : kept[pick].length - target(pick);
4571
+ if (best === null || room > best || room === best && (kept[index].length > kept[pick].length || kept[index].length === kept[pick].length && index === home)) {
4572
+ pick = index;
4573
+ }
4574
+ }
4575
+ if (pick === -1) return plan;
4576
+ kept[pick].splice(last(pick), 1);
4577
+ }
4578
+ return _extends({}, plan, {
4579
+ screens: plan.screens.map((screen, index)=>kept[index].length === screen.sections.length ? screen : _extends({}, screen, {
4580
+ sections: kept[index].map((entry)=>entry.section)
4581
+ }))
4582
+ });
4583
+ }
4422
4584
  /**
4423
4585
  * The Free wall (plan): a plan asks for no more sections than the Free taste
4424
4586
  * pays for at its worst beside what the job builds first (AGL-3070). A live
@@ -4429,14 +4591,10 @@ function slugKey(slug) {
4429
4591
  * proven with, so no plan the proof admits is refused. It is no building rule,
4430
4592
  * so it names none.
4431
4593
  */ export function detectPlanOverFreeWall(plan, inventory, capabilities) {
4432
- if (!(capabilities == null ? void 0 : capabilities.freeTaste)) return [];
4433
- const asked = plan.screens.reduce((sum, screen)=>sum + screen.sections.length, 0);
4434
- const layouts = freePlanLayouts(plan, inventory, capabilities);
4435
- const pages = Math.max(plan.screens.length, 1);
4436
- // A Free site scaffold (AGL-3594) is held to its page cap first, and then to
4437
- // what a SITE plan's worst case leaves for sections, with room for a retry.
4438
- const sitePages = capabilities.freeSitePages;
4439
- if (sitePages !== undefined && plan.screens.length > sitePages) {
4594
+ const wall = freePlanWall(plan, inventory, capabilities);
4595
+ if (!wall) return [];
4596
+ const { asked, layouts, pages, sitePages, fits, homeMin } = wall;
4597
+ if (sitePages !== undefined && wall.overPages) {
4440
4598
  return [
4441
4599
  {
4442
4600
  rule: null,
@@ -4448,26 +4606,9 @@ function slugKey(slug) {
4448
4606
  }
4449
4607
  ];
4450
4608
  }
4451
- // A site start's saved form is a step of its own (AGL-3596), counted on the wall since AGL-3660.
4452
- const forms = plan.create.filter((entry)=>entry.kind === 'form').length - aiPlanUncreatable(plan, capabilities).filter((entry)=>entry.kind === 'form').length;
4453
- const fits = sitePages !== undefined ? aiFreeSiteSectionsWithin({
4454
- layouts,
4455
- pages,
4456
- forms: Math.max(0, forms)
4457
- }, FREE_AI_TASTE_CREDITS_PER_MONTH) : aiFreePageSectionsWithin({
4458
- layouts,
4459
- pages
4460
- });
4461
4609
  if (asked <= fits) return [];
4462
4610
  const job = pages > 1 ? `a Free plan of ${pages} pages` : 'a Free page';
4463
4611
  const beside = layouts ? ` that creates ${layouts > 1 ? `${layouts} layouts` : 'its layout'}` : '';
4464
- // A site start's home keeps its full five (AGL-3660): told only "plan at
4465
- // most 8", the live yoga start of 2026-10-08 cut a home of 6 to 4 and kept
4466
- // its other page, so the redo names where the sections come from.
4467
- const homeMin = sitePages !== undefined && plan.screens.some(aiSitePlanIsHome) ? aiSiteHomeMinSections({
4468
- pages,
4469
- across: fits
4470
- }) : 0;
4471
4612
  const keepHome = homeMin ? ` Keep the home page at / at ${homeMin} or more sections, and take the rest from the other ${pages > 2 ? 'pages' : 'page'}.` : '';
4472
4613
  // A site start's redo keeps the plan full (AGL-3660): the wall's sections
4473
4614
  // are its budget to use, and a redo told only "at most" cuts far under it.