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

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 (130) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.js +5 -1
  3. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  4. package/src/lib/components/ai-job-links.js +14 -0
  5. package/src/lib/components/ai-job-links.js.map +1 -1
  6. package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
  7. package/src/lib/components/ai-site-start-card.component.js +92 -19
  8. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  9. package/src/lib/declarations.server.js +12 -0
  10. package/src/lib/declarations.server.js.map +1 -1
  11. package/src/lib/jobs/ai-build-capabilities.js +56 -2
  12. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  13. package/src/lib/jobs/ai-job-build-step.d.ts +3 -8
  14. package/src/lib/jobs/ai-job-build-step.js +49 -12
  15. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  16. package/src/lib/jobs/ai-job-form-dataset.d.ts +8 -5
  17. package/src/lib/jobs/ai-job-form-dataset.js +28 -10
  18. package/src/lib/jobs/ai-job-form-dataset.js.map +1 -1
  19. package/src/lib/jobs/ai-job-form-step.d.ts +20 -4
  20. package/src/lib/jobs/ai-job-form-step.js +98 -28
  21. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  22. package/src/lib/jobs/ai-job-layout-language.d.ts +6 -2
  23. package/src/lib/jobs/ai-job-layout-language.js +16 -5
  24. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  25. package/src/lib/jobs/ai-job-layout-step.js +4 -1
  26. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  27. package/src/lib/jobs/ai-job-media-step.d.ts +239 -0
  28. package/src/lib/jobs/ai-job-media-step.js +610 -0
  29. package/src/lib/jobs/ai-job-media-step.js.map +1 -0
  30. package/src/lib/jobs/ai-job-page-language.d.ts +2 -0
  31. package/src/lib/jobs/ai-job-page-language.js +9 -0
  32. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  33. package/src/lib/jobs/ai-job-page-sections.js +4 -3
  34. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  35. package/src/lib/jobs/ai-job-page-step.js +3 -2
  36. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  37. package/src/lib/jobs/ai-job-plan-step.d.ts +10 -1
  38. package/src/lib/jobs/ai-job-plan-step.js +73 -15
  39. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  40. package/src/lib/jobs/ai-job-settings-step.d.ts +0 -4
  41. package/src/lib/jobs/ai-job-settings-step.js +2 -7
  42. package/src/lib/jobs/ai-job-settings-step.js.map +1 -1
  43. package/src/lib/jobs/ai-job-site-content.d.ts +2 -2
  44. package/src/lib/jobs/ai-job-site-content.js +5 -3
  45. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  46. package/src/lib/jobs/ai-job-site-datasets.d.ts +1 -1
  47. package/src/lib/jobs/ai-job-site-datasets.js +3 -2
  48. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  49. package/src/lib/jobs/ai-job-site-events.d.ts +148 -0
  50. package/src/lib/jobs/ai-job-site-events.js +313 -0
  51. package/src/lib/jobs/ai-job-site-events.js.map +1 -0
  52. package/src/lib/jobs/ai-job-site-step.d.ts +9 -3
  53. package/src/lib/jobs/ai-job-site-step.js +63 -17
  54. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  55. package/src/lib/jobs/ai-job-tracking-step.d.ts +184 -0
  56. package/src/lib/jobs/ai-job-tracking-step.js +501 -0
  57. package/src/lib/jobs/ai-job-tracking-step.js.map +1 -0
  58. package/src/lib/jobs/ai-layout-stock-photos.d.ts +41 -0
  59. package/src/lib/jobs/ai-layout-stock-photos.js +71 -3
  60. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  61. package/src/lib/jobs/ai-site-publish.js +2 -3
  62. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  63. package/src/lib/jobs/ai-stock-photo-picker.d.ts +24 -0
  64. package/src/lib/jobs/ai-stock-photo-picker.js +108 -0
  65. package/src/lib/jobs/ai-stock-photo-picker.js.map +1 -0
  66. package/src/lib/jobs/fixtures/ai-jazz-events-build-plan-recording.d.ts +37 -0
  67. package/src/lib/jobs/fixtures/ai-jazz-events-build-plan-recording.js +47 -0
  68. package/src/lib/jobs/fixtures/ai-jazz-events-build-plan-recording.js.map +1 -0
  69. package/src/lib/jobs/fixtures/ai-stock-photo-agl3760-recordings.d.ts +21 -0
  70. package/src/lib/jobs/fixtures/ai-stock-photo-agl3760-recordings.js +128 -0
  71. package/src/lib/jobs/fixtures/ai-stock-photo-agl3760-recordings.js.map +1 -0
  72. package/src/lib/jobs/fixtures/ai-stock-photo-agl3769-recordings.d.ts +21 -0
  73. package/src/lib/jobs/fixtures/ai-stock-photo-agl3769-recordings.js +126 -0
  74. package/src/lib/jobs/fixtures/ai-stock-photo-agl3769-recordings.js.map +1 -0
  75. package/src/lib/layout-language/ai-layout-compiler.d.ts +10 -4
  76. package/src/lib/layout-language/ai-layout-compiler.js +38 -19
  77. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  78. package/src/lib/layout-language/ai-layout-frame.d.ts +7 -5
  79. package/src/lib/layout-language/ai-layout-frame.js +12 -9
  80. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  81. package/src/lib/layout-language/ai-layout-gaps.js +3 -1
  82. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
  83. package/src/lib/layout-language/ai-layout-labels.d.ts +70 -0
  84. package/src/lib/layout-language/ai-layout-labels.js +162 -0
  85. package/src/lib/layout-language/ai-layout-labels.js.map +1 -0
  86. package/src/lib/layout-language/ai-layout-links.js +5 -2
  87. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  88. package/src/lib/layout-language/ai-layout-listings.d.ts +14 -1
  89. package/src/lib/layout-language/ai-layout-listings.js +32 -5
  90. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  91. package/src/lib/model/ai-build-event-page.d.ts +70 -0
  92. package/src/lib/model/ai-build-event-page.js +145 -0
  93. package/src/lib/model/ai-build-event-page.js.map +1 -0
  94. package/src/lib/model/ai-build-job.d.ts +21 -0
  95. package/src/lib/model/ai-build-job.js +39 -0
  96. package/src/lib/model/ai-build-job.js.map +1 -1
  97. package/src/lib/model/ai-build-plan.d.ts +25 -14
  98. package/src/lib/model/ai-build-plan.js +33 -23
  99. package/src/lib/model/ai-build-plan.js.map +1 -1
  100. package/src/lib/model/ai-job-activity.js +3 -1
  101. package/src/lib/model/ai-job-activity.js.map +1 -1
  102. package/src/lib/model/ai-jobs.types.d.ts +10 -2
  103. package/src/lib/model/ai-jobs.types.js +3 -1
  104. package/src/lib/model/ai-jobs.types.js.map +1 -1
  105. package/src/lib/model/ai-plan-capabilities.d.ts +8 -0
  106. package/src/lib/model/ai-plan-capabilities.js.map +1 -1
  107. package/src/lib/model/ai-site-inventory.d.ts +2 -0
  108. package/src/lib/model/ai-site-inventory.js +5 -0
  109. package/src/lib/model/ai-site-inventory.js.map +1 -1
  110. package/src/lib/model/ai-site-job.d.ts +2 -2
  111. package/src/lib/model/ai-site-job.js +3 -3
  112. package/src/lib/model/ai-site-job.js.map +1 -1
  113. package/src/lib/runtime/ai-doctrine-validators.d.ts +25 -3
  114. package/src/lib/runtime/ai-doctrine-validators.js +53 -6
  115. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  116. package/src/lib/runtime/ai-doctrine.d.ts +6 -1
  117. package/src/lib/runtime/ai-doctrine.js +13 -9
  118. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  119. package/src/lib/runtime/ai-eval-live.d.ts +1 -1
  120. package/src/lib/runtime/ai-eval-live.js +1 -1
  121. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  122. package/src/lib/runtime/ai-palette.d.ts +3 -1
  123. package/src/lib/runtime/ai-palette.generated.js +498 -10
  124. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  125. package/src/lib/runtime/ai-palette.js +5 -3
  126. package/src/lib/runtime/ai-palette.js.map +1 -1
  127. package/src/lib/runtime/ai-template-examples.js +8 -2
  128. package/src/lib/runtime/ai-template-examples.js.map +1 -1
  129. package/src/lib/server.js +8 -0
  130. package/src/lib/server.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'\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 * The language a SITE plan's brief and copy are written in (AGL-3750), as\n * the Languages card stores a code (`aiPlanLanguage`). Absent is the site's\n * own language: every other kind's plan, a plan that said null, and one\n * whose code was not a language code, which is dropped rather than refused.\n */\n language?: string\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 Languages card's own pattern for one language code (`LOCALE_PATTERN`). */\nexport const AI_PLAN_LANGUAGE_PATTERN = /^[a-z]{2}(-[A-Za-z]{2,4})?$/\n\n/**\n * A plan's language as the Languages card would store it (AGL-3750), or\n * `null`: a two-letter language, optionally with a region or a script, in\n * BCP 47 casing — `es`, `pt-BR`, `zh-Hant`; `_` reads as `-`. Anything the\n * card would not accept is no language at all, so a plan carrying one is\n * kept without it.\n */\nexport function aiPlanLanguage(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const match = /^([A-Za-z]{2})(?:-([A-Za-z]{2}|[A-Za-z]{4}))?$/.exec(value.trim().replace(/_/g, '-'))\n if (!match) return null\n const [, language, sub] = match\n const lower = language.toLowerCase()\n const tag = !sub\n ? lower\n : sub.length === 2\n ? `${lower}-${sub.toUpperCase()}`\n : `${lower}-${sub[0].toUpperCase()}${sub.slice(1).toLowerCase()}`\n return AI_PLAN_LANGUAGE_PATTERN.test(tag) ? tag : null\n}\n\nconst LANGUAGE_SCHEMA = nullableString(\n \"The language the brief is written in, which the site's copy is written in too: a code such as en, es, fr or pt-BR. Null when unsure.\",\n)\n\n/**\n * Any plan tool with the site's language (AGL-3750), offered only to a\n * `site` job, whose guided start sets the site's language from it. One\n * schema for every site plan, so the tool is the same bytes across briefs\n * and the prompt cache keeps it; every other kind's tool is unchanged.\n */\nexport function withPlanLanguage(tool: AiTool): AiTool {\n return {\n ...tool,\n inputSchema: {\n ...tool.inputSchema,\n required: [...(tool.inputSchema['required'] as string[]), 'language'],\n properties: {\n ...(tool.inputSchema['properties'] as Record<string, unknown>),\n language: LANGUAGE_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; language?: 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 const built = options.items ? withBuildItems(tool) : tool\n return options.language ? withPlanLanguage(built) : built\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 language that is no code is dropped, never refused (AGL-3750): the\n // site keeps the language it has.\n const language = aiPlanLanguage(root['language'])\n if (typeof root['language'] === 'string' && root['language'].trim() && !language) {\n repairs.push(`language \"${root['language'].trim().slice(0, 40)}\" is not a language code; ignored`)\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 ...(language ? { language } : {}),\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","AI_PLAN_LANGUAGE_PATTERN","aiPlanLanguage","value","match","exec","trim","replace","language","sub","lower","toLowerCase","tag","length","toUpperCase","slice","test","LANGUAGE_SCHEMA","withPlanLanguage","aiBuildPlanToolFor","brief","options","video","records","built","isRecord","Array","isArray","PlanShapeError","Error","buildItemArgs","path","parsed","undefined","JSON","parse","key","raw","Object","entries","Number","isFinite","every","one","aiPlanLayoutRegionWords","words","field","listed","parts","split","part","word","some","seen","push","parseAiBuildPlan","input","repairs","trimmed","cut","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","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 { siteLanguageCode } from '@aglyn/aglyn/app-utils/seo-locale'\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 of the plugin's at runtime (its one runtime\n * import is core's `seo-locale`, which imports nothing), so the doctrine\n * validators, the plan step, the job document and the proposal can all read\n * it without 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 * The language a SITE plan's brief and copy are written in (AGL-3750), as\n * the Languages card stores a code (`siteLanguageCode`). Absent is the site's\n * own language: every other kind's plan, a plan that said null, and one\n * whose code was not a language code, which is dropped rather than refused.\n */\n language?: string\n /**\n * What a SITE plan's photos show, in English whatever language the brief\n * is in (AGL-3760): two to four words a stock photo library is searched by,\n * the business's subject first — \"bakery bread pastries\" for \"una panadería\n * … con pan dulce, conchas\". The libraries are tagged in English, so a site\n * in another language searches by these rather than its own words. Absent\n * on every other kind's plan, on one that said null, and on one whose words\n * were not plain English letters.\n */\n photoWords?: string\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\nconst LANGUAGE_SCHEMA = nullableString(\n \"The language the brief is written in, which the site's copy is written in too: a code such as en, es, fr or pt-BR. Null when unsure.\",\n)\n\n/** The most words a plan's photo words keep. */\nexport const AI_PLAN_PHOTO_WORDS_MAX = 4\n\n/**\n * A plan's photo words as a stock photo search takes them (AGL-3760), or\n * `null`: lowercase English words, at most {@link AI_PLAN_PHOTO_WORDS_MAX}.\n * Words with letters outside English's — \"panadería\" — are no English at all,\n * so the plan is kept without them.\n */\nexport function aiPlanPhotoWords(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const text = value.trim().toLowerCase()\n if (!text || !/^[a-z][a-z\\s,'&-]*$/.test(text)) return null\n const words = text\n .split(/[\\s,&]+/)\n .map((word) => word.replace(/^[-']+|[-']+$/g, ''))\n .filter((word) => word.length > 1)\n return words.length ? words.slice(0, AI_PLAN_PHOTO_WORDS_MAX).join(' ') : null\n}\n\nconst PHOTO_WORDS_SCHEMA = nullableString(\n \"For a brief not in English: 2-4 English words for its stock photos, subject first, e.g. bakery bread pastries; no place. Else null.\",\n)\n\n/**\n * Any plan tool with the site's language (AGL-3750) and its photo words in\n * English (AGL-3760), offered only to a `site` job, whose guided start sets\n * the site's language from one and searches its photos by the other. One\n * schema for every site plan, so the tool is the same bytes across briefs\n * and the prompt cache keeps it; every other kind's tool is unchanged.\n */\nexport function withPlanLanguage(tool: AiTool): AiTool {\n return {\n ...tool,\n inputSchema: {\n ...tool.inputSchema,\n required: [...(tool.inputSchema['required'] as string[]), 'language', 'photoWords'],\n properties: {\n ...(tool.inputSchema['properties'] as Record<string, unknown>),\n language: LANGUAGE_SCHEMA,\n photoWords: PHOTO_WORDS_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; language?: 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 const built = options.items ? withBuildItems(tool) : tool\n return options.language ? withPlanLanguage(built) : built\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 language that is no code is dropped, never refused (AGL-3750): the\n // site keeps the language it has.\n const language = siteLanguageCode(root['language'])\n if (typeof root['language'] === 'string' && root['language'].trim() && !language) {\n repairs.push(`language \"${root['language'].trim().slice(0, 40)}\" is not a language code; ignored`)\n }\n // Photo words that are not plain English are dropped, never refused (AGL-3760).\n const photoWords = aiPlanPhotoWords(root['photoWords'])\n if (typeof root['photoWords'] === 'string' && root['photoWords'].trim() && !photoWords) {\n repairs.push(`photoWords \"${root['photoWords'].trim().slice(0, 40)}\" is not plain English; ignored`)\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 ...(language ? { language } : {}),\n ...(photoWords ? { photoWords } : {}),\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":["siteLanguageCode","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","LANGUAGE_SCHEMA","AI_PLAN_PHOTO_WORDS_MAX","aiPlanPhotoWords","value","trim","toLowerCase","test","words","split","map","word","replace","filter","length","slice","join","PHOTO_WORDS_SCHEMA","withPlanLanguage","language","photoWords","aiBuildPlanToolFor","brief","options","video","records","built","isRecord","Array","isArray","PlanShapeError","Error","buildItemArgs","path","parsed","undefined","JSON","parse","key","raw","Object","entries","Number","isFinite","every","one","aiPlanLayoutRegionWords","field","listed","exec","parts","part","some","seen","push","parseAiBuildPlan","input","repairs","trimmed","cut","brk","search","trimEnd","nullable","list","oneOf","allowed","includes","planRecord","entry","aiPlanRecordBase","root","index","names","Set","has","add","fieldIndex","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","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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"}
@@ -161,7 +161,9 @@ export function aiJobsActivity(jobs) {
161
161
  build: 'build',
162
162
  navigation: 'menu',
163
163
  duplicate: 'copy',
164
- settings: 'settings'
164
+ settings: 'settings',
165
+ media: 'media library change',
166
+ tracking: 'tracking'
165
167
  };
166
168
  /** The noun for what a job of this kind makes, or `AI job` for a kind with none. */ export function aiJobKindNoun(kind) {
167
169
  var _KIND_NOUNS_kind;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-job-activity.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 {\n AI_JOB_PLAN_STEP,\n type AiJobKind,\n type AiJobReviewReason,\n type AiJobStatus,\n type AiJobStepStatus,\n} from './ai-jobs.types'\n\n/**\n * WHAT A JOB IS DOING, IN THE WORDS EVERY SURFACE SHOWS (AGL-3593).\n *\n * A job's `status` is the machine's word: `needs_review` covers both a plan\n * waiting to be confirmed and an answer that broke a building rule twice, and\n * `queued`/`running` say nothing about whether the job is still planning or\n * already building. The console's surfaces — the AI jobs drawer's header and\n * rows, the top-bar indicator, the Assist launcher's badge, the dialog that\n * started the job, and the notification the machine raises — each tell a\n * person the same thing, so they read it from here and nowhere else. A drawer\n * that counted a refused plan as \"running\" while the chip said \"needs you\" is\n * the disagreement this exists to rule out.\n *\n * Pure, and over the fields a stored job and its wire form share, so the\n * server's notifier and the console read the same answer.\n */\n\n/** The fields a phase is read from: a stored job and its wire summary both carry them. */\nexport interface AiJobPhaseSource {\n status: AiJobStatus\n review?: { reason: AiJobReviewReason } | null\n steps: ReadonlyArray<{ name: string; status: AiJobStepStatus }>\n /** The wire summary's: the machine confirms this job's plan itself. */\n autoConfirm?: boolean\n /** When the job last moved; the wire summary's ISO string. */\n updatedAt?: unknown\n}\n\n/**\n * How long a self-confirming plan may sit parked before it reads as waiting\n * for a person after all: a confirmation that never came.\n */\nexport const AI_JOB_SELF_CONFIRM_GRACE_MS = 5 * 60_000\n\n/**\n * Whether a job is parked on a plan the machine is about to confirm itself\n * (AGL-3596). A guided site start records its plan step — parking the job on\n * the plan as every planned job does — and confirms it in the next write, so\n * a surface that reads in between sees `needs_review` for an instant. That\n * instant is the build going on, never a stop: read as one, it showed the\n * person a failed site that turned green again a moment later. The job's\n * `updatedAt` there is the clock its step was claimed at, so the grace covers\n * the plan step's own run as well as the confirmation. Past\n * {@link AI_JOB_SELF_CONFIRM_GRACE_MS} without the confirmation, the park is\n * what it says.\n */\nexport function aiJobConfirmingOwnPlan(\n job: Pick<AiJobPhaseSource, 'status' | 'review' | 'autoConfirm' | 'updatedAt'>,\n now: number = Date.now(),\n): boolean {\n if (job.status !== 'needs_review' || job.review?.reason !== 'plan' || job.autoConfirm !== true) return false\n const at = typeof job.updatedAt === 'string' ? Date.parse(job.updatedAt) : NaN\n return !Number.isFinite(at) || now - at < AI_JOB_SELF_CONFIRM_GRACE_MS\n}\n\n/**\n * Where one job stands, as a person reads it:\n *\n * - `queued` — created, no step has started.\n * - `planning` — its plan step is the one running or next.\n * - `plan-ready` — its plan waits for the person to confirm it.\n * - `building` — a step after the plan (or a kind with no plan) is running or next.\n * - `attention` — it stopped for something only a person can change: an\n * answer that broke a building rule, a site at an allowance, the\n * workspace's credits or a switch.\n * - `done`, `failed`, `canceled` — settled.\n */\nexport type AiJobPhase =\n | 'queued'\n | 'planning'\n | 'plan-ready'\n | 'building'\n | 'attention'\n | 'done'\n | 'failed'\n | 'canceled'\n\nexport function aiJobPhase(job: AiJobPhaseSource): AiJobPhase {\n if (job.status === 'done' || job.status === 'failed' || job.status === 'canceled') {\n return job.status\n }\n if (job.status === 'needs_review' && !aiJobConfirmingOwnPlan(job)) {\n return job.review?.reason === 'plan' ? 'plan-ready' : 'attention'\n }\n if (job.status === 'needs_input') return 'attention'\n // Moving: the first step that has not finished says which half it is in.\n const next = job.steps.find((step) => step.status === 'running' || step.status === 'pending')\n if (next?.name === AI_JOB_PLAN_STEP) return 'planning'\n if (job.status === 'queued' && job.steps.every((step) => step.status === 'pending')) {\n return 'queued'\n }\n return 'building'\n}\n\n/** Each phase in the words a status line and a chip use. */\nexport const AI_JOB_PHASE_LABELS: Readonly<Record<AiJobPhase, string>> = {\n queued: 'Queued',\n planning: 'Planning',\n 'plan-ready': 'Plan ready',\n building: 'Building',\n attention: 'Needs attention',\n done: 'Done',\n failed: 'Failed',\n canceled: 'Canceled',\n}\n\n/**\n * What the top-bar indicator, the launcher badge and the drawer's header\n * count a job as, or `null` for a settled job, which none of them count.\n *\n * - `needs-you` — a plan waits to be confirmed: the one decision that, left\n * alone, leaves the person's site unbuilt.\n * - `attention` — stopped for something a person must change, which is not a\n * plan to confirm.\n * - `running` — queued, planning or building. Never a stopped job.\n */\nexport type AiJobActivityState = 'needs-you' | 'attention' | 'running'\n\n/** Loudest first: the order the indicator picks its one state by. */\nexport const AI_JOB_ACTIVITY_PRECEDENCE: readonly AiJobActivityState[] = [\n 'needs-you',\n 'attention',\n 'running',\n]\n\nexport function aiJobActivityState(job: AiJobPhaseSource): AiJobActivityState | null {\n const phase = aiJobPhase(job)\n if (phase === 'plan-ready') return 'needs-you'\n if (phase === 'attention') return 'attention'\n if (phase === 'queued' || phase === 'planning' || phase === 'building') return 'running'\n return null\n}\n\n/**\n * The statuses a job the indicator counts can have: every one that is not in\n * `AI_JOB_TERMINAL_STATUSES`. The jobs route's `status=active` list asks\n * Firestore for exactly these, under the (status, createdAt) index.\n */\nexport const AI_JOB_ACTIVE_STATUSES: readonly AiJobStatus[] = [\n 'queued',\n 'running',\n 'needs_input',\n 'needs_review',\n]\n\n/** How many of a list's jobs stand in each state. */\nexport type AiJobActivityCounts = Readonly<Record<AiJobActivityState, number>>\n\nexport function aiJobActivityCounts(jobs: readonly AiJobPhaseSource[]): AiJobActivityCounts {\n const counts: Record<AiJobActivityState, number> = { 'needs-you': 0, attention: 0, running: 0 }\n for (const job of jobs) {\n const state = aiJobActivityState(job)\n if (state) counts[state] += 1\n }\n return counts\n}\n\n/**\n * What the indicator and the launcher show for a list of jobs: the loudest\n * state any of them is in, how many are in it, and the job the indicator\n * opens — the oldest of them, which has waited longest. `null` when none is\n * moving or waiting, which is when both show nothing.\n */\nexport interface AiJobsActivity<T> {\n state: AiJobActivityState\n count: number\n /** The job a click targets. */\n job: T\n /** Planning rather than building, for the running chip's word. */\n planning: boolean\n}\n\nexport function aiJobsActivity<T extends AiJobPhaseSource>(\n jobs: readonly T[],\n): AiJobsActivity<T> | null {\n for (const state of AI_JOB_ACTIVITY_PRECEDENCE) {\n const matching = jobs.filter((job) => aiJobActivityState(job) === state)\n if (!matching.length) continue\n return {\n state,\n count: matching.length,\n // Lists arrive newest first.\n job: matching[matching.length - 1],\n planning:\n state === 'running' &&\n matching.every((job) => {\n const phase = aiJobPhase(job)\n return phase === 'planning' || phase === 'queued'\n }),\n }\n }\n return null\n}\n\n/** The chip's words: `AI · plan ready`, `AI · 2 need attention`, `AI · planning`. */\nexport function aiJobsActivityLabel(activity: AiJobsActivity<unknown>): string {\n const { state, count } = activity\n if (state === 'needs-you') {\n return count === 1 ? 'AI · plan ready' : `AI · ${count} plans ready`\n }\n if (state === 'attention') {\n return count === 1 ? 'AI · needs attention' : `AI · ${count} need attention`\n }\n const verb = activity.planning ? 'planning' : 'working'\n return count === 1 ? `AI · ${verb}` : `AI · ${count} ${verb}`\n}\n\n/**\n * The launcher's accessible name: what the button opens, and what waits in it.\n * `Aglyn Assist, 1 AI job needs you`.\n */\nexport function aiJobsLauncherLabel(\n productName: string,\n activity: AiJobsActivity<unknown> | null,\n): string {\n const base = `Open ${productName} Assist`\n if (!activity) return base\n const jobs = activity.count === 1 ? '1 AI job' : `${activity.count} AI jobs`\n const verb = activity.count === 1 ? 'needs' : 'need'\n if (activity.state === 'needs-you') return `${base}, ${jobs} ${verb} you`\n if (activity.state === 'attention') return `${base}, ${jobs} ${verb} attention`\n return `${base}, ${jobs} running`\n}\n\n/** The drawer header's chips, one per state that has a job, loudest first. */\nexport function aiJobsHeaderChips(\n counts: AiJobActivityCounts,\n): Array<{ state: AiJobActivityState; label: string }> {\n return AI_JOB_ACTIVITY_PRECEDENCE.filter((state) => counts[state] > 0).map((state) => ({\n state,\n label:\n state === 'needs-you'\n ? `${counts[state]} ${counts[state] === 1 ? 'needs' : 'need'} you`\n : state === 'attention'\n ? `${counts[state]} ${counts[state] === 1 ? 'needs' : 'need'} attention`\n : `${counts[state]} running`,\n }))\n}\n\n/** What a job makes, as a sentence's subject: `Your site`, `Your page`. */\nconst KIND_NOUNS: Partial<Record<AiJobKind, string>> = {\n site: 'site',\n page: 'page',\n template: 'page template',\n layout: 'layout',\n form: 'form',\n component: 'component',\n email: 'email',\n campaign: 'campaign',\n workflow: 'automation',\n logic: 'function',\n edit: 'change',\n theme: 'theme',\n products: 'products',\n experiment: 'experiment',\n insight: 'answer',\n build: 'build',\n navigation: 'menu',\n duplicate: 'copy',\n settings: 'settings',\n}\n\n/** The noun for what a job of this kind makes, or `AI job` for a kind with none. */\nexport function aiJobKindNoun(kind: AiJobKind): string {\n return KIND_NOUNS[kind] ?? 'AI job'\n}\n"],"names":["AI_JOB_PLAN_STEP","AI_JOB_SELF_CONFIRM_GRACE_MS","aiJobConfirmingOwnPlan","job","now","Date","status","review","reason","autoConfirm","at","updatedAt","parse","NaN","Number","isFinite","aiJobPhase","next","steps","find","step","name","every","AI_JOB_PHASE_LABELS","queued","planning","building","attention","done","failed","canceled","AI_JOB_ACTIVITY_PRECEDENCE","aiJobActivityState","phase","AI_JOB_ACTIVE_STATUSES","aiJobActivityCounts","jobs","counts","running","state","aiJobsActivity","matching","filter","length","count","aiJobsActivityLabel","activity","verb","aiJobsLauncherLabel","productName","base","aiJobsHeaderChips","map","label","KIND_NOUNS","site","page","template","layout","form","component","email","campaign","workflow","logic","edit","theme","products","experiment","insight","build","navigation","duplicate","settings","aiJobKindNoun","kind"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-job-activity.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 {\n AI_JOB_PLAN_STEP,\n type AiJobKind,\n type AiJobReviewReason,\n type AiJobStatus,\n type AiJobStepStatus,\n} from './ai-jobs.types'\n\n/**\n * WHAT A JOB IS DOING, IN THE WORDS EVERY SURFACE SHOWS (AGL-3593).\n *\n * A job's `status` is the machine's word: `needs_review` covers both a plan\n * waiting to be confirmed and an answer that broke a building rule twice, and\n * `queued`/`running` say nothing about whether the job is still planning or\n * already building. The console's surfaces — the AI jobs drawer's header and\n * rows, the top-bar indicator, the Assist launcher's badge, the dialog that\n * started the job, and the notification the machine raises — each tell a\n * person the same thing, so they read it from here and nowhere else. A drawer\n * that counted a refused plan as \"running\" while the chip said \"needs you\" is\n * the disagreement this exists to rule out.\n *\n * Pure, and over the fields a stored job and its wire form share, so the\n * server's notifier and the console read the same answer.\n */\n\n/** The fields a phase is read from: a stored job and its wire summary both carry them. */\nexport interface AiJobPhaseSource {\n status: AiJobStatus\n review?: { reason: AiJobReviewReason } | null\n steps: ReadonlyArray<{ name: string; status: AiJobStepStatus }>\n /** The wire summary's: the machine confirms this job's plan itself. */\n autoConfirm?: boolean\n /** When the job last moved; the wire summary's ISO string. */\n updatedAt?: unknown\n}\n\n/**\n * How long a self-confirming plan may sit parked before it reads as waiting\n * for a person after all: a confirmation that never came.\n */\nexport const AI_JOB_SELF_CONFIRM_GRACE_MS = 5 * 60_000\n\n/**\n * Whether a job is parked on a plan the machine is about to confirm itself\n * (AGL-3596). A guided site start records its plan step — parking the job on\n * the plan as every planned job does — and confirms it in the next write, so\n * a surface that reads in between sees `needs_review` for an instant. That\n * instant is the build going on, never a stop: read as one, it showed the\n * person a failed site that turned green again a moment later. The job's\n * `updatedAt` there is the clock its step was claimed at, so the grace covers\n * the plan step's own run as well as the confirmation. Past\n * {@link AI_JOB_SELF_CONFIRM_GRACE_MS} without the confirmation, the park is\n * what it says.\n */\nexport function aiJobConfirmingOwnPlan(\n job: Pick<AiJobPhaseSource, 'status' | 'review' | 'autoConfirm' | 'updatedAt'>,\n now: number = Date.now(),\n): boolean {\n if (job.status !== 'needs_review' || job.review?.reason !== 'plan' || job.autoConfirm !== true) return false\n const at = typeof job.updatedAt === 'string' ? Date.parse(job.updatedAt) : NaN\n return !Number.isFinite(at) || now - at < AI_JOB_SELF_CONFIRM_GRACE_MS\n}\n\n/**\n * Where one job stands, as a person reads it:\n *\n * - `queued` — created, no step has started.\n * - `planning` — its plan step is the one running or next.\n * - `plan-ready` — its plan waits for the person to confirm it.\n * - `building` — a step after the plan (or a kind with no plan) is running or next.\n * - `attention` — it stopped for something only a person can change: an\n * answer that broke a building rule, a site at an allowance, the\n * workspace's credits or a switch.\n * - `done`, `failed`, `canceled` — settled.\n */\nexport type AiJobPhase =\n | 'queued'\n | 'planning'\n | 'plan-ready'\n | 'building'\n | 'attention'\n | 'done'\n | 'failed'\n | 'canceled'\n\nexport function aiJobPhase(job: AiJobPhaseSource): AiJobPhase {\n if (job.status === 'done' || job.status === 'failed' || job.status === 'canceled') {\n return job.status\n }\n if (job.status === 'needs_review' && !aiJobConfirmingOwnPlan(job)) {\n return job.review?.reason === 'plan' ? 'plan-ready' : 'attention'\n }\n if (job.status === 'needs_input') return 'attention'\n // Moving: the first step that has not finished says which half it is in.\n const next = job.steps.find((step) => step.status === 'running' || step.status === 'pending')\n if (next?.name === AI_JOB_PLAN_STEP) return 'planning'\n if (job.status === 'queued' && job.steps.every((step) => step.status === 'pending')) {\n return 'queued'\n }\n return 'building'\n}\n\n/** Each phase in the words a status line and a chip use. */\nexport const AI_JOB_PHASE_LABELS: Readonly<Record<AiJobPhase, string>> = {\n queued: 'Queued',\n planning: 'Planning',\n 'plan-ready': 'Plan ready',\n building: 'Building',\n attention: 'Needs attention',\n done: 'Done',\n failed: 'Failed',\n canceled: 'Canceled',\n}\n\n/**\n * What the top-bar indicator, the launcher badge and the drawer's header\n * count a job as, or `null` for a settled job, which none of them count.\n *\n * - `needs-you` — a plan waits to be confirmed: the one decision that, left\n * alone, leaves the person's site unbuilt.\n * - `attention` — stopped for something a person must change, which is not a\n * plan to confirm.\n * - `running` — queued, planning or building. Never a stopped job.\n */\nexport type AiJobActivityState = 'needs-you' | 'attention' | 'running'\n\n/** Loudest first: the order the indicator picks its one state by. */\nexport const AI_JOB_ACTIVITY_PRECEDENCE: readonly AiJobActivityState[] = [\n 'needs-you',\n 'attention',\n 'running',\n]\n\nexport function aiJobActivityState(job: AiJobPhaseSource): AiJobActivityState | null {\n const phase = aiJobPhase(job)\n if (phase === 'plan-ready') return 'needs-you'\n if (phase === 'attention') return 'attention'\n if (phase === 'queued' || phase === 'planning' || phase === 'building') return 'running'\n return null\n}\n\n/**\n * The statuses a job the indicator counts can have: every one that is not in\n * `AI_JOB_TERMINAL_STATUSES`. The jobs route's `status=active` list asks\n * Firestore for exactly these, under the (status, createdAt) index.\n */\nexport const AI_JOB_ACTIVE_STATUSES: readonly AiJobStatus[] = [\n 'queued',\n 'running',\n 'needs_input',\n 'needs_review',\n]\n\n/** How many of a list's jobs stand in each state. */\nexport type AiJobActivityCounts = Readonly<Record<AiJobActivityState, number>>\n\nexport function aiJobActivityCounts(jobs: readonly AiJobPhaseSource[]): AiJobActivityCounts {\n const counts: Record<AiJobActivityState, number> = { 'needs-you': 0, attention: 0, running: 0 }\n for (const job of jobs) {\n const state = aiJobActivityState(job)\n if (state) counts[state] += 1\n }\n return counts\n}\n\n/**\n * What the indicator and the launcher show for a list of jobs: the loudest\n * state any of them is in, how many are in it, and the job the indicator\n * opens — the oldest of them, which has waited longest. `null` when none is\n * moving or waiting, which is when both show nothing.\n */\nexport interface AiJobsActivity<T> {\n state: AiJobActivityState\n count: number\n /** The job a click targets. */\n job: T\n /** Planning rather than building, for the running chip's word. */\n planning: boolean\n}\n\nexport function aiJobsActivity<T extends AiJobPhaseSource>(\n jobs: readonly T[],\n): AiJobsActivity<T> | null {\n for (const state of AI_JOB_ACTIVITY_PRECEDENCE) {\n const matching = jobs.filter((job) => aiJobActivityState(job) === state)\n if (!matching.length) continue\n return {\n state,\n count: matching.length,\n // Lists arrive newest first.\n job: matching[matching.length - 1],\n planning:\n state === 'running' &&\n matching.every((job) => {\n const phase = aiJobPhase(job)\n return phase === 'planning' || phase === 'queued'\n }),\n }\n }\n return null\n}\n\n/** The chip's words: `AI · plan ready`, `AI · 2 need attention`, `AI · planning`. */\nexport function aiJobsActivityLabel(activity: AiJobsActivity<unknown>): string {\n const { state, count } = activity\n if (state === 'needs-you') {\n return count === 1 ? 'AI · plan ready' : `AI · ${count} plans ready`\n }\n if (state === 'attention') {\n return count === 1 ? 'AI · needs attention' : `AI · ${count} need attention`\n }\n const verb = activity.planning ? 'planning' : 'working'\n return count === 1 ? `AI · ${verb}` : `AI · ${count} ${verb}`\n}\n\n/**\n * The launcher's accessible name: what the button opens, and what waits in it.\n * `Aglyn Assist, 1 AI job needs you`.\n */\nexport function aiJobsLauncherLabel(\n productName: string,\n activity: AiJobsActivity<unknown> | null,\n): string {\n const base = `Open ${productName} Assist`\n if (!activity) return base\n const jobs = activity.count === 1 ? '1 AI job' : `${activity.count} AI jobs`\n const verb = activity.count === 1 ? 'needs' : 'need'\n if (activity.state === 'needs-you') return `${base}, ${jobs} ${verb} you`\n if (activity.state === 'attention') return `${base}, ${jobs} ${verb} attention`\n return `${base}, ${jobs} running`\n}\n\n/** The drawer header's chips, one per state that has a job, loudest first. */\nexport function aiJobsHeaderChips(\n counts: AiJobActivityCounts,\n): Array<{ state: AiJobActivityState; label: string }> {\n return AI_JOB_ACTIVITY_PRECEDENCE.filter((state) => counts[state] > 0).map((state) => ({\n state,\n label:\n state === 'needs-you'\n ? `${counts[state]} ${counts[state] === 1 ? 'needs' : 'need'} you`\n : state === 'attention'\n ? `${counts[state]} ${counts[state] === 1 ? 'needs' : 'need'} attention`\n : `${counts[state]} running`,\n }))\n}\n\n/** What a job makes, as a sentence's subject: `Your site`, `Your page`. */\nconst KIND_NOUNS: Partial<Record<AiJobKind, string>> = {\n site: 'site',\n page: 'page',\n template: 'page template',\n layout: 'layout',\n form: 'form',\n component: 'component',\n email: 'email',\n campaign: 'campaign',\n workflow: 'automation',\n logic: 'function',\n edit: 'change',\n theme: 'theme',\n products: 'products',\n experiment: 'experiment',\n insight: 'answer',\n build: 'build',\n navigation: 'menu',\n duplicate: 'copy',\n settings: 'settings',\n media: 'media library change',\n tracking: 'tracking',\n}\n\n/** The noun for what a job of this kind makes, or `AI job` for a kind with none. */\nexport function aiJobKindNoun(kind: AiJobKind): string {\n return KIND_NOUNS[kind] ?? 'AI job'\n}\n"],"names":["AI_JOB_PLAN_STEP","AI_JOB_SELF_CONFIRM_GRACE_MS","aiJobConfirmingOwnPlan","job","now","Date","status","review","reason","autoConfirm","at","updatedAt","parse","NaN","Number","isFinite","aiJobPhase","next","steps","find","step","name","every","AI_JOB_PHASE_LABELS","queued","planning","building","attention","done","failed","canceled","AI_JOB_ACTIVITY_PRECEDENCE","aiJobActivityState","phase","AI_JOB_ACTIVE_STATUSES","aiJobActivityCounts","jobs","counts","running","state","aiJobsActivity","matching","filter","length","count","aiJobsActivityLabel","activity","verb","aiJobsLauncherLabel","productName","base","aiJobsHeaderChips","map","label","KIND_NOUNS","site","page","template","layout","form","component","email","campaign","workflow","logic","edit","theme","products","experiment","insight","build","navigation","duplicate","settings","media","tracking","aiJobKindNoun","kind"],"mappings":"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"}
@@ -61,7 +61,11 @@ export type AiJobKind = 'page' | 'component' | 'layout' | 'template' | 'edit' |
61
61
  /** A copy of something a site already has, made by core's duplicate (AGL-3740). */
62
62
  | 'duplicate'
63
63
  /** A change to a site's settings, made by code and saved live (AGL-3748). */
64
- | 'settings';
64
+ | 'settings'
65
+ /** Folders, tags, alt text and names in a site's media library, made by code and saved live (AGL-3752). */
66
+ | 'media'
67
+ /** A change to a site's analytics and advertising tags, made by code and saved live (AGL-3751). */
68
+ | 'tracking';
65
69
  /** The union as a value, so a route can validate a body against it. */
66
70
  export declare const AI_JOB_KINDS: readonly AiJobKind[];
67
71
  /**
@@ -166,7 +170,11 @@ export type AiJobOutputResource = 'screen' | 'reusableComponent' | 'layout' | 't
166
170
  /** A post a guided start wrote into the site's blog (AGL-3676): a content collection entry. */
167
171
  | 'entry'
168
172
  /** A site's settings a build changed (AGL-3748): saved live, on the host document. */
169
- | 'settings';
173
+ | 'settings'
174
+ /** Files in a site's media library a build organized (AGL-3752): saved live, on their documents. */
175
+ | 'media'
176
+ /** A site's tracking tags a build changed (AGL-3751): saved live, on the host document. */
177
+ | 'tracking';
170
178
  /**
171
179
  * One thing a job wrote. Addressed by resource and id so the console can
172
180
  * build the "open draft" link without knowing what the runner did; `text`
@@ -36,7 +36,9 @@
36
36
  'build',
37
37
  'navigation',
38
38
  'duplicate',
39
- 'settings'
39
+ 'settings',
40
+ 'media',
41
+ 'tracking'
40
42
  ];
41
43
  export const AI_JOB_TERMINAL_STATUSES = [
42
44
  'done',