@aglyn/plugins-ai 1.0.0-beta.285 → 1.0.0-beta.287

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 (210) hide show
  1. package/package.json +17 -17
  2. package/src/lib/assets/site-styles/nonprofit.webp +0 -0
  3. package/src/lib/assets/site-styles/professional.webp +0 -0
  4. package/src/lib/assets/site-styles/store.webp +0 -0
  5. package/src/lib/assets/site-styles/trades.webp +0 -0
  6. package/src/lib/components/ai-theme-proposal-card.component.js +12 -3
  7. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  8. package/src/lib/declarations.js +5 -2
  9. package/src/lib/declarations.js.map +1 -1
  10. package/src/lib/declarations.server.js +12 -0
  11. package/src/lib/declarations.server.js.map +1 -1
  12. package/src/lib/jobs/ai-build-capabilities.d.ts +3 -1
  13. package/src/lib/jobs/ai-build-capabilities.js +27 -2
  14. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  15. package/src/lib/jobs/ai-image-reads.d.ts +59 -0
  16. package/src/lib/jobs/ai-image-reads.js +138 -0
  17. package/src/lib/jobs/ai-image-reads.js.map +1 -0
  18. package/src/lib/jobs/ai-job-build-step.d.ts +29 -0
  19. package/src/lib/jobs/ai-job-build-step.js +83 -12
  20. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  21. package/src/lib/jobs/ai-job-duplicate-step.d.ts +2 -1
  22. package/src/lib/jobs/ai-job-duplicate-step.js +6 -6
  23. package/src/lib/jobs/ai-job-duplicate-step.js.map +1 -1
  24. package/src/lib/jobs/ai-job-import-step.d.ts +4 -2
  25. package/src/lib/jobs/ai-job-import-step.js +15 -11
  26. package/src/lib/jobs/ai-job-import-step.js.map +1 -1
  27. package/src/lib/jobs/ai-job-insight-step.d.ts +10 -0
  28. package/src/lib/jobs/ai-job-insight-step.js +23 -5
  29. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  30. package/src/lib/jobs/ai-job-layout-language.js +2 -1
  31. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  32. package/src/lib/jobs/ai-job-media-step.d.ts +2 -1
  33. package/src/lib/jobs/ai-job-media-step.js +6 -8
  34. package/src/lib/jobs/ai-job-media-step.js.map +1 -1
  35. package/src/lib/jobs/ai-job-navigation-step.d.ts +2 -1
  36. package/src/lib/jobs/ai-job-navigation-step.js +7 -9
  37. package/src/lib/jobs/ai-job-navigation-step.js.map +1 -1
  38. package/src/lib/jobs/ai-job-page-language.js +7 -1
  39. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  40. package/src/lib/jobs/ai-job-page-sections.js +16 -3
  41. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  42. package/src/lib/jobs/ai-job-page-step.js +9 -1
  43. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  44. package/src/lib/jobs/ai-job-plan-step.d.ts +30 -8
  45. package/src/lib/jobs/ai-job-plan-step.js +78 -13
  46. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  47. package/src/lib/jobs/ai-job-privacy-step.d.ts +4 -2
  48. package/src/lib/jobs/ai-job-privacy-step.js +14 -15
  49. package/src/lib/jobs/ai-job-privacy-step.js.map +1 -1
  50. package/src/lib/jobs/ai-job-products-step.js +5 -3
  51. package/src/lib/jobs/ai-job-products-step.js.map +1 -1
  52. package/src/lib/jobs/ai-job-settings-step.d.ts +2 -1
  53. package/src/lib/jobs/ai-job-settings-step.js +7 -8
  54. package/src/lib/jobs/ai-job-settings-step.js.map +1 -1
  55. package/src/lib/jobs/ai-job-site-content.d.ts +34 -1
  56. package/src/lib/jobs/ai-job-site-content.js +67 -8
  57. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  58. package/src/lib/jobs/ai-job-site-datasets.d.ts +53 -3
  59. package/src/lib/jobs/ai-job-site-datasets.js +298 -30
  60. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  61. package/src/lib/jobs/ai-job-site-step.js +6 -3
  62. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  63. package/src/lib/jobs/ai-job-site-store-pages.js +3 -3
  64. package/src/lib/jobs/ai-job-site-store-pages.js.map +1 -1
  65. package/src/lib/jobs/ai-job-tracking-step.d.ts +2 -1
  66. package/src/lib/jobs/ai-job-tracking-step.js +7 -8
  67. package/src/lib/jobs/ai-job-tracking-step.js.map +1 -1
  68. package/src/lib/jobs/ai-jobs-beat.d.ts +11 -7
  69. package/src/lib/jobs/ai-jobs-beat.js +18 -8
  70. package/src/lib/jobs/ai-jobs-beat.js.map +1 -1
  71. package/src/lib/jobs/ai-jobs-notify.d.ts +66 -1
  72. package/src/lib/jobs/ai-jobs-notify.js +136 -3
  73. package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
  74. package/src/lib/jobs/ai-layout-stock-photos.d.ts +59 -4
  75. package/src/lib/jobs/ai-layout-stock-photos.js +897 -81
  76. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  77. package/src/lib/jobs/ai-product-image.d.ts +14 -0
  78. package/src/lib/jobs/ai-product-image.js +73 -21
  79. package/src/lib/jobs/ai-product-image.js.map +1 -1
  80. package/src/lib/jobs/ai-theme-brand-inputs.d.ts +9 -1
  81. package/src/lib/jobs/ai-theme-brand-inputs.js +28 -2
  82. package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
  83. package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.d.ts +175 -0
  84. package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.js +601 -0
  85. package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.js.map +1 -0
  86. package/src/lib/jobs/fixtures/ai-site-agl4065-pages.d.ts +46 -0
  87. package/src/lib/jobs/fixtures/ai-site-agl4065-pages.js +2862 -0
  88. package/src/lib/jobs/fixtures/ai-site-agl4065-pages.js.map +1 -0
  89. package/src/lib/jobs/fixtures/ai-site-agl4065-plans.d.ts +74 -0
  90. package/src/lib/jobs/fixtures/ai-site-agl4065-plans.js +888 -0
  91. package/src/lib/jobs/fixtures/ai-site-agl4065-plans.js.map +1 -0
  92. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.d.ts +29 -0
  93. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.js +81 -0
  94. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.js.map +1 -0
  95. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.d.ts +87 -0
  96. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.js +225 -0
  97. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.js.map +1 -0
  98. package/src/lib/layout-language/ai-layout-compiler.d.ts +11 -0
  99. package/src/lib/layout-language/ai-layout-compiler.js +194 -18
  100. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  101. package/src/lib/layout-language/ai-layout-copy.d.ts +37 -0
  102. package/src/lib/layout-language/ai-layout-copy.js +383 -3
  103. package/src/lib/layout-language/ai-layout-copy.js.map +1 -1
  104. package/src/lib/layout-language/ai-layout-design.d.ts +19 -0
  105. package/src/lib/layout-language/ai-layout-design.js +31 -11
  106. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  107. package/src/lib/layout-language/ai-layout-listings.d.ts +7 -1
  108. package/src/lib/layout-language/ai-layout-listings.js +9 -1
  109. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  110. package/src/lib/layout-language/ai-layout-pictures.d.ts +30 -0
  111. package/src/lib/layout-language/ai-layout-pictures.js +131 -3
  112. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  113. package/src/lib/layout-language/ai-layout-storefront.d.ts +18 -4
  114. package/src/lib/layout-language/ai-layout-storefront.js +100 -20
  115. package/src/lib/layout-language/ai-layout-storefront.js.map +1 -1
  116. package/src/lib/model/ai-build-job.d.ts +7 -0
  117. package/src/lib/model/ai-build-job.js +36 -7
  118. package/src/lib/model/ai-build-job.js.map +1 -1
  119. package/src/lib/model/ai-build-plan.d.ts +28 -4
  120. package/src/lib/model/ai-build-plan.js +49 -9
  121. package/src/lib/model/ai-build-plan.js.map +1 -1
  122. package/src/lib/model/ai-build-progress.js +3 -1
  123. package/src/lib/model/ai-build-progress.js.map +1 -1
  124. package/src/lib/model/ai-job-notice.d.ts +8 -0
  125. package/src/lib/model/ai-job-notice.js +33 -0
  126. package/src/lib/model/ai-job-notice.js.map +1 -1
  127. package/src/lib/model/ai-job-plan-lines.d.ts +2 -1
  128. package/src/lib/model/ai-job-plan-lines.js +6 -1
  129. package/src/lib/model/ai-job-plan-lines.js.map +1 -1
  130. package/src/lib/model/ai-jobs.types.d.ts +8 -0
  131. package/src/lib/model/ai-jobs.types.js.map +1 -1
  132. package/src/lib/model/ai-permissions.d.ts +13 -0
  133. package/src/lib/model/ai-permissions.js +29 -1
  134. package/src/lib/model/ai-permissions.js.map +1 -1
  135. package/src/lib/model/ai-products.d.ts +18 -0
  136. package/src/lib/model/ai-products.js +32 -0
  137. package/src/lib/model/ai-products.js.map +1 -1
  138. package/src/lib/model/ai-site-inspiration.d.ts +3 -2
  139. package/src/lib/model/ai-site-inspiration.js +3 -2
  140. package/src/lib/model/ai-site-inspiration.js.map +1 -1
  141. package/src/lib/plugin.js +4 -13
  142. package/src/lib/plugin.js.map +1 -1
  143. package/src/lib/providers/anthropic.js +9 -2
  144. package/src/lib/providers/anthropic.js.map +1 -1
  145. package/src/lib/providers/catalog.js +14 -5
  146. package/src/lib/providers/catalog.js.map +1 -1
  147. package/src/lib/providers/contract.d.ts +24 -1
  148. package/src/lib/providers/contract.js.map +1 -1
  149. package/src/lib/providers/routing.js +5 -2
  150. package/src/lib/providers/routing.js.map +1 -1
  151. package/src/lib/runtime/ai-dataset-generation.d.ts +99 -1
  152. package/src/lib/runtime/ai-dataset-generation.js +251 -3
  153. package/src/lib/runtime/ai-dataset-generation.js.map +1 -1
  154. package/src/lib/runtime/ai-dev-replay.d.ts +1 -0
  155. package/src/lib/runtime/ai-dev-replay.js +4 -2
  156. package/src/lib/runtime/ai-dev-replay.js.map +1 -1
  157. package/src/lib/runtime/ai-doctrine-validators.d.ts +47 -3
  158. package/src/lib/runtime/ai-doctrine-validators.js +343 -27
  159. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  160. package/src/lib/runtime/ai-doctrine.js +8 -2
  161. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  162. package/src/lib/runtime/ai-icon-library.d.ts +27 -1
  163. package/src/lib/runtime/ai-icon-library.js +30 -3
  164. package/src/lib/runtime/ai-icon-library.js.map +1 -1
  165. package/src/lib/runtime/ai-products-generation.d.ts +11 -1
  166. package/src/lib/runtime/ai-products-generation.js +51 -4
  167. package/src/lib/runtime/ai-products-generation.js.map +1 -1
  168. package/src/lib/runtime/assist-answer-cache.d.ts +2 -0
  169. package/src/lib/runtime/assist-answer-cache.js +5 -2
  170. package/src/lib/runtime/assist-answer-cache.js.map +1 -1
  171. package/src/lib/server/ai-allotments.js +7 -2
  172. package/src/lib/server/ai-allotments.js.map +1 -1
  173. package/src/lib/server/ai-experiment-versions.js +5 -5
  174. package/src/lib/server/ai-experiment-versions.js.map +1 -1
  175. package/src/lib/server/ai-host-permissions-roster.d.ts +25 -0
  176. package/src/lib/server/ai-host-permissions-roster.js +33 -0
  177. package/src/lib/server/ai-host-permissions-roster.js.map +1 -0
  178. package/src/lib/server/ai-host-permissions.js +8 -6
  179. package/src/lib/server/ai-host-permissions.js.map +1 -1
  180. package/src/lib/server/ai-image-reads-route.d.ts +17 -0
  181. package/src/lib/server/ai-image-reads-route.js +106 -0
  182. package/src/lib/server/ai-image-reads-route.js.map +1 -0
  183. package/src/lib/server/ai-seo-apply.js +5 -5
  184. package/src/lib/server/ai-seo-apply.js.map +1 -1
  185. package/src/lib/server/ai-site-import-route.js +7 -4
  186. package/src/lib/server/ai-site-import-route.js.map +1 -1
  187. package/src/lib/server/assist-build.js +1 -0
  188. package/src/lib/server/assist-build.js.map +1 -1
  189. package/src/lib/server/assist-chat.d.ts +11 -7
  190. package/src/lib/server/assist-chat.js +185 -48
  191. package/src/lib/server/assist-chat.js.map +1 -1
  192. package/src/lib/server/assist-docs-query.d.ts +51 -0
  193. package/src/lib/server/assist-docs-query.js +57 -0
  194. package/src/lib/server/assist-docs-query.js.map +1 -0
  195. package/src/lib/server/assist-jobs-context.d.ts +102 -0
  196. package/src/lib/server/assist-jobs-context.js +227 -0
  197. package/src/lib/server/assist-jobs-context.js.map +1 -0
  198. package/src/lib/server/assist-view-context.d.ts +8 -4
  199. package/src/lib/server/assist-view-context.js +12 -7
  200. package/src/lib/server/assist-view-context.js.map +1 -1
  201. package/src/lib/server.js +12 -0
  202. package/src/lib/server.js.map +1 -1
  203. package/src/lib/tools/ai-dataset-tool.d.ts +11 -1
  204. package/src/lib/tools/ai-dataset-tool.js +24 -7
  205. package/src/lib/tools/ai-dataset-tool.js.map +1 -1
  206. package/src/lib/tools/ai-duplicate-resource-tool.js +5 -6
  207. package/src/lib/tools/ai-duplicate-resource-tool.js.map +1 -1
  208. package/src/lib/components/ai-collaborator-permissions-column.component.d.ts +0 -32
  209. package/src/lib/components/ai-collaborator-permissions-column.component.js +0 -132
  210. package/src/lib/components/ai-collaborator-permissions-column.component.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/server/assist-view-context.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 { resolveEffectivePlan } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport {\n ASSIST_EDIT_ACTION_ID,\n ASSIST_EDIT_DOCUMENT_KINDS,\n ASSIST_EDIT_TOOL_NAME,\n type AssistEditDocumentKind,\n} from '../model/assist-edit'\nimport {\n ASSIST_OPEN_DRAFT_ACTION_ID,\n ASSIST_OPEN_DRAFT_PARAM,\n type AssistBuildDraft,\n} from '../model/assist-follow-up'\n\n/**\n * Aglyn Assist level 2 — the GUIDE capability (AGL-1988, under AGL-1860).\n *\n * Level 1 answers from the docs. Level 2 answers about **the thing in front\n * of the user**: the console screen they are standing on. This module is the\n * whole of that knowledge, and three decisions shape it.\n *\n * ## 1. A curated descriptor, not a state dump\n *\n * The tempting design is to serialise the page — props, records, the loaded\n * list — into the prompt. That buys worse answers at higher cost: the model\n * gets rows when what it needs is *what this screen is for*, and every extra\n * token is billed per turn against a hard margin constraint.\n *\n * So the view block is assembled from a STATIC, in-repo table keyed by route\n * pattern, plus a fixed handful of scalars about the current org. Nothing is\n * read out of the customer's data to build it. That is also why the privacy\n * guard is cheap to state and cheap to prove: there is no path from a\n * Firestore document to this prompt except the two fields `safeOrgFacts`\n * names, and no path from client input except three sanitised strings.\n *\n * ## 2. One answer for both ends of the ICP range\n *\n * the requirement is a single feature that is \"easy for someone who\n * doesn't know code and even easier for someone who does\". Two modes would\n * be the obvious build and the wrong one — it makes the beginner choose a\n * label for themselves before they have a question, and it makes the\n * developer opt in to being taken seriously.\n *\n * Progressive disclosure instead: every view carries a `plain` layer (what\n * you can do here, in words a first-business owner reads) and a `technical`\n * layer (the route template, the ids in the path, the API or field names\n * behind it). The model leads with plain and appends the technical layer\n * under a fixed `Under the hood:` marker, which the panel renders collapsed.\n * Same answer, two depths, one message.\n *\n * ## 3. Actions are PROPOSALS, and a proposal cannot write\n *\n * \"Automate current view\" is level 2's edge and needs a hard boundary — the\n * AGL-1860 ladder puts acting in the besigner at level 3, after launch. The\n * boundary here is structural rather than behavioural, because a rule the\n * model is asked to follow is not a boundary:\n *\n * - An action descriptor has no field that could carry a write. There is no\n * method, no body, no endpoint — only a `route` template and the NAMES of\n * params. `assertInertActions()` proves that over the live table.\n * - The model never supplies a destination. It may name an `id` from the\n * closed set attached to the view it is already on, and supply values for\n * params that view declared. `resolveAssistProposal` builds the href from\n * the registry; an unknown id, a foreign id, or an undeclared param is\n * dropped rather than honoured.\n * - Confirming NAVIGATES. The destination form's own submit button, with\n * its own permission checks, stays the only thing that writes.\n *\n * `prefill` is deliberately `false` across the table today: no console page\n * reads `assist_*` search params yet, and a card that claims \"I filled that\n * in for you\" over a form that came up empty is worse than one that names the\n * values to type. The plumbing is here so switching a page on is a one-line\n * change, and `PREFILL_READY_ROUTES` is the allowlist that has to grow first.\n *\n * ## 4. The one action that edits, and where it opens (AGL-2906)\n *\n * Level 3 makes a single exception, on the besigner only: the `edit` action.\n * The server still writes nothing. The model proposes typed canvas operations\n * through a strict tool, the server validates them against the canvas the\n * request described, and the panel shows the change and applies it in the\n * author's own editor when they press Apply — an unsaved change, undone by\n * the editor's own undo. The rung is closed unless the org carries\n * `aiGenerative`, `release_ai_generative` is on for it and the caller holds\n * `ai.generate`. `offeredActions` hands an edit action only to a request that\n * cleared the rung, and `assertInertActions` fails any edit action offered\n * without it, or one that carries a destination the way a navigation does.\n */\n\n/**\n * One thing the assistant may PROPOSE from a given view, by navigating.\n *\n * Note what this interface cannot express. There is no `method`, no `body`,\n * no `endpoint`, no `submit` — a descriptor is a signpost, and the type is\n * the first line of the write boundary. Adding any of those fields would\n * break `assertInertActions()`, which is the point of writing it down.\n */\nexport interface AssistNavigateAction {\n /** `navigate`, or absent. */\n kind?: 'navigate'\n /** Stable id; the ONLY thing the model gets to choose from. */\n id: string\n /** Beginner-facing label on the confirm card. */\n label: string\n /** What the user will be looking at once they confirm. */\n outcome: string\n /**\n * Server-owned destination template (`/[orgSlug]/…`). Built here, never\n * supplied by the model — so the worst a bad completion can do is send the\n * user to a different page in their own console.\n */\n route: string\n /** Param names the model may supply values for. Anything else is dropped. */\n params: readonly string[]\n /**\n * Whether the destination actually reads `assist_*` search params. False\n * everywhere today — see the module header. When false the values are\n * shown on the card as \"use these\" rather than pushed into the URL.\n */\n prefill: boolean\n}\n\n/**\n * The besigner's edit action (AGL-2906). Its proposal is typed canvas\n * operations the panel applies on confirm — never a destination and never a\n * server write — so it carries none of a navigation's `route`, `params` or\n * `prefill`, and `assertInertActions` fails one that does.\n */\nexport interface AssistEditAction {\n kind: 'edit'\n /** Stable id; the ONLY thing the model gets to choose from. */\n id: string\n /** Beginner-facing label on the confirm button. */\n label: string\n /** What changes once the author confirms. */\n outcome: string\n /** The besigner documents the action opens on. */\n documents: readonly AssistEditDocumentKind[]\n}\n\nexport type AssistViewAction = AssistNavigateAction | AssistEditAction\n\n/** What a request cleared, which decides the actions a view offers it. */\nexport interface AssistActionRung {\n /**\n * The edit rung: `aiGenerative`, `release_ai_generative` and `ai.generate`,\n * on a versioned besigner route, with the canvas described.\n */\n edit: boolean\n}\n\n/** A console screen, described for the assistant rather than for a router. */\nexport interface AssistView {\n /** Registry key; also the analytics/debug handle. */\n key: string\n /** Route matcher. First match wins, so the table is ordered specific-first. */\n match: RegExp\n /** One line: what this screen is. */\n screen: string\n /** The beginner layer — what a person can do here, in plain words. */\n plain: readonly string[]\n /** The developer layer — route template, path ids, API surface. */\n technical: readonly string[]\n /** Proposals available from this view: navigations, and on the besigner the edit action. */\n actions: readonly AssistViewAction[]\n}\n\n/**\n * Destinations verified to read `assist_*` search params. EMPTY on purpose:\n * `assertInertActions()` fails any action with `prefill: true` whose route is\n * not listed, so turning prefill on requires wiring the page first rather\n * than remembering to.\n */\nexport const PREFILL_READY_ROUTES: readonly string[] = []\n\n/** Query-param prefix for proposed values. Namespaced so a page opting in\n * can never confuse an assistant suggestion with its own state. */\nexport const ASSIST_PREFILL_PREFIX = 'assist_'\n\nconst orgAction = (\n id: string,\n label: string,\n outcome: string,\n route: string,\n params: readonly string[] = [],\n): AssistNavigateAction => ({ id, label, outcome, route, params, prefill: false })\n\nconst isNavigateAction = (action: AssistViewAction): action is AssistNavigateAction =>\n action.kind !== 'edit'\n\n/**\n * The registry. Ordered specific-first — `describeView` takes the first\n * match, so a host sub-page must precede the host dashboard, and every\n * host route must precede the bare `/[orgSlug]` home.\n *\n * Content rule: every line here has to be TRUE of the shipped console. These\n * strings are asserted to the model as fact and it will repeat them, so a\n * guessed button label becomes a confidently wrong instruction. Where the\n * exact affordance is uncertain the line names the SCREEN and its purpose\n * and lets docs retrieval supply the steps — a vaguer sentence that is right\n * beats a specific one that is invented.\n */\nexport const ASSIST_VIEWS: readonly AssistView[] = [\n {\n key: 'besigner',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/(screens|components|templates|layouts|emails)\\/.*\\/besigner$/,\n screen: 'The Besigner — the visual editor for a page, component, template, layout or email.',\n plain: [\n 'Edit the page visually: pick an element on the canvas and change its text, styling and layout.',\n 'Work is saved to a draft version; publishing is a separate, deliberate step.',\n 'Preview shows the draft as a visitor would see it, without publishing.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/screens/[screenId]/versions/[versionId]/besigner (and the component, template, layout and email variants).',\n 'Editing writes to the version named in the path — versioning is the undo, so a save never overwrites what is live.',\n 'The preview route is the same path with /preview in place of /besigner.',\n ],\n actions: [\n {\n kind: 'edit',\n id: ASSIST_EDIT_ACTION_ID,\n label: 'Apply as draft',\n outcome: 'the proposed changes on the open canvas, unsaved until the document is saved',\n documents: ASSIST_EDIT_DOCUMENT_KINDS,\n },\n ],\n },\n {\n key: 'host-screens',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/screens(\\/|$)/,\n screen: 'Pages — the list of pages on this site.',\n plain: [\n 'Every page a visitor can reach is listed here.',\n 'Open a page to see its versions, then open the Besigner to edit it.',\n 'Publishing a page version is what puts a change on the live site.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/screens; detail is .../screens/[screenId]/versions/[versionId]/view.',\n '[host] in the path is the site (host) document id, not the domain name.',\n ],\n actions: [\n orgAction(\n 'open.host.screens',\n 'Open Pages',\n 'the list of pages on this site',\n '/[orgSlug]/hosts/[host]/screens',\n ),\n ],\n },\n {\n key: 'host-theme',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/theme(\\/|$)/,\n screen: 'Theme — the site-wide colors, type and spacing every page inherits.',\n plain: [\n 'Change something here and it changes everywhere on the site at once.',\n 'This is the place to fix a color or a font you keep re-setting on individual elements.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/theme.',\n 'Theme values resolve at render time, so pages pick them up without being republished individually.',\n ],\n actions: [],\n },\n {\n // The key is the analytics/debug handle and stays as it is; the section\n // it names is what was renamed. Both addresses match because the console\n // redirects the older one, so a client can report either.\n key: 'host-workflows',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/(automation|workflows)(\\/|$)/,\n screen:\n 'Automation — workflows, actions and webhooks that run when something happens on the site.',\n plain: [\n 'A workflow is a trigger plus the actions that follow it, such as sending an email when a form is submitted.',\n 'Build the trigger first, then add the actions it should run.',\n 'Workflows, Actions and Webhooks are the three sections of this page, in the rail beside it.',\n 'Create with AI, beside Add action and at the top of Workflows, drafts an automation from a description; it arrives switched off for you to review.',\n 'In a saved action’s editor, Explain it says what it does, and Change with AI or Fix with AI writes a changed copy, switched off, beside the original. A failed run in the Runs log has Why did this fail?.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/automation/workflows, /automation/actions or /automation/webhooks.',\n 'Webhook actions let a workflow call an external endpoint; see the automation docs for the payload shape.',\n 'Each action is one document stored with the site; every run is a row in the site’s activity log.',\n ],\n actions: [\n orgAction(\n 'open.host.automation.actions',\n 'Open Actions',\n 'this site’s actions, where Create with AI drafts a new one',\n '/[orgSlug]/hosts/[host]/automation/actions',\n ),\n orgAction(\n 'open.host.automation.workflows',\n 'Open Workflows',\n 'this site’s workflows',\n '/[orgSlug]/hosts/[host]/automation/workflows',\n ),\n ],\n },\n // CRM, Logic, Forms and Emails (AGL-3603), each ahead of the site\n // dashboard that would otherwise take them.\n {\n key: 'host-crm',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/crm(\\/|$)/,\n screen: 'CRM — the contacts, leads, companies, deals and tasks this site works with.',\n plain: [\n 'The sections are Contacts, Leads, Companies, Deals, Tasks, Reports, Fields and Settings, in the rail beside the page.',\n 'Records belong to the workspace; a site lists the ones shared with it.',\n 'On a contact’s, company’s, deal’s or lead’s page, AI can summarize where things stand and suggest a next step; nothing is saved until you save it.',\n 'In the email composer on a record, Draft the message writes a one-to-one email from what you describe. AI never sends it: you do.',\n 'Reports shows the pipeline and where people came from; Ask about your numbers in the Assist panel answers questions about those figures.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/crm/[section], with sections contacts, leads, companies, deals, tasks, reports, fields and settings.',\n 'Records are stored with the organization, one collection per record type, and are shared with a site through their visibleTo scope.',\n 'The CRM is included from the Starter plan; on a plan without it every section shows the upgrade notice.',\n ],\n actions: [\n orgAction('open.host.crm.contacts', 'Open Contacts', 'the contacts shared with this site', '/[orgSlug]/hosts/[host]/crm/contacts'),\n orgAction('open.host.crm.deals', 'Open Deals', 'the deals shared with this site, by stage', '/[orgSlug]/hosts/[host]/crm/deals'),\n orgAction('open.host.crm.reports', 'Open CRM reports', 'this site’s pipeline and lead source figures', '/[orgSlug]/hosts/[host]/crm/reports'),\n ],\n },\n {\n key: 'host-logic',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/logic(\\/|$)/,\n screen: 'Logic — the site’s functions and variables, and the health of the references that use them.',\n plain: [\n 'A variable holds a value pages and functions read by name; a function works something out from what it is given.',\n 'Create with AI at the top of the Functions or Variables card writes one from a description and opens it in the editor, unsaved.',\n 'In a saved function’s editor, Explain it, Change with AI and Fix with AI work on the function as it is saved; a change is put in the editor, unsaved.',\n 'Reference health lists references that point at something the site no longer has; one an automation holds offers Fix with AI, which writes a fixed copy of the automation, switched off.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/logic.',\n 'Functions run in the editor’s own evaluator: arithmetic, the listed built-ins, the function’s parameters and locals, and the site’s variables by name.',\n ],\n actions: [\n orgAction('open.host.logic', 'Open Logic', 'this site’s functions and variables', '/[orgSlug]/hosts/[host]/logic'),\n ],\n },\n {\n key: 'host-forms',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/forms(\\/|$)/,\n screen: 'Forms — the saved forms on this site and what visitors sent through them.',\n plain: [\n 'Create Form starts an empty form; Create with AI, beside it, plans a form from a description and builds it as a draft once you confirm the plan.',\n 'Open a form to see its submissions. Every submission also lands in the Inbox.',\n 'A form shows on the live site only once a page places it.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/forms; one form is /[orgSlug]/hosts/[host]/forms/[formId].',\n 'Forms are documents at hosts/[host]/forms/[formId], counted against the plan’s saved forms per site.',\n ],\n actions: [\n orgAction('open.host.forms', 'Open Forms', 'the saved forms on this site', '/[orgSlug]/hosts/[host]/forms'),\n ],\n },\n {\n key: 'host-emails',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/emails(\\/|$)/,\n screen: 'Emails — the messages this site sends, the templates they are built from, and who they reach.',\n plain: [\n 'The sections are Messages, Templates, Audiences, Topics, Sending, Consent groups and Suppressions, in the rail beside the page.',\n 'Messages lists every email sent or scheduled, each with its own report. A campaign that groups messages lives under Marketing.',\n 'Audiences are the lists you send to; Suppressions are the addresses a send skips; Topics are the streams a recipient can leave one at a time.',\n 'Sending sets the name and address the mail comes from.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/emails/[section], with sections messages, templates, audiences, topics, sending, consent-groups and suppressions.',\n 'Templates are Besigner documents, edited at .../emails/[templateKey]/versions/[versionId]/besigner.',\n 'The workspace-wide view is /[orgSlug]/emails.',\n ],\n actions: [\n orgAction('open.host.emails.messages', 'Open Messages', 'the emails this site sent or scheduled', '/[orgSlug]/hosts/[host]/emails/messages'),\n orgAction('open.host.emails.audiences', 'Open Audiences', 'the lists this site sends to', '/[orgSlug]/hosts/[host]/emails/audiences'),\n ],\n },\n {\n key: 'host-products',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/products(\\/|$)/,\n screen: 'Products — the commerce catalog for this site.',\n plain: [\n 'Add and edit the things this site sells, including price and availability.',\n 'A product has to exist here before a page can put it in front of a buyer.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/products.',\n 'Catalog data is site-scoped: products belong to [host], not to the workspace.',\n ],\n actions: [],\n },\n {\n key: 'host-redirects',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/redirects(\\/|$)/,\n screen: 'Redirects — rules that send one path on this site to another.',\n plain: [\n 'Use a redirect when a page has moved, so an old link still lands somewhere useful.',\n 'Each rule is a path to match and a destination to send it to.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/redirects.',\n 'Rules are evaluated by the tenant runtime on the request path, before the page is resolved.',\n ],\n actions: [\n orgAction(\n 'open.host.redirects',\n 'Open Redirects',\n 'the redirect rules for this site',\n '/[orgSlug]/hosts/[host]/redirects',\n ['source', 'target'],\n ),\n ],\n },\n {\n key: 'host-analytics',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/analytics(\\/|$)/,\n screen: 'Analytics — traffic and visitor figures for this site.',\n plain: [\n 'See how many people visited, and which pages they landed on.',\n 'Figures cover the live site only — drafts and previews are not counted.',\n ],\n technical: ['Route: /[orgSlug]/hosts/[host]/analytics.'],\n actions: [],\n },\n // The Marketing hub's sections (AGL-3603), one view each, ahead of the site\n // dashboard that would otherwise take them.\n {\n key: 'host-marketing-campaigns',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/marketing\\/campaigns(\\/|$)/,\n screen: 'Marketing → Campaigns — the email campaigns sent from this site.',\n plain: [\n 'A campaign groups the emails you send to a set of lists over a window of dates.',\n 'Create campaign starts an empty one. Create with AI writes an email design and a draft campaign from a brief; it is sent to nobody until you pick its lists and schedule it.',\n 'Open a campaign to see its report: delivery, opens, clicks, conversions and revenue, each with the population it is measured against.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/marketing/campaigns; one campaign is .../marketing/campaigns/[campaignId].',\n 'Campaigns and their sends are stored with the organization; a site lists the ones placed on it.',\n ],\n actions: [\n orgAction(\n 'open.host.marketing.campaigns',\n 'Open Campaigns',\n 'the campaigns sent from this site',\n '/[orgSlug]/hosts/[host]/marketing/campaigns',\n ),\n ],\n },\n {\n key: 'host-marketing-overlays',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/marketing\\/overlays(\\/|$)/,\n screen: 'Marketing → Overlays — the announcement bars and popups shown on this site.',\n plain: [\n 'Each bar or popup has its own schedule, the pages it shows on, and an on-switch.',\n 'When several match a page, the first bar and the first popup in the list show; the arrows change the order.',\n 'Create with AI writes a new bar or popup and saves it switched off; Write with AI in the editor fills its copy, unsaved until you save.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/marketing/overlays.',\n 'Each overlay is one document stored with the site, gated by the marketingOverlays plan entitlement.',\n ],\n actions: [\n orgAction(\n 'open.host.marketing.overlays',\n 'Open Overlays',\n 'the announcement bars and popups on this site',\n '/[orgSlug]/hosts/[host]/marketing/overlays',\n ),\n ],\n },\n {\n key: 'host-marketing-conversions',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/marketing\\/conversions(\\/|$)/,\n screen: 'Marketing → Conversions — what this site’s campaigns caused.',\n plain: [\n 'Form submissions, leads, contacts and bookings credited to a campaign, one kind at a time.',\n 'The kinds are never added together: one visit can make a submission, a lead and a contact.',\n 'Ask AI about these numbers explains the figures in words, citing the rows each number comes from.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/marketing/conversions; .../conversions/[campaignId] narrows the list to one campaign.',\n 'Each credit is a record in hosts/[host]/campaignAttributions, keyed by kind and the record it credits.',\n ],\n actions: [\n orgAction(\n 'open.host.marketing.conversions',\n 'Open Conversions',\n 'the conversions this site’s campaigns were credited with',\n '/[orgSlug]/hosts/[host]/marketing/conversions',\n ),\n ],\n },\n {\n key: 'host-marketing-experiments',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/marketing\\/experiments(\\/|$)/,\n screen: 'Marketing → A/B testing — the experiments running on this site.',\n plain: [\n 'An A/B test shows each visitor one of up to four variants of a page, a section or an email, and counts conversions for each.',\n 'Write variants with AI sits in a test’s editor, and Explain this result with AI below its results.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/marketing/experiments.',\n 'Each experiment is one document stored with the site, gated by the abTesting plan entitlement.',\n ],\n actions: [\n orgAction(\n 'open.host.marketing.experiments',\n 'Open A/B testing',\n 'the A/B tests on this site',\n '/[orgSlug]/hosts/[host]/marketing/experiments',\n ),\n ],\n },\n {\n key: 'host-marketing',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/marketing(\\/|$)/,\n screen: 'Marketing — this site’s campaigns, conversions, overlays and A/B tests.',\n plain: [\n 'The sections are Overview, Campaigns, Conversions, Overlays and A/B testing, in the rail beside the page.',\n 'Campaigns are shared with the rest of the workspace; overlays, A/B tests and conversions are this site’s own.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/marketing, with each section a route beneath it.',\n 'The workspace-wide view is /[orgSlug]/marketing.',\n ],\n actions: [\n orgAction(\n 'open.host.marketing',\n 'Open Marketing',\n 'this site’s Marketing page, on its first section',\n '/[orgSlug]/hosts/[host]/marketing',\n ),\n ],\n },\n {\n key: 'host-setup',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/setup(\\/|$)/,\n screen: 'Site setup — the site’s own settings, including its domain.',\n plain: [\n 'Connecting a custom domain starts here.',\n 'A domain change needs DNS records at whoever the domain is registered with; the console shows which ones.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/setup.',\n 'Owner/admin-only controls live on the sibling /admin page rather than here, so collaborators can use Setup safely.',\n ],\n actions: [\n orgAction(\n 'open.host.setup',\n 'Open site setup',\n 'this site’s settings, where a custom domain is connected',\n '/[orgSlug]/hosts/[host]/setup',\n ['domain'],\n ),\n ],\n },\n {\n key: 'host-data',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/data(\\/|$)/,\n screen: 'Site data — the datasets this site reads and writes.',\n plain: [\n 'A dataset is a structured list — products, posts, submissions — that pages can display.',\n 'Define the fields first; pages bind to them afterwards.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/data. The workspace-wide view is /[orgSlug]/data.',\n ],\n actions: [],\n },\n {\n key: 'host-dashboard',\n match: /^\\/[^/]+\\/hosts\\/[^/]+(\\/|$)/,\n screen: 'Site dashboard — the home page for one site in this workspace.',\n plain: [\n 'Everything about one site hangs off this dashboard: its pages, theme, data, commerce and settings.',\n 'A workspace can hold several sites; this is one of them.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]. The [host] segment is the site document id.',\n ],\n actions: [],\n },\n {\n key: 'org-hosts',\n match: /^\\/[^/]+\\/hosts(\\/|$)/,\n screen: 'Sites — every site in this workspace.',\n plain: [\n 'Each row is a site with its own pages, theme and domain.',\n 'Agencies typically run one site per client from here.',\n ],\n technical: ['Route: /[orgSlug]/hosts.'],\n actions: [\n orgAction(\n 'open.org.hosts',\n 'Open Sites',\n 'every site in this workspace',\n '/[orgSlug]/hosts',\n ),\n ],\n },\n {\n key: 'org-marketing',\n match: /^\\/[^/]+\\/marketing(\\/|$)/,\n screen: 'Marketing — campaigns, conversions, overlays and A/B tests across every site in this workspace.',\n plain: [\n 'The same sections as a site’s Marketing page, each answering for every site at once.',\n 'Campaigns belong to the workspace; overlays, A/B tests and conversions are each one site’s, so editing them happens on that site.',\n ],\n technical: [\n 'Route: /[orgSlug]/marketing, with /campaigns, /conversions, /overlays and /experiments beneath it.',\n 'A site’s own hub is /[orgSlug]/hosts/[host]/marketing.',\n ],\n actions: [\n orgAction(\n 'open.org.marketing.campaigns',\n 'Open Campaigns',\n 'every campaign in this workspace',\n '/[orgSlug]/marketing/campaigns',\n ),\n orgAction(\n 'open.org.marketing.conversions',\n 'Open Conversions',\n 'the conversions credited to campaigns, one site at a time',\n '/[orgSlug]/marketing/conversions',\n ),\n ],\n },\n {\n key: 'org-billing',\n match: /^\\/[^/]+\\/billing(\\/|$)/,\n screen: 'Billing & plans — the workspace subscription, plan and invoices.',\n plain: [\n 'The current plan and what it includes, and where to change it.',\n 'Plan limits that stop you elsewhere in the console are set here.',\n ],\n technical: [\n 'Route: /[orgSlug]/billing.',\n 'Entitlements are resolved from the org plan; a limit reached elsewhere reports the entitlement it needs.',\n ],\n actions: [\n orgAction(\n 'open.billing',\n 'Open Billing & plans',\n 'the plan for this workspace and how to change it',\n '/[orgSlug]/billing',\n ),\n ],\n },\n {\n key: 'org-team',\n match: /^\\/[^/]+\\/team(\\/|$)/,\n screen: 'Team — the people in this workspace and what each may do.',\n plain: [\n 'Invite a teammate by email, and choose the role that decides what they can reach.',\n 'Removing someone here removes their access to every site in the workspace.',\n ],\n technical: [\n 'Route: /[orgSlug]/team; one member is /[orgSlug]/team/[uid].',\n 'Seat counts follow the plan, so an invite can be refused by the billing entitlement rather than by permissions.',\n ],\n actions: [\n orgAction(\n 'open.team',\n 'Open Team',\n 'the members of this workspace, where invites are sent',\n '/[orgSlug]/team',\n ['email', 'role'],\n ),\n ],\n },\n {\n key: 'org-data',\n match: /^\\/[^/]+\\/data(\\/|$)/,\n screen: 'Data — datasets shared across the whole workspace.',\n plain: [\n 'Structured lists defined once here can be used by any site in the workspace.',\n 'If a list only belongs to one site, define it on that site’s own Data page instead.',\n 'Define the fields before building the page that displays them — pages bind to fields that already exist.',\n ],\n technical: [\n 'Route: /[orgSlug]/data. The per-site view is /[orgSlug]/hosts/[host]/data.',\n 'Workspace datasets are visible to every site in the workspace, so a field change here reaches all of them.',\n ],\n actions: [\n orgAction(\n 'open.org.data',\n 'Open Data',\n 'the datasets shared across this workspace',\n '/[orgSlug]/data',\n ),\n ],\n },\n {\n key: 'org-automation',\n match: /^\\/[^/]+\\/automation(\\/|$)/,\n screen: 'Automation — org automations, written once and placed on the sites you choose, and every site’s workflows, actions and webhooks.',\n plain: [\n 'An org automation starts on something the server sees — a form, a lead, a booking, a member or a CRM event — and runs on each site it is placed on, as that site.',\n 'Each site can pause an org automation for itself without changing it for the others.',\n 'Create with AI in the Org automations card’s header drafts one from a description and opens it in the editor, switched off and unsaved, for you to place on sites and save.',\n 'A site’s own actions, workflows and webhooks are built on that site’s Automation page.',\n ],\n technical: [\n 'Route: /[orgSlug]/automation/automations, with /workflows, /actions and /webhooks beside it.',\n 'Org automations are documents at orgs/[orgId]/automations/[automationId], written only through the plugin’s save route and gated by the actions plan entitlement.',\n ],\n actions: [\n orgAction('open.org.automation', 'Open org automations', 'the automations this workspace places on its sites', '/[orgSlug]/automation/automations'),\n ],\n },\n {\n key: 'org-crm',\n match: /^\\/[^/]+\\/crm(\\/|$)/,\n screen: 'CRM — the contacts, leads, companies, deals and tasks of every site in this workspace.',\n plain: [\n 'The same sections as a site’s CRM, each answering for every site at once.',\n 'Reports totals the pipeline across every site; Ask about your numbers in the Assist panel answers questions about the pipeline and the deals closed.',\n 'Record summaries and Draft the message work here as they do on a site.',\n ],\n technical: [\n 'Route: /[orgSlug]/crm/[section]. A site’s own hub is /[orgSlug]/hosts/[host]/crm.',\n 'Records live under orgs/[orgId]; the workspace view reads every one, a site’s view only those shared with it.',\n ],\n actions: [\n orgAction('open.org.crm.contacts', 'Open Contacts', 'every contact in this workspace', '/[orgSlug]/crm/contacts'),\n orgAction('open.org.crm.reports', 'Open CRM reports', 'the pipeline across every site', '/[orgSlug]/crm/reports'),\n ],\n },\n {\n key: 'org-emails',\n match: /^\\/[^/]+\\/emails(\\/|$)/,\n screen: 'Emails — the messages, templates and audiences of every site in this workspace.',\n plain: [\n 'The same sections as a site’s Emails page, each answering for every site; audiences and topics belong to the workspace.',\n 'Consent groups decide which sites share a signup, an unsubscribe and a CRM record.',\n ],\n technical: [\n 'Route: /[orgSlug]/emails/[section]. A site’s own page is /[orgSlug]/hosts/[host]/emails.',\n ],\n actions: [\n orgAction('open.org.emails.messages', 'Open Messages', 'every email this workspace sent or scheduled', '/[orgSlug]/emails/messages'),\n ],\n },\n {\n key: 'org-media',\n match: /^\\/[^/]+\\/media(\\/|$)/,\n screen: 'Media — images and files for this workspace.',\n plain: [\n 'Upload once here and use the file on any site in the workspace.',\n 'If the file belongs to one site only, upload it on that site’s own Media page instead.',\n 'Files are referenced by the pages that use them, so replacing a file changes it everywhere it appears.',\n ],\n technical: [\n 'Route: /[orgSlug]/media. The per-site library is /[orgSlug]/hosts/[host]/media.',\n 'Workspace media is shared across every site in the workspace; site media is scoped to that site.',\n ],\n actions: [\n orgAction(\n 'open.org.media',\n 'Open Media',\n 'the images and files shared across this workspace',\n '/[orgSlug]/media',\n ),\n ],\n },\n {\n key: 'org-plugins',\n match: /^\\/[^/]+\\/plugins(\\/|$)/,\n screen: 'Plugins — what is installed in this workspace.',\n plain: [\n 'A plugin adds a feature to the console, to your sites, or to both.',\n 'Open one to see its settings and the permissions it was granted.',\n ],\n technical: [\n 'Route: /[orgSlug]/plugins; one install is /[orgSlug]/plugins/[pluginRef].',\n 'Plugins run inside a sandbox; the permissions listed on the install page are the boundary.',\n ],\n actions: [],\n },\n {\n key: 'org-marketplace',\n match: /^\\/[^/]+\\/marketplace(\\/|$)/,\n screen: 'Marketplace — plugins and items available to install.',\n plain: ['Browse what can be added, and install into this workspace.'],\n technical: [\n 'Route: /[orgSlug]/marketplace; a listing is /[orgSlug]/marketplace/[listingId].',\n 'Publishing your own plugin is /[orgSlug]/marketplace/publish/plugin.',\n ],\n actions: [],\n },\n {\n key: 'org-support',\n match: /^\\/[^/]+\\/support(\\/|$)/,\n screen: 'Support — the channels for getting help.',\n plain: [\n 'Raise a ticket, or ask in the forum.',\n 'Which channels are available depends on the workspace plan.',\n ],\n technical: [\n 'Route: /[orgSlug]/support, which forwards to the channel the plan makes primary.',\n 'Channels are /[orgSlug]/support/tickets and /[orgSlug]/support/forum.',\n ],\n actions: [\n orgAction(\n 'open.support',\n 'Open Support',\n 'the support channels for this workspace',\n '/[orgSlug]/support',\n ),\n ],\n },\n {\n key: 'org-settings',\n match: /^\\/[^/]+\\/settings(\\/|$)/,\n screen: 'Workspace settings — the workspace’s own name, slug and options.',\n plain: ['Settings that apply to the whole workspace rather than to one site.'],\n technical: [\n 'Route: /[orgSlug]/settings.',\n 'The workspace slug is the [orgSlug] segment in every console URL, so changing it changes those links.',\n ],\n actions: [],\n },\n {\n key: 'org-home',\n match: /^\\/[^/]+\\/?$/,\n screen: 'Workspace home — the landing page for one workspace.',\n plain: [\n 'The starting point for a workspace: its sites, and the workspace-wide areas beside them.',\n ],\n technical: ['Route: /[orgSlug].'],\n actions: [],\n },\n]\n\n/** The view the user is standing on, or null where the route is unknown. */\nexport function describeView(route: string): AssistView | null {\n const path = sanitiseRoute(route)\n if (!path) return null\n return ASSIST_VIEWS.find((view) => view.match.test(path)) ?? null\n}\n\n/**\n * The actions a view offers a request: every navigation, and the edit action\n * only to a request that cleared the edit rung.\n */\nexport function offeredActions(\n view: AssistView | null,\n rung: AssistActionRung,\n): readonly AssistViewAction[] {\n if (!view) return []\n return view.actions.filter((action) => action.kind !== 'edit' || rung.edit)\n}\n\n/**\n * Client-supplied strings that end up inside a SYSTEM block have to be\n * treated as hostile, not merely untidy. `route` is whatever the panel says\n * the pathname is, and an attacker who can shape it can otherwise write\n * newlines, backticks and prose into the model's instructions — including a\n * forged action fence, which is the one construct in this feature the model\n * is told to treat as meaningful.\n *\n * So the allowed alphabet is exactly what a console path is made of. Anything\n * else is dropped rather than escaped: there is no legitimate route that\n * needs a backtick, and a sanitiser that tries to preserve intent is a\n * sanitiser with a bypass in it.\n */\nexport function sanitiseRoute(route: string): string {\n const cleaned = String(route ?? '')\n .replace(/[^A-Za-z0-9/_\\-.[\\]]/g, '')\n .slice(0, 200)\n return cleaned.startsWith('/') ? cleaned : ''\n}\n\n/**\n * Same reasoning as `sanitiseRoute`, for a Firestore-shaped document id.\n *\n * Rejects rather than truncates, which is the difference between refusing an\n * id and quietly pointing at a different one: `slice(0, 64)` on an over-long\n * value yields a well-formed id for some OTHER document, and this function's\n * output is substituted straight into a destination path.\n */\nexport function sanitiseId(value: string): string {\n const raw = String(value ?? '')\n return raw.length <= 64 && /^[A-Za-z0-9_-]+$/.test(raw) ? raw : ''\n}\n\n/**\n * The ONLY fields of the org document that may enter a prompt.\n *\n * An allowlist rather than a denylist, and stated as a literal rather than\n * derived, because the failure mode is silent: the org doc grows a field one\n * day — a Stripe customer id, an owner's email, a support note — and a\n * spread would carry it into a third party's API without anything failing.\n * Both fields named here are already on screen for any member who can open\n * the panel, so nothing is disclosed that the asker could not read anyway.\n *\n * The plan is the one the workspace resolves to, as the console's badges name\n * it (AGL-3034): a canceled subscription's stored plan grants nothing, and a\n * staff comp grants a plan the stored field may not name.\n */\nexport function safeOrgFacts(org: Record<string, unknown>): {\n name: string\n plan: string\n} {\n return {\n name: String(org?.['name'] ?? '').slice(0, 120),\n plan: resolveEffectivePlan(org as never).slice(0, 40),\n }\n}\n\n/**\n * The per-VIEW system block — the screen description, and nothing about who\n * is looking at it.\n *\n * The split from `viewFactsBlock` is the whole caching design, and it was\n * not the first shape this took. Folding the workspace name, plan, slug and\n * host id in here reads more natural and quietly destroys the cache: a\n * prefix carrying the tenant's name is unique to that tenant, so on a\n * multi-tenant console every org warms its own copy and the entry is usually\n * cold when it matters. Derived purely from the route, the same block serves\n * every workspace asking anything on that screen — which on a shared console\n * is the difference between a cache that pays and one that mostly writes.\n *\n * So this block is a pure function of the registry and the rung — two\n * variants a route at most, neither carrying a tenant byte — and everything\n * that varies per request lives after the breakpoint.\n */\nexport function viewScreenBlock(\n view: AssistView | null,\n rung: AssistActionRung = { edit: false },\n): string {\n const lines: string[] = []\n if (view) {\n lines.push(`This console page: ${view.screen}`)\n if (view.plain.length) {\n lines.push('What the user can do here:')\n for (const line of view.plain) lines.push(`- ${line}`)\n }\n if (view.technical.length) {\n lines.push('Technical detail for the \"Under the hood\" line:')\n for (const line of view.technical) lines.push(`- ${line}`)\n }\n const offered = offeredActions(view, rung)\n const navigations = offered.filter(isNavigateAction)\n if (navigations.length) {\n lines.push(\n 'Actions you may PROPOSE from this console page (ids are exact; propose at most one, and only when the user asked to get something done):',\n )\n for (const action of navigations) {\n const params = action.params.length\n ? ` params: ${action.params.join(', ')}`\n : ' params: none'\n lines.push(`- id \"${action.id}\" — ${action.label}: opens ${action.outcome}.${params}`)\n }\n } else if (offered.length) {\n lines.push('This console page offers nothing to open. Do not emit an action block here.')\n } else {\n lines.push(\n 'This console page offers no proposable actions. Do not emit an action block here.',\n )\n }\n for (const action of offered) {\n if (action.kind !== 'edit') continue\n lines.push(\n `Edits you may PROPOSE on this console page (id \"${action.id}\"): through the ${ASSIST_EDIT_TOOL_NAME} tool described below, never an action block.`,\n )\n }\n } else {\n lines.push(\n 'This exact console page is not in the assistant’s index of console pages. Answer from the documentation and describe navigation in words rather than asserting what this page contains.',\n 'Do not emit an action block.',\n )\n }\n return lines.join('\\n')\n}\n\n/**\n * The per-REQUEST facts. Everything here varies by workspace or by page, so\n * it sits after the last cache breakpoint where it invalidates nothing.\n *\n * Only `safeOrgFacts`' two allowlisted fields and the three sanitised client\n * strings ever reach this — see the module header.\n */\nexport function viewFactsBlock(facts: {\n route: string\n hostId: string\n orgSlug: string\n name: string\n plan: string\n}): string {\n const lines: string[] = ['Where the user is right now:']\n lines.push(`- Console page path: ${facts.route}`)\n if (facts.orgSlug) lines.push(`- Workspace URL slug: ${facts.orgSlug}`)\n if (facts.hostId) lines.push(`- Selected site (host) id: ${facts.hostId}`)\n if (facts.name) lines.push(`- Workspace name: ${facts.name}`)\n if (facts.plan) lines.push(`- Workspace plan: ${facts.plan}`)\n return lines.join('\\n')\n}\n\n/* ------------------------------------------------------------------ *\n * The proposal channel\n * ------------------------------------------------------------------ */\n\n/** Fence the model wraps a proposal in. Kept out of the visible answer. */\nexport const ASSIST_ACTION_FENCE = '```aglyn:action'\n\n/**\n * How much of `raw` is safe to stream to the user.\n *\n * The proposal fence must never reach the panel as text — a user watching\n * raw JSON appear mid-answer reads it as the assistant breaking. Trimming\n * only at the end is not enough either, because by then the tokens have\n * already been emitted as deltas.\n *\n * So the visible prefix is recomputed on every delta, and the tail is held\n * back only while it could still turn out to be the start of a fence. That\n * matters for latency: holding a fixed window would delay every answer by\n * the fence's length, whereas this holds nothing back unless the text\n * genuinely ends in a backtick run.\n */\nexport function visibleAssistText(raw: string): string {\n const cut = raw.indexOf(ASSIST_ACTION_FENCE)\n if (cut >= 0) return raw.slice(0, cut)\n for (let n = Math.min(ASSIST_ACTION_FENCE.length - 1, raw.length); n > 0; n--) {\n if (ASSIST_ACTION_FENCE.startsWith(raw.slice(raw.length - n))) {\n return raw.slice(0, raw.length - n)\n }\n }\n return raw\n}\n\n/**\n * The visible answer once the stream has ENDED — no holdback.\n *\n * `visibleAssistText` withholds a tail that might still become a fence,\n * which is right mid-stream and wrong at the end: an answer that genuinely\n * closes on a code fence would otherwise lose its last three characters\n * permanently, both on screen and in the stored exchange. Once there are no\n * more deltas the ambiguity is resolved, so the only cut left is a real one.\n */\nexport function finalAssistText(raw: string): string {\n const cut = raw.indexOf(ASSIST_ACTION_FENCE)\n return cut >= 0 ? raw.slice(0, cut) : raw\n}\n\n/** The raw JSON body between the fences, if the model emitted one. */\nexport function extractAssistAction(raw: string): string | null {\n const start = raw.indexOf(ASSIST_ACTION_FENCE)\n if (start < 0) return null\n const bodyStart = start + ASSIST_ACTION_FENCE.length\n const end = raw.indexOf('```', bodyStart)\n const body = (end < 0 ? raw.slice(bodyStart) : raw.slice(bodyStart, end)).trim()\n return body || null\n}\n\n/**\n * The navigation to a draft a build in this chat made (AGL-3616), offered\n * beside the view's own when the request listed any. Its destination is the\n * site's Pages list, where every draft waits: the panel, which holds the\n * draft's own address, sends the person to that draft's Besigner instead and\n * asks the request again there. The server never builds an address from a\n * draft, so a ref cannot steer anyone anywhere the registry does not point.\n */\nexport const ASSIST_OPEN_DRAFT_ACTION: AssistNavigateAction = orgAction(\n ASSIST_OPEN_DRAFT_ACTION_ID,\n 'Open the draft in the Besigner',\n 'a draft this chat built, in the Besigner, where your request is asked again',\n '/[orgSlug]/hosts/[host]/screens',\n [ASSIST_OPEN_DRAFT_PARAM],\n)\n\n/**\n * The view with the open-draft navigation added, where the request listed a\n * draft that is not the one open in this editor (AGL-3833): the open one is\n * edited where it stands, never opened again.\n */\nexport function withAssistOpenDraftAction(\n view: AssistView | null,\n drafts: readonly AssistBuildDraft[],\n): AssistView | null {\n if (!view || !drafts.some((draft) => !draft.open)) return view\n return { ...view, actions: [...view.actions, ASSIST_OPEN_DRAFT_ACTION] }\n}\n\n/** A proposal that survived validation — inert, and safe to show. */\nexport interface AssistProposal {\n id: string\n label: string\n outcome: string\n /** Fully-resolved console path. Server-built; the model never sees it. */\n href: string\n /** Values the model suggested, in the order the action declared them. */\n values: ReadonlyArray<{ name: string; value: string }>\n /** Whether `href` carries the values, or the card must ask the user to type them. */\n prefill: boolean\n /**\n * For the open-draft navigation (AGL-3616): the ref of the draft to open,\n * one the request listed. The panel resolves it to the draft's Besigner\n * and asks the request again there.\n */\n draft?: AssistBuildDraft\n}\n\nconst MAX_PARAM_VALUE_CHARS = 200\n\n/**\n * Turn a model-emitted action block into a proposal, or nothing.\n *\n * Everything here is a narrowing. The id must be one the CURRENT view\n * offers as a navigation — not merely a real id somewhere in the registry,\n * because \"propose the billing page from the besigner\" is exactly the kind\n * of plausible wandering that makes an assistant feel unsafe, and not the\n * edit action either, which is never a destination. Param names must be ones\n * that action declared. The href is composed from the registry template and\n * the caller's own server-resolved slug and host, so a completion cannot\n * steer the user anywhere the registry does not already point.\n *\n * Anything unexpected returns null. A dropped proposal costs the user a\n * button; an honoured bad one costs them trust.\n */\nexport function resolveAssistProposal(\n rawBlock: string | null,\n view: AssistView | null,\n scope: { orgSlug: string; hostId: string },\n drafts: readonly AssistBuildDraft[] = [],\n): AssistProposal | null {\n if (!rawBlock || !view) return null\n const navigations = view.actions.filter(isNavigateAction)\n if (!navigations.length) return null\n\n let parsed: Record<string, unknown>\n try {\n parsed = JSON.parse(rawBlock) as Record<string, unknown>\n } catch {\n return null\n }\n if (!parsed || typeof parsed !== 'object') return null\n\n const id = String(parsed['id'] ?? '').trim()\n const action = navigations.find((candidate) => candidate.id === id)\n if (!action) return null\n\n const supplied = (parsed['params'] ?? {}) as Record<string, unknown>\n const values: Array<{ name: string; value: string }> = []\n if (supplied && typeof supplied === 'object' && !Array.isArray(supplied)) {\n for (const name of action.params) {\n const value = String(supplied[name] ?? '')\n .replace(/[\\r\\n]+/g, ' ')\n .trim()\n .slice(0, MAX_PARAM_VALUE_CHARS)\n if (value) values.push({ name, value })\n }\n }\n\n const href = buildActionHref(action, scope, values)\n if (!href) return null\n\n // The open-draft navigation names a draft the request listed, or nothing.\n if (action.id === ASSIST_OPEN_DRAFT_ACTION_ID) {\n const ref = values.find((value) => value.name === ASSIST_OPEN_DRAFT_PARAM)?.value\n // Never the draft already open here (AGL-3833): that offer is a loop.\n const draft = drafts.find((one) => one.ref === ref && !one.open)\n if (!draft) return null\n return {\n id: action.id,\n label: `Open “${draft.label}” in the Besigner`,\n outcome: `the ${draft.noun} “${draft.label}” in the Besigner, where your request is asked again`,\n href,\n values: [],\n prefill: false,\n draft,\n }\n }\n\n return {\n id: action.id,\n label: action.label,\n outcome: action.outcome,\n href,\n values,\n prefill: action.prefill,\n }\n}\n\n/**\n * Compose the destination from the registry template and the server's own\n * scope values. Returns '' when the template needs a segment this page does\n * not have — a half-substituted path like `/acme/hosts//setup` is a 404 with\n * a confirm button on it, which is worse than no button.\n */\nfunction buildActionHref(\n action: AssistNavigateAction,\n scope: { orgSlug: string; hostId: string },\n values: ReadonlyArray<{ name: string; value: string }>,\n): string {\n const orgSlug = sanitiseId(scope.orgSlug)\n const hostId = sanitiseId(scope.hostId)\n let path = action.route\n if (path.includes('[orgSlug]')) {\n if (!orgSlug) return ''\n path = path.replace('[orgSlug]', orgSlug)\n }\n if (path.includes('[host]')) {\n if (!hostId) return ''\n path = path.replace('[host]', hostId)\n }\n // Nothing above can produce these, which is exactly why they are worth\n // asserting: this is the last line before a path reaches a router.\n if (!path.startsWith('/') || path.includes('..') || path.includes('//')) return ''\n if (/\\[|\\]/.test(path)) return ''\n\n if (!action.prefill || !values.length) return path\n const query = values\n .map(\n ({ name, value }) =>\n `${ASSIST_PREFILL_PREFIX}${encodeURIComponent(name)}=${encodeURIComponent(value)}`,\n )\n .join('&')\n return `${path}?${query}`\n}\n\n/**\n * The write boundary, asserted over the live table rather than described in\n * a comment. Called by the spec; exported so the check travels with the data\n * it checks rather than living in a test file someone can delete.\n *\n * Every action is inert UNLESS it is an edit action and the request cleared\n * the edit rung: a navigation carries no write-capable field and no ops, an\n * edit action carries no destination, and an edit action offered to a\n * request below the rung is a violation. `offer` is the function the doors\n * decide with; a spec passes a broken one to show this check can fail.\n *\n * Returns the list of violations so a failure names the offending action.\n */\nexport function assertInertActions(\n views: readonly AssistView[] = ASSIST_VIEWS,\n rung: AssistActionRung = { edit: false },\n offer: typeof offeredActions = offeredActions,\n): string[] {\n const problems: string[] = []\n const seen = new Set<string>()\n const forbidden = ['method', 'body', 'endpoint', 'submit', 'url', 'api', 'mutation']\n for (const view of views) {\n const offered = offer(view, rung)\n for (const action of view.actions) {\n const where = `${view.key}/${action.id}`\n if (seen.has(action.id)) problems.push(`${where}: duplicate action id`)\n seen.add(action.id)\n for (const key of Object.keys(action)) {\n if (forbidden.includes(key.toLowerCase())) {\n problems.push(`${where}: action carries a write-capable field \"${key}\"`)\n }\n }\n if (action.kind === 'edit') {\n if (!rung.edit && offered.includes(action)) {\n problems.push(\n `${where}: an edit action is offered to a request that did not clear the edit rung`,\n )\n }\n for (const key of ['route', 'params', 'prefill']) {\n if (key in action) {\n problems.push(\n `${where}: an edit action carries \"${key}\" — it applies ops on the open canvas and never navigates`,\n )\n }\n }\n const documents = action.documents ?? []\n if (\n !documents.length ||\n documents.some((kind) => !ASSIST_EDIT_DOCUMENT_KINDS.includes(kind))\n ) {\n problems.push(`${where}: an edit action names a document the edit rung does not open on`)\n }\n continue\n }\n if ('ops' in action) {\n problems.push(`${where}: only an edit action carries ops`)\n }\n if (!action.route.startsWith('/')) {\n problems.push(`${where}: route is not a root-relative console path`)\n }\n if (action.route.includes('/api/')) {\n problems.push(`${where}: route points at an API endpoint`)\n }\n if (action.prefill && !PREFILL_READY_ROUTES.includes(action.route)) {\n problems.push(\n `${where}: prefill:true but ${action.route} is not in PREFILL_READY_ROUTES — wire the page to read ${ASSIST_PREFILL_PREFIX}* params first`,\n )\n }\n }\n }\n return problems\n}\n"],"names":["resolveEffectivePlan","ASSIST_EDIT_ACTION_ID","ASSIST_EDIT_DOCUMENT_KINDS","ASSIST_EDIT_TOOL_NAME","ASSIST_OPEN_DRAFT_ACTION_ID","ASSIST_OPEN_DRAFT_PARAM","PREFILL_READY_ROUTES","ASSIST_PREFILL_PREFIX","orgAction","id","label","outcome","route","params","prefill","isNavigateAction","action","kind","ASSIST_VIEWS","key","match","screen","plain","technical","actions","documents","describeView","path","sanitiseRoute","find","view","test","offeredActions","rung","filter","edit","cleaned","String","replace","slice","startsWith","sanitiseId","value","raw","length","safeOrgFacts","org","name","plan","viewScreenBlock","lines","push","line","offered","navigations","join","viewFactsBlock","facts","orgSlug","hostId","ASSIST_ACTION_FENCE","visibleAssistText","cut","indexOf","n","Math","min","finalAssistText","extractAssistAction","start","bodyStart","end","body","trim","ASSIST_OPEN_DRAFT_ACTION","withAssistOpenDraftAction","drafts","some","draft","open","MAX_PARAM_VALUE_CHARS","resolveAssistProposal","rawBlock","scope","parsed","JSON","parse","candidate","supplied","values","Array","isArray","href","buildActionHref","ref","one","noun","includes","query","map","encodeURIComponent","assertInertActions","views","offer","problems","seen","Set","forbidden","where","has","add","Object","keys","toLowerCase"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,oBAAoB,QAAQ,2CAA0C;AAC/E,SACEC,qBAAqB,EACrBC,0BAA0B,EAC1BC,qBAAqB,QAEhB,0BAAsB;AAC7B,SACEC,2BAA2B,EAC3BC,uBAAuB,QAElB,+BAA2B;AA0JlC;;;;;CAKC,GACD,OAAO,MAAMC,uBAA0C,EAAE,CAAA;AAEzD;iEACiE,GACjE,OAAO,MAAMC,wBAAwB,UAAS;AAE9C,MAAMC,YAAY,CAChBC,IACAC,OACAC,SACAC,OACAC,SAA4B,EAAE,GACJ,CAAA;QAAEJ;QAAIC;QAAOC;QAASC;QAAOC;QAAQC,SAAS;IAAM,CAAA;AAEhF,MAAMC,mBAAmB,CAACC,SACxBA,OAAOC,IAAI,KAAK;AAElB;;;;;;;;;;;CAWC,GACD,OAAO,MAAMC,eAAsC;IACjD;QACEC,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;YACA;SACD;QACDC,SAAS;YACP;gBACEP,MAAM;gBACNR,IAAIR;gBACJS,OAAO;gBACPC,SAAS;gBACTc,WAAWvB;YACb;SACD;IACH;IACA;QACEiB,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,qBACA,cACA,kCACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS,EAAE;IACb;IACA;QACE,wEAAwE;QACxE,yEAAyE;QACzE,0DAA0D;QAC1DL,KAAK;QACLC,OAAO;QACPC,QACE;QACFC,OAAO;YACL;YACA;YACA;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;YACA;SACD;QACDC,SAAS;YACPhB,UACE,gCACA,gBACA,8DACA;YAEFA,UACE,kCACA,kBACA,yBACA;SAEH;IACH;IACA,kEAAkE;IAClE,4CAA4C;IAC5C;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;YACA;SACD;QACDC,SAAS;YACPhB,UAAU,0BAA0B,iBAAiB,sCAAsC;YAC3FA,UAAU,uBAAuB,cAAc,6CAA6C;YAC5FA,UAAU,yBAAyB,oBAAoB,gDAAgD;SACxG;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UAAU,mBAAmB,cAAc,uCAAuC;SACnF;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UAAU,mBAAmB,cAAc,gCAAgC;SAC5E;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;YACA;SACD;QACDC,SAAS;YACPhB,UAAU,6BAA6B,iBAAiB,0CAA0C;YAClGA,UAAU,8BAA8B,kBAAkB,gCAAgC;SAC3F;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS,EAAE;IACb;IACA;QACEL,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,uBACA,kBACA,oCACA,qCACA;gBAAC;gBAAU;aAAS;SAEvB;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YAAC;SAA4C;QACxDC,SAAS,EAAE;IACb;IACA,4EAA4E;IAC5E,4CAA4C;IAC5C;QACEL,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,iCACA,kBACA,qCACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,gCACA,iBACA,iDACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,mCACA,oBACA,4DACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,mCACA,oBACA,8BACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,uBACA,kBACA,oDACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,mBACA,mBACA,4DACA,iCACA;gBAAC;aAAS;SAEb;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;SACD;QACDC,SAAS,EAAE;IACb;IACA;QACEL,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;SACD;QACDC,SAAS,EAAE;IACb;IACA;QACEL,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YAAC;SAA2B;QACvCC,SAAS;YACPhB,UACE,kBACA,cACA,gCACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,gCACA,kBACA,oCACA;YAEFA,UACE,kCACA,oBACA,6DACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,gBACA,wBACA,oDACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,aACA,aACA,yDACA,mBACA;gBAAC;gBAAS;aAAO;SAEpB;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,iBACA,aACA,6CACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UAAU,uBAAuB,wBAAwB,sDAAsD;SAChH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UAAU,yBAAyB,iBAAiB,mCAAmC;YACvFA,UAAU,wBAAwB,oBAAoB,kCAAkC;SACzF;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;SACD;QACDC,SAAS;YACPhB,UAAU,4BAA4B,iBAAiB,gDAAgD;SACxG;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,kBACA,cACA,qDACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;Q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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/server/assist-view-context.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 { PLAN_LABELS, resolveEffectivePlan } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport {\n ASSIST_EDIT_ACTION_ID,\n ASSIST_EDIT_DOCUMENT_KINDS,\n ASSIST_EDIT_TOOL_NAME,\n type AssistEditDocumentKind,\n} from '../model/assist-edit'\nimport {\n ASSIST_OPEN_DRAFT_ACTION_ID,\n ASSIST_OPEN_DRAFT_PARAM,\n type AssistBuildDraft,\n} from '../model/assist-follow-up'\n\n/**\n * Aglyn Assist level 2 — the GUIDE capability (AGL-1988, under AGL-1860).\n *\n * Level 1 answers from the docs. Level 2 answers about **the thing in front\n * of the user**: the console screen they are standing on. This module is the\n * whole of that knowledge, and three decisions shape it.\n *\n * ## 1. A curated descriptor, not a state dump\n *\n * The tempting design is to serialise the page — props, records, the loaded\n * list — into the prompt. That buys worse answers at higher cost: the model\n * gets rows when what it needs is *what this screen is for*, and every extra\n * token is billed per turn against a hard margin constraint.\n *\n * So the view block is assembled from a STATIC, in-repo table keyed by route\n * pattern, plus a fixed handful of scalars about the current org. Nothing is\n * read out of the customer's data to build it. That is also why the privacy\n * guard is cheap to state and cheap to prove: there is no path from a\n * Firestore document to this prompt except the two fields `safeOrgFacts`\n * names, and no path from client input except three sanitised strings.\n *\n * ## 2. One answer for both ends of the ICP range\n *\n * the requirement is a single feature that is \"easy for someone who\n * doesn't know code and even easier for someone who does\". Two modes would\n * be the obvious build and the wrong one — it makes the beginner choose a\n * label for themselves before they have a question, and it makes the\n * developer opt in to being taken seriously.\n *\n * Progressive disclosure instead: every view carries a `plain` layer (what\n * you can do here, in words a first-business owner reads) and a `technical`\n * layer (the route template, the ids in the path, the API or field names\n * behind it). The model leads with plain and appends the technical layer\n * under a fixed `Under the hood:` marker, which the panel renders collapsed.\n * Same answer, two depths, one message.\n *\n * ## 3. Actions are PROPOSALS, and a proposal cannot write\n *\n * \"Automate current view\" is level 2's edge and needs a hard boundary — the\n * AGL-1860 ladder puts acting in the besigner at level 3, after launch. The\n * boundary here is structural rather than behavioural, because a rule the\n * model is asked to follow is not a boundary:\n *\n * - An action descriptor has no field that could carry a write. There is no\n * method, no body, no endpoint — only a `route` template and the NAMES of\n * params. `assertInertActions()` proves that over the live table.\n * - The model never supplies a destination. It may name an `id` from the\n * closed set attached to the view it is already on, and supply values for\n * params that view declared. `resolveAssistProposal` builds the href from\n * the registry; an unknown id, a foreign id, or an undeclared param is\n * dropped rather than honoured.\n * - Confirming NAVIGATES. The destination form's own submit button, with\n * its own permission checks, stays the only thing that writes.\n *\n * `prefill` is deliberately `false` across the table today: no console page\n * reads `assist_*` search params yet, and a card that claims \"I filled that\n * in for you\" over a form that came up empty is worse than one that names the\n * values to type. The plumbing is here so switching a page on is a one-line\n * change, and `PREFILL_READY_ROUTES` is the allowlist that has to grow first.\n *\n * ## 4. The one action that edits, and where it opens (AGL-2906)\n *\n * Level 3 makes a single exception, on the besigner only: the `edit` action.\n * The server still writes nothing. The model proposes typed canvas operations\n * through a strict tool, the server validates them against the canvas the\n * request described, and the panel shows the change and applies it in the\n * author's own editor when they press Apply — an unsaved change, undone by\n * the editor's own undo. The rung is closed unless the org carries\n * `aiGenerative`, `release_ai_generative` is on for it and the caller holds\n * `ai.generate`. `offeredActions` hands an edit action only to a request that\n * cleared the rung, and `assertInertActions` fails any edit action offered\n * without it, or one that carries a destination the way a navigation does.\n */\n\n/**\n * One thing the assistant may PROPOSE from a given view, by navigating.\n *\n * Note what this interface cannot express. There is no `method`, no `body`,\n * no `endpoint`, no `submit` — a descriptor is a signpost, and the type is\n * the first line of the write boundary. Adding any of those fields would\n * break `assertInertActions()`, which is the point of writing it down.\n */\nexport interface AssistNavigateAction {\n /** `navigate`, or absent. */\n kind?: 'navigate'\n /** Stable id; the ONLY thing the model gets to choose from. */\n id: string\n /** Beginner-facing label on the confirm card. */\n label: string\n /** What the user will be looking at once they confirm. */\n outcome: string\n /**\n * Server-owned destination template (`/[orgSlug]/…`). Built here, never\n * supplied by the model — so the worst a bad completion can do is send the\n * user to a different page in their own console.\n */\n route: string\n /** Param names the model may supply values for. Anything else is dropped. */\n params: readonly string[]\n /**\n * Whether the destination actually reads `assist_*` search params. False\n * everywhere today — see the module header. When false the values are\n * shown on the card as \"use these\" rather than pushed into the URL.\n */\n prefill: boolean\n}\n\n/**\n * The besigner's edit action (AGL-2906). Its proposal is typed canvas\n * operations the panel applies on confirm — never a destination and never a\n * server write — so it carries none of a navigation's `route`, `params` or\n * `prefill`, and `assertInertActions` fails one that does.\n */\nexport interface AssistEditAction {\n kind: 'edit'\n /** Stable id; the ONLY thing the model gets to choose from. */\n id: string\n /** Beginner-facing label on the confirm button. */\n label: string\n /** What changes once the author confirms. */\n outcome: string\n /** The besigner documents the action opens on. */\n documents: readonly AssistEditDocumentKind[]\n}\n\nexport type AssistViewAction = AssistNavigateAction | AssistEditAction\n\n/** What a request cleared, which decides the actions a view offers it. */\nexport interface AssistActionRung {\n /**\n * The edit rung: `aiGenerative`, `release_ai_generative` and `ai.generate`,\n * on a versioned besigner route, with the canvas described.\n */\n edit: boolean\n}\n\n/** A console screen, described for the assistant rather than for a router. */\nexport interface AssistView {\n /** Registry key; also the analytics/debug handle. */\n key: string\n /** Route matcher. First match wins, so the table is ordered specific-first. */\n match: RegExp\n /** One line: what this screen is. */\n screen: string\n /** The beginner layer — what a person can do here, in plain words. */\n plain: readonly string[]\n /** The developer layer — route template, path ids, API surface. */\n technical: readonly string[]\n /** Proposals available from this view: navigations, and on the besigner the edit action. */\n actions: readonly AssistViewAction[]\n}\n\n/**\n * Destinations verified to read `assist_*` search params. EMPTY on purpose:\n * `assertInertActions()` fails any action with `prefill: true` whose route is\n * not listed, so turning prefill on requires wiring the page first rather\n * than remembering to.\n */\nexport const PREFILL_READY_ROUTES: readonly string[] = []\n\n/** Query-param prefix for proposed values. Namespaced so a page opting in\n * can never confuse an assistant suggestion with its own state. */\nexport const ASSIST_PREFILL_PREFIX = 'assist_'\n\nconst orgAction = (\n id: string,\n label: string,\n outcome: string,\n route: string,\n params: readonly string[] = [],\n): AssistNavigateAction => ({ id, label, outcome, route, params, prefill: false })\n\nconst isNavigateAction = (action: AssistViewAction): action is AssistNavigateAction =>\n action.kind !== 'edit'\n\n/**\n * The registry. Ordered specific-first — `describeView` takes the first\n * match, so a host sub-page must precede the host dashboard, and every\n * host route must precede the bare `/[orgSlug]` home.\n *\n * Content rule: every line here has to be TRUE of the shipped console. These\n * strings are asserted to the model as fact and it will repeat them, so a\n * guessed button label becomes a confidently wrong instruction. Where the\n * exact affordance is uncertain the line names the SCREEN and its purpose\n * and lets docs retrieval supply the steps — a vaguer sentence that is right\n * beats a specific one that is invented.\n */\nexport const ASSIST_VIEWS: readonly AssistView[] = [\n {\n key: 'besigner',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/(screens|components|templates|layouts|emails)\\/.*\\/besigner$/,\n screen: 'The Besigner — the visual editor for a page, component, template, layout or email.',\n plain: [\n 'Edit the page visually: pick an element on the canvas and change its text, styling and layout.',\n 'Work is saved to a draft version; publishing is a separate, deliberate step.',\n 'Preview shows the draft as a visitor would see it, without publishing.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/screens/[screenId]/versions/[versionId]/besigner (and the component, template, layout and email variants).',\n 'Editing writes to the version named in the path — versioning is the undo, so a save never overwrites what is live.',\n 'The preview route is the same path with /preview in place of /besigner.',\n ],\n actions: [\n {\n kind: 'edit',\n id: ASSIST_EDIT_ACTION_ID,\n label: 'Apply as draft',\n outcome: 'the proposed changes on the open canvas, unsaved until the document is saved',\n documents: ASSIST_EDIT_DOCUMENT_KINDS,\n },\n ],\n },\n {\n key: 'host-screens',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/screens(\\/|$)/,\n screen: 'Pages — the list of pages on this site.',\n plain: [\n 'Every page a visitor can reach is listed here.',\n 'Open a page to see its versions, then open the Besigner to edit it.',\n 'Publishing a page version is what puts a change on the live site.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/screens; detail is .../screens/[screenId]/versions/[versionId]/view.',\n '[host] in the path is the site (host) document id, not the domain name.',\n ],\n actions: [\n orgAction(\n 'open.host.screens',\n 'Open Pages',\n 'the list of pages on this site',\n '/[orgSlug]/hosts/[host]/screens',\n ),\n ],\n },\n {\n key: 'host-theme',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/theme(\\/|$)/,\n screen: 'Theme — the site-wide colors, type and spacing every page inherits.',\n plain: [\n 'Change something here and it changes everywhere on the site at once.',\n 'This is the place to fix a color or a font you keep re-setting on individual elements.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/theme.',\n 'Theme values resolve at render time, so pages pick them up without being republished individually.',\n ],\n actions: [],\n },\n {\n // The key is the analytics/debug handle and stays as it is; the section\n // it names is what was renamed. Both addresses match because the console\n // redirects the older one, so a client can report either.\n key: 'host-workflows',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/(automation|workflows)(\\/|$)/,\n screen:\n 'Automation — workflows, actions and webhooks that run when something happens on the site.',\n plain: [\n 'A workflow is a trigger plus the actions that follow it, such as sending an email when a form is submitted.',\n 'Build the trigger first, then add the actions it should run.',\n 'Workflows, Actions and Webhooks are the three sections of this page, in the rail beside it.',\n 'Create with AI, beside Add action and at the top of Workflows, drafts an automation from a description; it arrives switched off for you to review.',\n 'In a saved action’s editor, Explain it says what it does, and Change with AI or Fix with AI writes a changed copy, switched off, beside the original. A failed run in the Runs log has Why did this fail?.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/automation/workflows, /automation/actions or /automation/webhooks.',\n 'Webhook actions let a workflow call an external endpoint; see the automation docs for the payload shape.',\n 'Each action is one document stored with the site; every run is a row in the site’s activity log.',\n ],\n actions: [\n orgAction(\n 'open.host.automation.actions',\n 'Open Actions',\n 'this site’s actions, where Create with AI drafts a new one',\n '/[orgSlug]/hosts/[host]/automation/actions',\n ),\n orgAction(\n 'open.host.automation.workflows',\n 'Open Workflows',\n 'this site’s workflows',\n '/[orgSlug]/hosts/[host]/automation/workflows',\n ),\n ],\n },\n // CRM, Logic, Forms and Emails (AGL-3603), each ahead of the site\n // dashboard that would otherwise take them.\n {\n key: 'host-crm',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/crm(\\/|$)/,\n screen: 'CRM — the contacts, leads, companies, deals and tasks this site works with.',\n plain: [\n 'The sections are Contacts, Leads, Companies, Deals, Tasks, Reports, Fields and Settings, in the rail beside the page.',\n 'Records belong to the workspace; a site lists the ones shared with it.',\n 'On a contact’s, company’s, deal’s or lead’s page, AI can summarize where things stand and suggest a next step; nothing is saved until you save it.',\n 'In the email composer on a record, Draft the message writes a one-to-one email from what you describe. AI never sends it: you do.',\n 'Reports shows the pipeline and where people came from; Ask about your numbers in the Assist panel answers questions about those figures.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/crm/[section], with sections contacts, leads, companies, deals, tasks, reports, fields and settings.',\n 'Records are stored with the organization, one collection per record type, and are shared with a site through their visibleTo scope.',\n 'The CRM is included from the Starter plan; on a plan without it every section shows the upgrade notice.',\n ],\n actions: [\n orgAction('open.host.crm.contacts', 'Open Contacts', 'the contacts shared with this site', '/[orgSlug]/hosts/[host]/crm/contacts'),\n orgAction('open.host.crm.deals', 'Open Deals', 'the deals shared with this site, by stage', '/[orgSlug]/hosts/[host]/crm/deals'),\n orgAction('open.host.crm.reports', 'Open CRM reports', 'this site’s pipeline and lead source figures', '/[orgSlug]/hosts/[host]/crm/reports'),\n ],\n },\n {\n key: 'host-logic',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/logic(\\/|$)/,\n screen: 'Logic — the site’s functions and variables, and the health of the references that use them.',\n plain: [\n 'A variable holds a value pages and functions read by name; a function works something out from what it is given.',\n 'Create with AI at the top of the Functions or Variables card writes one from a description and opens it in the editor, unsaved.',\n 'In a saved function’s editor, Explain it, Change with AI and Fix with AI work on the function as it is saved; a change is put in the editor, unsaved.',\n 'Reference health lists references that point at something the site no longer has; one an automation holds offers Fix with AI, which writes a fixed copy of the automation, switched off.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/logic.',\n 'Functions run in the editor’s own evaluator: arithmetic, the listed built-ins, the function’s parameters and locals, and the site’s variables by name.',\n ],\n actions: [\n orgAction('open.host.logic', 'Open Logic', 'this site’s functions and variables', '/[orgSlug]/hosts/[host]/logic'),\n ],\n },\n {\n key: 'host-forms',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/forms(\\/|$)/,\n screen: 'Forms — the saved forms on this site and what visitors sent through them.',\n plain: [\n 'Create Form starts an empty form; Create with AI, beside it, plans a form from a description and builds it as a draft once you confirm the plan.',\n 'Open a form to see its submissions. Every submission also lands in the Inbox.',\n 'A form shows on the live site only once a page places it.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/forms; one form is /[orgSlug]/hosts/[host]/forms/[formId].',\n 'Forms are documents at hosts/[host]/forms/[formId], counted against the plan’s saved forms per site.',\n ],\n actions: [\n orgAction('open.host.forms', 'Open Forms', 'the saved forms on this site', '/[orgSlug]/hosts/[host]/forms'),\n ],\n },\n {\n key: 'host-emails',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/emails(\\/|$)/,\n screen: 'Emails — the messages this site sends, the templates they are built from, and who they reach.',\n plain: [\n 'The sections are Messages, Templates, Audiences, Topics, Sending, Consent groups and Suppressions, in the rail beside the page.',\n 'Messages lists every email sent or scheduled, each with its own report. A campaign that groups messages lives under Marketing.',\n 'Audiences are the lists you send to; Suppressions are the addresses a send skips; Topics are the streams a recipient can leave one at a time.',\n 'Sending sets the name and address the mail comes from.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/emails/[section], with sections messages, templates, audiences, topics, sending, consent-groups and suppressions.',\n 'Templates are Besigner documents, edited at .../emails/[templateKey]/versions/[versionId]/besigner.',\n 'The workspace-wide view is /[orgSlug]/emails.',\n ],\n actions: [\n orgAction('open.host.emails.messages', 'Open Messages', 'the emails this site sent or scheduled', '/[orgSlug]/hosts/[host]/emails/messages'),\n orgAction('open.host.emails.audiences', 'Open Audiences', 'the lists this site sends to', '/[orgSlug]/hosts/[host]/emails/audiences'),\n ],\n },\n {\n key: 'host-products',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/products(\\/|$)/,\n screen: 'Products — the commerce catalog for this site.',\n plain: [\n 'Add and edit the things this site sells, including price and availability.',\n 'A product has to exist here before a page can put it in front of a buyer.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/products.',\n 'Catalog data is site-scoped: products belong to [host], not to the workspace.',\n ],\n actions: [],\n },\n {\n key: 'host-redirects',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/redirects(\\/|$)/,\n screen: 'Redirects — rules that send one path on this site to another.',\n plain: [\n 'Use a redirect when a page has moved, so an old link still lands somewhere useful.',\n 'Each rule is a path to match and a destination to send it to.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/redirects.',\n 'Rules are evaluated by the tenant runtime on the request path, before the page is resolved.',\n ],\n actions: [\n orgAction(\n 'open.host.redirects',\n 'Open Redirects',\n 'the redirect rules for this site',\n '/[orgSlug]/hosts/[host]/redirects',\n ['source', 'target'],\n ),\n ],\n },\n {\n key: 'host-analytics',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/analytics(\\/|$)/,\n screen: 'Analytics — traffic and visitor figures for this site.',\n plain: [\n 'See how many people visited, and which pages they landed on.',\n 'Figures cover the live site only — drafts and previews are not counted.',\n ],\n technical: ['Route: /[orgSlug]/hosts/[host]/analytics.'],\n actions: [],\n },\n // The Marketing hub's sections (AGL-3603), one view each, ahead of the site\n // dashboard that would otherwise take them.\n {\n key: 'host-marketing-campaigns',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/marketing\\/campaigns(\\/|$)/,\n screen: 'Marketing → Campaigns — the email campaigns sent from this site.',\n plain: [\n 'A campaign groups the emails you send to a set of lists over a window of dates.',\n 'Create campaign starts an empty one. Create with AI writes an email design and a draft campaign from a brief; it is sent to nobody until you pick its lists and schedule it.',\n 'Open a campaign to see its report: delivery, opens, clicks, conversions and revenue, each with the population it is measured against.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/marketing/campaigns; one campaign is .../marketing/campaigns/[campaignId].',\n 'Campaigns and their sends are stored with the organization; a site lists the ones placed on it.',\n ],\n actions: [\n orgAction(\n 'open.host.marketing.campaigns',\n 'Open Campaigns',\n 'the campaigns sent from this site',\n '/[orgSlug]/hosts/[host]/marketing/campaigns',\n ),\n ],\n },\n {\n key: 'host-marketing-overlays',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/marketing\\/overlays(\\/|$)/,\n screen: 'Marketing → Overlays — the announcement bars and popups shown on this site.',\n plain: [\n 'Each bar or popup has its own schedule, the pages it shows on, and an on-switch.',\n 'When several match a page, the first bar and the first popup in the list show; the arrows change the order.',\n 'Create with AI writes a new bar or popup and saves it switched off; Write with AI in the editor fills its copy, unsaved until you save.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/marketing/overlays.',\n 'Each overlay is one document stored with the site, gated by the marketingOverlays plan entitlement.',\n ],\n actions: [\n orgAction(\n 'open.host.marketing.overlays',\n 'Open Overlays',\n 'the announcement bars and popups on this site',\n '/[orgSlug]/hosts/[host]/marketing/overlays',\n ),\n ],\n },\n {\n key: 'host-marketing-conversions',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/marketing\\/conversions(\\/|$)/,\n screen: 'Marketing → Conversions — what this site’s campaigns caused.',\n plain: [\n 'Form submissions, leads, contacts and bookings credited to a campaign, one kind at a time.',\n 'The kinds are never added together: one visit can make a submission, a lead and a contact.',\n 'Ask AI about these numbers explains the figures in words, citing the rows each number comes from.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/marketing/conversions; .../conversions/[campaignId] narrows the list to one campaign.',\n 'Each credit is a record in hosts/[host]/campaignAttributions, keyed by kind and the record it credits.',\n ],\n actions: [\n orgAction(\n 'open.host.marketing.conversions',\n 'Open Conversions',\n 'the conversions this site’s campaigns were credited with',\n '/[orgSlug]/hosts/[host]/marketing/conversions',\n ),\n ],\n },\n {\n key: 'host-marketing-experiments',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/marketing\\/experiments(\\/|$)/,\n screen: 'Marketing → A/B testing — the experiments running on this site.',\n plain: [\n 'An A/B test shows each visitor one of up to four variants of a page, a section or an email, and counts conversions for each.',\n 'Write variants with AI sits in a test’s editor, and Explain this result with AI below its results.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/marketing/experiments.',\n 'Each experiment is one document stored with the site, gated by the abTesting plan entitlement.',\n ],\n actions: [\n orgAction(\n 'open.host.marketing.experiments',\n 'Open A/B testing',\n 'the A/B tests on this site',\n '/[orgSlug]/hosts/[host]/marketing/experiments',\n ),\n ],\n },\n {\n key: 'host-marketing',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/marketing(\\/|$)/,\n screen: 'Marketing — this site’s campaigns, conversions, overlays and A/B tests.',\n plain: [\n 'The sections are Overview, Campaigns, Conversions, Overlays and A/B testing, in the rail beside the page.',\n 'Campaigns are shared with the rest of the workspace; overlays, A/B tests and conversions are this site’s own.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/marketing, with each section a route beneath it.',\n 'The workspace-wide view is /[orgSlug]/marketing.',\n ],\n actions: [\n orgAction(\n 'open.host.marketing',\n 'Open Marketing',\n 'this site’s Marketing page, on its first section',\n '/[orgSlug]/hosts/[host]/marketing',\n ),\n ],\n },\n {\n key: 'host-setup',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/setup(\\/|$)/,\n screen: 'Site setup — the site’s own settings, including its domain.',\n plain: [\n 'Connecting a custom domain starts here.',\n 'A domain change needs DNS records at whoever the domain is registered with; the console shows which ones.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/setup.',\n 'Owner/admin-only controls live on the sibling /admin page rather than here, so collaborators can use Setup safely.',\n ],\n actions: [\n orgAction(\n 'open.host.setup',\n 'Open site setup',\n 'this site’s settings, where a custom domain is connected',\n '/[orgSlug]/hosts/[host]/setup',\n ['domain'],\n ),\n ],\n },\n {\n key: 'host-data',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/data(\\/|$)/,\n screen: 'Site data — the datasets this site reads and writes.',\n plain: [\n 'A dataset is a structured list — products, posts, submissions — that pages can display.',\n 'Define the fields first; pages bind to them afterwards.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/data. The workspace-wide view is /[orgSlug]/data.',\n ],\n actions: [],\n },\n {\n key: 'host-dashboard',\n match: /^\\/[^/]+\\/hosts\\/[^/]+(\\/|$)/,\n screen: 'Site dashboard — the home page for one site in this workspace.',\n plain: [\n 'Everything about one site hangs off this dashboard: its pages, theme, data, commerce and settings.',\n 'A workspace can hold several sites; this is one of them.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]. The [host] segment is the site document id.',\n ],\n actions: [],\n },\n {\n key: 'org-hosts',\n match: /^\\/[^/]+\\/hosts(\\/|$)/,\n screen: 'Sites — every site in this workspace.',\n plain: [\n 'Each row is a site with its own pages, theme and domain.',\n 'Agencies typically run one site per client from here.',\n ],\n technical: ['Route: /[orgSlug]/hosts.'],\n actions: [\n orgAction(\n 'open.org.hosts',\n 'Open Sites',\n 'every site in this workspace',\n '/[orgSlug]/hosts',\n ),\n ],\n },\n {\n key: 'org-marketing',\n match: /^\\/[^/]+\\/marketing(\\/|$)/,\n screen: 'Marketing — campaigns, conversions, overlays and A/B tests across every site in this workspace.',\n plain: [\n 'The same sections as a site’s Marketing page, each answering for every site at once.',\n 'Campaigns belong to the workspace; overlays, A/B tests and conversions are each one site’s, so editing them happens on that site.',\n ],\n technical: [\n 'Route: /[orgSlug]/marketing, with /campaigns, /conversions, /overlays and /experiments beneath it.',\n 'A site’s own hub is /[orgSlug]/hosts/[host]/marketing.',\n ],\n actions: [\n orgAction(\n 'open.org.marketing.campaigns',\n 'Open Campaigns',\n 'every campaign in this workspace',\n '/[orgSlug]/marketing/campaigns',\n ),\n orgAction(\n 'open.org.marketing.conversions',\n 'Open Conversions',\n 'the conversions credited to campaigns, one site at a time',\n '/[orgSlug]/marketing/conversions',\n ),\n ],\n },\n {\n key: 'org-billing',\n match: /^\\/[^/]+\\/billing(\\/|$)/,\n screen: 'Billing & plans — the workspace subscription, plan and invoices.',\n plain: [\n 'The current plan and what it includes, and where to change it.',\n 'Plan limits that stop you elsewhere in the console are set here.',\n ],\n technical: [\n 'Route: /[orgSlug]/billing.',\n 'Entitlements are resolved from the org plan; a limit reached elsewhere reports the entitlement it needs.',\n ],\n actions: [\n orgAction(\n 'open.billing',\n 'Open Billing & plans',\n 'the plan for this workspace and how to change it',\n '/[orgSlug]/billing',\n ),\n ],\n },\n {\n key: 'org-team',\n match: /^\\/[^/]+\\/team(\\/|$)/,\n screen: 'Team — the people in this workspace and what each may do.',\n plain: [\n 'Invite a teammate by email, and choose the role that decides what they can reach.',\n 'Removing someone here removes their access to every site in the workspace.',\n ],\n technical: [\n 'Route: /[orgSlug]/team; one member is /[orgSlug]/team/[uid].',\n 'Seat counts follow the plan, so an invite can be refused by the billing entitlement rather than by permissions.',\n ],\n actions: [\n orgAction(\n 'open.team',\n 'Open Team',\n 'the members of this workspace, where invites are sent',\n '/[orgSlug]/team',\n ['email', 'role'],\n ),\n ],\n },\n {\n key: 'org-data',\n match: /^\\/[^/]+\\/data(\\/|$)/,\n screen: 'Data — datasets shared across the whole workspace.',\n plain: [\n 'Structured lists defined once here can be used by any site in the workspace.',\n 'If a list only belongs to one site, define it on that site’s own Data page instead.',\n 'Define the fields before building the page that displays them — pages bind to fields that already exist.',\n ],\n technical: [\n 'Route: /[orgSlug]/data. The per-site view is /[orgSlug]/hosts/[host]/data.',\n 'Workspace datasets are visible to every site in the workspace, so a field change here reaches all of them.',\n ],\n actions: [\n orgAction(\n 'open.org.data',\n 'Open Data',\n 'the datasets shared across this workspace',\n '/[orgSlug]/data',\n ),\n ],\n },\n {\n key: 'org-automation',\n match: /^\\/[^/]+\\/automation(\\/|$)/,\n screen: 'Automation — org automations, written once and placed on the sites you choose, and every site’s workflows, actions and webhooks.',\n plain: [\n 'An org automation starts on something the server sees — a form, a lead, a booking, a member or a CRM event — and runs on each site it is placed on, as that site.',\n 'Each site can pause an org automation for itself without changing it for the others.',\n 'Create with AI in the Org automations card’s header drafts one from a description and opens it in the editor, switched off and unsaved, for you to place on sites and save.',\n 'A site’s own actions, workflows and webhooks are built on that site’s Automation page.',\n ],\n technical: [\n 'Route: /[orgSlug]/automation/automations, with /workflows, /actions and /webhooks beside it.',\n 'Org automations are documents at orgs/[orgId]/automations/[automationId], written only through the plugin’s save route and gated by the actions plan entitlement.',\n ],\n actions: [\n orgAction('open.org.automation', 'Open org automations', 'the automations this workspace places on its sites', '/[orgSlug]/automation/automations'),\n ],\n },\n {\n key: 'org-crm',\n match: /^\\/[^/]+\\/crm(\\/|$)/,\n screen: 'CRM — the contacts, leads, companies, deals and tasks of every site in this workspace.',\n plain: [\n 'The same sections as a site’s CRM, each answering for every site at once.',\n 'Reports totals the pipeline across every site; Ask about your numbers in the Assist panel answers questions about the pipeline and the deals closed.',\n 'Record summaries and Draft the message work here as they do on a site.',\n ],\n technical: [\n 'Route: /[orgSlug]/crm/[section]. A site’s own hub is /[orgSlug]/hosts/[host]/crm.',\n 'Records live under orgs/[orgId]; the workspace view reads every one, a site’s view only those shared with it.',\n ],\n actions: [\n orgAction('open.org.crm.contacts', 'Open Contacts', 'every contact in this workspace', '/[orgSlug]/crm/contacts'),\n orgAction('open.org.crm.reports', 'Open CRM reports', 'the pipeline across every site', '/[orgSlug]/crm/reports'),\n ],\n },\n {\n key: 'org-emails',\n match: /^\\/[^/]+\\/emails(\\/|$)/,\n screen: 'Emails — the messages, templates and audiences of every site in this workspace.',\n plain: [\n 'The same sections as a site’s Emails page, each answering for every site; audiences and topics belong to the workspace.',\n 'Consent groups decide which sites share a signup, an unsubscribe and a CRM record.',\n ],\n technical: [\n 'Route: /[orgSlug]/emails/[section]. A site’s own page is /[orgSlug]/hosts/[host]/emails.',\n ],\n actions: [\n orgAction('open.org.emails.messages', 'Open Messages', 'every email this workspace sent or scheduled', '/[orgSlug]/emails/messages'),\n ],\n },\n {\n key: 'org-media',\n match: /^\\/[^/]+\\/media(\\/|$)/,\n screen: 'Media — images and files for this workspace.',\n plain: [\n 'Upload once here and use the file on any site in the workspace.',\n 'If the file belongs to one site only, upload it on that site’s own Media page instead.',\n 'Files are referenced by the pages that use them, so replacing a file changes it everywhere it appears.',\n ],\n technical: [\n 'Route: /[orgSlug]/media. The per-site library is /[orgSlug]/hosts/[host]/media.',\n 'Workspace media is shared across every site in the workspace; site media is scoped to that site.',\n ],\n actions: [\n orgAction(\n 'open.org.media',\n 'Open Media',\n 'the images and files shared across this workspace',\n '/[orgSlug]/media',\n ),\n ],\n },\n {\n key: 'org-plugins',\n match: /^\\/[^/]+\\/plugins(\\/|$)/,\n screen: 'Plugins — what is installed in this workspace.',\n plain: [\n 'A plugin adds a feature to the console, to your sites, or to both.',\n 'Open one to see its settings and the permissions it was granted.',\n ],\n technical: [\n 'Route: /[orgSlug]/plugins; one install is /[orgSlug]/plugins/[pluginRef].',\n 'Plugins run inside a sandbox; the permissions listed on the install page are the boundary.',\n ],\n actions: [],\n },\n {\n key: 'org-marketplace',\n match: /^\\/[^/]+\\/marketplace(\\/|$)/,\n screen: 'Marketplace — plugins and items available to install.',\n plain: ['Browse what can be added, and install into this workspace.'],\n technical: [\n 'Route: /[orgSlug]/marketplace; a listing is /[orgSlug]/marketplace/[listingId].',\n 'Publishing your own plugin is /[orgSlug]/marketplace/publish/plugin.',\n ],\n actions: [],\n },\n {\n key: 'org-support',\n match: /^\\/[^/]+\\/support(\\/|$)/,\n screen: 'Support — the channels for getting help.',\n plain: [\n 'Raise a ticket, or ask in the forum.',\n 'Which channels are available depends on the workspace plan.',\n ],\n technical: [\n 'Route: /[orgSlug]/support, which forwards to the channel the plan makes primary.',\n 'Channels are /[orgSlug]/support/tickets and /[orgSlug]/support/forum.',\n ],\n actions: [\n orgAction(\n 'open.support',\n 'Open Support',\n 'the support channels for this workspace',\n '/[orgSlug]/support',\n ),\n ],\n },\n {\n key: 'org-settings',\n match: /^\\/[^/]+\\/settings(\\/|$)/,\n screen: 'Workspace settings — the workspace’s own name, slug and options.',\n plain: ['Settings that apply to the whole workspace rather than to one site.'],\n technical: [\n 'Route: /[orgSlug]/settings.',\n 'The workspace slug is the [orgSlug] segment in every console URL, so changing it changes those links.',\n ],\n actions: [],\n },\n {\n key: 'org-home',\n match: /^\\/[^/]+\\/?$/,\n screen: 'Workspace home — the landing page for one workspace.',\n plain: [\n 'The starting point for a workspace: its sites, and the workspace-wide areas beside them.',\n ],\n technical: ['Route: /[orgSlug].'],\n actions: [],\n },\n]\n\n/** The view the user is standing on, or null where the route is unknown. */\nexport function describeView(route: string): AssistView | null {\n const path = sanitiseRoute(route)\n if (!path) return null\n return ASSIST_VIEWS.find((view) => view.match.test(path)) ?? null\n}\n\n/**\n * The actions a view offers a request: every navigation, and the edit action\n * only to a request that cleared the edit rung.\n */\nexport function offeredActions(\n view: AssistView | null,\n rung: AssistActionRung,\n): readonly AssistViewAction[] {\n if (!view) return []\n return view.actions.filter((action) => action.kind !== 'edit' || rung.edit)\n}\n\n/**\n * Client-supplied strings that end up inside a SYSTEM block have to be\n * treated as hostile, not merely untidy. `route` is whatever the panel says\n * the pathname is, and an attacker who can shape it can otherwise write\n * newlines, backticks and prose into the model's instructions — including a\n * forged action fence, which is the one construct in this feature the model\n * is told to treat as meaningful.\n *\n * So the allowed alphabet is exactly what a console path is made of. Anything\n * else is dropped rather than escaped: there is no legitimate route that\n * needs a backtick, and a sanitiser that tries to preserve intent is a\n * sanitiser with a bypass in it.\n */\nexport function sanitiseRoute(route: string): string {\n const cleaned = String(route ?? '')\n .replace(/[^A-Za-z0-9/_\\-.[\\]]/g, '')\n .slice(0, 200)\n return cleaned.startsWith('/') ? cleaned : ''\n}\n\n/**\n * Same reasoning as `sanitiseRoute`, for a Firestore-shaped document id.\n *\n * Rejects rather than truncates, which is the difference between refusing an\n * id and quietly pointing at a different one: `slice(0, 64)` on an over-long\n * value yields a well-formed id for some OTHER document, and this function's\n * output is substituted straight into a destination path.\n */\nexport function sanitiseId(value: string): string {\n const raw = String(value ?? '')\n return raw.length <= 64 && /^[A-Za-z0-9_-]+$/.test(raw) ? raw : ''\n}\n\n/**\n * The ONLY fields of the org document that may enter a prompt.\n *\n * An allowlist rather than a denylist, and stated as a literal rather than\n * derived, because the failure mode is silent: the org doc grows a field one\n * day — a Stripe customer id, an owner's email, a support note — and a\n * spread would carry it into a third party's API without anything failing.\n * Both fields named here are already on screen for any member who can open\n * the panel, so nothing is disclosed that the asker could not read anyway.\n *\n * The plan is the one the workspace resolves to, as the console's badges name\n * it (AGL-3034): a canceled subscription's stored plan grants nothing, and a\n * staff comp grants a plan the stored field may not name. By its LABEL\n * (AGL-4075), \"Pro\" rather than `pro`, because the model repeats what it is\n * told and the person reads the badge's word.\n */\nexport function safeOrgFacts(org: Record<string, unknown>): {\n name: string\n plan: string\n} {\n const plan = resolveEffectivePlan(org as never)\n return {\n name: String(org?.['name'] ?? '').slice(0, 120),\n plan: (PLAN_LABELS[plan] ?? plan).slice(0, 40),\n }\n}\n\n/**\n * The per-VIEW system block — the screen description, and nothing about who\n * is looking at it.\n *\n * The split from `viewFactsBlock` is the whole caching design, and it was\n * not the first shape this took. Folding the workspace name, plan, slug and\n * host id in here reads more natural and quietly destroys the cache: a\n * prefix carrying the tenant's name is unique to that tenant, so on a\n * multi-tenant console every org warms its own copy and the entry is usually\n * cold when it matters. Derived purely from the route, the same block serves\n * every workspace asking anything on that screen — which on a shared console\n * is the difference between a cache that pays and one that mostly writes.\n *\n * So this block is a pure function of the registry and the rung — two\n * variants a route at most, neither carrying a tenant byte — and everything\n * that varies per request lives after the breakpoint.\n */\nexport function viewScreenBlock(\n view: AssistView | null,\n rung: AssistActionRung = { edit: false },\n): string {\n const lines: string[] = []\n if (view) {\n lines.push(`This console page: ${view.screen}`)\n if (view.plain.length) {\n lines.push('What the user can do here:')\n for (const line of view.plain) lines.push(`- ${line}`)\n }\n if (view.technical.length) {\n lines.push('Technical detail for the \"Under the hood\" line:')\n for (const line of view.technical) lines.push(`- ${line}`)\n }\n const offered = offeredActions(view, rung)\n const navigations = offered.filter(isNavigateAction)\n if (navigations.length) {\n lines.push(\n 'Actions you may PROPOSE from this console page (ids are exact; propose at most one, and only when the user asked to get something done):',\n )\n for (const action of navigations) {\n const params = action.params.length\n ? ` params: ${action.params.join(', ')}`\n : ' params: none'\n lines.push(`- id \"${action.id}\" — ${action.label}: opens ${action.outcome}.${params}`)\n }\n } else if (offered.length) {\n lines.push('This console page offers nothing to open. Do not emit an action block here.')\n } else {\n lines.push(\n 'This console page offers no proposable actions. Do not emit an action block here.',\n )\n }\n for (const action of offered) {\n if (action.kind !== 'edit') continue\n lines.push(\n `Edits you may PROPOSE on this console page (id \"${action.id}\"): through the ${ASSIST_EDIT_TOOL_NAME} tool described below, never an action block.`,\n )\n }\n } else {\n lines.push(\n 'This exact console page is not in the assistant’s index of console pages. Answer from the documentation and describe navigation in words rather than asserting what this page contains.',\n 'Do not emit an action block.',\n )\n }\n return lines.join('\\n')\n}\n\n/**\n * The per-REQUEST facts. Everything here varies by workspace or by page, so\n * it sits after the last cache breakpoint where it invalidates nothing.\n *\n * Only `safeOrgFacts`' allowlisted name and the three sanitised client\n * strings ever reach this — see the module header. The plan is not here:\n * since AGL-4075 every workspace, Free included, is told its plan in the\n * workspace block (`assistWorkspaceBlock`), so this block would only say it\n * twice.\n */\nexport function viewFactsBlock(facts: {\n route: string\n hostId: string\n orgSlug: string\n name: string\n}): string {\n const lines: string[] = ['Where the user is right now:']\n lines.push(`- Console page path: ${facts.route}`)\n if (facts.orgSlug) lines.push(`- Workspace URL slug: ${facts.orgSlug}`)\n if (facts.hostId) lines.push(`- Selected site (host) id: ${facts.hostId}`)\n if (facts.name) lines.push(`- Workspace name: ${facts.name}`)\n return lines.join('\\n')\n}\n\n/* ------------------------------------------------------------------ *\n * The proposal channel\n * ------------------------------------------------------------------ */\n\n/** Fence the model wraps a proposal in. Kept out of the visible answer. */\nexport const ASSIST_ACTION_FENCE = '```aglyn:action'\n\n/**\n * How much of `raw` is safe to stream to the user.\n *\n * The proposal fence must never reach the panel as text — a user watching\n * raw JSON appear mid-answer reads it as the assistant breaking. Trimming\n * only at the end is not enough either, because by then the tokens have\n * already been emitted as deltas.\n *\n * So the visible prefix is recomputed on every delta, and the tail is held\n * back only while it could still turn out to be the start of a fence. That\n * matters for latency: holding a fixed window would delay every answer by\n * the fence's length, whereas this holds nothing back unless the text\n * genuinely ends in a backtick run.\n */\nexport function visibleAssistText(raw: string): string {\n const cut = raw.indexOf(ASSIST_ACTION_FENCE)\n if (cut >= 0) return raw.slice(0, cut)\n for (let n = Math.min(ASSIST_ACTION_FENCE.length - 1, raw.length); n > 0; n--) {\n if (ASSIST_ACTION_FENCE.startsWith(raw.slice(raw.length - n))) {\n return raw.slice(0, raw.length - n)\n }\n }\n return raw\n}\n\n/**\n * The visible answer once the stream has ENDED — no holdback.\n *\n * `visibleAssistText` withholds a tail that might still become a fence,\n * which is right mid-stream and wrong at the end: an answer that genuinely\n * closes on a code fence would otherwise lose its last three characters\n * permanently, both on screen and in the stored exchange. Once there are no\n * more deltas the ambiguity is resolved, so the only cut left is a real one.\n */\nexport function finalAssistText(raw: string): string {\n const cut = raw.indexOf(ASSIST_ACTION_FENCE)\n return cut >= 0 ? raw.slice(0, cut) : raw\n}\n\n/** The raw JSON body between the fences, if the model emitted one. */\nexport function extractAssistAction(raw: string): string | null {\n const start = raw.indexOf(ASSIST_ACTION_FENCE)\n if (start < 0) return null\n const bodyStart = start + ASSIST_ACTION_FENCE.length\n const end = raw.indexOf('```', bodyStart)\n const body = (end < 0 ? raw.slice(bodyStart) : raw.slice(bodyStart, end)).trim()\n return body || null\n}\n\n/**\n * The navigation to a draft a build in this chat made (AGL-3616), offered\n * beside the view's own when the request listed any. Its destination is the\n * site's Pages list, where every draft waits: the panel, which holds the\n * draft's own address, sends the person to that draft's Besigner instead and\n * asks the request again there. The server never builds an address from a\n * draft, so a ref cannot steer anyone anywhere the registry does not point.\n */\nexport const ASSIST_OPEN_DRAFT_ACTION: AssistNavigateAction = orgAction(\n ASSIST_OPEN_DRAFT_ACTION_ID,\n 'Open the draft in the Besigner',\n 'a draft this chat built, in the Besigner, where your request is asked again',\n '/[orgSlug]/hosts/[host]/screens',\n [ASSIST_OPEN_DRAFT_PARAM],\n)\n\n/**\n * The view with the open-draft navigation added, where the request listed a\n * draft that is not the one open in this editor (AGL-3833): the open one is\n * edited where it stands, never opened again.\n */\nexport function withAssistOpenDraftAction(\n view: AssistView | null,\n drafts: readonly AssistBuildDraft[],\n): AssistView | null {\n if (!view || !drafts.some((draft) => !draft.open)) return view\n return { ...view, actions: [...view.actions, ASSIST_OPEN_DRAFT_ACTION] }\n}\n\n/** A proposal that survived validation — inert, and safe to show. */\nexport interface AssistProposal {\n id: string\n label: string\n outcome: string\n /** Fully-resolved console path. Server-built; the model never sees it. */\n href: string\n /** Values the model suggested, in the order the action declared them. */\n values: ReadonlyArray<{ name: string; value: string }>\n /** Whether `href` carries the values, or the card must ask the user to type them. */\n prefill: boolean\n /**\n * For the open-draft navigation (AGL-3616): the ref of the draft to open,\n * one the request listed. The panel resolves it to the draft's Besigner\n * and asks the request again there.\n */\n draft?: AssistBuildDraft\n}\n\nconst MAX_PARAM_VALUE_CHARS = 200\n\n/**\n * Turn a model-emitted action block into a proposal, or nothing.\n *\n * Everything here is a narrowing. The id must be one the CURRENT view\n * offers as a navigation — not merely a real id somewhere in the registry,\n * because \"propose the billing page from the besigner\" is exactly the kind\n * of plausible wandering that makes an assistant feel unsafe, and not the\n * edit action either, which is never a destination. Param names must be ones\n * that action declared. The href is composed from the registry template and\n * the caller's own server-resolved slug and host, so a completion cannot\n * steer the user anywhere the registry does not already point.\n *\n * Anything unexpected returns null. A dropped proposal costs the user a\n * button; an honoured bad one costs them trust.\n */\nexport function resolveAssistProposal(\n rawBlock: string | null,\n view: AssistView | null,\n scope: { orgSlug: string; hostId: string },\n drafts: readonly AssistBuildDraft[] = [],\n): AssistProposal | null {\n if (!rawBlock || !view) return null\n const navigations = view.actions.filter(isNavigateAction)\n if (!navigations.length) return null\n\n let parsed: Record<string, unknown>\n try {\n parsed = JSON.parse(rawBlock) as Record<string, unknown>\n } catch {\n return null\n }\n if (!parsed || typeof parsed !== 'object') return null\n\n const id = String(parsed['id'] ?? '').trim()\n const action = navigations.find((candidate) => candidate.id === id)\n if (!action) return null\n\n const supplied = (parsed['params'] ?? {}) as Record<string, unknown>\n const values: Array<{ name: string; value: string }> = []\n if (supplied && typeof supplied === 'object' && !Array.isArray(supplied)) {\n for (const name of action.params) {\n const value = String(supplied[name] ?? '')\n .replace(/[\\r\\n]+/g, ' ')\n .trim()\n .slice(0, MAX_PARAM_VALUE_CHARS)\n if (value) values.push({ name, value })\n }\n }\n\n const href = buildActionHref(action, scope, values)\n if (!href) return null\n\n // The open-draft navigation names a draft the request listed, or nothing.\n if (action.id === ASSIST_OPEN_DRAFT_ACTION_ID) {\n const ref = values.find((value) => value.name === ASSIST_OPEN_DRAFT_PARAM)?.value\n // Never the draft already open here (AGL-3833): that offer is a loop.\n const draft = drafts.find((one) => one.ref === ref && !one.open)\n if (!draft) return null\n return {\n id: action.id,\n label: `Open “${draft.label}” in the Besigner`,\n outcome: `the ${draft.noun} “${draft.label}” in the Besigner, where your request is asked again`,\n href,\n values: [],\n prefill: false,\n draft,\n }\n }\n\n return {\n id: action.id,\n label: action.label,\n outcome: action.outcome,\n href,\n values,\n prefill: action.prefill,\n }\n}\n\n/**\n * Compose the destination from the registry template and the server's own\n * scope values. Returns '' when the template needs a segment this page does\n * not have — a half-substituted path like `/acme/hosts//setup` is a 404 with\n * a confirm button on it, which is worse than no button.\n */\nfunction buildActionHref(\n action: AssistNavigateAction,\n scope: { orgSlug: string; hostId: string },\n values: ReadonlyArray<{ name: string; value: string }>,\n): string {\n const orgSlug = sanitiseId(scope.orgSlug)\n const hostId = sanitiseId(scope.hostId)\n let path = action.route\n if (path.includes('[orgSlug]')) {\n if (!orgSlug) return ''\n path = path.replace('[orgSlug]', orgSlug)\n }\n if (path.includes('[host]')) {\n if (!hostId) return ''\n path = path.replace('[host]', hostId)\n }\n // Nothing above can produce these, which is exactly why they are worth\n // asserting: this is the last line before a path reaches a router.\n if (!path.startsWith('/') || path.includes('..') || path.includes('//')) return ''\n if (/\\[|\\]/.test(path)) return ''\n\n if (!action.prefill || !values.length) return path\n const query = values\n .map(\n ({ name, value }) =>\n `${ASSIST_PREFILL_PREFIX}${encodeURIComponent(name)}=${encodeURIComponent(value)}`,\n )\n .join('&')\n return `${path}?${query}`\n}\n\n/**\n * The write boundary, asserted over the live table rather than described in\n * a comment. Called by the spec; exported so the check travels with the data\n * it checks rather than living in a test file someone can delete.\n *\n * Every action is inert UNLESS it is an edit action and the request cleared\n * the edit rung: a navigation carries no write-capable field and no ops, an\n * edit action carries no destination, and an edit action offered to a\n * request below the rung is a violation. `offer` is the function the doors\n * decide with; a spec passes a broken one to show this check can fail.\n *\n * Returns the list of violations so a failure names the offending action.\n */\nexport function assertInertActions(\n views: readonly AssistView[] = ASSIST_VIEWS,\n rung: AssistActionRung = { edit: false },\n offer: typeof offeredActions = offeredActions,\n): string[] {\n const problems: string[] = []\n const seen = new Set<string>()\n const forbidden = ['method', 'body', 'endpoint', 'submit', 'url', 'api', 'mutation']\n for (const view of views) {\n const offered = offer(view, rung)\n for (const action of view.actions) {\n const where = `${view.key}/${action.id}`\n if (seen.has(action.id)) problems.push(`${where}: duplicate action id`)\n seen.add(action.id)\n for (const key of Object.keys(action)) {\n if (forbidden.includes(key.toLowerCase())) {\n problems.push(`${where}: action carries a write-capable field \"${key}\"`)\n }\n }\n if (action.kind === 'edit') {\n if (!rung.edit && offered.includes(action)) {\n problems.push(\n `${where}: an edit action is offered to a request that did not clear the edit rung`,\n )\n }\n for (const key of ['route', 'params', 'prefill']) {\n if (key in action) {\n problems.push(\n `${where}: an edit action carries \"${key}\" — it applies ops on the open canvas and never navigates`,\n )\n }\n }\n const documents = action.documents ?? []\n if (\n !documents.length ||\n documents.some((kind) => !ASSIST_EDIT_DOCUMENT_KINDS.includes(kind))\n ) {\n problems.push(`${where}: an edit action names a document the edit rung does not open on`)\n }\n continue\n }\n if ('ops' in action) {\n problems.push(`${where}: only an edit action carries ops`)\n }\n if (!action.route.startsWith('/')) {\n problems.push(`${where}: route is not a root-relative console path`)\n }\n if (action.route.includes('/api/')) {\n problems.push(`${where}: route points at an API endpoint`)\n }\n if (action.prefill && !PREFILL_READY_ROUTES.includes(action.route)) {\n problems.push(\n `${where}: prefill:true but ${action.route} is not in PREFILL_READY_ROUTES — wire the page to read ${ASSIST_PREFILL_PREFIX}* params first`,\n )\n }\n }\n }\n return problems\n}\n"],"names":["PLAN_LABELS","resolveEffectivePlan","ASSIST_EDIT_ACTION_ID","ASSIST_EDIT_DOCUMENT_KINDS","ASSIST_EDIT_TOOL_NAME","ASSIST_OPEN_DRAFT_ACTION_ID","ASSIST_OPEN_DRAFT_PARAM","PREFILL_READY_ROUTES","ASSIST_PREFILL_PREFIX","orgAction","id","label","outcome","route","params","prefill","isNavigateAction","action","kind","ASSIST_VIEWS","key","match","screen","plain","technical","actions","documents","describeView","path","sanitiseRoute","find","view","test","offeredActions","rung","filter","edit","cleaned","String","replace","slice","startsWith","sanitiseId","value","raw","length","safeOrgFacts","org","plan","name","viewScreenBlock","lines","push","line","offered","navigations","join","viewFactsBlock","facts","orgSlug","hostId","ASSIST_ACTION_FENCE","visibleAssistText","cut","indexOf","n","Math","min","finalAssistText","extractAssistAction","start","bodyStart","end","body","trim","ASSIST_OPEN_DRAFT_ACTION","withAssistOpenDraftAction","drafts","some","draft","open","MAX_PARAM_VALUE_CHARS","resolveAssistProposal","rawBlock","scope","parsed","JSON","parse","candidate","supplied","values","Array","isArray","href","buildActionHref","ref","one","noun","includes","query","map","encodeURIComponent","assertInertActions","views","offer","problems","seen","Set","forbidden","where","has","add","Object","keys","toLowerCase"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,WAAW,EAAEC,oBAAoB,QAAQ,2CAA0C;AAC5F,SACEC,qBAAqB,EACrBC,0BAA0B,EAC1BC,qBAAqB,QAEhB,0BAAsB;AAC7B,SACEC,2BAA2B,EAC3BC,uBAAuB,QAElB,+BAA2B;AA0JlC;;;;;CAKC,GACD,OAAO,MAAMC,uBAA0C,EAAE,CAAA;AAEzD;iEACiE,GACjE,OAAO,MAAMC,wBAAwB,UAAS;AAE9C,MAAMC,YAAY,CAChBC,IACAC,OACAC,SACAC,OACAC,SAA4B,EAAE,GACJ,CAAA;QAAEJ;QAAIC;QAAOC;QAASC;QAAOC;QAAQC,SAAS;IAAM,CAAA;AAEhF,MAAMC,mBAAmB,CAACC,SACxBA,OAAOC,IAAI,KAAK;AAElB;;;;;;;;;;;CAWC,GACD,OAAO,MAAMC,eAAsC;IACjD;QACEC,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;YACA;SACD;QACDC,SAAS;YACP;gBACEP,MAAM;gBACNR,IAAIR;gBACJS,OAAO;gBACPC,SAAS;gBACTc,WAAWvB;YACb;SACD;IACH;IACA;QACEiB,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,qBACA,cACA,kCACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS,EAAE;IACb;IACA;QACE,wEAAwE;QACxE,yEAAyE;QACzE,0DAA0D;QAC1DL,KAAK;QACLC,OAAO;QACPC,QACE;QACFC,OAAO;YACL;YACA;YACA;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;YACA;SACD;QACDC,SAAS;YACPhB,UACE,gCACA,gBACA,8DACA;YAEFA,UACE,kCACA,kBACA,yBACA;SAEH;IACH;IACA,kEAAkE;IAClE,4CAA4C;IAC5C;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;YACA;SACD;QACDC,SAAS;YACPhB,UAAU,0BAA0B,iBAAiB,sCAAsC;YAC3FA,UAAU,uBAAuB,cAAc,6CAA6C;YAC5FA,UAAU,yBAAyB,oBAAoB,gDAAgD;SACxG;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UAAU,mBAAmB,cAAc,uCAAuC;SACnF;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UAAU,mBAAmB,cAAc,gCAAgC;SAC5E;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;YACA;SACD;QACDC,SAAS;YACPhB,UAAU,6BAA6B,iBAAiB,0CAA0C;YAClGA,UAAU,8BAA8B,kBAAkB,gCAAgC;SAC3F;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS,EAAE;IACb;IACA;QACEL,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,uBACA,kBACA,oCACA,qCACA;gBAAC;gBAAU;aAAS;SAEvB;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YAAC;SAA4C;QACxDC,SAAS,EAAE;IACb;IACA,4EAA4E;IAC5E,4CAA4C;IAC5C;QACEL,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,iCACA,kBACA,qCACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,gCACA,iBACA,iDACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,mCACA,oBACA,4DACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,mCACA,oBACA,8BACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,uBACA,kBACA,oDACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,mBACA,mBACA,4DACA,iCACA;gBAAC;aAAS;SAEb;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;SACD;QACDC,SAAS,EAAE;IACb;IACA;QACEL,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;SACD;QACDC,SAAS,EAAE;IACb;IACA;QACEL,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YAAC;SAA2B;QACvCC,SAAS;YACPhB,UACE,kBACA,cACA,gCACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,gCACA,kBACA,oCACA;YAEFA,UACE,kCACA,oBACA,6DACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,gBACA,wBACA,oDACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,aACA,aACA,yDACA,mBACA;gBAAC;gBAAS;aAAO;SAEpB;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,iBACA,aACA,6CACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UAAU,uBAAuB,wBAAwB,sDAAsD;SAChH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UAAU,yBAAyB,iBAAiB,mCAAmC;YACvFA,UAAU,wBAAwB,oBAAoB,kCAAkC;SACzF;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;SACD;QACDC,SAAS;YACPhB,UAAU,4BAA4B,iBAAiB,gDAAgD;SACxG;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,kBACA,cACA,qDACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS,EAAE;IACb;IACA;QACEL,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YAAC;SAA6D;QACrEC,WAAW;YACT;YACA;SACD;QACDC,SAAS,EAAE;IACb;IACA;QACEL,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,gBACA,gBACA,2CACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YAAC;SAAsE;QAC9EC,WAAW;YACT;YACA;SACD;QACDC,SAAS,EAAE;IACb;IACA;QACEL,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;SACD;QACDC,WAAW;YAAC;SAAqB;QACjCC,SAAS,EAAE;IACb;CACD,CAAA;AAED,0EAA0E,GAC1E,OAAO,SAASE,aAAad,KAAa;QAGjCM;IAFP,MAAMS,OAAOC,cAAchB;IAC3B,IAAI,CAACe,MAAM,OAAO;IAClB,QAAOT,qBAAAA,aAAaW,IAAI,CAAC,CAACC,OAASA,KAAKV,KAAK,CAACW,IAAI,CAACJ,kBAA5CT,qBAAsD;AAC/D;AAEA;;;CAGC,GACD,OAAO,SAASc,eACdF,IAAuB,EACvBG,IAAsB;IAEtB,IAAI,CAACH,MAAM,OAAO,EAAE;IACpB,OAAOA,KAAKN,OAAO,CAACU,MAAM,CAAC,CAAClB,SAAWA,OAAOC,IAAI,KAAK,UAAUgB,KAAKE,IAAI;AAC5E;AAEA;;;;;;;;;;;;CAYC,GACD,OAAO,SAASP,cAAchB,KAAa;IACzC,MAAMwB,UAAUC,OAAOzB,gBAAAA,QAAS,IAC7B0B,OAAO,CAAC,yBAAyB,IACjCC,KAAK,CAAC,GAAG;IACZ,OAAOH,QAAQI,UAAU,CAAC,OAAOJ,UAAU;AAC7C;AAEA;;;;;;;CAOC,GACD,OAAO,SAASK,WAAWC,KAAa;IACtC,MAAMC,MAAMN,OAAOK,gBAAAA,QAAS;IAC5B,OAAOC,IAAIC,MAAM,IAAI,MAAM,mBAAmBb,IAAI,CAACY,OAAOA,MAAM;AAClE;AAEA;;;;;;;;;;;;;;;CAeC,GACD,OAAO,SAASE,aAAaC,GAA4B;cAO9C/C;IAHT,MAAMgD,OAAO/C,qBAAqB8C;IAClC,OAAO;QACLE,MAAMX,eAAOS,uBAAAA,GAAK,CAAC,OAAO,mBAAI,IAAIP,KAAK,CAAC,GAAG;QAC3CQ,MAAM,EAAChD,oBAAAA,WAAW,CAACgD,KAAK,YAAjBhD,oBAAqBgD,MAAMR,KAAK,CAAC,GAAG;IAC7C;AACF;AAEA;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASU,gBACdnB,IAAuB,EACvBG,OAAyB;IAAEE,MAAM;AAAM,CAAC;IAExC,MAAMe,QAAkB,EAAE;IAC1B,IAAIpB,MAAM;QACRoB,MAAMC,IAAI,CAAC,CAAC,mBAAmB,EAAErB,KAAKT,MAAM,EAAE;QAC9C,IAAIS,KAAKR,KAAK,CAACsB,MAAM,EAAE;YACrBM,MAAMC,IAAI,CAAC;YACX,KAAK,MAAMC,QAAQtB,KAAKR,KAAK,CAAE4B,MAAMC,IAAI,CAAC,CAAC,EAAE,EAAEC,MAAM;QACvD;QACA,IAAItB,KAAKP,SAAS,CAACqB,MAAM,EAAE;YACzBM,MAAMC,IAAI,CAAC;YACX,KAAK,MAAMC,QAAQtB,KAAKP,SAAS,CAAE2B,MAAMC,IAAI,CAAC,CAAC,EAAE,EAAEC,MAAM;QAC3D;QACA,MAAMC,UAAUrB,eAAeF,MAAMG;QACrC,MAAMqB,cAAcD,QAAQnB,MAAM,CAACnB;QACnC,IAAIuC,YAAYV,MAAM,EAAE;YACtBM,MAAMC,IAAI,CACR;YAEF,KAAK,MAAMnC,UAAUsC,YAAa;gBAChC,MAAMzC,SAASG,OAAOH,MAAM,CAAC+B,MAAM,GAC/B,CAAC,SAAS,EAAE5B,OAAOH,MAAM,CAAC0C,IAAI,CAAC,OAAO,GACtC;gBACJL,MAAMC,IAAI,CAAC,CAAC,MAAM,EAAEnC,OAAOP,EAAE,CAAC,IAAI,EAAEO,OAAON,KAAK,CAAC,QAAQ,EAAEM,OAAOL,OAAO,CAAC,CAAC,EAAEE,QAAQ;YACvF;QACF,OAAO,IAAIwC,QAAQT,MAAM,EAAE;YACzBM,MAAMC,IAAI,CAAC;QACb,OAAO;YACLD,MAAMC,IAAI,CACR;QAEJ;QACA,KAAK,MAAMnC,UAAUqC,QAAS;YAC5B,IAAIrC,OAAOC,IAAI,KAAK,QAAQ;YAC5BiC,MAAMC,IAAI,CACR,CAAC,gDAAgD,EAAEnC,OAAOP,EAAE,CAAC,gBAAgB,EAAEN,sBAAsB,6CAA6C,CAAC;QAEvJ;IACF,OAAO;QACL+C,MAAMC,IAAI,CACR,2LACA;IAEJ;IACA,OAAOD,MAAMK,IAAI,CAAC;AACpB;AAEA;;;;;;;;;CASC,GACD,OAAO,SAASC,eAAeC,KAK9B;IACC,MAAMP,QAAkB;QAAC;KAA+B;IACxDA,MAAMC,IAAI,CAAC,CAAC,qBAAqB,EAAEM,MAAM7C,KAAK,EAAE;IAChD,IAAI6C,MAAMC,OAAO,EAAER,MAAMC,IAAI,CAAC,CAAC,sBAAsB,EAAEM,MAAMC,OAAO,EAAE;IACtE,IAAID,MAAME,MAAM,EAAET,MAAMC,IAAI,CAAC,CAAC,2BAA2B,EAAEM,MAAME,MAAM,EAAE;IACzE,IAAIF,MAAMT,IAAI,EAAEE,MAAMC,IAAI,CAAC,CAAC,kBAAkB,EAAEM,MAAMT,IAAI,EAAE;IAC5D,OAAOE,MAAMK,IAAI,CAAC;AACpB;AAEA;;sEAEsE,GAEtE,yEAAyE,GACzE,OAAO,MAAMK,sBAAsB,kBAAiB;AAEpD;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASC,kBAAkBlB,GAAW;IAC3C,MAAMmB,MAAMnB,IAAIoB,OAAO,CAACH;IACxB,IAAIE,OAAO,GAAG,OAAOnB,IAAIJ,KAAK,CAAC,GAAGuB;IAClC,IAAK,IAAIE,IAAIC,KAAKC,GAAG,CAACN,oBAAoBhB,MAAM,GAAG,GAAGD,IAAIC,MAAM,GAAGoB,IAAI,GAAGA,IAAK;QAC7E,IAAIJ,oBAAoBpB,UAAU,CAACG,IAAIJ,KAAK,CAACI,IAAIC,MAAM,GAAGoB,KAAK;YAC7D,OAAOrB,IAAIJ,KAAK,CAAC,GAAGI,IAAIC,MAAM,GAAGoB;QACnC;IACF;IACA,OAAOrB;AACT;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASwB,gBAAgBxB,GAAW;IACzC,MAAMmB,MAAMnB,IAAIoB,OAAO,CAACH;IACxB,OAAOE,OAAO,IAAInB,IAAIJ,KAAK,CAAC,GAAGuB,OAAOnB;AACxC;AAEA,oEAAoE,GACpE,OAAO,SAASyB,oBAAoBzB,GAAW;IAC7C,MAAM0B,QAAQ1B,IAAIoB,OAAO,CAACH;IAC1B,IAAIS,QAAQ,GAAG,OAAO;IACtB,MAAMC,YAAYD,QAAQT,oBAAoBhB,MAAM;IACpD,MAAM2B,MAAM5B,IAAIoB,OAAO,CAAC,OAAOO;IAC/B,MAAME,OAAO,AAACD,CAAAA,MAAM,IAAI5B,IAAIJ,KAAK,CAAC+B,aAAa3B,IAAIJ,KAAK,CAAC+B,WAAWC,IAAG,EAAGE,IAAI;IAC9E,OAAOD,QAAQ;AACjB;AAEA;;;;;;;CAOC,GACD,OAAO,MAAME,2BAAiDlE,UAC5DJ,6BACA,kCACA,+EACA,mCACA;IAACC;CAAwB,EAC1B;AAED;;;;CAIC,GACD,OAAO,SAASsE,0BACd7C,IAAuB,EACvB8C,MAAmC;IAEnC,IAAI,CAAC9C,QAAQ,CAAC8C,OAAOC,IAAI,CAAC,CAACC,QAAU,CAACA,MAAMC,IAAI,GAAG,OAAOjD;IAC1D,OAAO,aAAKA;QAAMN,SAAS;eAAIM,KAAKN,OAAO;YAAEkD;SAAyB;;AACxE;AAqBA,MAAMM,wBAAwB;AAE9B;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASC,sBACdC,QAAuB,EACvBpD,IAAuB,EACvBqD,KAA0C,EAC1CP,SAAsC,EAAE;QActBQ,YAIAA;IAhBlB,IAAI,CAACF,YAAY,CAACpD,MAAM,OAAO;IAC/B,MAAMwB,cAAcxB,KAAKN,OAAO,CAACU,MAAM,CAACnB;IACxC,IAAI,CAACuC,YAAYV,MAAM,EAAE,OAAO;IAEhC,IAAIwC;IACJ,IAAI;QACFA,SAASC,KAAKC,KAAK,CAACJ;IACtB,EAAE,eAAM;QACN,OAAO;IACT;IACA,IAAI,CAACE,UAAU,OAAOA,WAAW,UAAU,OAAO;IAElD,MAAM3E,KAAK4B,QAAO+C,aAAAA,MAAM,CAAC,KAAK,YAAZA,aAAgB,IAAIX,IAAI;IAC1C,MAAMzD,SAASsC,YAAYzB,IAAI,CAAC,CAAC0D,YAAcA,UAAU9E,EAAE,KAAKA;IAChE,IAAI,CAACO,QAAQ,OAAO;IAEpB,MAAMwE,YAAYJ,iBAAAA,MAAM,CAAC,SAAS,YAAhBA,iBAAoB,CAAC;IACvC,MAAMK,SAAiD,EAAE;IACzD,IAAID,YAAY,OAAOA,aAAa,YAAY,CAACE,MAAMC,OAAO,CAACH,WAAW;QACxE,KAAK,MAAMxC,QAAQhC,OAAOH,MAAM,CAAE;gBACX2E;YAArB,MAAM9C,QAAQL,QAAOmD,iBAAAA,QAAQ,CAACxC,KAAK,YAAdwC,iBAAkB,IACpClD,OAAO,CAAC,YAAY,KACpBmC,IAAI,GACJlC,KAAK,CAAC,GAAGyC;YACZ,IAAItC,OAAO+C,OAAOtC,IAAI,CAAC;gBAAEH;gBAAMN;YAAM;QACvC;IACF;IAEA,MAAMkD,OAAOC,gBAAgB7E,QAAQmE,OAAOM;IAC5C,IAAI,CAACG,MAAM,OAAO;IAElB,0EAA0E;IAC1E,IAAI5E,OAAOP,EAAE,KAAKL,6BAA6B;YACjCqF;QAAZ,MAAMK,OAAML,eAAAA,OAAO5D,IAAI,CAAC,CAACa,QAAUA,MAAMM,IAAI,KAAK3C,6CAAtCoF,aAAgE/C,KAAK;QACjF,sEAAsE;QACtE,MAAMoC,QAAQF,OAAO/C,IAAI,CAAC,CAACkE,MAAQA,IAAID,GAAG,KAAKA,OAAO,CAACC,IAAIhB,IAAI;QAC/D,IAAI,CAACD,OAAO,OAAO;QACnB,OAAO;YACLrE,IAAIO,OAAOP,EAAE;YACbC,OAAO,CAAC,MAAM,EAAEoE,MAAMpE,KAAK,CAAC,iBAAiB,CAAC;YAC9CC,SAAS,CAAC,IAAI,EAAEmE,MAAMkB,IAAI,CAAC,EAAE,EAAElB,MAAMpE,KAAK,CAAC,oDAAoD,CAAC;YAChGkF;YACAH,QAAQ,EAAE;YACV3E,SAAS;YACTgE;QACF;IACF;IAEA,OAAO;QACLrE,IAAIO,OAAOP,EAAE;QACbC,OAAOM,OAAON,KAAK;QACnBC,SAASK,OAAOL,OAAO;QACvBiF;QACAH;QACA3E,SAASE,OAAOF,OAAO;IACzB;AACF;AAEA;;;;;CAKC,GACD,SAAS+E,gBACP7E,MAA4B,EAC5BmE,KAA0C,EAC1CM,MAAsD;IAEtD,MAAM/B,UAAUjB,WAAW0C,MAAMzB,OAAO;IACxC,MAAMC,SAASlB,WAAW0C,MAAMxB,MAAM;IACtC,IAAIhC,OAAOX,OAAOJ,KAAK;IACvB,IAAIe,KAAKsE,QAAQ,CAAC,cAAc;QAC9B,IAAI,CAACvC,SAAS,OAAO;QACrB/B,OAAOA,KAAKW,OAAO,CAAC,aAAaoB;IACnC;IACA,IAAI/B,KAAKsE,QAAQ,CAAC,WAAW;QAC3B,IAAI,CAACtC,QAAQ,OAAO;QACpBhC,OAAOA,KAAKW,OAAO,CAAC,UAAUqB;IAChC;IACA,uEAAuE;IACvE,mEAAmE;IACnE,IAAI,CAAChC,KAAKa,UAAU,CAAC,QAAQb,KAAKsE,QAAQ,CAAC,SAAStE,KAAKsE,QAAQ,CAAC,OAAO,OAAO;IAChF,IAAI,QAAQlE,IAAI,CAACJ,OAAO,OAAO;IAE/B,IAAI,CAACX,OAAOF,OAAO,IAAI,CAAC2E,OAAO7C,MAAM,EAAE,OAAOjB;IAC9C,MAAMuE,QAAQT,OACXU,GAAG,CACF,CAAC,EAAEnD,IAAI,EAAEN,KAAK,EAAE,GACd,GAAGnC,wBAAwB6F,mBAAmBpD,MAAM,CAAC,EAAEoD,mBAAmB1D,QAAQ,EAErFa,IAAI,CAAC;IACR,OAAO,GAAG5B,KAAK,CAAC,EAAEuE,OAAO;AAC3B;AAEA;;;;;;;;;;;;CAYC,GACD,OAAO,SAASG,mBACdC,QAA+BpF,YAAY,EAC3Ce,OAAyB;IAAEE,MAAM;AAAM,CAAC,EACxCoE,QAA+BvE,cAAc;IAE7C,MAAMwE,WAAqB,EAAE;IAC7B,MAAMC,OAAO,IAAIC;IACjB,MAAMC,YAAY;QAAC;QAAU;QAAQ;QAAY;QAAU;QAAO;QAAO;KAAW;IACpF,KAAK,MAAM7E,QAAQwE,MAAO;QACxB,MAAMjD,UAAUkD,MAAMzE,MAAMG;QAC5B,KAAK,MAAMjB,UAAUc,KAAKN,OAAO,CAAE;YACjC,MAAMoF,QAAQ,GAAG9E,KAAKX,GAAG,CAAC,CAAC,EAAEH,OAAOP,EAAE,EAAE;YACxC,IAAIgG,KAAKI,GAAG,CAAC7F,OAAOP,EAAE,GAAG+F,SAASrD,IAAI,CAAC,GAAGyD,MAAM,qBAAqB,CAAC;YACtEH,KAAKK,GAAG,CAAC9F,OAAOP,EAAE;YAClB,KAAK,MAAMU,OAAO4F,OAAOC,IAAI,CAAChG,QAAS;gBACrC,IAAI2F,UAAUV,QAAQ,CAAC9E,IAAI8F,WAAW,KAAK;oBACzCT,SAASrD,IAAI,CAAC,GAAGyD,MAAM,wCAAwC,EAAEzF,IAAI,CAAC,CAAC;gBACzE;YACF;YACA,IAAIH,OAAOC,IAAI,KAAK,QAAQ;oBAaRD;gBAZlB,IAAI,CAACiB,KAAKE,IAAI,IAAIkB,QAAQ4C,QAAQ,CAACjF,SAAS;oBAC1CwF,SAASrD,IAAI,CACX,GAAGyD,MAAM,yEAAyE,CAAC;gBAEvF;gBACA,KAAK,MAAMzF,OAAO;oBAAC;oBAAS;oBAAU;iBAAU,CAAE;oBAChD,IAAIA,OAAOH,QAAQ;wBACjBwF,SAASrD,IAAI,CACX,GAAGyD,MAAM,0BAA0B,EAAEzF,IAAI,yDAAyD,CAAC;oBAEvG;gBACF;gBACA,MAAMM,aAAYT,oBAAAA,OAAOS,SAAS,YAAhBT,oBAAoB,EAAE;gBACxC,IACE,CAACS,UAAUmB,MAAM,IACjBnB,UAAUoD,IAAI,CAAC,CAAC5D,OAAS,CAACf,2BAA2B+F,QAAQ,CAAChF,QAC9D;oBACAuF,SAASrD,IAAI,CAAC,GAAGyD,MAAM,gEAAgE,CAAC;gBAC1F;gBACA;YACF;YACA,IAAI,SAAS5F,QAAQ;gBACnBwF,SAASrD,IAAI,CAAC,GAAGyD,MAAM,iCAAiC,CAAC;YAC3D;YACA,IAAI,CAAC5F,OAAOJ,KAAK,CAAC4B,UAAU,CAAC,MAAM;gBACjCgE,SAASrD,IAAI,CAAC,GAAGyD,MAAM,2CAA2C,CAAC;YACrE;YACA,IAAI5F,OAAOJ,KAAK,CAACqF,QAAQ,CAAC,UAAU;gBAClCO,SAASrD,IAAI,CAAC,GAAGyD,MAAM,iCAAiC,CAAC;YAC3D;YACA,IAAI5F,OAAOF,OAAO,IAAI,CAACR,qBAAqB2F,QAAQ,CAACjF,OAAOJ,KAAK,GAAG;gBAClE4F,SAASrD,IAAI,CACX,GAAGyD,MAAM,mBAAmB,EAAE5F,OAAOJ,KAAK,CAAC,wDAAwD,EAAEL,sBAAsB,cAAc,CAAC;YAE9I;QACF;IACF;IACA,OAAOiG;AACT"}
package/src/lib/server.js CHANGED
@@ -275,6 +275,18 @@ export * from "./runtime/ai-runtime.js";
275
275
  registerPluginApiRoute(AI_JOBS_BEAT_PATH, {
276
276
  web: runAiJobsBeat
277
277
  });
278
+ // The image reads behind the products and theme steps (AGL-4048): a
279
+ // product's photo and the site logo are decoded with `sharp`, whose native
280
+ // library only the console's plugins' image function ships
281
+ // (`/api/plugin-images/…`), so neither the doors that run a first step nor
282
+ // the beat carry it, and on Vercel both ask this route. Called by our own
283
+ // servers, signed; a machine's door, loaded with its first call.
284
+ registerPluginApiRoute('ai/image-reads', {
285
+ web: async (request)=>(await import("./server/ai-image-reads-route.js")).aiImageReadsRoute(request)
286
+ }, {
287
+ machine: true,
288
+ images: true
289
+ });
278
290
  // The weekly insights (AGL-2915): Monday's digests made for the people who
279
291
  // asked for them, and delivered once the beat has written them. Beside the
280
292
  // beat under `/api/admin/`, where the console's scheduled sweeps live.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/server.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 { registerPluginApiRoute } from '@aglyn/aglyn/server'\nimport { registerAiDeclarations } from './declarations'\nimport { AI_JOBS_BEAT_PATH } from './jobs/ai-jobs-beat'\nimport { registerAiJobPlan } from './jobs/ai-job-plan-step'\nimport { registerAiJobsPause } from './jobs/ai-jobs-pause'\nimport { registerAiJobsNotify } from './jobs/ai-jobs-notify'\nimport { registerAiComponentJob } from './jobs/ai-job-component-step'\nimport { registerAiCrmJob } from './jobs/ai-job-crm-step'\nimport { registerAiLayoutJob } from './jobs/ai-job-layout-step'\nimport { registerAiTemplateJob } from './jobs/ai-job-template-step'\nimport { registerAiExperimentJob } from './jobs/ai-job-experiment-step'\nimport { registerAiFormJob } from './jobs/ai-job-form-step'\nimport { registerAiPageJob } from './jobs/ai-job-page-step'\nimport { registerAiProductsJob } from './jobs/ai-job-products-step'\nimport { registerAiEmailJob } from './jobs/ai-job-email-step'\nimport { registerAiCampaignJob } from './jobs/ai-job-campaign-step'\nimport { registerAiSiteJob } from './jobs/ai-job-site-step'\nimport { registerAiSiteImportJob } from './jobs/ai-job-import-step'\nimport { registerAiBuildJob } from './jobs/ai-job-build-step'\nimport { registerAiBuildCapabilities } from './jobs/ai-build-capabilities'\nimport { registerAiWorkflowJob } from './jobs/ai-job-workflow-step'\nimport { registerAiLogicJob } from './jobs/ai-job-logic-step'\nimport { registerAiEditJob } from './jobs/ai-job-edit-step'\nimport { registerAiNavigationJob } from './jobs/ai-job-navigation-step'\nimport { registerAiDuplicateJob } from './jobs/ai-job-duplicate-step'\nimport { registerAiSettingsJob } from './jobs/ai-job-settings-step'\nimport { registerAiMediaJob } from './jobs/ai-job-media-step'\nimport { registerAiTrackingJob } from './jobs/ai-job-tracking-step'\nimport { registerAiPrivacyJob } from './jobs/ai-job-privacy-step'\nimport { registerAiInsightJob } from './jobs/ai-job-insight-step'\nimport { registerAiFigureReaders } from './insights/ai-figure-readers'\nimport { registerAiTextGenerator } from './server/plugin-text-generation'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport { ensureFirstPartyAiProviders } from './providers/registry'\nimport { aiAssistHandler } from './server/ai-assist'\nimport { GET as aiCrmAnswer } from './server/ai-crm-answer'\nimport { POST as runAiJobsBeat } from './server/ai-jobs-beat-route'\nimport { POST as cancelAiJob } from './server/ai-jobs-cancel'\nimport { GET as aiJobEvents } from './server/ai-jobs-events-route'\nimport { POST as createAiSiteBatch } from './server/ai-jobs-batch'\nimport { GET as listAiJobs, POST as createAiJob } from './server/ai-jobs-route'\nimport { GET as aiCreateDoor, GET_FOLLOW as aiCreateFollow } from './server/ai-create-route'\nimport { POST as resumeAiJob } from './server/ai-jobs-resume'\nimport { POST as aiGenerateComponent } from './server/ai-generate-component'\nimport { POST as createAiMediaImages } from './server/ai-media-image'\nimport { POST as applyAiSeoAudit } from './server/ai-seo-apply'\nimport { POST as createAiExperimentVersions } from './server/ai-experiment-versions'\nimport { POST as assistChat } from './server/assist-chat'\nimport { POST as assistEditApplied } from './server/assist-edit-applied'\nimport { POST as assistFeedback } from './server/assist-feedback'\nimport { PATCH as aiHostPermissions } from './server/ai-host-permissions'\nimport { GET as aiAdminOrg } from './server/ai-admin-org'\nimport { GET as aiAdminConversations } from './server/ai-admin-conversations'\nimport { GET as aiAdminOrgsSpend } from './server/ai-admin-orgs-spend'\nimport { GET as aiAdminSignals } from './server/ai-admin-signals'\nimport { GET as aiAdminUser } from './server/ai-admin-user'\nimport { POST as aiAdminOverage } from './server/ai-admin-overage'\nimport { POST as aiAdminCredits } from './server/ai-admin-credits'\nimport { GET as aiUsage } from './server/ai-usage'\nimport { GET as aiAllotments } from './server/ai-allotments'\nimport { GET as aiInsightAnswer } from './server/ai-insight-answer'\nimport { POST as runAiInsightsDigest } from './server/ai-insight-digest-route'\nimport {\n listSiteImportOwnership,\n postSiteImportCrawl,\n postSiteImportOwnership,\n} from './server/ai-site-import-route'\nimport { AI_INSIGHTS_DIGEST_PATH } from './insights/ai-insight-digest'\nimport { GET as aiModels } from './server/ai-models'\nimport { GET as billingCredits } from './server/billing-credits'\nimport { POST as billingOverage } from './server/billing-overage'\n\nexport * from './providers/contract'\nexport * from './providers/catalog'\nexport * from './providers/registry'\nexport * from './providers/routing'\nexport * from './runtime/ai-runtime'\n\n/**\n * The two first-party adapters, registered once per process. A marketplace\n * plugin registers its own against `AI_PROVIDER_CONTRACT` from its own\n * entry and never touches this.\n */\nconst registerFirstPartyProviders = ensureFirstPartyAiProviders\n\n/**\n * Every job kind this plugin runs beyond the three the machine registers\n * itself (AGL-3025). Each is a CALL, never an import made for what the\n * module does as it loads: this package declares only this file\n * effect-ful, so a bundler honoring `sideEffects` deletes an import whose\n * exports go unused — and a step registered that way ran in every spec and\n * in neither app. `registrations-survive-a-bundler.spec.ts` holds this.\n *\n * The console surface is the only caller (AGL-3026): its doors run a job's\n * first step inline, and its beat runs every step after it. The tenant app\n * loads no server surface of this plugin at all, so nothing that serves a\n * published site can reach a provider.\n */\nfunction registerAiJobKinds(): void {\n // The workspace AI pause the machine asks before it claims any step, so a\n // paused workspace's queued jobs spend nothing (AGL-3037).\n registerAiJobsPause()\n // The person who started a job is told when it needs them, finishes or\n // stops (AGL-3593), from the write that changed it.\n registerAiJobsNotify()\n // The plan step every planned kind runs first (AGL-2935).\n registerAiJobPlan()\n // Components, layouts and templates (AGL-2908, AGL-2909).\n registerAiComponentJob()\n registerAiLayoutJob()\n registerAiTemplateJob()\n // Forms (AGL-2913).\n registerAiExperimentJob()\n registerAiFormJob()\n // Pages, with the least time one pass needs (AGL-2907).\n registerAiPageJob()\n // Email designs and campaigns, whose drafts the email and marketing\n // plugins write on the resource-draft seam (AGL-2912).\n registerAiEmailJob()\n registerAiCampaignJob()\n // Automations drafted from a description, and explained, whose drafts the\n // workflows plugin writes on the resource-draft seam (AGL-2919).\n registerAiWorkflowJob()\n // Site functions and variables proposed from a description, and functions\n // explained, as proposals the logic editor opens unsaved (AGL-3603).\n registerAiLogicJob()\n // A change to a page or a layout the site has, from a description, saved\n // where no visitor sees it — the Assist edit rung run as a job (AGL-3616).\n registerAiEditJob()\n // A change to a site's header or footer menu, made by code from a build's\n // item and saved beside the live layout (AGL-3728).\n registerAiNavigationJob()\n // A copy of something a site already has, through core's duplicate — the\n // console's own Duplicate… — as a build's item (AGL-3740).\n registerAiDuplicateJob()\n // A change to a site's settings — its name, search defaults, icons,\n // language, time zone and contact details — saved live (AGL-3748).\n registerAiSettingsJob()\n // Folders, tags, alt text and names of files already in a site's media\n // library, as its Details drawer saves them, live (AGL-3752).\n registerAiMediaJob()\n // The site's analytics and advertising tags, as Setup → Tracking sets\n // them, saved live (AGL-3751).\n registerAiTrackingJob()\n registerAiPrivacyJob()\n // Product copy, and a store's products, categories and discounts from a\n // brief, as proposals the commerce plugin's surfaces apply (AGL-2916).\n registerAiProductsJob()\n // The site scaffold, which builds a whole site through the steps above\n // (AGL-2911).\n registerAiSiteJob()\n // Bring my site (AGL-3976): a proved website brought in as draft pages, in\n // waves a person starts one at a time, through the page and layout steps.\n registerAiSiteImportJob()\n // A build from one request (AGL-3616): pages, creations and other\n // plugins' drafts, through the steps above and the writers those plugins\n // register, settled item by item. What this plugin lets a build make is\n // registered on the core's capability contract like any plugin's.\n registerAiBuildCapabilities()\n registerAiBuildJob()\n // Insights (AGL-2915): a question about a site's figures, answered from\n // tables the figure readers return, and the weekly digest. The readers for\n // the platform's own records — page views, forms, datasets — are this\n // plugin's; every other plugin registers its own from its console surface.\n registerAiFigureReaders(() => firebaseAdmin.app().firestore())\n registerAiInsightJob()\n // CRM by AI (AGL-2917): a record's summary and next step, an email draft\n // and an import's column matches, read through the CRM's facts readers.\n registerAiCrmJob()\n}\n\n/**\n * The console-side API (AGL-2939): every AI door, under the `ai` and\n * `assist` prefixes the plugin owns. The URLs the panel and the besigner\n * call are unchanged — `/api/assist/chat`, `/api/ai/assist`,\n * `/api/ai/jobs` — because the dispatcher serves them from the registry\n * exactly where the named routes used to; the two billing doors and the\n * per-member usage read (`ai/usage`) live under the plugin's own prefix.\n * The jobs beat is the one path outside them, for the reason\n * `AI_JOBS_BEAT_PATH` gives.\n */\nexport function registerAiConsoleApi(): void {\n registerAiDeclarations()\n registerFirstPartyProviders()\n registerAiJobKinds()\n // This plugin as the workspace's text generator on the core's seam\n // (AGL-3324): another plugin drafts a paragraph through it under the\n // generation doors' own rules, metered as a text job is.\n registerAiTextGenerator()\n registerPluginApiRoute('assist/chat', { web: assistChat })\n registerPluginApiRoute('assist/feedback', { web: assistFeedback })\n // The applied-edit record (AGL-2906): counts of a proposal the author\n // applied in their editor, written to the site's activity log.\n registerPluginApiRoute('assist/edit-applied', { web: assistEditApplied })\n // A collaborator's AI toggles on one site (AGL-2927), set from the site's\n // collaborators card through this plugin's column there.\n registerPluginApiRoute('ai/host-permissions', { web: aiHostPermissions })\n registerPluginApiRoute('ai/assist', aiAssistHandler)\n registerPluginApiRoute('ai/jobs', {\n web: (request) => (request.method === 'GET' ? listAiJobs(request) : createAiJob(request)),\n })\n // The agency batch (AGL-2911): one brief, one `site` job per named site,\n // under one batch id. Before the `:jobId` routes, which it is not one of.\n registerPluginApiRoute('ai/jobs/batch', { web: createAiSiteBatch })\n registerPluginApiRoute('ai/jobs/:jobId/cancel', {\n web: (request, context) =>\n cancelAiJob(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n registerPluginApiRoute('ai/jobs/:jobId/resume', {\n web: (request, context) =>\n resumeAiJob(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // An insight job's answer (AGL-2915), which is kept off the job document\n // and served only to the member who asked, or for a digest to the site's\n // members.\n registerPluginApiRoute('ai/insights/:jobId', {\n web: (request, context) =>\n aiInsightAnswer(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // A CRM job's answer (AGL-2917), which is kept off the job document and\n // served only to a member the CRM still lets read the record.\n registerPluginApiRoute('ai/crm/:jobId', {\n web: (request, context) =>\n aiCrmAnswer(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // Create with AI in the apps (AGL-3854): whether a list draws the entry and\n // every word its brief says, and the job it started as the dialog's follow\n // card reads it. Both read; the jobs doors above start, confirm and cancel.\n registerPluginApiRoute('ai/create', { web: aiCreateDoor })\n registerPluginApiRoute('ai/create/:jobId', {\n web: (request, context) =>\n aiCreateFollow(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n registerPluginApiRoute('ai/jobs/:jobId/events', {\n web: (request, context) =>\n aiJobEvents(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // The jobs beat (AGL-3026): every step the doors leave queued, run every\n // minute by the console's scheduler on the cron secret, on the one surface\n // that holds the provider's key.\n registerPluginApiRoute(AI_JOBS_BEAT_PATH, { web: runAiJobsBeat })\n // The weekly insights (AGL-2915): Monday's digests made for the people who\n // asked for them, and delivered once the beat has written them. Beside the\n // beat under `/api/admin/`, where the console's scheduled sweeps live.\n // A machine's route: Cloud Scheduler calls it on the cron secret with no\n // site and no member, and the route checks that secret and the AI release\n // flag itself. Unmarked, it was counted in the dispatcher's per-address\n // write bucket and refused with a 429 at 14:00Z on 2026-10-09.\n registerPluginApiRoute(AI_INSIGHTS_DIGEST_PATH, { web: runAiInsightsDigest }, { machine: true })\n // A site SEO audit's \"Apply all\" (AGL-2910): content fixes as new\n // unpublished versions, listing values staged for their SEO cards.\n registerPluginApiRoute('ai/seo/apply', { web: applyAiSeoAudit })\n // A page's or a section's A/B variants as draft versions (AGL-3603): the\n // published version copied once per proposed variant, the variant's copy\n // put in, stored unpublished for the experiment editor to pin.\n registerPluginApiRoute('ai/experiments/versions', { web: createAiExperimentVersions })\n // Save the selection as a reusable component, with AI (AGL-2908): the\n // second entry point of the component job, from the besigner's Attributes\n // panel. It proposes, and the person's Apply writes.\n registerPluginApiRoute('ai/generate/component', { web: aiGenerateComponent })\n // Pictures from a description, in Media (AGL-3602): photos and SVG\n // illustrations, stored through the media upload route. POST only — the\n // button draws from the shell's gates and asks nothing until it is used.\n registerPluginApiRoute('ai/media/images', { web: createAiMediaImages })\n // \"Bring my site\" (AGL-3974): proving the workspace owns a website, and\n // reading it once it has. Behind `release_ai_site_import`, paid plans,\n // owners and admins; the crawl refuses a domain without a current proof.\n registerPluginApiRoute('ai/site-import/ownership', {\n web: (request) =>\n request.method === 'GET' ? listSiteImportOwnership(request) : postSiteImportOwnership(request),\n })\n registerPluginApiRoute('ai/site-import/crawl', { web: (request) => postSiteImportCrawl(request) })\n registerPluginApiRoute('ai/billing/credits', { web: billingCredits })\n registerPluginApiRoute('ai/billing/overage', { web: billingOverage })\n registerPluginApiRoute('ai/usage', { web: aiUsage })\n // The allotments a manager sets, and the options the model switch lists\n // (AGL-2942). One handler answers the allotments' GET and POST.\n registerPluginApiRoute('ai/allotments', { web: aiAllotments })\n registerPluginApiRoute('ai/models', { web: aiModels })\n // The staff doors (AGL-2928, AGL-2930, AGL-2252): one org's AI in full,\n // one account's usage across its workspaces, and the fleet's Assist\n // signals behind the Assist signal staff page.\n registerPluginApiRoute('ai/admin/org', { web: aiAdminOrg })\n registerPluginApiRoute('ai/admin/orgs-spend', { web: aiAdminOrgsSpend })\n registerPluginApiRoute('ai/admin/user', { web: aiAdminUser })\n registerPluginApiRoute('ai/admin/signals', { web: aiAdminSignals })\n // What an org asked Aglyn AI and what it answered (AGL-3675): one page at a\n // time, read on request, each page an audited access.\n registerPluginApiRoute('ai/admin/conversations', { web: aiAdminConversations })\n // One workspace's overage standing and the four acts staff have over it\n // (AGL-3011): the ceiling override, lifting a pause — the only way a\n // dispute pause comes off — and resetting the step. Every act audited.\n registerPluginApiRoute('ai/admin/overage', { web: aiAdminOverage })\n // Credits given back to a workspace's band or a Free owner's allowance\n // (AGL-3595): compensation for a fault of ours, bounded by what the month\n // used, keyed against a double-click, and audited.\n registerPluginApiRoute('ai/admin/credits', { web: aiAdminCredits })\n}\n\n// Type-only (AGL-3080): the plugin's entitlement keys, declared by module\n// augmentation, for every program that loads this entry point.\nexport type { aiPlanEntitlements } from './plan-entitlements'\n"],"names":["registerPluginApiRoute","registerAiDeclarations","AI_JOBS_BEAT_PATH","registerAiJobPlan","registerAiJobsPause","registerAiJobsNotify","registerAiComponentJob","registerAiCrmJob","registerAiLayoutJob","registerAiTemplateJob","registerAiExperimentJob","registerAiFormJob","registerAiPageJob","registerAiProductsJob","registerAiEmailJob","registerAiCampaignJob","registerAiSiteJob","registerAiSiteImportJob","registerAiBuildJob","registerAiBuildCapabilities","registerAiWorkflowJob","registerAiLogicJob","registerAiEditJob","registerAiNavigationJob","registerAiDuplicateJob","registerAiSettingsJob","registerAiMediaJob","registerAiTrackingJob","registerAiPrivacyJob","registerAiInsightJob","registerAiFigureReaders","registerAiTextGenerator","firebaseAdmin","ensureFirstPartyAiProviders","aiAssistHandler","GET","aiCrmAnswer","POST","runAiJobsBeat","cancelAiJob","aiJobEvents","createAiSiteBatch","listAiJobs","createAiJob","aiCreateDoor","GET_FOLLOW","aiCreateFollow","resumeAiJob","aiGenerateComponent","createAiMediaImages","applyAiSeoAudit","createAiExperimentVersions","assistChat","assistEditApplied","assistFeedback","PATCH","aiHostPermissions","aiAdminOrg","aiAdminConversations","aiAdminOrgsSpend","aiAdminSignals","aiAdminUser","aiAdminOverage","aiAdminCredits","aiUsage","aiAllotments","aiInsightAnswer","runAiInsightsDigest","listSiteImportOwnership","postSiteImportCrawl","postSiteImportOwnership","AI_INSIGHTS_DIGEST_PATH","aiModels","billingCredits","billingOverage","registerFirstPartyProviders","registerAiJobKinds","app","firestore","registerAiConsoleApi","web","request","method","context","params","Promise","resolve","jobId","String","machine"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/server.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 { registerPluginApiRoute } from '@aglyn/aglyn/server'\nimport { registerAiDeclarations } from './declarations'\nimport { AI_JOBS_BEAT_PATH } from './jobs/ai-jobs-beat'\nimport { registerAiJobPlan } from './jobs/ai-job-plan-step'\nimport { registerAiJobsPause } from './jobs/ai-jobs-pause'\nimport { registerAiJobsNotify } from './jobs/ai-jobs-notify'\nimport { registerAiComponentJob } from './jobs/ai-job-component-step'\nimport { registerAiCrmJob } from './jobs/ai-job-crm-step'\nimport { registerAiLayoutJob } from './jobs/ai-job-layout-step'\nimport { registerAiTemplateJob } from './jobs/ai-job-template-step'\nimport { registerAiExperimentJob } from './jobs/ai-job-experiment-step'\nimport { registerAiFormJob } from './jobs/ai-job-form-step'\nimport { registerAiPageJob } from './jobs/ai-job-page-step'\nimport { registerAiProductsJob } from './jobs/ai-job-products-step'\nimport { registerAiEmailJob } from './jobs/ai-job-email-step'\nimport { registerAiCampaignJob } from './jobs/ai-job-campaign-step'\nimport { registerAiSiteJob } from './jobs/ai-job-site-step'\nimport { registerAiSiteImportJob } from './jobs/ai-job-import-step'\nimport { registerAiBuildJob } from './jobs/ai-job-build-step'\nimport { registerAiBuildCapabilities } from './jobs/ai-build-capabilities'\nimport { registerAiWorkflowJob } from './jobs/ai-job-workflow-step'\nimport { registerAiLogicJob } from './jobs/ai-job-logic-step'\nimport { registerAiEditJob } from './jobs/ai-job-edit-step'\nimport { registerAiNavigationJob } from './jobs/ai-job-navigation-step'\nimport { registerAiDuplicateJob } from './jobs/ai-job-duplicate-step'\nimport { registerAiSettingsJob } from './jobs/ai-job-settings-step'\nimport { registerAiMediaJob } from './jobs/ai-job-media-step'\nimport { registerAiTrackingJob } from './jobs/ai-job-tracking-step'\nimport { registerAiPrivacyJob } from './jobs/ai-job-privacy-step'\nimport { registerAiInsightJob } from './jobs/ai-job-insight-step'\nimport { registerAiFigureReaders } from './insights/ai-figure-readers'\nimport { registerAiTextGenerator } from './server/plugin-text-generation'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport { ensureFirstPartyAiProviders } from './providers/registry'\nimport { aiAssistHandler } from './server/ai-assist'\nimport { GET as aiCrmAnswer } from './server/ai-crm-answer'\nimport { POST as runAiJobsBeat } from './server/ai-jobs-beat-route'\nimport { POST as cancelAiJob } from './server/ai-jobs-cancel'\nimport { GET as aiJobEvents } from './server/ai-jobs-events-route'\nimport { POST as createAiSiteBatch } from './server/ai-jobs-batch'\nimport { GET as listAiJobs, POST as createAiJob } from './server/ai-jobs-route'\nimport { GET as aiCreateDoor, GET_FOLLOW as aiCreateFollow } from './server/ai-create-route'\nimport { POST as resumeAiJob } from './server/ai-jobs-resume'\nimport { POST as aiGenerateComponent } from './server/ai-generate-component'\nimport { POST as createAiMediaImages } from './server/ai-media-image'\nimport { POST as applyAiSeoAudit } from './server/ai-seo-apply'\nimport { POST as createAiExperimentVersions } from './server/ai-experiment-versions'\nimport { POST as assistChat } from './server/assist-chat'\nimport { POST as assistEditApplied } from './server/assist-edit-applied'\nimport { POST as assistFeedback } from './server/assist-feedback'\nimport { PATCH as aiHostPermissions } from './server/ai-host-permissions'\nimport { GET as aiAdminOrg } from './server/ai-admin-org'\nimport { GET as aiAdminConversations } from './server/ai-admin-conversations'\nimport { GET as aiAdminOrgsSpend } from './server/ai-admin-orgs-spend'\nimport { GET as aiAdminSignals } from './server/ai-admin-signals'\nimport { GET as aiAdminUser } from './server/ai-admin-user'\nimport { POST as aiAdminOverage } from './server/ai-admin-overage'\nimport { POST as aiAdminCredits } from './server/ai-admin-credits'\nimport { GET as aiUsage } from './server/ai-usage'\nimport { GET as aiAllotments } from './server/ai-allotments'\nimport { GET as aiInsightAnswer } from './server/ai-insight-answer'\nimport { POST as runAiInsightsDigest } from './server/ai-insight-digest-route'\nimport {\n listSiteImportOwnership,\n postSiteImportCrawl,\n postSiteImportOwnership,\n} from './server/ai-site-import-route'\nimport { AI_INSIGHTS_DIGEST_PATH } from './insights/ai-insight-digest'\nimport { GET as aiModels } from './server/ai-models'\nimport { GET as billingCredits } from './server/billing-credits'\nimport { POST as billingOverage } from './server/billing-overage'\n\nexport * from './providers/contract'\nexport * from './providers/catalog'\nexport * from './providers/registry'\nexport * from './providers/routing'\nexport * from './runtime/ai-runtime'\n\n/**\n * The two first-party adapters, registered once per process. A marketplace\n * plugin registers its own against `AI_PROVIDER_CONTRACT` from its own\n * entry and never touches this.\n */\nconst registerFirstPartyProviders = ensureFirstPartyAiProviders\n\n/**\n * Every job kind this plugin runs beyond the three the machine registers\n * itself (AGL-3025). Each is a CALL, never an import made for what the\n * module does as it loads: this package declares only this file\n * effect-ful, so a bundler honoring `sideEffects` deletes an import whose\n * exports go unused — and a step registered that way ran in every spec and\n * in neither app. `registrations-survive-a-bundler.spec.ts` holds this.\n *\n * The console surface is the only caller (AGL-3026): its doors run a job's\n * first step inline, and its beat runs every step after it. The tenant app\n * loads no server surface of this plugin at all, so nothing that serves a\n * published site can reach a provider.\n */\nfunction registerAiJobKinds(): void {\n // The workspace AI pause the machine asks before it claims any step, so a\n // paused workspace's queued jobs spend nothing (AGL-3037).\n registerAiJobsPause()\n // The person who started a job is told when it needs them, finishes or\n // stops (AGL-3593), from the write that changed it.\n registerAiJobsNotify()\n // The plan step every planned kind runs first (AGL-2935).\n registerAiJobPlan()\n // Components, layouts and templates (AGL-2908, AGL-2909).\n registerAiComponentJob()\n registerAiLayoutJob()\n registerAiTemplateJob()\n // Forms (AGL-2913).\n registerAiExperimentJob()\n registerAiFormJob()\n // Pages, with the least time one pass needs (AGL-2907).\n registerAiPageJob()\n // Email designs and campaigns, whose drafts the email and marketing\n // plugins write on the resource-draft seam (AGL-2912).\n registerAiEmailJob()\n registerAiCampaignJob()\n // Automations drafted from a description, and explained, whose drafts the\n // workflows plugin writes on the resource-draft seam (AGL-2919).\n registerAiWorkflowJob()\n // Site functions and variables proposed from a description, and functions\n // explained, as proposals the logic editor opens unsaved (AGL-3603).\n registerAiLogicJob()\n // A change to a page or a layout the site has, from a description, saved\n // where no visitor sees it — the Assist edit rung run as a job (AGL-3616).\n registerAiEditJob()\n // A change to a site's header or footer menu, made by code from a build's\n // item and saved beside the live layout (AGL-3728).\n registerAiNavigationJob()\n // A copy of something a site already has, through core's duplicate — the\n // console's own Duplicate… — as a build's item (AGL-3740).\n registerAiDuplicateJob()\n // A change to a site's settings — its name, search defaults, icons,\n // language, time zone and contact details — saved live (AGL-3748).\n registerAiSettingsJob()\n // Folders, tags, alt text and names of files already in a site's media\n // library, as its Details drawer saves them, live (AGL-3752).\n registerAiMediaJob()\n // The site's analytics and advertising tags, as Setup → Tracking sets\n // them, saved live (AGL-3751).\n registerAiTrackingJob()\n registerAiPrivacyJob()\n // Product copy, and a store's products, categories and discounts from a\n // brief, as proposals the commerce plugin's surfaces apply (AGL-2916).\n registerAiProductsJob()\n // The site scaffold, which builds a whole site through the steps above\n // (AGL-2911).\n registerAiSiteJob()\n // Bring my site (AGL-3976): a proved website brought in as draft pages, in\n // waves a person starts one at a time, through the page and layout steps.\n registerAiSiteImportJob()\n // A build from one request (AGL-3616): pages, creations and other\n // plugins' drafts, through the steps above and the writers those plugins\n // register, settled item by item. What this plugin lets a build make is\n // registered on the core's capability contract like any plugin's.\n registerAiBuildCapabilities()\n registerAiBuildJob()\n // Insights (AGL-2915): a question about a site's figures, answered from\n // tables the figure readers return, and the weekly digest. The readers for\n // the platform's own records — page views, forms, datasets — are this\n // plugin's; every other plugin registers its own from its console surface.\n registerAiFigureReaders(() => firebaseAdmin.app().firestore())\n registerAiInsightJob()\n // CRM by AI (AGL-2917): a record's summary and next step, an email draft\n // and an import's column matches, read through the CRM's facts readers.\n registerAiCrmJob()\n}\n\n/**\n * The console-side API (AGL-2939): every AI door, under the `ai` and\n * `assist` prefixes the plugin owns. The URLs the panel and the besigner\n * call are unchanged — `/api/assist/chat`, `/api/ai/assist`,\n * `/api/ai/jobs` — because the dispatcher serves them from the registry\n * exactly where the named routes used to; the two billing doors and the\n * per-member usage read (`ai/usage`) live under the plugin's own prefix.\n * The jobs beat is the one path outside them, for the reason\n * `AI_JOBS_BEAT_PATH` gives.\n */\nexport function registerAiConsoleApi(): void {\n registerAiDeclarations()\n registerFirstPartyProviders()\n registerAiJobKinds()\n // This plugin as the workspace's text generator on the core's seam\n // (AGL-3324): another plugin drafts a paragraph through it under the\n // generation doors' own rules, metered as a text job is.\n registerAiTextGenerator()\n registerPluginApiRoute('assist/chat', { web: assistChat })\n registerPluginApiRoute('assist/feedback', { web: assistFeedback })\n // The applied-edit record (AGL-2906): counts of a proposal the author\n // applied in their editor, written to the site's activity log.\n registerPluginApiRoute('assist/edit-applied', { web: assistEditApplied })\n // A collaborator's AI toggles on one site (AGL-2927), set from the site's\n // collaborators card through this plugin's column there.\n registerPluginApiRoute('ai/host-permissions', { web: aiHostPermissions })\n registerPluginApiRoute('ai/assist', aiAssistHandler)\n registerPluginApiRoute('ai/jobs', {\n web: (request) => (request.method === 'GET' ? listAiJobs(request) : createAiJob(request)),\n })\n // The agency batch (AGL-2911): one brief, one `site` job per named site,\n // under one batch id. Before the `:jobId` routes, which it is not one of.\n registerPluginApiRoute('ai/jobs/batch', { web: createAiSiteBatch })\n registerPluginApiRoute('ai/jobs/:jobId/cancel', {\n web: (request, context) =>\n cancelAiJob(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n registerPluginApiRoute('ai/jobs/:jobId/resume', {\n web: (request, context) =>\n resumeAiJob(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // An insight job's answer (AGL-2915), which is kept off the job document\n // and served only to the member who asked, or for a digest to the site's\n // members.\n registerPluginApiRoute('ai/insights/:jobId', {\n web: (request, context) =>\n aiInsightAnswer(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // A CRM job's answer (AGL-2917), which is kept off the job document and\n // served only to a member the CRM still lets read the record.\n registerPluginApiRoute('ai/crm/:jobId', {\n web: (request, context) =>\n aiCrmAnswer(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // Create with AI in the apps (AGL-3854): whether a list draws the entry and\n // every word its brief says, and the job it started as the dialog's follow\n // card reads it. Both read; the jobs doors above start, confirm and cancel.\n registerPluginApiRoute('ai/create', { web: aiCreateDoor })\n registerPluginApiRoute('ai/create/:jobId', {\n web: (request, context) =>\n aiCreateFollow(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n registerPluginApiRoute('ai/jobs/:jobId/events', {\n web: (request, context) =>\n aiJobEvents(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // The jobs beat (AGL-3026): every step the doors leave queued, run every\n // minute by the console's scheduler on the cron secret, on the one surface\n // that holds the provider's key.\n registerPluginApiRoute(AI_JOBS_BEAT_PATH, { web: runAiJobsBeat })\n // The image reads behind the products and theme steps (AGL-4048): a\n // product's photo and the site logo are decoded with `sharp`, whose native\n // library only the console's plugins' image function ships\n // (`/api/plugin-images/…`), so neither the doors that run a first step nor\n // the beat carry it, and on Vercel both ask this route. Called by our own\n // servers, signed; a machine's door, loaded with its first call.\n registerPluginApiRoute(\n 'ai/image-reads',\n {\n web: async (request) =>\n (await import('./server/ai-image-reads-route')).aiImageReadsRoute(request),\n },\n { machine: true, images: true },\n )\n // The weekly insights (AGL-2915): Monday's digests made for the people who\n // asked for them, and delivered once the beat has written them. Beside the\n // beat under `/api/admin/`, where the console's scheduled sweeps live.\n // A machine's route: Cloud Scheduler calls it on the cron secret with no\n // site and no member, and the route checks that secret and the AI release\n // flag itself. Unmarked, it was counted in the dispatcher's per-address\n // write bucket and refused with a 429 at 14:00Z on 2026-10-09.\n registerPluginApiRoute(AI_INSIGHTS_DIGEST_PATH, { web: runAiInsightsDigest }, { machine: true })\n // A site SEO audit's \"Apply all\" (AGL-2910): content fixes as new\n // unpublished versions, listing values staged for their SEO cards.\n registerPluginApiRoute('ai/seo/apply', { web: applyAiSeoAudit })\n // A page's or a section's A/B variants as draft versions (AGL-3603): the\n // published version copied once per proposed variant, the variant's copy\n // put in, stored unpublished for the experiment editor to pin.\n registerPluginApiRoute('ai/experiments/versions', { web: createAiExperimentVersions })\n // Save the selection as a reusable component, with AI (AGL-2908): the\n // second entry point of the component job, from the besigner's Attributes\n // panel. It proposes, and the person's Apply writes.\n registerPluginApiRoute('ai/generate/component', { web: aiGenerateComponent })\n // Pictures from a description, in Media (AGL-3602): photos and SVG\n // illustrations, stored through the media upload route. POST only — the\n // button draws from the shell's gates and asks nothing until it is used.\n registerPluginApiRoute('ai/media/images', { web: createAiMediaImages })\n // \"Bring my site\" (AGL-3974): proving the workspace owns a website, and\n // reading it once it has. Behind `release_ai_site_import`, paid plans,\n // owners and admins; the crawl refuses a domain without a current proof.\n registerPluginApiRoute('ai/site-import/ownership', {\n web: (request) =>\n request.method === 'GET' ? listSiteImportOwnership(request) : postSiteImportOwnership(request),\n })\n registerPluginApiRoute('ai/site-import/crawl', { web: (request) => postSiteImportCrawl(request) })\n registerPluginApiRoute('ai/billing/credits', { web: billingCredits })\n registerPluginApiRoute('ai/billing/overage', { web: billingOverage })\n registerPluginApiRoute('ai/usage', { web: aiUsage })\n // The allotments a manager sets, and the options the model switch lists\n // (AGL-2942). One handler answers the allotments' GET and POST.\n registerPluginApiRoute('ai/allotments', { web: aiAllotments })\n registerPluginApiRoute('ai/models', { web: aiModels })\n // The staff doors (AGL-2928, AGL-2930, AGL-2252): one org's AI in full,\n // one account's usage across its workspaces, and the fleet's Assist\n // signals behind the Assist signal staff page.\n registerPluginApiRoute('ai/admin/org', { web: aiAdminOrg })\n registerPluginApiRoute('ai/admin/orgs-spend', { web: aiAdminOrgsSpend })\n registerPluginApiRoute('ai/admin/user', { web: aiAdminUser })\n registerPluginApiRoute('ai/admin/signals', { web: aiAdminSignals })\n // What an org asked Aglyn AI and what it answered (AGL-3675): one page at a\n // time, read on request, each page an audited access.\n registerPluginApiRoute('ai/admin/conversations', { web: aiAdminConversations })\n // One workspace's overage standing and the four acts staff have over it\n // (AGL-3011): the ceiling override, lifting a pause — the only way a\n // dispute pause comes off — and resetting the step. Every act audited.\n registerPluginApiRoute('ai/admin/overage', { web: aiAdminOverage })\n // Credits given back to a workspace's band or a Free owner's allowance\n // (AGL-3595): compensation for a fault of ours, bounded by what the month\n // used, keyed against a double-click, and audited.\n registerPluginApiRoute('ai/admin/credits', { web: aiAdminCredits })\n}\n\n// Type-only (AGL-3080): the plugin's entitlement keys, declared by module\n// augmentation, for every program that loads this entry point.\nexport type { aiPlanEntitlements } from './plan-entitlements'\n"],"names":["registerPluginApiRoute","registerAiDeclarations","AI_JOBS_BEAT_PATH","registerAiJobPlan","registerAiJobsPause","registerAiJobsNotify","registerAiComponentJob","registerAiCrmJob","registerAiLayoutJob","registerAiTemplateJob","registerAiExperimentJob","registerAiFormJob","registerAiPageJob","registerAiProductsJob","registerAiEmailJob","registerAiCampaignJob","registerAiSiteJob","registerAiSiteImportJob","registerAiBuildJob","registerAiBuildCapabilities","registerAiWorkflowJob","registerAiLogicJob","registerAiEditJob","registerAiNavigationJob","registerAiDuplicateJob","registerAiSettingsJob","registerAiMediaJob","registerAiTrackingJob","registerAiPrivacyJob","registerAiInsightJob","registerAiFigureReaders","registerAiTextGenerator","firebaseAdmin","ensureFirstPartyAiProviders","aiAssistHandler","GET","aiCrmAnswer","POST","runAiJobsBeat","cancelAiJob","aiJobEvents","createAiSiteBatch","listAiJobs","createAiJob","aiCreateDoor","GET_FOLLOW","aiCreateFollow","resumeAiJob","aiGenerateComponent","createAiMediaImages","applyAiSeoAudit","createAiExperimentVersions","assistChat","assistEditApplied","assistFeedback","PATCH","aiHostPermissions","aiAdminOrg","aiAdminConversations","aiAdminOrgsSpend","aiAdminSignals","aiAdminUser","aiAdminOverage","aiAdminCredits","aiUsage","aiAllotments","aiInsightAnswer","runAiInsightsDigest","listSiteImportOwnership","postSiteImportCrawl","postSiteImportOwnership","AI_INSIGHTS_DIGEST_PATH","aiModels","billingCredits","billingOverage","registerFirstPartyProviders","registerAiJobKinds","app","firestore","registerAiConsoleApi","web","request","method","context","params","Promise","resolve","jobId","String","aiImageReadsRoute","machine","images"],"mappings":"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"}
@@ -27,7 +27,9 @@ import type { AiGenerationCheckResult } from '../runtime/ai-doctrine';
27
27
  * no price the brief does not state, and the storefront copy rules hold every
28
28
  * value (no health, financial or legal promise; no award or certification the
29
29
  * brief does not give). The planned field names are kept, in order, because
30
- * the pages planned around the dataset already name them.
30
+ * the pages planned around the dataset already name them. A record the brief
31
+ * gives no entry for is a SAMPLE, marked so, for the owner to replace
32
+ * (AGL-4065); a numbered placeholder is never one.
31
33
  */
32
34
  export declare const AI_DATASET_TOOL_NAME = "submit_dataset";
33
35
  /** The field types a dataset's design may use: the data plugin writer's own words for them. */
@@ -55,6 +57,12 @@ export interface AiDataset {
55
57
  }>;
56
58
  /** Each record's values in field order; an empty string is no value. */
57
59
  records: string[][];
60
+ /**
61
+ * Which records are SAMPLES the owner replaces, by position (AGL-4065):
62
+ * the brief gave no entries for the list, so the record is a typical one
63
+ * of its kind. Absent where none is.
64
+ */
65
+ samples?: boolean[];
58
66
  }
59
67
  export declare const AI_DATASET_TOOL: AiTool;
60
68
  /**
@@ -65,4 +73,6 @@ export declare const AI_DATASET_TOOL: AiTool;
65
73
  export declare function checkAiDataset(answer: Record<string, unknown>, context: {
66
74
  planned: readonly string[];
67
75
  merchantWords: string;
76
+ /** The fewest records a non-empty answer may hold; the list's own minimum by default. */
77
+ recordsMin?: number;
68
78
  }): AiGenerationCheckResult<AiDataset>;