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

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 (92) 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-content.js +3 -2
  38. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  39. package/src/lib/jobs/ai-job-site-datasets.d.ts +76 -2
  40. package/src/lib/jobs/ai-job-site-datasets.js +206 -13
  41. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  42. package/src/lib/jobs/ai-job-site-step.d.ts +18 -2
  43. package/src/lib/jobs/ai-job-site-step.js +109 -22
  44. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  45. package/src/lib/jobs/ai-job-site-store-pages.d.ts +75 -0
  46. package/src/lib/jobs/ai-job-site-store-pages.js +195 -0
  47. package/src/lib/jobs/ai-job-site-store-pages.js.map +1 -0
  48. package/src/lib/jobs/ai-job-store-links-layout.d.ts +71 -0
  49. package/src/lib/jobs/ai-job-store-links-layout.js +95 -0
  50. package/src/lib/jobs/ai-job-store-links-layout.js.map +1 -0
  51. package/src/lib/jobs/ai-layout-stock-photos.d.ts +129 -8
  52. package/src/lib/jobs/ai-layout-stock-photos.js +919 -75
  53. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  54. package/src/lib/jobs/ai-site-publish.d.ts +18 -0
  55. package/src/lib/jobs/ai-site-publish.js +76 -15
  56. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  57. package/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.d.ts +43 -0
  58. package/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.js +61 -0
  59. package/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.js.map +1 -0
  60. package/src/lib/jobs/fixtures/ai-stock-photo-saltmarsh-recordings.d.ts +39 -0
  61. package/src/lib/jobs/fixtures/ai-stock-photo-saltmarsh-recordings.js +508 -0
  62. package/src/lib/jobs/fixtures/ai-stock-photo-saltmarsh-recordings.js.map +1 -0
  63. package/src/lib/layout-language/ai-layout-compiler.js +25 -2
  64. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  65. package/src/lib/layout-language/ai-layout-frame.d.ts +10 -0
  66. package/src/lib/layout-language/ai-layout-frame.js +58 -4
  67. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  68. package/src/lib/layout-language/ai-layout-listings.d.ts +6 -0
  69. package/src/lib/layout-language/ai-layout-listings.js +3 -0
  70. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  71. package/src/lib/model/ai-build-job.d.ts +2 -0
  72. package/src/lib/model/ai-build-job.js +23 -3
  73. package/src/lib/model/ai-build-job.js.map +1 -1
  74. package/src/lib/model/ai-build-plan.d.ts +7 -0
  75. package/src/lib/model/ai-build-plan.js.map +1 -1
  76. package/src/lib/model/ai-build-progress.js +3 -1
  77. package/src/lib/model/ai-build-progress.js.map +1 -1
  78. package/src/lib/model/ai-build-store-pages.d.ts +133 -0
  79. package/src/lib/model/ai-build-store-pages.js +209 -0
  80. package/src/lib/model/ai-build-store-pages.js.map +1 -0
  81. package/src/lib/model/ai-layout-store-links.d.ts +64 -0
  82. package/src/lib/model/ai-layout-store-links.js +165 -0
  83. package/src/lib/model/ai-layout-store-links.js.map +1 -0
  84. package/src/lib/model/ai-site-inventory.d.ts +5 -0
  85. package/src/lib/model/ai-site-inventory.js.map +1 -1
  86. package/src/lib/model/ai-site-store-pages.d.ts +192 -0
  87. package/src/lib/model/ai-site-store-pages.js +383 -0
  88. package/src/lib/model/ai-site-store-pages.js.map +1 -0
  89. package/src/lib/runtime/site-inventory.js +8 -3
  90. package/src/lib/runtime/site-inventory.js.map +1 -1
  91. package/src/lib/testing/stand-in-dataset-index.js +8 -4
  92. package/src/lib/testing/stand-in-dataset-index.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-build-plan.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AiTool } from '../providers/contract'\n\n/**\n * The plan a generation job builds from (AGL-2935): what the job's plan step\n * (`jobs/ai-job-plan-step.ts`) returns, before a single node is generated.\n *\n * This module is the plan's MODEL — its shape, its tool schema and its\n * reader — and imports nothing at runtime, so the doctrine validators, the\n * plan step, the job document and the proposal can all read it without\n * importing each other.\n *\n * A plan is REFERENCES, never documents (AGL-2937). It names what the site\n * already has by inventory id, what the job will create by a short name, and\n * which screens it builds from which of those — so the doctrine can be held\n * against the plan while fixing it costs a sentence, not a regenerated page,\n * and the customer reads what the job will do before it spends a credit on\n * doing it.\n *\n * `new:<name>` is how an entry refers to something the plan itself creates;\n * any other reference is an id from the site inventory.\n */\n\nexport const AI_PLAN_NEW_REF_PREFIX = 'new:'\n\n/** Records a plan may reuse, by the inventory list they come from. */\nexport type AiBuildPlanReuseKind =\n | 'component'\n | 'layout'\n | 'template'\n | 'form'\n | 'dataset'\n | 'collection'\n | 'screen'\n | 'theme'\n\nexport const AI_BUILD_PLAN_REUSE_KINDS: readonly AiBuildPlanReuseKind[] = [\n 'component',\n 'layout',\n 'template',\n 'form',\n 'dataset',\n 'collection',\n 'screen',\n 'theme',\n]\n\n/** What a plan may create. A theme change is a draft through the theme door, never a literal. */\nexport type AiBuildPlanCreateKind =\n | 'component'\n | 'layout'\n | 'template'\n | 'form'\n | 'theme-change'\n | 'dataset'\n | 'email'\n\n/**\n * What each creation is called in a sentence, and where a member makes one.\n *\n * Total over the union deliberately: a refusal that cannot name a creation is\n * a refusal a member cannot act on, so widening `AiBuildPlanCreateKind`\n * without saying where the new thing is made does not compile.\n */\nexport const AI_BUILD_PLAN_CREATION_NOUNS: Record<\n AiBuildPlanCreateKind,\n { noun: string; where: string }\n> = {\n component: { noun: 'component', where: 'on the Components page' },\n form: { noun: 'form', where: 'on the Forms page' },\n layout: { noun: 'layout', where: 'on the Layouts page' },\n template: { noun: 'template', where: 'in the Templates library' },\n 'theme-change': { noun: 'theme change', where: 'in the Theme section' },\n dataset: { noun: 'dataset', where: 'on the Datasets page' },\n email: { noun: 'email design', where: 'in Emails → Templates' },\n}\n\nexport const AI_BUILD_PLAN_CREATE_KINDS: readonly AiBuildPlanCreateKind[] = [\n 'component',\n 'layout',\n 'template',\n 'form',\n 'theme-change',\n 'dataset',\n 'email',\n]\n\nexport interface AiBuildPlanReuse {\n kind: AiBuildPlanReuseKind\n /** The inventory id. */\n id: string\n /** What the plan uses it for, in a few words. */\n purpose: string\n}\n\nexport interface AiBuildPlanCreate {\n kind: AiBuildPlanCreateKind\n /** Unique within the plan; referenced as `new:<name>`. */\n name: string\n /** Why nothing the site has will do — shown in the proposal. */\n why: string\n /** The inventory id this starts from as a duplicate, or null. */\n duplicateOf: string | null\n /**\n * A component: its props as `name:type`, an icon as `name:icon`\n * (AGL-3054). A layout: the regions it has, from the closed vocabulary\n * `AI_LAYOUT_REGIONS` holds (AGL-3024) — a layout has no props, and a\n * region named here is what the built layout is held to. A template: the\n * binding tokens its page shows, from the subject's own closed catalog\n * (`AI_TEMPLATE_SUBJECT_DEFINITIONS[subject].tokens`, AGL-3143) — the\n * template's analogue of a layout's regions, and what the built template is\n * held to. A form or a dataset: its field names. A theme change: the\n * palette paths it adds or changes.\n */\n fields: string[]\n /**\n * A form only: the dataset each of its submissions is also written to as a\n * record (AGL-3616, Zach 2026-10-10: a volunteer sign-up writing to a\n * Volunteers dataset) — `new:<name>` of a dataset this plan creates, or a\n * dataset id from the inventory. Said by the plan, never inferred from a\n * section's `uses`. Absent or null on every other creation, and on a form\n * whose submissions only reach the Inbox.\n */\n writesTo?: string | null\n /**\n * The id the draft this creation becomes is written under, where a unit of\n * the job builds it (AGL-3079). Minted when the job keeps the plan, never\n * part of the model's answer.\n */\n id?: string\n}\n\nexport interface AiBuildPlanSection {\n /** A few words: \"hero\", \"services grid\", \"contact form\". */\n name: string\n /** What the section places: inventory ids or `new:<name>` references. */\n uses: string[]\n /** How many repeated items it shows (cards, rows, tiles); 0 when none. */\n items: number\n}\n\n/**\n * A screen that is one dataset's record template (AGL-3475): the data plugin\n * serves it once per record at `/{base}/{slug}`, its copy bound with\n * `{{item.<field>}}`. The plan names the binding and the build drafts the\n * page; the member saves the binding in Page Properties → Record pages, since\n * a binding is a route and a job writes drafts only (rule 13).\n */\nexport interface AiBuildPlanRecord {\n /** The dataset: an inventory id or `new:<name>`. */\n dataset: string\n /** The path its record pages live under, one or more segments: `services`, `services/residential`. */\n base: string\n}\n\nexport interface AiBuildPlanScreen {\n title: string\n slug: string\n /** The layout it renders inside. */\n layout: string | null\n /** The template it applies, or null for a one-off page. */\n template: string | null\n /** The inventory screen it starts from as a duplicate, or null. */\n duplicateOf: string | null\n /** Whether the site navigation gains an entry for it. */\n nav: boolean\n seoTitle: string\n seoDescription: string\n sections: AiBuildPlanSection[]\n /**\n * The dataset this page is the record template of, or null for a page that\n * is one page. A plan kept before record templates carries none, which\n * reads as null.\n */\n record: AiBuildPlanRecord | null\n /**\n * The id the draft screen is written under, where a unit of the job builds\n * it (AGL-3079). Minted when the job keeps the plan, never part of the\n * model's answer.\n */\n id?: string\n}\n\n/** The third-party players a plan may embed: the hosts the Video embed element plays. */\nexport type AiBuildPlanEmbedHost = 'youtube' | 'vimeo'\n\nexport const AI_BUILD_PLAN_EMBED_HOSTS: readonly AiBuildPlanEmbedHost[] = ['youtube', 'vimeo']\n\n/** What each host is called in a sentence. */\nexport const AI_BUILD_PLAN_EMBED_HOST_NAMES: Record<AiBuildPlanEmbedHost, string> = {\n youtube: 'YouTube',\n vimeo: 'Vimeo',\n}\n\n/**\n * A third-party player the brief asks for (rule 16, AGL-3433). It loads its\n * host's code on the page that carries it, so it is planned, where the member\n * reads that cost before confirming, and a build admits only what the\n * confirmed plan lists.\n */\nexport interface AiBuildPlanEmbed {\n host: AiBuildPlanEmbedHost\n /** Where it plays: a screen of the plan by its slug, or `new:<name>` for a component the plan creates. */\n where: string\n /** The brief's own words asking for it, which the plan step finds in the brief. */\n asked: string\n /** The video's link as the brief gives it, or null for the site owner to paste. */\n url: string | null\n}\n\n/** How the items that depend on a failed item behave (AGL-3616). */\nexport type AiBuildItemDegrade = 'omit' | 'fallback'\n\nexport const AI_BUILD_ITEM_DEGRADES: readonly AiBuildItemDegrade[] = ['omit', 'fallback']\n\n/** One argument value of an item: scalars and lists of text only. */\nexport type AiBuildItemArg = string | number | boolean | string[]\n\n/**\n * One thing a `build` plan makes that its creations and screens do not\n * (AGL-3616): an operation another plugin — or this one — registered as an\n * AI capability, with the arguments its schema declares. `reuse`, `create`\n * and `screens` keep their meaning; an item is everything else.\n */\nexport interface AiBuildItem {\n /** Stable within the plan: `i0`, `i1`… in plan order. */\n slot: string\n /** The capability's operation. */\n op: string\n /** Unique within the plan, creations included; referenced as `new:<name>`. */\n name: string\n /** Why the brief needs it, in one sentence. */\n why: string\n /** What it is built after: `new:<name>` of a creation or another item. */\n dependsOn: string[]\n /** How the items that depend on it behave when it fails. */\n degrade: AiBuildItemDegrade\n /** The operation's arguments, in the shape its capability declares. */\n args: Record<string, AiBuildItemArg>\n /** The id its draft is written under, minted when the job keeps the plan; never the model's. */\n id?: string\n /** The most it can cost in credits, from its capability, recorded when the plan is kept. */\n credits?: number\n}\n\nexport interface AiBuildPlan {\n reuse: AiBuildPlanReuse[]\n create: AiBuildPlanCreate[]\n screens: AiBuildPlanScreen[]\n /**\n * The third-party players the brief asks for. Absent is none: a plan kept\n * before the list existed, and every plan whose brief asks for no player.\n */\n embeds?: AiBuildPlanEmbed[]\n /**\n * What a `build` plan makes beyond its creations and pages (AGL-3616).\n * Absent is none: every other kind's plan.\n */\n items?: AiBuildItem[]\n}\n\n/**\n * How much one plan may hold. A job builds what one proposal can describe\n * and one person can read before confirming; a brief asking for more is\n * several jobs.\n */\nexport const AI_BUILD_PLAN_LIMITS = {\n reuse: 40,\n create: 20,\n screens: 12,\n sections: 16,\n uses: 8,\n fields: 30,\n embeds: 4,\n text: 200,\n seoDescription: 320,\n items: 500,\n /** A build plan's items (AGL-3616). */\n buildItems: 16,\n /** What one item depends on. */\n dependsOn: 8,\n /** The arguments one item carries, as JSON. */\n argsChars: 2_000,\n} as const\n\nconst string = (description: string) => ({ type: 'string', description })\nconst nullableString = (description: string) => ({\n anyOf: [{ type: 'string' }, { type: 'null' }],\n description,\n})\nconst strings = (description: string) => ({\n type: 'array',\n items: { type: 'string' },\n description,\n})\n\n/**\n * The ceiling `parseAiBuildPlan` cuts a field's text at, in words (AGL-3022).\n * The reader repairs an overlong value by cutting it, and a model never told\n * where the cut falls writes a rationale that stops mid-sentence there.\n */\nconst atMost = (limit: number = AI_BUILD_PLAN_LIMITS.text) => `at most ${limit} characters`\n\n/**\n * The plan as a strict tool's input schema. Only the keywords strict\n * structured output accepts: every object closes its properties and lists\n * every one as required, an optional value is a `null` branch, and no\n * length or count bound appears as a keyword — those are enforced by\n * `parseAiBuildPlan`, which every answer passes through anyway.\n *\n * A text ceiling is stated in words instead, on each field the model writes.\n * A reference is copied from the inventory or from a creation's name, so its\n * length is not the model's to choose. A search title and description are\n * held by rule 10 to ceilings far inside the reader's, which the doctrine\n * states, so naming the reader's here would contradict the rule.\n */\nexport const AI_BUILD_PLAN_TOOL: AiTool = {\n name: 'submit_build_plan',\n description:\n 'Submit the build plan: what the site already has that this job reuses, what it creates and why, and the pages it builds from them.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['reuse', 'create', 'screens'],\n properties: {\n reuse: {\n type: 'array',\n description: 'Records the site already has that this plan uses, by inventory id.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['kind', 'id', 'purpose'],\n properties: {\n kind: { type: 'string', enum: [...AI_BUILD_PLAN_REUSE_KINDS] },\n id: string('The id from the site inventory.'),\n purpose: string(`What the plan uses it for, in a few words, ${atMost()}.`),\n },\n },\n },\n create: {\n type: 'array',\n description:\n 'What the plan creates. Creating is the exception: each entry says why nothing listed will do.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['kind', 'name', 'why', 'duplicateOf', 'fields'],\n properties: {\n kind: { type: 'string', enum: [...AI_BUILD_PLAN_CREATE_KINDS] },\n name: string(\n `Unique within the plan, ${atMost()}; other entries refer to it as new:<name>.`,\n ),\n why: string(`Why nothing the site already has will do, in one sentence of ${atMost()}.`),\n duplicateOf: nullableString(\n 'The inventory id this starts from as a duplicate, or null.',\n ),\n fields: strings(\n `A component: its props as name:type, an icon as name:icon. A layout: the regions it has, each one of header, nav, sidebar, main, footer — list every region the finished layout has, not only the ones it gains. A form or dataset: its field names. A theme change: the palette paths it adds or changes. Each ${atMost()}.`,\n ),\n },\n },\n },\n screens: {\n type: 'array',\n description: 'The pages the job builds, each a new draft.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: [\n 'title',\n 'slug',\n 'layout',\n 'template',\n 'duplicateOf',\n 'nav',\n 'seoTitle',\n 'seoDescription',\n 'sections',\n ],\n properties: {\n title: string(`The page name, ${atMost()}.`),\n slug: string(\n `The path, ${atMost()}: lowercase words joined by hyphens, e.g. /services/roof-repair.`,\n ),\n layout: nullableString(\n 'The layout it renders inside: an inventory id or new:<name>.',\n ),\n template: nullableString(\n 'The template it applies (inventory id or new:<name>), or null for a one-off page.',\n ),\n duplicateOf: nullableString(\n 'The inventory page it starts from as a duplicate, or null.',\n ),\n nav: {\n type: 'boolean',\n description: 'Whether the site navigation gains an entry for it.',\n },\n seoTitle: string('The search-result title.'),\n seoDescription: string('The search-result description.'),\n sections: {\n type: 'array',\n description: 'The sections, top to bottom.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['name', 'uses', 'items'],\n properties: {\n name: string(`A few words, ${atMost()}: hero, services grid, contact form.`),\n uses: strings('What the section places: inventory ids or new:<name>.'),\n items: {\n type: 'integer',\n description: 'How many repeated items it shows; 0 when none.',\n },\n },\n },\n },\n },\n },\n },\n },\n },\n}\n\n/** Words that ask for a video, or a link to one. */\nexport const AI_PLAN_ASKS_FOR_VIDEO = /\\b(?:videos?|films?|clips?|youtube|vimeo|player)\\b|youtu\\.be/i\n\nconst EMBEDS_SCHEMA = {\n type: 'array',\n description:\n 'A YouTube or Vimeo player the brief asks for, on a screen or a created component. Empty unless the brief asks; a library film is a Video.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['host', 'where', 'asked', 'url'],\n properties: {\n host: { type: 'string', enum: [...AI_BUILD_PLAN_EMBED_HOSTS] },\n where: string('A screen slug, or new:<name> of a component.'),\n asked: string('The words of the brief that ask for it, copied exactly.'),\n url: nullableString('The video link the brief gives, or null.'),\n },\n },\n}\n\n/**\n * The plan tool with its list of third-party players, offered only to a brief\n * that asks for a video (AGL-3433). Every other plan's request is the plain\n * tool, byte for byte, so the list costs nothing on the plans that cannot use\n * it, and a model answering a brief that names no video has nowhere to plan a\n * player it was never asked for.\n */\nexport const AI_BUILD_PLAN_EMBEDS_TOOL: AiTool = {\n ...AI_BUILD_PLAN_TOOL,\n inputSchema: {\n ...AI_BUILD_PLAN_TOOL.inputSchema,\n required: [...(AI_BUILD_PLAN_TOOL.inputSchema['required'] as string[]), 'embeds'],\n properties: {\n ...(AI_BUILD_PLAN_TOOL.inputSchema['properties'] as Record<string, unknown>),\n embeds: EMBEDS_SCHEMA,\n },\n },\n}\n\nconst RECORD_SCHEMA = {\n anyOf: [\n {\n type: 'object',\n additionalProperties: false,\n required: ['dataset', 'base'],\n properties: {\n dataset: string('The dataset: an inventory id or new:<name>.'),\n base: string('The path its record pages live under, e.g. services for /services/roofing.'),\n },\n },\n { type: 'null' },\n ],\n description:\n \"When this page is one dataset's record template, serving a page per record at /<base>/<record address> with its copy bound as {{item.<field>}}: the dataset and the base. Otherwise null.\",\n}\n\nconst WRITES_TO_SCHEMA = nullableString(\n 'A form only, and only where each submission should also be kept as a record (a volunteer sign-up, an RSVP, an inquiry): the dataset it writes to, as new:<name> of a dataset this plan creates or a dataset id from the inventory. Otherwise null.',\n)\n\n/**\n * A plan tool whose screens each name the dataset they are the record\n * template of, or null, and whose creations each name the dataset a form\n * writes its submissions to, or null (AGL-3616).\n */\nfunction withRecords(tool: AiTool): AiTool {\n const properties = tool.inputSchema['properties'] as Record<string, unknown>\n const screens = properties['screens'] as { items: Record<string, unknown> }\n const create = properties['create'] as { items: Record<string, unknown> }\n return {\n ...tool,\n inputSchema: {\n ...tool.inputSchema,\n properties: {\n ...properties,\n create: {\n ...create,\n items: {\n ...create.items,\n required: [...(create.items['required'] as string[]), 'writesTo'],\n properties: {\n ...(create.items['properties'] as Record<string, unknown>),\n writesTo: WRITES_TO_SCHEMA,\n },\n },\n },\n screens: {\n ...screens,\n items: {\n ...screens.items,\n required: [...(screens.items['required'] as string[]), 'record'],\n properties: {\n ...(screens.items['properties'] as Record<string, unknown>),\n record: RECORD_SCHEMA,\n },\n },\n },\n },\n },\n }\n}\n\n/**\n * The plan tools with a record template on every screen (AGL-3475) and the\n * dataset a form writes to on every creation (AGL-3616), offered only where\n * the job may bind a dataset: a site with a dataset, or a job that may create\n * one. Record pages are a Starter feature, and every other plan's\n * request is the plain tool, byte for byte, so the field costs a Free taste\n * nothing and a model planning a site with no dataset has none to bind.\n */\nexport const AI_BUILD_PLAN_RECORDS_TOOL: AiTool = withRecords(AI_BUILD_PLAN_TOOL)\nexport const AI_BUILD_PLAN_EMBEDS_RECORDS_TOOL: AiTool = withRecords(AI_BUILD_PLAN_EMBEDS_TOOL)\n\nconst ITEMS_SCHEMA = {\n type: 'array',\n description:\n 'What the plan makes beyond its creations and pages, each one operation from the list in the request. Empty when the brief asks for nothing else.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['op', 'name', 'why', 'dependsOn', 'degrade', 'args'],\n properties: {\n op: string('The operation, exactly as the request lists it.'),\n name: string(`Unique within the plan, creations included, ${atMost()}; other entries refer to it as new:<name>.`),\n why: string(`Why the brief needs it, in one sentence of ${atMost()}.`),\n dependsOn: strings('What it is built after: new:<name> of a creation or another item. Empty when nothing.'),\n degrade: {\n type: 'string',\n enum: [...AI_BUILD_ITEM_DEGRADES],\n description:\n 'omit: what depends on it is built without it if it fails. fallback: what depends on it uses something the site already has instead.',\n },\n args: string('Its arguments as one JSON object, with exactly the fields the request lists for the operation.'),\n },\n },\n}\n\n/**\n * Any plan tool with a build's items (AGL-3616), offered only to a `build`\n * job. The items' arguments travel as one JSON string whose fields the\n * request lists per operation, so the tool stays byte for byte the same\n * whichever plugins registered operations, and the prompt cache keeps it.\n */\nexport function withBuildItems(tool: AiTool): AiTool {\n return {\n ...tool,\n inputSchema: {\n ...tool.inputSchema,\n required: [...(tool.inputSchema['required'] as string[]), 'items'],\n properties: {\n ...(tool.inputSchema['properties'] as Record<string, unknown>),\n items: ITEMS_SCHEMA,\n },\n },\n }\n}\n\n/** The plan tool a brief is answered with, on a job that may or may not bind a dataset. */\nexport function aiBuildPlanToolFor(\n brief: string,\n options: { records?: boolean; items?: boolean } = {},\n): AiTool {\n const video = AI_PLAN_ASKS_FOR_VIDEO.test(brief)\n const tool = options.records\n ? video\n ? AI_BUILD_PLAN_EMBEDS_RECORDS_TOOL\n : AI_BUILD_PLAN_RECORDS_TOOL\n : video\n ? AI_BUILD_PLAN_EMBEDS_TOOL\n : AI_BUILD_PLAN_TOOL\n return options.items ? withBuildItems(tool) : tool\n}\n\nexport type AiBuildPlanParse =\n | { ok: true; plan: AiBuildPlan; repairs: string[] }\n | { ok: false; error: string }\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nclass PlanShapeError extends Error {}\n\n/**\n * An item's arguments as the plan keeps them: the model writes one JSON\n * string, a kept plan stores the object. Values are scalars and lists of\n * text; anything else is a shape the item cannot be built from.\n */\nfunction buildItemArgs(value: unknown, path: string): Record<string, AiBuildItemArg> {\n let parsed: unknown = value\n if (value === undefined || value === null || value === '') return {}\n if (typeof value === 'string') {\n if (value.length > AI_BUILD_PLAN_LIMITS.argsChars) {\n throw new PlanShapeError(`${path} is over ${AI_BUILD_PLAN_LIMITS.argsChars} characters`)\n }\n try {\n parsed = JSON.parse(value)\n } catch {\n throw new PlanShapeError(`${path} is not a JSON object`)\n }\n }\n if (!isRecord(parsed)) throw new PlanShapeError(`${path} is not a JSON object`)\n const args: Record<string, AiBuildItemArg> = {}\n for (const [key, raw] of Object.entries(parsed)) {\n if (raw === null || raw === undefined) continue\n if (typeof raw === 'string' || typeof raw === 'boolean' || (typeof raw === 'number' && Number.isFinite(raw))) {\n args[key] = raw\n } else if (Array.isArray(raw) && raw.every((one) => typeof one === 'string')) {\n args[key] = raw as string[]\n } else {\n throw new PlanShapeError(`${path}.${key} is not text, a number, true or false, or a list of text`)\n }\n }\n return args\n}\n\n/**\n * Read a plan out of a tool call, or name what makes it unreadable.\n *\n * Structure is refused — a missing list, an unknown kind, more screens than\n * a job holds, two creations under one name — because a plan that reads\n * differently from what the model meant is worse than a re-ask. Length is\n * repaired: copy past a ceiling is cut and the cut is listed.\n */\n/**\n * A layout's regions, one word each. A plan sometimes writes the whole list as\n * one entry — \"regions: header, nav, main, footer\" — which names regions the\n * platform builds but reads as one unknown word, and the plan was refused for\n * it (job AvVxbFbzQx). A leading label is dropped and a list is split on its\n * commas, slashes, plus signs and \"and\", so each region is checked on its own;\n * a duplicate keeps its first place.\n */\nexport function aiPlanLayoutRegionWords(fields: readonly string[]): string[] {\n const words: string[] = []\n for (const field of fields) {\n const listed = /^\\s*(?:the\\s+)?(?:layout\\s+)?(?:regions?|areas?)\\s*[:=-]\\s*(.+)$/i.exec(field)?.[1] ?? field\n const parts = /[,/+]|\\band\\b/i.test(listed)\n ? listed.split(/\\s*(?:,|\\/|\\+|\\band\\b)\\s*/i)\n : [listed]\n for (const part of parts) {\n const word = part.trim()\n if (word && !words.some((seen) => seen.toLowerCase() === word.toLowerCase())) words.push(word)\n }\n }\n return words\n}\n\nexport function parseAiBuildPlan(input: unknown): AiBuildPlanParse {\n const repairs: string[] = []\n const text = (\n value: unknown,\n path: string,\n limit: number = AI_BUILD_PLAN_LIMITS.text,\n ): string => {\n if (typeof value !== 'string') throw new PlanShapeError(`${path} is not text`)\n const trimmed = value.trim()\n if (trimmed.length > limit) {\n repairs.push(`${path} was over ${limit} characters; truncated`)\n // On the last word break inside the ceiling, not through a word: a\n // rationale is read by the member confirming the plan, and half a word\n // reads as a broken answer rather than a long one (AGL-3022). A run of\n // `limit` characters with no break in it still has to be cut where the\n // ceiling falls.\n const cut = trimmed.slice(0, limit)\n const brk = cut.search(/\\s\\S*$/)\n return (brk > 0 ? cut.slice(0, brk) : cut).trimEnd()\n }\n return trimmed\n }\n const nullable = (value: unknown, path: string): string | null =>\n value === null || value === undefined || value === '' ? null : text(value, path) || null\n const list = (value: unknown, path: string, limit: number): unknown[] => {\n if (!Array.isArray(value)) throw new PlanShapeError(`${path} is not a list`)\n if (value.length > limit) {\n throw new PlanShapeError(`${path} lists ${value.length}; one plan holds at most ${limit}`)\n }\n return value\n }\n const record = (value: unknown, path: string): Record<string, unknown> => {\n if (!isRecord(value)) throw new PlanShapeError(`${path} is not an object`)\n return value\n }\n const oneOf = <T extends string>(value: unknown, allowed: readonly T[], path: string): T => {\n if (typeof value !== 'string' || !(allowed as readonly string[]).includes(value)) {\n throw new PlanShapeError(`${path} is not one of ${allowed.join(', ')}`)\n }\n return value as T\n }\n // Absent or null is a page that is one page. A base is held to the shape a\n // record page's address takes, so the binding the member saves from it is\n // one the save route admits.\n const planRecord = (value: unknown, path: string): AiBuildPlanRecord | null => {\n if (value === null || value === undefined) return null\n const entry = record(value, path)\n const dataset = text(entry['dataset'], `${path}.dataset`)\n if (!dataset) throw new PlanShapeError(`${path}.dataset is empty`)\n const base = aiPlanRecordBase(text(entry['base'], `${path}.base`))\n if (!base) throw new PlanShapeError(`${path}.base is not a path of lowercase words joined by hyphens`)\n return { dataset, base }\n }\n\n try {\n const root = record(input, 'plan')\n const reuse = list(root['reuse'], 'reuse', AI_BUILD_PLAN_LIMITS.reuse).map((raw, index) => {\n const entry = record(raw, `reuse[${index}]`)\n const id = text(entry['id'], `reuse[${index}].id`)\n if (!id) throw new PlanShapeError(`reuse[${index}].id is empty`)\n return {\n kind: oneOf(entry['kind'], AI_BUILD_PLAN_REUSE_KINDS, `reuse[${index}].kind`),\n id,\n purpose: text(entry['purpose'], `reuse[${index}].purpose`),\n }\n })\n const names = new Set<string>()\n const create = list(root['create'], 'create', AI_BUILD_PLAN_LIMITS.create).map(\n (raw, index) => {\n const entry = record(raw, `create[${index}]`)\n const name = text(entry['name'], `create[${index}].name`)\n if (!name) throw new PlanShapeError(`create[${index}].name is empty`)\n const key = name.toLowerCase()\n if (names.has(key)) {\n throw new PlanShapeError(`create[${index}].name \"${name}\" is used twice`)\n }\n names.add(key)\n const kind = oneOf(entry['kind'], AI_BUILD_PLAN_CREATE_KINDS, `create[${index}].kind`)\n const fields = list(entry['fields'] ?? [], `create[${index}].fields`, AI_BUILD_PLAN_LIMITS.fields)\n .map((field, fieldIndex) => text(field, `create[${index}].fields[${fieldIndex}]`))\n .filter(Boolean)\n // Only a form writes its submissions to a dataset (AGL-3616); kept\n // only where it says one, so a plan without one reads as it always did.\n const writesTo = kind === 'form' ? nullable(entry['writesTo'], `create[${index}].writesTo`) : null\n return {\n kind,\n name,\n why: text(entry['why'], `create[${index}].why`),\n duplicateOf: nullable(entry['duplicateOf'], `create[${index}].duplicateOf`),\n fields: kind === 'layout' ? aiPlanLayoutRegionWords(fields) : fields,\n ...(writesTo ? { writesTo } : {}),\n }\n },\n )\n const screens = list(root['screens'], 'screens', AI_BUILD_PLAN_LIMITS.screens).map(\n (raw, index) => {\n const entry = record(raw, `screens[${index}]`)\n if (typeof entry['nav'] !== 'boolean') {\n throw new PlanShapeError(`screens[${index}].nav is not true or false`)\n }\n return {\n title: text(entry['title'], `screens[${index}].title`),\n slug: text(entry['slug'], `screens[${index}].slug`),\n layout: nullable(entry['layout'], `screens[${index}].layout`),\n template: nullable(entry['template'], `screens[${index}].template`),\n duplicateOf: nullable(entry['duplicateOf'], `screens[${index}].duplicateOf`),\n nav: entry['nav'],\n seoTitle: text(entry['seoTitle'], `screens[${index}].seoTitle`),\n seoDescription: text(\n entry['seoDescription'],\n `screens[${index}].seoDescription`,\n AI_BUILD_PLAN_LIMITS.seoDescription,\n ),\n sections: list(\n entry['sections'],\n `screens[${index}].sections`,\n AI_BUILD_PLAN_LIMITS.sections,\n ).map((rawSection, sectionIndex) => {\n const path = `screens[${index}].sections[${sectionIndex}]`\n const section = record(rawSection, path)\n const items = Number(section['items'] ?? 0)\n if (!Number.isInteger(items) || items < 0 || items > AI_BUILD_PLAN_LIMITS.items) {\n throw new PlanShapeError(`${path}.items is not a count`)\n }\n return {\n name: text(section['name'], `${path}.name`),\n uses: list(section['uses'] ?? [], `${path}.uses`, AI_BUILD_PLAN_LIMITS.uses)\n .map((ref, refIndex) => text(ref, `${path}.uses[${refIndex}]`))\n .filter(Boolean),\n items,\n }\n }),\n record: planRecord(entry['record'], `screens[${index}].record`),\n }\n },\n )\n const embeds = list(root['embeds'] ?? [], 'embeds', AI_BUILD_PLAN_LIMITS.embeds).map(\n (raw, index) => {\n const entry = record(raw, `embeds[${index}]`)\n const where = text(entry['where'], `embeds[${index}].where`)\n if (!where) throw new PlanShapeError(`embeds[${index}].where is empty`)\n return {\n host: oneOf(entry['host'], AI_BUILD_PLAN_EMBED_HOSTS, `embeds[${index}].host`),\n where,\n asked: text(entry['asked'], `embeds[${index}].asked`),\n url: nullable(entry['url'], `embeds[${index}].url`),\n }\n },\n )\n const items = list(root['items'] ?? [], 'items', AI_BUILD_PLAN_LIMITS.buildItems).map(\n (raw, index): AiBuildItem => {\n const path = `items[${index}]`\n const entry = record(raw, path)\n const op = text(entry['op'], `${path}.op`)\n if (!op) throw new PlanShapeError(`${path}.op is empty`)\n const name = text(entry['name'], `${path}.name`)\n if (!name) throw new PlanShapeError(`${path}.name is empty`)\n const key = name.toLowerCase()\n if (names.has(key)) throw new PlanShapeError(`${path}.name \"${name}\" is used twice`)\n names.add(key)\n return {\n slot: `i${index}`,\n op,\n name,\n why: text(entry['why'] ?? '', `${path}.why`),\n dependsOn: list(entry['dependsOn'] ?? [], `${path}.dependsOn`, AI_BUILD_PLAN_LIMITS.dependsOn)\n .map((ref, refIndex) => text(ref, `${path}.dependsOn[${refIndex}]`))\n .filter(Boolean),\n degrade:\n entry['degrade'] === undefined || entry['degrade'] === null\n ? 'omit'\n : oneOf(entry['degrade'], AI_BUILD_ITEM_DEGRADES, `${path}.degrade`),\n args: buildItemArgs(entry['args'], `${path}.args`),\n ...(typeof entry['id'] === 'string' && entry['id'] ? { id: entry['id'] } : {}),\n ...(typeof entry['credits'] === 'number' && Number.isFinite(entry['credits'])\n ? { credits: Math.max(0, Math.floor(entry['credits'])) }\n : {}),\n }\n },\n )\n // A plan whose brief asks for no player carries no list, as it did before the list existed;\n // so does a plan with no items.\n return {\n ok: true,\n plan: {\n reuse,\n create,\n screens,\n ...(embeds.length ? { embeds } : {}),\n ...(items.length ? { items } : {}),\n },\n repairs,\n }\n } catch (error) {\n if (error instanceof PlanShapeError) return { ok: false, error: error.message }\n throw error\n }\n}\n\n/**\n * A record template's base as the data plugin's save route stores it\n * (`normalizeRecordPageBase`, which this plugin may not import): one to four\n * lowercase segments of letters, digits and hyphens, with no leading or\n * trailing slash; null for a base that is no such path.\n */\nexport function aiPlanRecordBase(base: string): string | null {\n const segments = base.trim().toLowerCase().split('/').filter(Boolean)\n if (!segments.length || segments.length > 4) return null\n return segments.every(\n (segment) => segment.length <= 60 && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(segment),\n )\n ? segments.join('/')\n : null\n}\n\n/** Whether a reference names something the plan creates. */\nexport function isAiPlanNewRef(ref: string | null | undefined): ref is string {\n return typeof ref === 'string' && ref.startsWith(AI_PLAN_NEW_REF_PREFIX)\n}\n\n/** The creation a `new:<name>` reference names, case-insensitively. */\nexport function aiPlanCreateFor(\n plan: AiBuildPlan,\n ref: string,\n): AiBuildPlanCreate | undefined {\n if (!isAiPlanNewRef(ref)) return undefined\n const name = ref.slice(AI_PLAN_NEW_REF_PREFIX.length).trim().toLowerCase()\n return plan.create.find((entry) => entry.name.toLowerCase() === name)\n}\n\n/** The operation a build's dataset item is planned as (AGL-3616), owned by the data plugin. */\nexport const AI_PLAN_DATASET_ITEM_OP = 'dataset'\n\n/**\n * The datasets this plan makes that one of its forms writes its submissions\n * to (AGL-3616), by name lowercased: a dataset creation of a site start, or a\n * build's dataset item. Such a dataset starts with no records — its records\n * are the submissions — so a site start designs it from its planned fields\n * with no model, and no page lists it.\n */\nexport function aiPlanFormDatasetNames(plan: Pick<AiBuildPlan, 'create'> & Partial<Pick<AiBuildPlan, 'items'>>): Set<string> {\n const names = new Set<string>()\n for (const entry of plan.create) {\n if (entry.kind !== 'form' || !isAiPlanNewRef(entry.writesTo)) continue\n const name = entry.writesTo.slice(AI_PLAN_NEW_REF_PREFIX.length).trim().toLowerCase()\n const made =\n plan.create.some((one) => one.kind === 'dataset' && one.name.trim().toLowerCase() === name) ||\n (plan.items ?? []).some((one) => one.op === AI_PLAN_DATASET_ITEM_OP && one.name.trim().toLowerCase() === name)\n if (made) names.add(name)\n }\n return names\n}\n\n/** The item a `new:<name>` reference names, case-insensitively (AGL-3616). */\nexport function aiPlanItemFor(\n plan: Pick<AiBuildPlan, 'items'>,\n ref: string,\n): AiBuildItem | undefined {\n if (!isAiPlanNewRef(ref)) return undefined\n const name = ref.slice(AI_PLAN_NEW_REF_PREFIX.length).trim().toLowerCase()\n return (plan.items ?? []).find((entry) => entry.name.toLowerCase() === name)\n}\n\n/** A slug as two spellings of one path compare: lowercase, one leading slash, no trailing one. */\nexport function aiPlanSlugKey(slug: string): string {\n const path = slug.trim().toLowerCase().replace(/^\\/*/, '/').replace(/\\/+$/, '')\n return path || '/'\n}\n\n/**\n * The players the plan lists for one output (AGL-3433): a screen by its slug,\n * or a creation by its name. A build admits these and no other.\n */\nexport function aiPlanEmbedsFor(\n plan: Pick<AiBuildPlan, 'embeds'> | null | undefined,\n target: { slug: string } | { create: string },\n): AiBuildPlanEmbed[] {\n const key =\n 'slug' in target\n ? aiPlanSlugKey(target.slug)\n : `${AI_PLAN_NEW_REF_PREFIX}${target.create.trim().toLowerCase()}`\n return (plan?.embeds ?? []).filter((embed) =>\n 'slug' in target\n ? !isAiPlanNewRef(embed.where) && aiPlanSlugKey(embed.where) === key\n : embed.where.trim().toLowerCase() === key,\n )\n}\n\n/** The host a video link plays from, or null for a link neither host serves. */\nexport function aiEmbedHostOf(url: string): AiBuildPlanEmbedHost | null {\n try {\n const parsed = new URL(url.trim())\n if (parsed.protocol !== 'https:') return null\n const host = parsed.hostname.replace(/^(?:www|m)\\./, '')\n if (host === 'youtu.be' || host === 'youtube.com' || host === 'youtube-nocookie.com') {\n return 'youtube'\n }\n if (host === 'vimeo.com' || host === 'player.vimeo.com') return 'vimeo'\n } catch {\n /* not a link */\n }\n return null\n}\n\n/**\n * One video however its link is spelled — `youtu.be/<id>`, a watch link, an\n * embed or a Shorts link — as `<host>:<id>`, the way the Video embed element\n * reads a link; null for a link it cannot play.\n */\nexport function aiEmbedVideoKey(url: string): string | null {\n const host = aiEmbedHostOf(url)\n if (!host) return null\n const parsed = new URL(url.trim())\n const parts = parsed.pathname.split('/').filter(Boolean)\n const id =\n host === 'vimeo'\n ? parts.find((part) => /^\\d{6,}$/.test(part))\n : parsed.hostname.endsWith('youtu.be')\n ? parts[0]\n : (parsed.searchParams.get('v') ??\n (parts[0] === 'embed' || parts[0] === 'shorts' ? parts[1] : undefined))\n return id && /^[\\w-]{6,20}$/.test(id) ? `${host}:${id}` : null\n}\n\n/**\n * A `new:<name>` reference that names no entry of the plan's create list: a\n * screen's own layout or template, or what one of its sections places, with\n * that section's index.\n */\nexport type AiPlanUndeclaredRef = {\n /** The name the reference gives the creation, as written after `new:`. */\n name: string\n /** Where the plan writes it: `screens[0].template`, `screens[0].sections[4].uses[0]`. */\n path: string\n /** The screen that writes it, by index. */\n screenIndex: number\n} & ({ field: 'layout' | 'template'; sectionIndex: null } | { field: 'uses'; sectionIndex: number })\n\n/**\n * Every `new:<name>` reference the plan's create list does not carry, in plan\n * order: each screen's layout, then its template, then what its sections\n * place. A plan that places a creation it never declares has nothing to build\n * it from, so the plan rules refuse each (AGL-3040), and the page and\n * scaffold doors still name one in a plan confirmed before they did.\n */\nexport function aiPlanUndeclaredRefs(plan: AiBuildPlan): AiPlanUndeclaredRef[] {\n const found: AiPlanUndeclaredRef[] = []\n const undeclared = (ref: string | null): ref is string =>\n isAiPlanNewRef(ref) && !aiPlanCreateFor(plan, ref) && !aiPlanItemFor(plan, ref)\n const nameOf = (ref: string) => ref.slice(AI_PLAN_NEW_REF_PREFIX.length).trim()\n plan.screens.forEach((screen, screenIndex) => {\n for (const field of ['layout', 'template'] as const) {\n const ref = screen[field]\n if (!undeclared(ref)) continue\n found.push({\n name: nameOf(ref),\n path: `screens[${screenIndex}].${field}`,\n screenIndex,\n field,\n sectionIndex: null,\n })\n }\n screen.sections.forEach((section, sectionIndex) => {\n section.uses.forEach((ref, useIndex) => {\n if (!undeclared(ref)) return\n found.push({\n name: nameOf(ref),\n path: `screens[${screenIndex}].sections[${sectionIndex}].uses[${useIndex}]`,\n screenIndex,\n field: 'uses',\n sectionIndex,\n })\n })\n })\n })\n return found\n}\n"],"names":["AI_PLAN_NEW_REF_PREFIX","AI_BUILD_PLAN_REUSE_KINDS","AI_BUILD_PLAN_CREATION_NOUNS","component","noun","where","form","layout","template","dataset","email","AI_BUILD_PLAN_CREATE_KINDS","AI_BUILD_PLAN_EMBED_HOSTS","AI_BUILD_PLAN_EMBED_HOST_NAMES","youtube","vimeo","AI_BUILD_ITEM_DEGRADES","AI_BUILD_PLAN_LIMITS","reuse","create","screens","sections","uses","fields","embeds","text","seoDescription","items","buildItems","dependsOn","argsChars","string","description","type","nullableString","anyOf","strings","atMost","limit","AI_BUILD_PLAN_TOOL","name","strict","inputSchema","additionalProperties","required","properties","kind","enum","id","purpose","why","duplicateOf","title","slug","nav","seoTitle","AI_PLAN_ASKS_FOR_VIDEO","EMBEDS_SCHEMA","host","asked","url","AI_BUILD_PLAN_EMBEDS_TOOL","RECORD_SCHEMA","base","WRITES_TO_SCHEMA","withRecords","tool","writesTo","record","AI_BUILD_PLAN_RECORDS_TOOL","AI_BUILD_PLAN_EMBEDS_RECORDS_TOOL","ITEMS_SCHEMA","op","degrade","args","withBuildItems","aiBuildPlanToolFor","brief","options","video","test","records","isRecord","value","Array","isArray","PlanShapeError","Error","buildItemArgs","path","parsed","undefined","length","JSON","parse","key","raw","Object","entries","Number","isFinite","every","one","aiPlanLayoutRegionWords","words","field","listed","exec","parts","split","part","word","trim","some","seen","toLowerCase","push","parseAiBuildPlan","input","repairs","trimmed","cut","slice","brk","search","trimEnd","nullable","list","oneOf","allowed","includes","join","planRecord","entry","aiPlanRecordBase","root","map","index","names","Set","has","add","fieldIndex","filter","Boolean","rawSection","sectionIndex","section","isInteger","ref","refIndex","slot","credits","Math","max","floor","ok","plan","error","message","segments","segment","isAiPlanNewRef","startsWith","aiPlanCreateFor","find","AI_PLAN_DATASET_ITEM_OP","aiPlanFormDatasetNames","made","aiPlanItemFor","aiPlanSlugKey","replace","aiPlanEmbedsFor","target","embed","aiEmbedHostOf","URL","protocol","hostname","aiEmbedVideoKey","pathname","endsWith","searchParams","get","aiPlanUndeclaredRefs","found","undeclared","nameOf","forEach","screen","screenIndex","useIndex"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-build-plan.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AiTool } from '../providers/contract'\nimport type { AiBuildStorePages } from './ai-build-store-pages'\n\n/**\n * The plan a generation job builds from (AGL-2935): what the job's plan step\n * (`jobs/ai-job-plan-step.ts`) returns, before a single node is generated.\n *\n * This module is the plan's MODEL — its shape, its tool schema and its\n * reader — and imports nothing at runtime, so the doctrine validators, the\n * plan step, the job document and the proposal can all read it without\n * importing each other.\n *\n * A plan is REFERENCES, never documents (AGL-2937). It names what the site\n * already has by inventory id, what the job will create by a short name, and\n * which screens it builds from which of those — so the doctrine can be held\n * against the plan while fixing it costs a sentence, not a regenerated page,\n * and the customer reads what the job will do before it spends a credit on\n * doing it.\n *\n * `new:<name>` is how an entry refers to something the plan itself creates;\n * any other reference is an id from the site inventory.\n */\n\nexport const AI_PLAN_NEW_REF_PREFIX = 'new:'\n\n/** Records a plan may reuse, by the inventory list they come from. */\nexport type AiBuildPlanReuseKind =\n | 'component'\n | 'layout'\n | 'template'\n | 'form'\n | 'dataset'\n | 'collection'\n | 'screen'\n | 'theme'\n\nexport const AI_BUILD_PLAN_REUSE_KINDS: readonly AiBuildPlanReuseKind[] = [\n 'component',\n 'layout',\n 'template',\n 'form',\n 'dataset',\n 'collection',\n 'screen',\n 'theme',\n]\n\n/** What a plan may create. A theme change is a draft through the theme door, never a literal. */\nexport type AiBuildPlanCreateKind =\n | 'component'\n | 'layout'\n | 'template'\n | 'form'\n | 'theme-change'\n | 'dataset'\n | 'email'\n\n/**\n * What each creation is called in a sentence, and where a member makes one.\n *\n * Total over the union deliberately: a refusal that cannot name a creation is\n * a refusal a member cannot act on, so widening `AiBuildPlanCreateKind`\n * without saying where the new thing is made does not compile.\n */\nexport const AI_BUILD_PLAN_CREATION_NOUNS: Record<\n AiBuildPlanCreateKind,\n { noun: string; where: string }\n> = {\n component: { noun: 'component', where: 'on the Components page' },\n form: { noun: 'form', where: 'on the Forms page' },\n layout: { noun: 'layout', where: 'on the Layouts page' },\n template: { noun: 'template', where: 'in the Templates library' },\n 'theme-change': { noun: 'theme change', where: 'in the Theme section' },\n dataset: { noun: 'dataset', where: 'on the Datasets page' },\n email: { noun: 'email design', where: 'in Emails → Templates' },\n}\n\nexport const AI_BUILD_PLAN_CREATE_KINDS: readonly AiBuildPlanCreateKind[] = [\n 'component',\n 'layout',\n 'template',\n 'form',\n 'theme-change',\n 'dataset',\n 'email',\n]\n\nexport interface AiBuildPlanReuse {\n kind: AiBuildPlanReuseKind\n /** The inventory id. */\n id: string\n /** What the plan uses it for, in a few words. */\n purpose: string\n}\n\nexport interface AiBuildPlanCreate {\n kind: AiBuildPlanCreateKind\n /** Unique within the plan; referenced as `new:<name>`. */\n name: string\n /** Why nothing the site has will do — shown in the proposal. */\n why: string\n /** The inventory id this starts from as a duplicate, or null. */\n duplicateOf: string | null\n /**\n * A component: its props as `name:type`, an icon as `name:icon`\n * (AGL-3054). A layout: the regions it has, from the closed vocabulary\n * `AI_LAYOUT_REGIONS` holds (AGL-3024) — a layout has no props, and a\n * region named here is what the built layout is held to. A template: the\n * binding tokens its page shows, from the subject's own closed catalog\n * (`AI_TEMPLATE_SUBJECT_DEFINITIONS[subject].tokens`, AGL-3143) — the\n * template's analogue of a layout's regions, and what the built template is\n * held to. A form or a dataset: its field names. A theme change: the\n * palette paths it adds or changes.\n */\n fields: string[]\n /**\n * A form only: the dataset each of its submissions is also written to as a\n * record (AGL-3616, Zach 2026-10-10: a volunteer sign-up writing to a\n * Volunteers dataset) — `new:<name>` of a dataset this plan creates, or a\n * dataset id from the inventory. Said by the plan, never inferred from a\n * section's `uses`. Absent or null on every other creation, and on a form\n * whose submissions only reach the Inbox.\n */\n writesTo?: string | null\n /**\n * The id the draft this creation becomes is written under, where a unit of\n * the job builds it (AGL-3079). Minted when the job keeps the plan, never\n * part of the model's answer.\n */\n id?: string\n}\n\nexport interface AiBuildPlanSection {\n /** A few words: \"hero\", \"services grid\", \"contact form\". */\n name: string\n /** What the section places: inventory ids or `new:<name>` references. */\n uses: string[]\n /** How many repeated items it shows (cards, rows, tiles); 0 when none. */\n items: number\n}\n\n/**\n * A screen that is one dataset's record template (AGL-3475): the data plugin\n * serves it once per record at `/{base}/{slug}`, its copy bound with\n * `{{item.<field>}}`. The plan names the binding and the build drafts the\n * page; the member saves the binding in Page Properties → Record pages, since\n * a binding is a route and a job writes drafts only (rule 13).\n */\nexport interface AiBuildPlanRecord {\n /** The dataset: an inventory id or `new:<name>`. */\n dataset: string\n /** The path its record pages live under, one or more segments: `services`, `services/residential`. */\n base: string\n}\n\nexport interface AiBuildPlanScreen {\n title: string\n slug: string\n /** The layout it renders inside. */\n layout: string | null\n /** The template it applies, or null for a one-off page. */\n template: string | null\n /** The inventory screen it starts from as a duplicate, or null. */\n duplicateOf: string | null\n /** Whether the site navigation gains an entry for it. */\n nav: boolean\n seoTitle: string\n seoDescription: string\n sections: AiBuildPlanSection[]\n /**\n * The dataset this page is the record template of, or null for a page that\n * is one page. A plan kept before record templates carries none, which\n * reads as null.\n */\n record: AiBuildPlanRecord | null\n /**\n * The id the draft screen is written under, where a unit of the job builds\n * it (AGL-3079). Minted when the job keeps the plan, never part of the\n * model's answer.\n */\n id?: string\n}\n\n/** The third-party players a plan may embed: the hosts the Video embed element plays. */\nexport type AiBuildPlanEmbedHost = 'youtube' | 'vimeo'\n\nexport const AI_BUILD_PLAN_EMBED_HOSTS: readonly AiBuildPlanEmbedHost[] = ['youtube', 'vimeo']\n\n/** What each host is called in a sentence. */\nexport const AI_BUILD_PLAN_EMBED_HOST_NAMES: Record<AiBuildPlanEmbedHost, string> = {\n youtube: 'YouTube',\n vimeo: 'Vimeo',\n}\n\n/**\n * A third-party player the brief asks for (rule 16, AGL-3433). It loads its\n * host's code on the page that carries it, so it is planned, where the member\n * reads that cost before confirming, and a build admits only what the\n * confirmed plan lists.\n */\nexport interface AiBuildPlanEmbed {\n host: AiBuildPlanEmbedHost\n /** Where it plays: a screen of the plan by its slug, or `new:<name>` for a component the plan creates. */\n where: string\n /** The brief's own words asking for it, which the plan step finds in the brief. */\n asked: string\n /** The video's link as the brief gives it, or null for the site owner to paste. */\n url: string | null\n}\n\n/** How the items that depend on a failed item behave (AGL-3616). */\nexport type AiBuildItemDegrade = 'omit' | 'fallback'\n\nexport const AI_BUILD_ITEM_DEGRADES: readonly AiBuildItemDegrade[] = ['omit', 'fallback']\n\n/** One argument value of an item: scalars and lists of text only. */\nexport type AiBuildItemArg = string | number | boolean | string[]\n\n/**\n * One thing a `build` plan makes that its creations and screens do not\n * (AGL-3616): an operation another plugin — or this one — registered as an\n * AI capability, with the arguments its schema declares. `reuse`, `create`\n * and `screens` keep their meaning; an item is everything else.\n */\nexport interface AiBuildItem {\n /** Stable within the plan: `i0`, `i1`… in plan order. */\n slot: string\n /** The capability's operation. */\n op: string\n /** Unique within the plan, creations included; referenced as `new:<name>`. */\n name: string\n /** Why the brief needs it, in one sentence. */\n why: string\n /** What it is built after: `new:<name>` of a creation or another item. */\n dependsOn: string[]\n /** How the items that depend on it behave when it fails. */\n degrade: AiBuildItemDegrade\n /** The operation's arguments, in the shape its capability declares. */\n args: Record<string, AiBuildItemArg>\n /** The id its draft is written under, minted when the job keeps the plan; never the model's. */\n id?: string\n /** The most it can cost in credits, from its capability, recorded when the plan is kept. */\n credits?: number\n}\n\nexport interface AiBuildPlan {\n reuse: AiBuildPlanReuse[]\n create: AiBuildPlanCreate[]\n screens: AiBuildPlanScreen[]\n /**\n * The third-party players the brief asks for. Absent is none: a plan kept\n * before the list existed, and every plan whose brief asks for no player.\n */\n embeds?: AiBuildPlanEmbed[]\n /**\n * What a `build` plan makes beyond its creations and pages (AGL-3616).\n * Absent is none: every other kind's plan.\n */\n items?: AiBuildItem[]\n /**\n * The store pages a `build` adds where its plan turns the site into a store\n * (AGL-3676): worked out by code as the plan is kept, never by the model,\n * and built by a unit of their own after every planned one. Absent is none.\n */\n storePages?: AiBuildStorePages\n}\n\n/**\n * How much one plan may hold. A job builds what one proposal can describe\n * and one person can read before confirming; a brief asking for more is\n * several jobs.\n */\nexport const AI_BUILD_PLAN_LIMITS = {\n reuse: 40,\n create: 20,\n screens: 12,\n sections: 16,\n uses: 8,\n fields: 30,\n embeds: 4,\n text: 200,\n seoDescription: 320,\n items: 500,\n /** A build plan's items (AGL-3616). */\n buildItems: 16,\n /** What one item depends on. */\n dependsOn: 8,\n /** The arguments one item carries, as JSON. */\n argsChars: 2_000,\n} as const\n\nconst string = (description: string) => ({ type: 'string', description })\nconst nullableString = (description: string) => ({\n anyOf: [{ type: 'string' }, { type: 'null' }],\n description,\n})\nconst strings = (description: string) => ({\n type: 'array',\n items: { type: 'string' },\n description,\n})\n\n/**\n * The ceiling `parseAiBuildPlan` cuts a field's text at, in words (AGL-3022).\n * The reader repairs an overlong value by cutting it, and a model never told\n * where the cut falls writes a rationale that stops mid-sentence there.\n */\nconst atMost = (limit: number = AI_BUILD_PLAN_LIMITS.text) => `at most ${limit} characters`\n\n/**\n * The plan as a strict tool's input schema. Only the keywords strict\n * structured output accepts: every object closes its properties and lists\n * every one as required, an optional value is a `null` branch, and no\n * length or count bound appears as a keyword — those are enforced by\n * `parseAiBuildPlan`, which every answer passes through anyway.\n *\n * A text ceiling is stated in words instead, on each field the model writes.\n * A reference is copied from the inventory or from a creation's name, so its\n * length is not the model's to choose. A search title and description are\n * held by rule 10 to ceilings far inside the reader's, which the doctrine\n * states, so naming the reader's here would contradict the rule.\n */\nexport const AI_BUILD_PLAN_TOOL: AiTool = {\n name: 'submit_build_plan',\n description:\n 'Submit the build plan: what the site already has that this job reuses, what it creates and why, and the pages it builds from them.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['reuse', 'create', 'screens'],\n properties: {\n reuse: {\n type: 'array',\n description: 'Records the site already has that this plan uses, by inventory id.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['kind', 'id', 'purpose'],\n properties: {\n kind: { type: 'string', enum: [...AI_BUILD_PLAN_REUSE_KINDS] },\n id: string('The id from the site inventory.'),\n purpose: string(`What the plan uses it for, in a few words, ${atMost()}.`),\n },\n },\n },\n create: {\n type: 'array',\n description:\n 'What the plan creates. Creating is the exception: each entry says why nothing listed will do.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['kind', 'name', 'why', 'duplicateOf', 'fields'],\n properties: {\n kind: { type: 'string', enum: [...AI_BUILD_PLAN_CREATE_KINDS] },\n name: string(\n `Unique within the plan, ${atMost()}; other entries refer to it as new:<name>.`,\n ),\n why: string(`Why nothing the site already has will do, in one sentence of ${atMost()}.`),\n duplicateOf: nullableString(\n 'The inventory id this starts from as a duplicate, or null.',\n ),\n fields: strings(\n `A component: its props as name:type, an icon as name:icon. A layout: the regions it has, each one of header, nav, sidebar, main, footer — list every region the finished layout has, not only the ones it gains. A form or dataset: its field names. A theme change: the palette paths it adds or changes. Each ${atMost()}.`,\n ),\n },\n },\n },\n screens: {\n type: 'array',\n description: 'The pages the job builds, each a new draft.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: [\n 'title',\n 'slug',\n 'layout',\n 'template',\n 'duplicateOf',\n 'nav',\n 'seoTitle',\n 'seoDescription',\n 'sections',\n ],\n properties: {\n title: string(`The page name, ${atMost()}.`),\n slug: string(\n `The path, ${atMost()}: lowercase words joined by hyphens, e.g. /services/roof-repair.`,\n ),\n layout: nullableString(\n 'The layout it renders inside: an inventory id or new:<name>.',\n ),\n template: nullableString(\n 'The template it applies (inventory id or new:<name>), or null for a one-off page.',\n ),\n duplicateOf: nullableString(\n 'The inventory page it starts from as a duplicate, or null.',\n ),\n nav: {\n type: 'boolean',\n description: 'Whether the site navigation gains an entry for it.',\n },\n seoTitle: string('The search-result title.'),\n seoDescription: string('The search-result description.'),\n sections: {\n type: 'array',\n description: 'The sections, top to bottom.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['name', 'uses', 'items'],\n properties: {\n name: string(`A few words, ${atMost()}: hero, services grid, contact form.`),\n uses: strings('What the section places: inventory ids or new:<name>.'),\n items: {\n type: 'integer',\n description: 'How many repeated items it shows; 0 when none.',\n },\n },\n },\n },\n },\n },\n },\n },\n },\n}\n\n/** Words that ask for a video, or a link to one. */\nexport const AI_PLAN_ASKS_FOR_VIDEO = /\\b(?:videos?|films?|clips?|youtube|vimeo|player)\\b|youtu\\.be/i\n\nconst EMBEDS_SCHEMA = {\n type: 'array',\n description:\n 'A YouTube or Vimeo player the brief asks for, on a screen or a created component. Empty unless the brief asks; a library film is a Video.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['host', 'where', 'asked', 'url'],\n properties: {\n host: { type: 'string', enum: [...AI_BUILD_PLAN_EMBED_HOSTS] },\n where: string('A screen slug, or new:<name> of a component.'),\n asked: string('The words of the brief that ask for it, copied exactly.'),\n url: nullableString('The video link the brief gives, or null.'),\n },\n },\n}\n\n/**\n * The plan tool with its list of third-party players, offered only to a brief\n * that asks for a video (AGL-3433). Every other plan's request is the plain\n * tool, byte for byte, so the list costs nothing on the plans that cannot use\n * it, and a model answering a brief that names no video has nowhere to plan a\n * player it was never asked for.\n */\nexport const AI_BUILD_PLAN_EMBEDS_TOOL: AiTool = {\n ...AI_BUILD_PLAN_TOOL,\n inputSchema: {\n ...AI_BUILD_PLAN_TOOL.inputSchema,\n required: [...(AI_BUILD_PLAN_TOOL.inputSchema['required'] as string[]), 'embeds'],\n properties: {\n ...(AI_BUILD_PLAN_TOOL.inputSchema['properties'] as Record<string, unknown>),\n embeds: EMBEDS_SCHEMA,\n },\n },\n}\n\nconst RECORD_SCHEMA = {\n anyOf: [\n {\n type: 'object',\n additionalProperties: false,\n required: ['dataset', 'base'],\n properties: {\n dataset: string('The dataset: an inventory id or new:<name>.'),\n base: string('The path its record pages live under, e.g. services for /services/roofing.'),\n },\n },\n { type: 'null' },\n ],\n description:\n \"When this page is one dataset's record template, serving a page per record at /<base>/<record address> with its copy bound as {{item.<field>}}: the dataset and the base. Otherwise null.\",\n}\n\nconst WRITES_TO_SCHEMA = nullableString(\n 'A form only, and only where each submission should also be kept as a record (a volunteer sign-up, an RSVP, an inquiry): the dataset it writes to, as new:<name> of a dataset this plan creates or a dataset id from the inventory. Otherwise null.',\n)\n\n/**\n * A plan tool whose screens each name the dataset they are the record\n * template of, or null, and whose creations each name the dataset a form\n * writes its submissions to, or null (AGL-3616).\n */\nfunction withRecords(tool: AiTool): AiTool {\n const properties = tool.inputSchema['properties'] as Record<string, unknown>\n const screens = properties['screens'] as { items: Record<string, unknown> }\n const create = properties['create'] as { items: Record<string, unknown> }\n return {\n ...tool,\n inputSchema: {\n ...tool.inputSchema,\n properties: {\n ...properties,\n create: {\n ...create,\n items: {\n ...create.items,\n required: [...(create.items['required'] as string[]), 'writesTo'],\n properties: {\n ...(create.items['properties'] as Record<string, unknown>),\n writesTo: WRITES_TO_SCHEMA,\n },\n },\n },\n screens: {\n ...screens,\n items: {\n ...screens.items,\n required: [...(screens.items['required'] as string[]), 'record'],\n properties: {\n ...(screens.items['properties'] as Record<string, unknown>),\n record: RECORD_SCHEMA,\n },\n },\n },\n },\n },\n }\n}\n\n/**\n * The plan tools with a record template on every screen (AGL-3475) and the\n * dataset a form writes to on every creation (AGL-3616), offered only where\n * the job may bind a dataset: a site with a dataset, or a job that may create\n * one. Record pages are a Starter feature, and every other plan's\n * request is the plain tool, byte for byte, so the field costs a Free taste\n * nothing and a model planning a site with no dataset has none to bind.\n */\nexport const AI_BUILD_PLAN_RECORDS_TOOL: AiTool = withRecords(AI_BUILD_PLAN_TOOL)\nexport const AI_BUILD_PLAN_EMBEDS_RECORDS_TOOL: AiTool = withRecords(AI_BUILD_PLAN_EMBEDS_TOOL)\n\nconst ITEMS_SCHEMA = {\n type: 'array',\n description:\n 'What the plan makes beyond its creations and pages, each one operation from the list in the request. Empty when the brief asks for nothing else.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['op', 'name', 'why', 'dependsOn', 'degrade', 'args'],\n properties: {\n op: string('The operation, exactly as the request lists it.'),\n name: string(`Unique within the plan, creations included, ${atMost()}; other entries refer to it as new:<name>.`),\n why: string(`Why the brief needs it, in one sentence of ${atMost()}.`),\n dependsOn: strings('What it is built after: new:<name> of a creation or another item. Empty when nothing.'),\n degrade: {\n type: 'string',\n enum: [...AI_BUILD_ITEM_DEGRADES],\n description:\n 'omit: what depends on it is built without it if it fails. fallback: what depends on it uses something the site already has instead.',\n },\n args: string('Its arguments as one JSON object, with exactly the fields the request lists for the operation.'),\n },\n },\n}\n\n/**\n * Any plan tool with a build's items (AGL-3616), offered only to a `build`\n * job. The items' arguments travel as one JSON string whose fields the\n * request lists per operation, so the tool stays byte for byte the same\n * whichever plugins registered operations, and the prompt cache keeps it.\n */\nexport function withBuildItems(tool: AiTool): AiTool {\n return {\n ...tool,\n inputSchema: {\n ...tool.inputSchema,\n required: [...(tool.inputSchema['required'] as string[]), 'items'],\n properties: {\n ...(tool.inputSchema['properties'] as Record<string, unknown>),\n items: ITEMS_SCHEMA,\n },\n },\n }\n}\n\n/** The plan tool a brief is answered with, on a job that may or may not bind a dataset. */\nexport function aiBuildPlanToolFor(\n brief: string,\n options: { records?: boolean; items?: boolean } = {},\n): AiTool {\n const video = AI_PLAN_ASKS_FOR_VIDEO.test(brief)\n const tool = options.records\n ? video\n ? AI_BUILD_PLAN_EMBEDS_RECORDS_TOOL\n : AI_BUILD_PLAN_RECORDS_TOOL\n : video\n ? AI_BUILD_PLAN_EMBEDS_TOOL\n : AI_BUILD_PLAN_TOOL\n return options.items ? withBuildItems(tool) : tool\n}\n\nexport type AiBuildPlanParse =\n | { ok: true; plan: AiBuildPlan; repairs: string[] }\n | { ok: false; error: string }\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nclass PlanShapeError extends Error {}\n\n/**\n * An item's arguments as the plan keeps them: the model writes one JSON\n * string, a kept plan stores the object. Values are scalars and lists of\n * text; anything else is a shape the item cannot be built from.\n */\nfunction buildItemArgs(value: unknown, path: string): Record<string, AiBuildItemArg> {\n let parsed: unknown = value\n if (value === undefined || value === null || value === '') return {}\n if (typeof value === 'string') {\n if (value.length > AI_BUILD_PLAN_LIMITS.argsChars) {\n throw new PlanShapeError(`${path} is over ${AI_BUILD_PLAN_LIMITS.argsChars} characters`)\n }\n try {\n parsed = JSON.parse(value)\n } catch {\n throw new PlanShapeError(`${path} is not a JSON object`)\n }\n }\n if (!isRecord(parsed)) throw new PlanShapeError(`${path} is not a JSON object`)\n const args: Record<string, AiBuildItemArg> = {}\n for (const [key, raw] of Object.entries(parsed)) {\n if (raw === null || raw === undefined) continue\n if (typeof raw === 'string' || typeof raw === 'boolean' || (typeof raw === 'number' && Number.isFinite(raw))) {\n args[key] = raw\n } else if (Array.isArray(raw) && raw.every((one) => typeof one === 'string')) {\n args[key] = raw as string[]\n } else {\n throw new PlanShapeError(`${path}.${key} is not text, a number, true or false, or a list of text`)\n }\n }\n return args\n}\n\n/**\n * Read a plan out of a tool call, or name what makes it unreadable.\n *\n * Structure is refused — a missing list, an unknown kind, more screens than\n * a job holds, two creations under one name — because a plan that reads\n * differently from what the model meant is worse than a re-ask. Length is\n * repaired: copy past a ceiling is cut and the cut is listed.\n */\n/**\n * A layout's regions, one word each. A plan sometimes writes the whole list as\n * one entry — \"regions: header, nav, main, footer\" — which names regions the\n * platform builds but reads as one unknown word, and the plan was refused for\n * it (job AvVxbFbzQx). A leading label is dropped and a list is split on its\n * commas, slashes, plus signs and \"and\", so each region is checked on its own;\n * a duplicate keeps its first place.\n */\nexport function aiPlanLayoutRegionWords(fields: readonly string[]): string[] {\n const words: string[] = []\n for (const field of fields) {\n const listed = /^\\s*(?:the\\s+)?(?:layout\\s+)?(?:regions?|areas?)\\s*[:=-]\\s*(.+)$/i.exec(field)?.[1] ?? field\n const parts = /[,/+]|\\band\\b/i.test(listed)\n ? listed.split(/\\s*(?:,|\\/|\\+|\\band\\b)\\s*/i)\n : [listed]\n for (const part of parts) {\n const word = part.trim()\n if (word && !words.some((seen) => seen.toLowerCase() === word.toLowerCase())) words.push(word)\n }\n }\n return words\n}\n\nexport function parseAiBuildPlan(input: unknown): AiBuildPlanParse {\n const repairs: string[] = []\n const text = (\n value: unknown,\n path: string,\n limit: number = AI_BUILD_PLAN_LIMITS.text,\n ): string => {\n if (typeof value !== 'string') throw new PlanShapeError(`${path} is not text`)\n const trimmed = value.trim()\n if (trimmed.length > limit) {\n repairs.push(`${path} was over ${limit} characters; truncated`)\n // On the last word break inside the ceiling, not through a word: a\n // rationale is read by the member confirming the plan, and half a word\n // reads as a broken answer rather than a long one (AGL-3022). A run of\n // `limit` characters with no break in it still has to be cut where the\n // ceiling falls.\n const cut = trimmed.slice(0, limit)\n const brk = cut.search(/\\s\\S*$/)\n return (brk > 0 ? cut.slice(0, brk) : cut).trimEnd()\n }\n return trimmed\n }\n const nullable = (value: unknown, path: string): string | null =>\n value === null || value === undefined || value === '' ? null : text(value, path) || null\n const list = (value: unknown, path: string, limit: number): unknown[] => {\n if (!Array.isArray(value)) throw new PlanShapeError(`${path} is not a list`)\n if (value.length > limit) {\n throw new PlanShapeError(`${path} lists ${value.length}; one plan holds at most ${limit}`)\n }\n return value\n }\n const record = (value: unknown, path: string): Record<string, unknown> => {\n if (!isRecord(value)) throw new PlanShapeError(`${path} is not an object`)\n return value\n }\n const oneOf = <T extends string>(value: unknown, allowed: readonly T[], path: string): T => {\n if (typeof value !== 'string' || !(allowed as readonly string[]).includes(value)) {\n throw new PlanShapeError(`${path} is not one of ${allowed.join(', ')}`)\n }\n return value as T\n }\n // Absent or null is a page that is one page. A base is held to the shape a\n // record page's address takes, so the binding the member saves from it is\n // one the save route admits.\n const planRecord = (value: unknown, path: string): AiBuildPlanRecord | null => {\n if (value === null || value === undefined) return null\n const entry = record(value, path)\n const dataset = text(entry['dataset'], `${path}.dataset`)\n if (!dataset) throw new PlanShapeError(`${path}.dataset is empty`)\n const base = aiPlanRecordBase(text(entry['base'], `${path}.base`))\n if (!base) throw new PlanShapeError(`${path}.base is not a path of lowercase words joined by hyphens`)\n return { dataset, base }\n }\n\n try {\n const root = record(input, 'plan')\n const reuse = list(root['reuse'], 'reuse', AI_BUILD_PLAN_LIMITS.reuse).map((raw, index) => {\n const entry = record(raw, `reuse[${index}]`)\n const id = text(entry['id'], `reuse[${index}].id`)\n if (!id) throw new PlanShapeError(`reuse[${index}].id is empty`)\n return {\n kind: oneOf(entry['kind'], AI_BUILD_PLAN_REUSE_KINDS, `reuse[${index}].kind`),\n id,\n purpose: text(entry['purpose'], `reuse[${index}].purpose`),\n }\n })\n const names = new Set<string>()\n const create = list(root['create'], 'create', AI_BUILD_PLAN_LIMITS.create).map(\n (raw, index) => {\n const entry = record(raw, `create[${index}]`)\n const name = text(entry['name'], `create[${index}].name`)\n if (!name) throw new PlanShapeError(`create[${index}].name is empty`)\n const key = name.toLowerCase()\n if (names.has(key)) {\n throw new PlanShapeError(`create[${index}].name \"${name}\" is used twice`)\n }\n names.add(key)\n const kind = oneOf(entry['kind'], AI_BUILD_PLAN_CREATE_KINDS, `create[${index}].kind`)\n const fields = list(entry['fields'] ?? [], `create[${index}].fields`, AI_BUILD_PLAN_LIMITS.fields)\n .map((field, fieldIndex) => text(field, `create[${index}].fields[${fieldIndex}]`))\n .filter(Boolean)\n // Only a form writes its submissions to a dataset (AGL-3616); kept\n // only where it says one, so a plan without one reads as it always did.\n const writesTo = kind === 'form' ? nullable(entry['writesTo'], `create[${index}].writesTo`) : null\n return {\n kind,\n name,\n why: text(entry['why'], `create[${index}].why`),\n duplicateOf: nullable(entry['duplicateOf'], `create[${index}].duplicateOf`),\n fields: kind === 'layout' ? aiPlanLayoutRegionWords(fields) : fields,\n ...(writesTo ? { writesTo } : {}),\n }\n },\n )\n const screens = list(root['screens'], 'screens', AI_BUILD_PLAN_LIMITS.screens).map(\n (raw, index) => {\n const entry = record(raw, `screens[${index}]`)\n if (typeof entry['nav'] !== 'boolean') {\n throw new PlanShapeError(`screens[${index}].nav is not true or false`)\n }\n return {\n title: text(entry['title'], `screens[${index}].title`),\n slug: text(entry['slug'], `screens[${index}].slug`),\n layout: nullable(entry['layout'], `screens[${index}].layout`),\n template: nullable(entry['template'], `screens[${index}].template`),\n duplicateOf: nullable(entry['duplicateOf'], `screens[${index}].duplicateOf`),\n nav: entry['nav'],\n seoTitle: text(entry['seoTitle'], `screens[${index}].seoTitle`),\n seoDescription: text(\n entry['seoDescription'],\n `screens[${index}].seoDescription`,\n AI_BUILD_PLAN_LIMITS.seoDescription,\n ),\n sections: list(\n entry['sections'],\n `screens[${index}].sections`,\n AI_BUILD_PLAN_LIMITS.sections,\n ).map((rawSection, sectionIndex) => {\n const path = `screens[${index}].sections[${sectionIndex}]`\n const section = record(rawSection, path)\n const items = Number(section['items'] ?? 0)\n if (!Number.isInteger(items) || items < 0 || items > AI_BUILD_PLAN_LIMITS.items) {\n throw new PlanShapeError(`${path}.items is not a count`)\n }\n return {\n name: text(section['name'], `${path}.name`),\n uses: list(section['uses'] ?? [], `${path}.uses`, AI_BUILD_PLAN_LIMITS.uses)\n .map((ref, refIndex) => text(ref, `${path}.uses[${refIndex}]`))\n .filter(Boolean),\n items,\n }\n }),\n record: planRecord(entry['record'], `screens[${index}].record`),\n }\n },\n )\n const embeds = list(root['embeds'] ?? [], 'embeds', AI_BUILD_PLAN_LIMITS.embeds).map(\n (raw, index) => {\n const entry = record(raw, `embeds[${index}]`)\n const where = text(entry['where'], `embeds[${index}].where`)\n if (!where) throw new PlanShapeError(`embeds[${index}].where is empty`)\n return {\n host: oneOf(entry['host'], AI_BUILD_PLAN_EMBED_HOSTS, `embeds[${index}].host`),\n where,\n asked: text(entry['asked'], `embeds[${index}].asked`),\n url: nullable(entry['url'], `embeds[${index}].url`),\n }\n },\n )\n const items = list(root['items'] ?? [], 'items', AI_BUILD_PLAN_LIMITS.buildItems).map(\n (raw, index): AiBuildItem => {\n const path = `items[${index}]`\n const entry = record(raw, path)\n const op = text(entry['op'], `${path}.op`)\n if (!op) throw new PlanShapeError(`${path}.op is empty`)\n const name = text(entry['name'], `${path}.name`)\n if (!name) throw new PlanShapeError(`${path}.name is empty`)\n const key = name.toLowerCase()\n if (names.has(key)) throw new PlanShapeError(`${path}.name \"${name}\" is used twice`)\n names.add(key)\n return {\n slot: `i${index}`,\n op,\n name,\n why: text(entry['why'] ?? '', `${path}.why`),\n dependsOn: list(entry['dependsOn'] ?? [], `${path}.dependsOn`, AI_BUILD_PLAN_LIMITS.dependsOn)\n .map((ref, refIndex) => text(ref, `${path}.dependsOn[${refIndex}]`))\n .filter(Boolean),\n degrade:\n entry['degrade'] === undefined || entry['degrade'] === null\n ? 'omit'\n : oneOf(entry['degrade'], AI_BUILD_ITEM_DEGRADES, `${path}.degrade`),\n args: buildItemArgs(entry['args'], `${path}.args`),\n ...(typeof entry['id'] === 'string' && entry['id'] ? { id: entry['id'] } : {}),\n ...(typeof entry['credits'] === 'number' && Number.isFinite(entry['credits'])\n ? { credits: Math.max(0, Math.floor(entry['credits'])) }\n : {}),\n }\n },\n )\n // A plan whose brief asks for no player carries no list, as it did before the list existed;\n // so does a plan with no items.\n return {\n ok: true,\n plan: {\n reuse,\n create,\n screens,\n ...(embeds.length ? { embeds } : {}),\n ...(items.length ? { items } : {}),\n },\n repairs,\n }\n } catch (error) {\n if (error instanceof PlanShapeError) return { ok: false, error: error.message }\n throw error\n }\n}\n\n/**\n * A record template's base as the data plugin's save route stores it\n * (`normalizeRecordPageBase`, which this plugin may not import): one to four\n * lowercase segments of letters, digits and hyphens, with no leading or\n * trailing slash; null for a base that is no such path.\n */\nexport function aiPlanRecordBase(base: string): string | null {\n const segments = base.trim().toLowerCase().split('/').filter(Boolean)\n if (!segments.length || segments.length > 4) return null\n return segments.every(\n (segment) => segment.length <= 60 && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(segment),\n )\n ? segments.join('/')\n : null\n}\n\n/** Whether a reference names something the plan creates. */\nexport function isAiPlanNewRef(ref: string | null | undefined): ref is string {\n return typeof ref === 'string' && ref.startsWith(AI_PLAN_NEW_REF_PREFIX)\n}\n\n/** The creation a `new:<name>` reference names, case-insensitively. */\nexport function aiPlanCreateFor(\n plan: AiBuildPlan,\n ref: string,\n): AiBuildPlanCreate | undefined {\n if (!isAiPlanNewRef(ref)) return undefined\n const name = ref.slice(AI_PLAN_NEW_REF_PREFIX.length).trim().toLowerCase()\n return plan.create.find((entry) => entry.name.toLowerCase() === name)\n}\n\n/** The operation a build's dataset item is planned as (AGL-3616), owned by the data plugin. */\nexport const AI_PLAN_DATASET_ITEM_OP = 'dataset'\n\n/**\n * The datasets this plan makes that one of its forms writes its submissions\n * to (AGL-3616), by name lowercased: a dataset creation of a site start, or a\n * build's dataset item. Such a dataset starts with no records — its records\n * are the submissions — so a site start designs it from its planned fields\n * with no model, and no page lists it.\n */\nexport function aiPlanFormDatasetNames(plan: Pick<AiBuildPlan, 'create'> & Partial<Pick<AiBuildPlan, 'items'>>): Set<string> {\n const names = new Set<string>()\n for (const entry of plan.create) {\n if (entry.kind !== 'form' || !isAiPlanNewRef(entry.writesTo)) continue\n const name = entry.writesTo.slice(AI_PLAN_NEW_REF_PREFIX.length).trim().toLowerCase()\n const made =\n plan.create.some((one) => one.kind === 'dataset' && one.name.trim().toLowerCase() === name) ||\n (plan.items ?? []).some((one) => one.op === AI_PLAN_DATASET_ITEM_OP && one.name.trim().toLowerCase() === name)\n if (made) names.add(name)\n }\n return names\n}\n\n/** The item a `new:<name>` reference names, case-insensitively (AGL-3616). */\nexport function aiPlanItemFor(\n plan: Pick<AiBuildPlan, 'items'>,\n ref: string,\n): AiBuildItem | undefined {\n if (!isAiPlanNewRef(ref)) return undefined\n const name = ref.slice(AI_PLAN_NEW_REF_PREFIX.length).trim().toLowerCase()\n return (plan.items ?? []).find((entry) => entry.name.toLowerCase() === name)\n}\n\n/** A slug as two spellings of one path compare: lowercase, one leading slash, no trailing one. */\nexport function aiPlanSlugKey(slug: string): string {\n const path = slug.trim().toLowerCase().replace(/^\\/*/, '/').replace(/\\/+$/, '')\n return path || '/'\n}\n\n/**\n * The players the plan lists for one output (AGL-3433): a screen by its slug,\n * or a creation by its name. A build admits these and no other.\n */\nexport function aiPlanEmbedsFor(\n plan: Pick<AiBuildPlan, 'embeds'> | null | undefined,\n target: { slug: string } | { create: string },\n): AiBuildPlanEmbed[] {\n const key =\n 'slug' in target\n ? aiPlanSlugKey(target.slug)\n : `${AI_PLAN_NEW_REF_PREFIX}${target.create.trim().toLowerCase()}`\n return (plan?.embeds ?? []).filter((embed) =>\n 'slug' in target\n ? !isAiPlanNewRef(embed.where) && aiPlanSlugKey(embed.where) === key\n : embed.where.trim().toLowerCase() === key,\n )\n}\n\n/** The host a video link plays from, or null for a link neither host serves. */\nexport function aiEmbedHostOf(url: string): AiBuildPlanEmbedHost | null {\n try {\n const parsed = new URL(url.trim())\n if (parsed.protocol !== 'https:') return null\n const host = parsed.hostname.replace(/^(?:www|m)\\./, '')\n if (host === 'youtu.be' || host === 'youtube.com' || host === 'youtube-nocookie.com') {\n return 'youtube'\n }\n if (host === 'vimeo.com' || host === 'player.vimeo.com') return 'vimeo'\n } catch {\n /* not a link */\n }\n return null\n}\n\n/**\n * One video however its link is spelled — `youtu.be/<id>`, a watch link, an\n * embed or a Shorts link — as `<host>:<id>`, the way the Video embed element\n * reads a link; null for a link it cannot play.\n */\nexport function aiEmbedVideoKey(url: string): string | null {\n const host = aiEmbedHostOf(url)\n if (!host) return null\n const parsed = new URL(url.trim())\n const parts = parsed.pathname.split('/').filter(Boolean)\n const id =\n host === 'vimeo'\n ? parts.find((part) => /^\\d{6,}$/.test(part))\n : parsed.hostname.endsWith('youtu.be')\n ? parts[0]\n : (parsed.searchParams.get('v') ??\n (parts[0] === 'embed' || parts[0] === 'shorts' ? parts[1] : undefined))\n return id && /^[\\w-]{6,20}$/.test(id) ? `${host}:${id}` : null\n}\n\n/**\n * A `new:<name>` reference that names no entry of the plan's create list: a\n * screen's own layout or template, or what one of its sections places, with\n * that section's index.\n */\nexport type AiPlanUndeclaredRef = {\n /** The name the reference gives the creation, as written after `new:`. */\n name: string\n /** Where the plan writes it: `screens[0].template`, `screens[0].sections[4].uses[0]`. */\n path: string\n /** The screen that writes it, by index. */\n screenIndex: number\n} & ({ field: 'layout' | 'template'; sectionIndex: null } | { field: 'uses'; sectionIndex: number })\n\n/**\n * Every `new:<name>` reference the plan's create list does not carry, in plan\n * order: each screen's layout, then its template, then what its sections\n * place. A plan that places a creation it never declares has nothing to build\n * it from, so the plan rules refuse each (AGL-3040), and the page and\n * scaffold doors still name one in a plan confirmed before they did.\n */\nexport function aiPlanUndeclaredRefs(plan: AiBuildPlan): AiPlanUndeclaredRef[] {\n const found: AiPlanUndeclaredRef[] = []\n const undeclared = (ref: string | null): ref is string =>\n isAiPlanNewRef(ref) && !aiPlanCreateFor(plan, ref) && !aiPlanItemFor(plan, ref)\n const nameOf = (ref: string) => ref.slice(AI_PLAN_NEW_REF_PREFIX.length).trim()\n plan.screens.forEach((screen, screenIndex) => {\n for (const field of ['layout', 'template'] as const) {\n const ref = screen[field]\n if (!undeclared(ref)) continue\n found.push({\n name: nameOf(ref),\n path: `screens[${screenIndex}].${field}`,\n screenIndex,\n field,\n sectionIndex: null,\n })\n }\n screen.sections.forEach((section, sectionIndex) => {\n section.uses.forEach((ref, useIndex) => {\n if (!undeclared(ref)) return\n found.push({\n name: nameOf(ref),\n path: `screens[${screenIndex}].sections[${sectionIndex}].uses[${useIndex}]`,\n screenIndex,\n field: 'uses',\n sectionIndex,\n })\n })\n })\n })\n return found\n}\n"],"names":["AI_PLAN_NEW_REF_PREFIX","AI_BUILD_PLAN_REUSE_KINDS","AI_BUILD_PLAN_CREATION_NOUNS","component","noun","where","form","layout","template","dataset","email","AI_BUILD_PLAN_CREATE_KINDS","AI_BUILD_PLAN_EMBED_HOSTS","AI_BUILD_PLAN_EMBED_HOST_NAMES","youtube","vimeo","AI_BUILD_ITEM_DEGRADES","AI_BUILD_PLAN_LIMITS","reuse","create","screens","sections","uses","fields","embeds","text","seoDescription","items","buildItems","dependsOn","argsChars","string","description","type","nullableString","anyOf","strings","atMost","limit","AI_BUILD_PLAN_TOOL","name","strict","inputSchema","additionalProperties","required","properties","kind","enum","id","purpose","why","duplicateOf","title","slug","nav","seoTitle","AI_PLAN_ASKS_FOR_VIDEO","EMBEDS_SCHEMA","host","asked","url","AI_BUILD_PLAN_EMBEDS_TOOL","RECORD_SCHEMA","base","WRITES_TO_SCHEMA","withRecords","tool","writesTo","record","AI_BUILD_PLAN_RECORDS_TOOL","AI_BUILD_PLAN_EMBEDS_RECORDS_TOOL","ITEMS_SCHEMA","op","degrade","args","withBuildItems","aiBuildPlanToolFor","brief","options","video","test","records","isRecord","value","Array","isArray","PlanShapeError","Error","buildItemArgs","path","parsed","undefined","length","JSON","parse","key","raw","Object","entries","Number","isFinite","every","one","aiPlanLayoutRegionWords","words","field","listed","exec","parts","split","part","word","trim","some","seen","toLowerCase","push","parseAiBuildPlan","input","repairs","trimmed","cut","slice","brk","search","trimEnd","nullable","list","oneOf","allowed","includes","join","planRecord","entry","aiPlanRecordBase","root","map","index","names","Set","has","add","fieldIndex","filter","Boolean","rawSection","sectionIndex","section","isInteger","ref","refIndex","slot","credits","Math","max","floor","ok","plan","error","message","segments","segment","isAiPlanNewRef","startsWith","aiPlanCreateFor","find","AI_PLAN_DATASET_ITEM_OP","aiPlanFormDatasetNames","made","aiPlanItemFor","aiPlanSlugKey","replace","aiPlanEmbedsFor","target","embed","aiEmbedHostOf","URL","protocol","hostname","aiEmbedVideoKey","pathname","endsWith","searchParams","get","aiPlanUndeclaredRefs","found","undeclared","nameOf","forEach","screen","screenIndex","useIndex"],"mappings":"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"}
@@ -30,7 +30,9 @@ const OP_NOUNS = {
30
30
  workflow: 'Automation',
31
31
  // A guided start's first posts and products (AGL-3676).
32
32
  posts: 'Blog',
33
- products: 'Store'
33
+ products: 'Store',
34
+ // A store's own account, cart and policy pages (AGL-3676).
35
+ store: 'Store'
34
36
  };
35
37
  /** What an operation is called at the start of a row: its own noun, or the op spelled as words. */ export function aiBuildOpNoun(op) {
36
38
  const known = OP_NOUNS[op];
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-build-progress.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { aiCustomerSafeItem } from './ai-job-failure-copy'\nimport { AI_JOB_PLAN_STEP, type AiJobItemLedger, type AiJobSummary } from './ai-jobs.types'\nimport { aiBuildCreditRange, aiBuildRetryLedger, aiBuildUnits, aiLedgerUnits } from './ai-build-job'\nimport {\n AI_CREDIT_RANGE_ZERO,\n aiCreditRangeAdd,\n aiCreditRangeOf,\n aiCreditRangeOrdered,\n type AiCreditRange,\n} from './ai-credit-estimate'\nimport {\n AI_SITE_NOMINAL_SECTIONS,\n AI_SITE_PASS_CREDITS,\n aiCreationMeasuredCredits,\n aiStandaloneScreenCreditRange,\n} from './ai-site-job'\n\n/**\n * What a `build` job shows of each item (AGL-3616), wherever it is shown —\n * the AI jobs drawer, the job's own page, the chat card: the item, where it\n * stands, what it cost, and what became of its credits when it failed.\n *\n * Pure: no React, no request.\n */\n\n/** Where one item stands, as a row's icon reads it. */\nexport type AiBuildItemRowState = 'done' | 'active' | 'waiting' | 'failed' | 'skipped'\n\nexport interface AiBuildItemRow {\n slot: string\n /** \"Page: About\", \"Form: Contact\". */\n label: string\n state: AiBuildItemRowState\n /** What the person should know: the failure, the note, what was given back. */\n detail: string | null\n /** When the active item started, for the elapsed time beside it; absent on every other row. */\n startedAt?: string | null\n}\n\nconst OP_NOUNS: Readonly<Record<string, string>> = {\n page: 'Page',\n layout: 'Layout',\n form: 'Form',\n component: 'Component',\n email: 'Email design',\n template: 'Page template',\n campaign: 'Email campaign',\n workflow: 'Automation',\n // A guided start's first posts and products (AGL-3676).\n posts: 'Blog',\n products: 'Store',\n}\n\n/** What an operation is called at the start of a row: its own noun, or the op spelled as words. */\nexport function aiBuildOpNoun(op: string): string {\n const known = OP_NOUNS[op]\n if (known) return known\n const words = op.replace(/[-_]+/g, ' ').trim()\n return words ? `${words[0].toUpperCase()}${words.slice(1)}` : 'Item'\n}\n\n/**\n * What became of a failed item's credits (AGL-3616), in the words every\n * surface uses for a failure on our side: \"This one’s on us — you weren’t\n * charged.\" A failure that was not ours — the model declining, a site at its\n * allowance — says what it used, which was charged. `null` for an item that\n * did not fail.\n */\nexport function aiBuildItemRefundCopy(\n row: Pick<AiJobItemLedger, 'status' | 'failure' | 'creditsRefunded' | 'creditsSpent'>,\n): string | null {\n if (row.status !== 'failed') return null\n if (row.failure?.ours) {\n const refunded = Math.max(0, Math.floor(row.creditsRefunded ?? 0))\n if (refunded > 0) {\n return `This one’s on us — you weren’t charged. The ${refunded} ${refunded === 1 ? 'credit' : 'credits'} it used ${refunded === 1 ? 'is' : 'are'} back in your AI credits.`\n }\n return 'This one’s on us — you weren’t charged.'\n }\n const spent = Math.max(0, Math.floor(row.creditsSpent ?? 0))\n return spent > 0 ? `It used ${spent} ${spent === 1 ? 'credit' : 'credits'}.` : null\n}\n\nconst STATES: Readonly<Record<AiJobItemLedger['status'], AiBuildItemRowState>> = {\n pending: 'waiting',\n running: 'active',\n succeeded: 'done',\n degraded: 'done',\n failed: 'failed',\n skipped: 'skipped',\n}\n\n/**\n * The item the build is on (AGL-3596): the one its ledger marks running, else\n * — while the job moves — the first still pending, which is the one its runner\n * builds next. The runner settles an item in the write that records its pass,\n * so the item it is writing reads `pending` until it is done; read as such,\n * every row waited and the build looked stalled. `null` when the job is not\n * moving or nothing is left.\n */\nexport function aiBuildActiveSlot(\n job: Pick<AiJobSummary, 'items'> & Partial<Pick<AiJobSummary, 'status'>>,\n): string | null {\n const items = job.items ?? []\n const running = items.find((row) => row.status === 'running')\n if (running) return running.slot\n if (job.status !== 'queued' && job.status !== 'running') return null\n return items.find((row) => row.status === 'pending')?.slot ?? null\n}\n\n/**\n * When the active item started: when the last item before it settled, which\n * the ledger records, else when the build step started — the first item's\n * start, and the best a ledger written before settling was recorded has.\n */\nexport function aiBuildActiveStartedAt(\n job: Pick<AiJobSummary, 'items'> & Partial<Pick<AiJobSummary, 'steps'>>,\n): string | null {\n let latest: { at: number; iso: string } | null = null\n for (const row of job.items ?? []) {\n const at = row.settledAt ? Date.parse(row.settledAt) : NaN\n if (Number.isFinite(at) && (!latest || at > latest.at)) latest = { at, iso: row.settledAt as string }\n }\n if (latest) return latest.iso\n const step = (job.steps ?? []).find((one) => one.name !== AI_JOB_PLAN_STEP && one.startedAt)\n return step?.startedAt ?? null\n}\n\n/** One row an item, in the order the build planned them; the item it is on is the active one. */\nexport function aiBuildItemRows(\n job: Pick<AiJobSummary, 'items'> & Partial<Pick<AiJobSummary, 'status' | 'steps'>>,\n): AiBuildItemRow[] {\n const active = aiBuildActiveSlot(job)\n return (job.items ?? []).map((ledger) => {\n // A building rule's own words never reach a customer (AGL-3596).\n const row = aiCustomerSafeItem(ledger)\n const parts: string[] = []\n if (row.status === 'failed' && row.failure?.message) parts.push(row.failure.message)\n if (row.note) parts.push(row.note)\n const refund = aiBuildItemRefundCopy(row)\n if (refund) parts.push(refund)\n return {\n slot: row.slot,\n label: `${aiBuildOpNoun(row.op)}: ${row.label}`,\n state: row.slot === active ? 'active' : (STATES[row.status] ?? 'waiting'),\n detail: parts.length ? parts.join(' ') : null,\n ...(row.slot === active ? { startedAt: aiBuildActiveStartedAt(job) } : {}),\n }\n })\n}\n\n/** How a build came out, in one line: \"4 of 5 built; 1 failed.\" `null` while it runs. */\nexport function aiBuildOutcomeLine(job: Pick<AiJobSummary, 'items' | 'status'>): string | null {\n const items = job.items ?? []\n if (!items.length || (job.status !== 'done' && job.status !== 'failed')) return null\n const built = items.filter((row) => row.status === 'succeeded' || row.status === 'degraded').length\n const failed = items.filter((row) => row.status === 'failed').length\n const skipped = items.filter((row) => row.status === 'skipped').length\n const tail = [failed ? `${failed} failed` : '', skipped ? `${skipped} not built` : ''].filter(Boolean)\n return `${built} of ${items.length} built${tail.length ? `; ${tail.join(', ')}` : ''}.`\n}\n\n/** Whether a finished build — or a site that built part of itself — has failed items to try again. */\nexport function aiBuildCanRetry(job: Pick<AiJobSummary, 'kind' | 'status' | 'items'>): boolean {\n // A site that built nothing starts over from its own answers instead.\n const finished = job.kind === 'build' ? job.status === 'done' || job.status === 'failed' : job.kind === 'site' && job.status === 'done'\n return (\n finished &&\n (job.items ?? []).some((row) => row.status === 'failed' || (row.status === 'skipped' && !row.degradedBy?.length))\n )\n}\n\n/**\n * What Try again is likely to cost (AGL-3722), quoted beside its button: a\n * build's retried items by `aiBuildCreditRange`; a site's retried rows each\n * at what its kind measured — a page at the nominal sections, written on its\n * own as a retry is — held at the reserve a row is retried at. `null` when nothing would run.\n */\nexport function aiBuildRetryCreditRange(\n job: Pick<AiJobSummary, 'kind' | 'items' | 'plan'>,\n): AiCreditRange | null {\n const items = job.items ?? []\n if (!items.length) return null\n if (job.kind === 'build') {\n if (!job.plan) return null\n const plan = job.plan as unknown as Parameters<typeof aiBuildUnits>[0]\n const { retried } = aiBuildRetryLedger(items, aiBuildUnits(plan))\n return retried.length ? aiBuildCreditRange(plan, { slots: new Set(retried) }) : null\n }\n const { retried } = aiBuildRetryLedger(items, aiLedgerUnits(items))\n if (!retried.length) return null\n const ops = new Map(items.map((row) => [row.slot, row.op]))\n return aiCreditRangeOrdered(\n retried.reduce((total, slot) => {\n const op = ops.get(slot) ?? ''\n const row =\n op === 'page'\n ? aiStandaloneScreenCreditRange(AI_SITE_NOMINAL_SECTIONS)\n : aiCreditRangeOf(aiCreationMeasuredCredits(op, { standalone: true }), AI_SITE_PASS_CREDITS)\n return aiCreditRangeAdd(total, row)\n }, AI_CREDIT_RANGE_ZERO),\n )\n}\n\n/**\n * What a guided start that built part of its site says (AGL-3616), in the\n * words every surface uses: \"Built 1 of 2 pages. Home couldn’t be built —\n * that one’s on us…\". `null` when every page was built, or none was.\n */\nexport function aiSitePartialCopy(job: Pick<AiJobSummary, 'items' | 'status'>): string | null {\n const pages = (job.items ?? []).filter((row) => row.op === 'page')\n const built = pages.filter((row) => row.status === 'succeeded' || row.status === 'degraded')\n const unbuilt = (job.items ?? []).filter((row) => row.status === 'failed' || row.status === 'skipped')\n if (job.status !== 'done' || !unbuilt.length || !built.length) return null\n const names = unbuilt.map((row) => (row.op === 'page' ? row.label : `The ${aiBuildOpNoun(row.op).toLowerCase()} “${row.label}”`))\n const list = names.length === 1 ? names[0] : `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`\n const refunded = unbuilt.reduce((total, row) => total + Math.max(0, Math.floor(row.creditsRefunded ?? 0)), 0)\n const back = refunded > 0 ? ` The ${refunded} ${refunded === 1 ? 'credit' : 'credits'} it used ${refunded === 1 ? 'is' : 'are'} back in your AI credits.` : ''\n // Only a failure on our side says so; a part the AI declined or an allowance\n // stopped was charged for what it used, and the row says that.\n const ours = unbuilt.every((row) => row.status === 'skipped' || row.failure?.ours === true)\n const why = ours ? ` — that one’s on us, you weren’t charged for ${unbuilt.length === 1 ? 'it' : 'them'}.` : '.'\n return `Built ${built.length} of ${pages.length} ${pages.length === 1 ? 'page' : 'pages'}. ${list} couldn’t be built${why}${back} Try again builds only what failed.`\n}\n"],"names":["aiCustomerSafeItem","AI_JOB_PLAN_STEP","aiBuildCreditRange","aiBuildRetryLedger","aiBuildUnits","aiLedgerUnits","AI_CREDIT_RANGE_ZERO","aiCreditRangeAdd","aiCreditRangeOf","aiCreditRangeOrdered","AI_SITE_NOMINAL_SECTIONS","AI_SITE_PASS_CREDITS","aiCreationMeasuredCredits","aiStandaloneScreenCreditRange","OP_NOUNS","page","layout","form","component","email","template","campaign","workflow","posts","products","aiBuildOpNoun","op","known","words","replace","trim","toUpperCase","slice","aiBuildItemRefundCopy","row","status","failure","ours","refunded","Math","max","floor","creditsRefunded","spent","creditsSpent","STATES","pending","running","succeeded","degraded","failed","skipped","aiBuildActiveSlot","job","items","find","slot","aiBuildActiveStartedAt","latest","at","settledAt","Date","parse","NaN","Number","isFinite","iso","step","steps","one","name","startedAt","aiBuildItemRows","active","map","ledger","parts","message","push","note","refund","label","state","detail","length","join","aiBuildOutcomeLine","built","filter","tail","Boolean","aiBuildCanRetry","finished","kind","some","degradedBy","aiBuildRetryCreditRange","plan","retried","slots","Set","ops","Map","reduce","total","get","standalone","aiSitePartialCopy","pages","unbuilt","names","toLowerCase","list","back","every","why"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-build-progress.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { aiCustomerSafeItem } from './ai-job-failure-copy'\nimport { AI_JOB_PLAN_STEP, type AiJobItemLedger, type AiJobSummary } from './ai-jobs.types'\nimport { aiBuildCreditRange, aiBuildRetryLedger, aiBuildUnits, aiLedgerUnits } from './ai-build-job'\nimport {\n AI_CREDIT_RANGE_ZERO,\n aiCreditRangeAdd,\n aiCreditRangeOf,\n aiCreditRangeOrdered,\n type AiCreditRange,\n} from './ai-credit-estimate'\nimport {\n AI_SITE_NOMINAL_SECTIONS,\n AI_SITE_PASS_CREDITS,\n aiCreationMeasuredCredits,\n aiStandaloneScreenCreditRange,\n} from './ai-site-job'\n\n/**\n * What a `build` job shows of each item (AGL-3616), wherever it is shown —\n * the AI jobs drawer, the job's own page, the chat card: the item, where it\n * stands, what it cost, and what became of its credits when it failed.\n *\n * Pure: no React, no request.\n */\n\n/** Where one item stands, as a row's icon reads it. */\nexport type AiBuildItemRowState = 'done' | 'active' | 'waiting' | 'failed' | 'skipped'\n\nexport interface AiBuildItemRow {\n slot: string\n /** \"Page: About\", \"Form: Contact\". */\n label: string\n state: AiBuildItemRowState\n /** What the person should know: the failure, the note, what was given back. */\n detail: string | null\n /** When the active item started, for the elapsed time beside it; absent on every other row. */\n startedAt?: string | null\n}\n\nconst OP_NOUNS: Readonly<Record<string, string>> = {\n page: 'Page',\n layout: 'Layout',\n form: 'Form',\n component: 'Component',\n email: 'Email design',\n template: 'Page template',\n campaign: 'Email campaign',\n workflow: 'Automation',\n // A guided start's first posts and products (AGL-3676).\n posts: 'Blog',\n products: 'Store',\n // A store's own account, cart and policy pages (AGL-3676).\n store: 'Store',\n}\n\n/** What an operation is called at the start of a row: its own noun, or the op spelled as words. */\nexport function aiBuildOpNoun(op: string): string {\n const known = OP_NOUNS[op]\n if (known) return known\n const words = op.replace(/[-_]+/g, ' ').trim()\n return words ? `${words[0].toUpperCase()}${words.slice(1)}` : 'Item'\n}\n\n/**\n * What became of a failed item's credits (AGL-3616), in the words every\n * surface uses for a failure on our side: \"This one’s on us — you weren’t\n * charged.\" A failure that was not ours — the model declining, a site at its\n * allowance — says what it used, which was charged. `null` for an item that\n * did not fail.\n */\nexport function aiBuildItemRefundCopy(\n row: Pick<AiJobItemLedger, 'status' | 'failure' | 'creditsRefunded' | 'creditsSpent'>,\n): string | null {\n if (row.status !== 'failed') return null\n if (row.failure?.ours) {\n const refunded = Math.max(0, Math.floor(row.creditsRefunded ?? 0))\n if (refunded > 0) {\n return `This one’s on us — you weren’t charged. The ${refunded} ${refunded === 1 ? 'credit' : 'credits'} it used ${refunded === 1 ? 'is' : 'are'} back in your AI credits.`\n }\n return 'This one’s on us — you weren’t charged.'\n }\n const spent = Math.max(0, Math.floor(row.creditsSpent ?? 0))\n return spent > 0 ? `It used ${spent} ${spent === 1 ? 'credit' : 'credits'}.` : null\n}\n\nconst STATES: Readonly<Record<AiJobItemLedger['status'], AiBuildItemRowState>> = {\n pending: 'waiting',\n running: 'active',\n succeeded: 'done',\n degraded: 'done',\n failed: 'failed',\n skipped: 'skipped',\n}\n\n/**\n * The item the build is on (AGL-3596): the one its ledger marks running, else\n * — while the job moves — the first still pending, which is the one its runner\n * builds next. The runner settles an item in the write that records its pass,\n * so the item it is writing reads `pending` until it is done; read as such,\n * every row waited and the build looked stalled. `null` when the job is not\n * moving or nothing is left.\n */\nexport function aiBuildActiveSlot(\n job: Pick<AiJobSummary, 'items'> & Partial<Pick<AiJobSummary, 'status'>>,\n): string | null {\n const items = job.items ?? []\n const running = items.find((row) => row.status === 'running')\n if (running) return running.slot\n if (job.status !== 'queued' && job.status !== 'running') return null\n return items.find((row) => row.status === 'pending')?.slot ?? null\n}\n\n/**\n * When the active item started: when the last item before it settled, which\n * the ledger records, else when the build step started — the first item's\n * start, and the best a ledger written before settling was recorded has.\n */\nexport function aiBuildActiveStartedAt(\n job: Pick<AiJobSummary, 'items'> & Partial<Pick<AiJobSummary, 'steps'>>,\n): string | null {\n let latest: { at: number; iso: string } | null = null\n for (const row of job.items ?? []) {\n const at = row.settledAt ? Date.parse(row.settledAt) : NaN\n if (Number.isFinite(at) && (!latest || at > latest.at)) latest = { at, iso: row.settledAt as string }\n }\n if (latest) return latest.iso\n const step = (job.steps ?? []).find((one) => one.name !== AI_JOB_PLAN_STEP && one.startedAt)\n return step?.startedAt ?? null\n}\n\n/** One row an item, in the order the build planned them; the item it is on is the active one. */\nexport function aiBuildItemRows(\n job: Pick<AiJobSummary, 'items'> & Partial<Pick<AiJobSummary, 'status' | 'steps'>>,\n): AiBuildItemRow[] {\n const active = aiBuildActiveSlot(job)\n return (job.items ?? []).map((ledger) => {\n // A building rule's own words never reach a customer (AGL-3596).\n const row = aiCustomerSafeItem(ledger)\n const parts: string[] = []\n if (row.status === 'failed' && row.failure?.message) parts.push(row.failure.message)\n if (row.note) parts.push(row.note)\n const refund = aiBuildItemRefundCopy(row)\n if (refund) parts.push(refund)\n return {\n slot: row.slot,\n label: `${aiBuildOpNoun(row.op)}: ${row.label}`,\n state: row.slot === active ? 'active' : (STATES[row.status] ?? 'waiting'),\n detail: parts.length ? parts.join(' ') : null,\n ...(row.slot === active ? { startedAt: aiBuildActiveStartedAt(job) } : {}),\n }\n })\n}\n\n/** How a build came out, in one line: \"4 of 5 built; 1 failed.\" `null` while it runs. */\nexport function aiBuildOutcomeLine(job: Pick<AiJobSummary, 'items' | 'status'>): string | null {\n const items = job.items ?? []\n if (!items.length || (job.status !== 'done' && job.status !== 'failed')) return null\n const built = items.filter((row) => row.status === 'succeeded' || row.status === 'degraded').length\n const failed = items.filter((row) => row.status === 'failed').length\n const skipped = items.filter((row) => row.status === 'skipped').length\n const tail = [failed ? `${failed} failed` : '', skipped ? `${skipped} not built` : ''].filter(Boolean)\n return `${built} of ${items.length} built${tail.length ? `; ${tail.join(', ')}` : ''}.`\n}\n\n/** Whether a finished build — or a site that built part of itself — has failed items to try again. */\nexport function aiBuildCanRetry(job: Pick<AiJobSummary, 'kind' | 'status' | 'items'>): boolean {\n // A site that built nothing starts over from its own answers instead.\n const finished = job.kind === 'build' ? job.status === 'done' || job.status === 'failed' : job.kind === 'site' && job.status === 'done'\n return (\n finished &&\n (job.items ?? []).some((row) => row.status === 'failed' || (row.status === 'skipped' && !row.degradedBy?.length))\n )\n}\n\n/**\n * What Try again is likely to cost (AGL-3722), quoted beside its button: a\n * build's retried items by `aiBuildCreditRange`; a site's retried rows each\n * at what its kind measured — a page at the nominal sections, written on its\n * own as a retry is — held at the reserve a row is retried at. `null` when nothing would run.\n */\nexport function aiBuildRetryCreditRange(\n job: Pick<AiJobSummary, 'kind' | 'items' | 'plan'>,\n): AiCreditRange | null {\n const items = job.items ?? []\n if (!items.length) return null\n if (job.kind === 'build') {\n if (!job.plan) return null\n const plan = job.plan as unknown as Parameters<typeof aiBuildUnits>[0]\n const { retried } = aiBuildRetryLedger(items, aiBuildUnits(plan))\n return retried.length ? aiBuildCreditRange(plan, { slots: new Set(retried) }) : null\n }\n const { retried } = aiBuildRetryLedger(items, aiLedgerUnits(items))\n if (!retried.length) return null\n const ops = new Map(items.map((row) => [row.slot, row.op]))\n return aiCreditRangeOrdered(\n retried.reduce((total, slot) => {\n const op = ops.get(slot) ?? ''\n const row =\n op === 'page'\n ? aiStandaloneScreenCreditRange(AI_SITE_NOMINAL_SECTIONS)\n : aiCreditRangeOf(aiCreationMeasuredCredits(op, { standalone: true }), AI_SITE_PASS_CREDITS)\n return aiCreditRangeAdd(total, row)\n }, AI_CREDIT_RANGE_ZERO),\n )\n}\n\n/**\n * What a guided start that built part of its site says (AGL-3616), in the\n * words every surface uses: \"Built 1 of 2 pages. Home couldn’t be built —\n * that one’s on us…\". `null` when every page was built, or none was.\n */\nexport function aiSitePartialCopy(job: Pick<AiJobSummary, 'items' | 'status'>): string | null {\n const pages = (job.items ?? []).filter((row) => row.op === 'page')\n const built = pages.filter((row) => row.status === 'succeeded' || row.status === 'degraded')\n const unbuilt = (job.items ?? []).filter((row) => row.status === 'failed' || row.status === 'skipped')\n if (job.status !== 'done' || !unbuilt.length || !built.length) return null\n const names = unbuilt.map((row) => (row.op === 'page' ? row.label : `The ${aiBuildOpNoun(row.op).toLowerCase()} “${row.label}”`))\n const list = names.length === 1 ? names[0] : `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`\n const refunded = unbuilt.reduce((total, row) => total + Math.max(0, Math.floor(row.creditsRefunded ?? 0)), 0)\n const back = refunded > 0 ? ` The ${refunded} ${refunded === 1 ? 'credit' : 'credits'} it used ${refunded === 1 ? 'is' : 'are'} back in your AI credits.` : ''\n // Only a failure on our side says so; a part the AI declined or an allowance\n // stopped was charged for what it used, and the row says that.\n const ours = unbuilt.every((row) => row.status === 'skipped' || row.failure?.ours === true)\n const why = ours ? ` — that one’s on us, you weren’t charged for ${unbuilt.length === 1 ? 'it' : 'them'}.` : '.'\n return `Built ${built.length} of ${pages.length} ${pages.length === 1 ? 'page' : 'pages'}. ${list} couldn’t be built${why}${back} Try again builds only what failed.`\n}\n"],"names":["aiCustomerSafeItem","AI_JOB_PLAN_STEP","aiBuildCreditRange","aiBuildRetryLedger","aiBuildUnits","aiLedgerUnits","AI_CREDIT_RANGE_ZERO","aiCreditRangeAdd","aiCreditRangeOf","aiCreditRangeOrdered","AI_SITE_NOMINAL_SECTIONS","AI_SITE_PASS_CREDITS","aiCreationMeasuredCredits","aiStandaloneScreenCreditRange","OP_NOUNS","page","layout","form","component","email","template","campaign","workflow","posts","products","store","aiBuildOpNoun","op","known","words","replace","trim","toUpperCase","slice","aiBuildItemRefundCopy","row","status","failure","ours","refunded","Math","max","floor","creditsRefunded","spent","creditsSpent","STATES","pending","running","succeeded","degraded","failed","skipped","aiBuildActiveSlot","job","items","find","slot","aiBuildActiveStartedAt","latest","at","settledAt","Date","parse","NaN","Number","isFinite","iso","step","steps","one","name","startedAt","aiBuildItemRows","active","map","ledger","parts","message","push","note","refund","label","state","detail","length","join","aiBuildOutcomeLine","built","filter","tail","Boolean","aiBuildCanRetry","finished","kind","some","degradedBy","aiBuildRetryCreditRange","plan","retried","slots","Set","ops","Map","reduce","total","get","standalone","aiSitePartialCopy","pages","unbuilt","names","toLowerCase","list","back","every","why"],"mappings":"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"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { AiBuildItem, AiBuildPlanScreen } from './ai-build-plan';
18
+ import type { AiStoreLayoutLinks } from './ai-layout-store-links';
19
+ import { type AiStoreFinishStep, type AiStorePageFacts, type AiStorePagePlan } from './ai-site-store-pages';
20
+ /**
21
+ * A STORE'S OWN PAGES, ADDED BY AN ASSIST BUILD (AGL-3676).
22
+ *
23
+ * A site start that builds a store adds its account, cart and policy pages
24
+ * (`ai-site-store-pages.ts`). An Assist build has no site kind, so a member
25
+ * who asked Assist to "add a shop" got products and a Shop page and nowhere
26
+ * for a shopper to sign in, see a cart or read the store's policies.
27
+ *
28
+ * So a build whose plan turns the site into a store — it makes products, a
29
+ * product page template, or plans a Shop page — gets the same store pages,
30
+ * written by the same code-built unit, behind the same gate (a plan with
31
+ * commerce, Commerce on for the site, an editor). Only the pages the site
32
+ * does not already have: a page of the site's own, or of the plan, that
33
+ * holds one's address or says it is one ("Privacy Policy" at
34
+ * /privacy-policy) stands for it, and nothing is written for that one.
35
+ *
36
+ * The plan keeps what it adds (`AiBuildPlan.storePages`), worked out by code
37
+ * when the plan is kept, never asked of the model; the plan card shows those
38
+ * pages as added by the platform, at no credits, and the build writes them in
39
+ * one unit of its own after every planned unit. The build checks the site
40
+ * again before writing, so a page made since the plan is not made twice.
41
+ *
42
+ * Pure: safe for the console to import.
43
+ */
44
+ /** The ledger slot, and the op, of a build's store pages unit. Persisted; never renamed. */
45
+ export declare const AI_BUILD_STORE_SLOT = "store";
46
+ export declare const AI_BUILD_STORE_OP = "store";
47
+ /** The commerce plugin's product operation (`PRODUCT_AI_OP`), which a build that makes products plans. */
48
+ export declare const AI_BUILD_PRODUCT_OP = "product";
49
+ /** What the build's row for its store pages reads. */
50
+ export declare const AI_BUILD_STORE_PAGES_LABEL = "Account, cart and policy pages";
51
+ /** What a build's store pages row says where the site already had every one by the time it ran. */
52
+ export declare const AI_BUILD_STORE_PAGES_PRESENT_NOTE = "Your site already has its account, cart and policy pages.";
53
+ /** What a build adds for a store: its store pages, each written or stood for by a page, and what their words link. */
54
+ export interface AiBuildStorePages {
55
+ pages: AiStorePagePlan[];
56
+ facts: AiStorePageFacts;
57
+ }
58
+ /** A page a build reads to decide what a store lacks: a planned page, or one the site has. */
59
+ export interface AiStoreSitePage {
60
+ /** The site's page id, where it is one of the site's own. */
61
+ id?: string;
62
+ slug: string;
63
+ title: string;
64
+ /** A record template or entry template, which is no page at its own address. */
65
+ template?: boolean;
66
+ /** The layout the page renders inside, where it is one of the site's own. */
67
+ layoutId?: string | null;
68
+ }
69
+ /**
70
+ * Whether a build's plan turns its site into a store: it makes products, a
71
+ * page template for products, or plans the store's own page (Shop, Store,
72
+ * Products). A page that only names collections or a range is not enough:
73
+ * a gallery has those too.
74
+ */
75
+ export declare function aiBuildMakesStore(plan: {
76
+ screens: ReadonlyArray<Pick<AiBuildPlanScreen, 'slug' | 'title' | 'record'>>;
77
+ items?: ReadonlyArray<Pick<AiBuildItem, 'op' | 'args'>>;
78
+ }): boolean;
79
+ /**
80
+ * The store pages for these pages — the plan's and the site's own together:
81
+ * `aiStorePagesOfPlan`'s answer, and then any store page still to write that
82
+ * a page's words say it already is.
83
+ */
84
+ export declare function aiStorePagesForSite(pages: readonly AiStoreSitePage[]): AiStorePagePlan[];
85
+ /** What a store page's words link, read off the pages a site has or plans: its contact page, its shop and its shipping page. */
86
+ export declare function aiStorePageFactsOf(pages: readonly AiStoreSitePage[], storePages: readonly AiStorePagePlan[]): AiStorePageFacts;
87
+ /**
88
+ * What a build adds for a store (AGL-3676), or `null` where it adds nothing:
89
+ * the plan does not make a store, the site cannot have one (`store` false:
90
+ * no commerce on the plan, or Commerce off for the site), or every store page
91
+ * is already there. The site's own pages come after the plan's, so a page the
92
+ * plan builds at an address is the one linked.
93
+ */
94
+ export declare function aiBuildStorePagesFor(plan: Parameters<typeof aiBuildMakesStore>[0], site: {
95
+ pages: readonly AiStoreSitePage[];
96
+ store: boolean;
97
+ }): AiBuildStorePages | null;
98
+ /**
99
+ * The store pages a build still writes once it runs: those its plan kept,
100
+ * less any the site has gained since — by address or by what the page says
101
+ * it is. `[]` when the site now has them all.
102
+ */
103
+ export declare function aiBuildStorePagesStillMissing(kept: readonly AiStorePagePlan[], sitePages: readonly AiStoreSitePage[]): AiStorePagePlan[];
104
+ /** Whether a value read off a kept plan is a build's store pages; a plan kept before them carries none. */
105
+ export declare function aiBuildStorePagesOf(plan: {
106
+ storePages?: unknown;
107
+ } | null | undefined): AiBuildStorePages | null;
108
+ /** The store pages a build's plan card lists as added for the store: the ones written, not the ones a page stands for. */
109
+ export declare function aiBuildStorePagesAdded(plan: {
110
+ storePages?: unknown;
111
+ } | null | undefined): AiStorePagePlan[];
112
+ /**
113
+ * What a store's links in a layout record on the build's outputs (AGL-3676),
114
+ * as `proposal.storeLinks`: `draft` on the layout version written with them,
115
+ * `missing` where the layout had no header or footer list to add them to.
116
+ */
117
+ export type AiBuildStoreLinksState = 'draft' | 'missing';
118
+ /** What a header's link to the store's account and cart say (`AI_LAYOUT_ACCOUNT_LINK`). */
119
+ export declare const AI_BUILD_STORE_ACCOUNT_LINK = "Account";
120
+ export declare const AI_BUILD_STORE_CART_LINK = "Cart";
121
+ /**
122
+ * The links a build adds to the site's own layout for these store pages:
123
+ * Account in the header, the cart there too where the header has none, and
124
+ * the account and the policies in the footer — only for the pages that are
125
+ * there, written by the build or stood for by a page the site has.
126
+ */
127
+ export declare function aiBuildStoreLayoutLinks(pages: readonly AiStorePagePlan[], written: ReadonlySet<string>): AiStoreLayoutLinks;
128
+ /** The layout a build's store pages render inside, and its links go in: the Shop page's, else the home page's, else the one most of the site's pages use. */
129
+ export declare function aiBuildStoreLayoutOf(shopLayoutId: string | null, sitePages: readonly AiStoreSitePage[]): string | null;
130
+ /** The finish card's step where the site's layout had no list for the store's links: add them by hand. */
131
+ export declare const AI_BUILD_STORE_LINKS_MISSING_STEP: AiStoreFinishStep;
132
+ /** The finish card's step where the store's links wait in a draft version of the site's layout: publish it. */
133
+ export declare const AI_BUILD_STORE_LINKS_DRAFT_STEP: AiStoreFinishStep;