@aglyn/plugins-ai 1.0.0-beta.231 → 1.0.0-beta.233

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 (234) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.js +8 -4
  3. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  4. package/src/lib/components/ai-job-follow.component.js +1 -0
  5. package/src/lib/components/ai-job-follow.component.js.map +1 -1
  6. package/src/lib/components/ai-job-links.js +13 -0
  7. package/src/lib/components/ai-job-links.js.map +1 -1
  8. package/src/lib/components/ai-jobs-list-page.component.js +1 -0
  9. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  10. package/src/lib/components/ai-media-create.component.d.ts +20 -7
  11. package/src/lib/components/ai-media-create.component.js +107 -55
  12. package/src/lib/components/ai-media-create.component.js.map +1 -1
  13. package/src/lib/components/ai-seo-fields-card.component.js +1 -0
  14. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  15. package/src/lib/components/ai-site-memory-card.component.d.ts +32 -0
  16. package/src/lib/components/ai-site-memory-card.component.js +187 -0
  17. package/src/lib/components/ai-site-memory-card.component.js.map +1 -0
  18. package/src/lib/components/ai-site-start-card.component.js +80 -77
  19. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  20. package/src/lib/components/ai-theme-proposal-card.component.js +4 -0
  21. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  22. package/src/lib/components/ai-usage-strip.component.js +1 -0
  23. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  24. package/src/lib/components/assist-panel.component.d.ts +10 -0
  25. package/src/lib/components/assist-panel.component.js +119 -9
  26. package/src/lib/components/assist-panel.component.js.map +1 -1
  27. package/src/lib/components/assist-signals-page.component.js +2 -0
  28. package/src/lib/components/assist-signals-page.component.js.map +1 -1
  29. package/src/lib/components/billing-ai-allotments.component.js +3 -0
  30. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  31. package/src/lib/components/billing-ai-top-users.component.js +1 -0
  32. package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
  33. package/src/lib/components/billing-assist-overage-card.component.js +2 -0
  34. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  35. package/src/lib/components/member-ai-usage-card.component.js +10 -0
  36. package/src/lib/components/member-ai-usage-card.component.js.map +1 -1
  37. package/src/lib/components/staff-org-ai-card.component.js +1 -0
  38. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  39. package/src/lib/components/staff-org-ai-conversations.component.d.ts +23 -0
  40. package/src/lib/components/staff-org-ai-conversations.component.js +346 -0
  41. package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -0
  42. package/src/lib/declarations.server.js +7 -4
  43. package/src/lib/declarations.server.js.map +1 -1
  44. package/src/lib/jobs/ai-build-capabilities.d.ts +13 -1
  45. package/src/lib/jobs/ai-build-capabilities.js +109 -9
  46. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  47. package/src/lib/jobs/ai-business-profile-prefill.d.ts +29 -0
  48. package/src/lib/jobs/ai-business-profile-prefill.js +85 -0
  49. package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -0
  50. package/src/lib/jobs/ai-job-build-step.js +3 -1
  51. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  52. package/src/lib/jobs/ai-job-edit-step.d.ts +225 -0
  53. package/src/lib/jobs/ai-job-edit-step.js +631 -0
  54. package/src/lib/jobs/ai-job-edit-step.js.map +1 -0
  55. package/src/lib/jobs/ai-job-insight-step.js +21 -4
  56. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  57. package/src/lib/jobs/ai-job-layout-language.d.ts +8 -3
  58. package/src/lib/jobs/ai-job-layout-language.js +8 -2
  59. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  60. package/src/lib/jobs/ai-job-layout-step.js +7 -6
  61. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  62. package/src/lib/jobs/ai-job-logic-step.d.ts +20 -0
  63. package/src/lib/jobs/ai-job-logic-step.js +87 -6
  64. package/src/lib/jobs/ai-job-logic-step.js.map +1 -1
  65. package/src/lib/jobs/ai-job-page-language.d.ts +52 -7
  66. package/src/lib/jobs/ai-job-page-language.js +72 -9
  67. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  68. package/src/lib/jobs/ai-job-page-step.js +12 -2
  69. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  70. package/src/lib/jobs/ai-job-plan-step.d.ts +24 -5
  71. package/src/lib/jobs/ai-job-plan-step.js +68 -21
  72. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  73. package/src/lib/jobs/ai-job-site-content.d.ts +163 -0
  74. package/src/lib/jobs/ai-job-site-content.js +443 -0
  75. package/src/lib/jobs/ai-job-site-content.js.map +1 -0
  76. package/src/lib/jobs/ai-job-site-look.d.ts +112 -0
  77. package/src/lib/jobs/ai-job-site-look.js +322 -0
  78. package/src/lib/jobs/ai-job-site-look.js.map +1 -0
  79. package/src/lib/jobs/ai-job-site-step.d.ts +26 -5
  80. package/src/lib/jobs/ai-job-site-step.js +163 -24
  81. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  82. package/src/lib/jobs/ai-jobs-notify.d.ts +16 -1
  83. package/src/lib/jobs/ai-jobs-notify.js +81 -3
  84. package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
  85. package/src/lib/jobs/ai-jobs.d.ts +14 -0
  86. package/src/lib/jobs/ai-jobs.js +28 -10
  87. package/src/lib/jobs/ai-jobs.js.map +1 -1
  88. package/src/lib/layout-language/ai-layout-compiler.d.ts +9 -0
  89. package/src/lib/layout-language/ai-layout-compiler.js +120 -33
  90. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  91. package/src/lib/layout-language/ai-layout-frame.js +5 -4
  92. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  93. package/src/lib/layout-language/ai-layout-language.d.ts +12 -5
  94. package/src/lib/layout-language/ai-layout-language.js +92 -9
  95. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  96. package/src/lib/layout-language/ai-layout-pictures.d.ts +144 -0
  97. package/src/lib/layout-language/ai-layout-pictures.js +214 -0
  98. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -0
  99. package/src/lib/layout-language/ai-layout-store.d.ts +2 -0
  100. package/src/lib/layout-language/ai-layout-store.js +7 -1
  101. package/src/lib/layout-language/ai-layout-store.js.map +1 -1
  102. package/src/lib/model/ai-build-progress.js +4 -1
  103. package/src/lib/model/ai-build-progress.js.map +1 -1
  104. package/src/lib/model/ai-edit-job.d.ts +109 -0
  105. package/src/lib/model/ai-edit-job.js +157 -0
  106. package/src/lib/model/ai-edit-job.js.map +1 -0
  107. package/src/lib/model/ai-job-activity.js +1 -0
  108. package/src/lib/model/ai-job-activity.js.map +1 -1
  109. package/src/lib/model/ai-jobs.types.d.ts +3 -1
  110. package/src/lib/model/ai-jobs.types.js.map +1 -1
  111. package/src/lib/model/ai-logic-job.d.ts +18 -1
  112. package/src/lib/model/ai-logic-job.js +24 -3
  113. package/src/lib/model/ai-logic-job.js.map +1 -1
  114. package/src/lib/model/ai-media-credits.d.ts +8 -4
  115. package/src/lib/model/ai-media-credits.js +11 -4
  116. package/src/lib/model/ai-media-credits.js.map +1 -1
  117. package/src/lib/model/ai-media-kinds.d.ts +68 -0
  118. package/src/lib/model/ai-media-kinds.js +98 -0
  119. package/src/lib/model/ai-media-kinds.js.map +1 -0
  120. package/src/lib/model/ai-plan-capabilities.d.ts +16 -0
  121. package/src/lib/model/ai-plan-capabilities.js +5 -0
  122. package/src/lib/model/ai-plan-capabilities.js.map +1 -1
  123. package/src/lib/model/ai-site-build-progress.d.ts +4 -0
  124. package/src/lib/model/ai-site-build-progress.js +28 -11
  125. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  126. package/src/lib/model/ai-site-context.d.ts +111 -0
  127. package/src/lib/model/ai-site-context.js +222 -0
  128. package/src/lib/model/ai-site-context.js.map +1 -0
  129. package/src/lib/model/ai-site-job.d.ts +10 -2
  130. package/src/lib/model/ai-site-job.js +12 -5
  131. package/src/lib/model/ai-site-job.js.map +1 -1
  132. package/src/lib/model/ai-site-kinds.d.ts +164 -0
  133. package/src/lib/model/ai-site-kinds.js +1942 -0
  134. package/src/lib/model/ai-site-kinds.js.map +1 -0
  135. package/src/lib/model/ai-site-look.d.ts +150 -0
  136. package/src/lib/model/ai-site-look.js +1236 -0
  137. package/src/lib/model/ai-site-look.js.map +1 -0
  138. package/src/lib/model/ai-site-memory.d.ts +80 -0
  139. package/src/lib/model/ai-site-memory.js +171 -0
  140. package/src/lib/model/ai-site-memory.js.map +1 -0
  141. package/src/lib/model/ai-site-start.d.ts +10 -1
  142. package/src/lib/model/ai-site-start.js +16 -2
  143. package/src/lib/model/ai-site-start.js.map +1 -1
  144. package/src/lib/model/assist-follow-up.d.ts +92 -0
  145. package/src/lib/model/assist-follow-up.js +128 -0
  146. package/src/lib/model/assist-follow-up.js.map +1 -0
  147. package/src/lib/operator-alerts.d.ts +18 -0
  148. package/src/lib/operator-alerts.js +42 -0
  149. package/src/lib/operator-alerts.js.map +1 -1
  150. package/src/lib/plugin-config.js +2 -0
  151. package/src/lib/plugin-config.js.map +1 -1
  152. package/src/lib/plugin.js +20 -1
  153. package/src/lib/plugin.js.map +1 -1
  154. package/src/lib/providers/anthropic.d.ts +9 -1
  155. package/src/lib/providers/anthropic.js +143 -9
  156. package/src/lib/providers/anthropic.js.map +1 -1
  157. package/src/lib/providers/catalog.d.ts +37 -7
  158. package/src/lib/providers/catalog.js +103 -11
  159. package/src/lib/providers/catalog.js.map +1 -1
  160. package/src/lib/providers/contract.d.ts +29 -0
  161. package/src/lib/providers/contract.js.map +1 -1
  162. package/src/lib/providers/image-contract.d.ts +22 -1
  163. package/src/lib/providers/image-contract.js +40 -1
  164. package/src/lib/providers/image-contract.js.map +1 -1
  165. package/src/lib/providers/image-endpoints.d.ts +24 -0
  166. package/src/lib/providers/image-endpoints.js +28 -0
  167. package/src/lib/providers/image-endpoints.js.map +1 -0
  168. package/src/lib/providers/model-choice.js +23 -2
  169. package/src/lib/providers/model-choice.js.map +1 -1
  170. package/src/lib/providers/routing.js +18 -0
  171. package/src/lib/providers/routing.js.map +1 -1
  172. package/src/lib/providers/vertex-image-endpoint.d.ts +48 -0
  173. package/src/lib/providers/vertex-image-endpoint.js +80 -0
  174. package/src/lib/providers/vertex-image-endpoint.js.map +1 -0
  175. package/src/lib/providers/vertex-image.d.ts +13 -27
  176. package/src/lib/providers/vertex-image.js +15 -42
  177. package/src/lib/providers/vertex-image.js.map +1 -1
  178. package/src/lib/runtime/ai-blog-post-generation.d.ts +62 -0
  179. package/src/lib/runtime/ai-blog-post-generation.js +89 -0
  180. package/src/lib/runtime/ai-blog-post-generation.js.map +1 -0
  181. package/src/lib/runtime/ai-dev-env.d.ts +53 -0
  182. package/src/lib/runtime/ai-dev-env.js +72 -0
  183. package/src/lib/runtime/ai-dev-env.js.map +1 -0
  184. package/src/lib/runtime/ai-dev-replay.d.ts +116 -0
  185. package/src/lib/runtime/ai-dev-replay.js +313 -0
  186. package/src/lib/runtime/ai-dev-replay.js.map +1 -0
  187. package/src/lib/runtime/ai-doctrine-validators.d.ts +7 -0
  188. package/src/lib/runtime/ai-doctrine-validators.js +9 -2
  189. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  190. package/src/lib/runtime/ai-doctrine.js +5 -1
  191. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  192. package/src/lib/runtime/ai-eval-live.js +4 -0
  193. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  194. package/src/lib/runtime/ai-eval.d.ts +13 -1
  195. package/src/lib/runtime/ai-eval.js +84 -1
  196. package/src/lib/runtime/ai-eval.js.map +1 -1
  197. package/src/lib/runtime/ai-live-batch.d.ts +45 -0
  198. package/src/lib/runtime/ai-live-batch.js +89 -0
  199. package/src/lib/runtime/ai-live-batch.js.map +1 -0
  200. package/src/lib/runtime/ai-runtime.js +46 -2
  201. package/src/lib/runtime/ai-runtime.js.map +1 -1
  202. package/src/lib/runtime/site-context.d.ts +58 -0
  203. package/src/lib/runtime/site-context.js +127 -0
  204. package/src/lib/runtime/site-context.js.map +1 -0
  205. package/src/lib/server/ai-admin-conversations.d.ts +22 -0
  206. package/src/lib/server/ai-admin-conversations.js +197 -0
  207. package/src/lib/server/ai-admin-conversations.js.map +1 -0
  208. package/src/lib/server/ai-media-image.d.ts +5 -5
  209. package/src/lib/server/ai-media-image.js +25 -11
  210. package/src/lib/server/ai-media-image.js.map +1 -1
  211. package/src/lib/server/ai-media-raster-prompt.d.ts +34 -0
  212. package/src/lib/server/ai-media-raster-prompt.js +53 -0
  213. package/src/lib/server/ai-media-raster-prompt.js.map +1 -0
  214. package/src/lib/server/assist-chat.d.ts +11 -0
  215. package/src/lib/server/assist-chat.js +57 -18
  216. package/src/lib/server/assist-chat.js.map +1 -1
  217. package/src/lib/server/assist-edit-applied.js +16 -3
  218. package/src/lib/server/assist-edit-applied.js.map +1 -1
  219. package/src/lib/server/assist-view-context.d.ts +19 -1
  220. package/src/lib/server/assist-view-context.js +38 -1
  221. package/src/lib/server/assist-view-context.js.map +1 -1
  222. package/src/lib/server.js +10 -0
  223. package/src/lib/server.js.map +1 -1
  224. package/src/lib/subprocessors.js +17 -1
  225. package/src/lib/subprocessors.js.map +1 -1
  226. package/src/lib/tools/ai-blog-post-tool.d.ts +60 -0
  227. package/src/lib/tools/ai-blog-post-tool.js +175 -0
  228. package/src/lib/tools/ai-blog-post-tool.js.map +1 -0
  229. package/src/lib/usage/assist-usage.d.ts +3 -0
  230. package/src/lib/usage/assist-usage.js +4 -2
  231. package/src/lib/usage/assist-usage.js.map +1 -1
  232. package/src/lib/usage/staff-org-ai-conversations.d.ts +103 -0
  233. package/src/lib/usage/staff-org-ai-conversations.js +152 -0
  234. package/src/lib/usage/staff-org-ai-conversations.js.map +1 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-edit-step.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 { checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { SCREEN_KIND_TEMPLATE } from '@aglyn/aglyn/app-utils/screen-route'\nimport { screenSeoStageKey, SCREEN_SEO_TEXT_FIELDS } from '@aglyn/aglyn/app-utils/screen-seo-fields'\nimport { decodeStoredNodes, encodeStoredNodes } from '@aglyn/aglyn/app-utils/stored-nodes'\nimport { CanvasManager } from '@aglyn/aglyn/canvas-manager/canvas-manager'\nimport { FEATURE_FLAG } from '@aglyn/aglyn/foundation/constants/shared'\nimport type { EditorSession } from '@aglyn/aglyn/plugin-manager/editor-sessions'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport { applyAssistEdit } from '../components/assist-edit-canvas'\nimport {\n AI_EDIT_COMPONENT_LEFT_OUT,\n AI_EDIT_EMAIL_COPY,\n AI_EDIT_GONE_COPY,\n AI_EDIT_NEEDS_VERSIONING_COPY,\n AI_EDIT_NO_NODES_COPY,\n AI_EDIT_NO_SITE_COPY,\n AI_EDIT_NOTHING_COPY,\n AI_EDIT_REFUSED_COPY,\n AI_EDIT_SEO_LEFT_OUT,\n AI_EDIT_STALE_COPY,\n AI_EDIT_VERSION_GONE_COPY,\n aiEditJobOutline,\n aiEditOutputNote,\n aiEditVersionName,\n parseAiEditJobInputs,\n type AiEditJobInputs,\n type AiEditPlacement,\n type AiEditTargetKind,\n} from '../model/ai-edit-job'\nimport type { AiJob, AiJobOutput } from '../model/ai-jobs.types'\nimport {\n ASSIST_EDIT_TOOL_NAME,\n type AssistEditCanvasContext,\n type AssistEditOp,\n type AssistEditProposal,\n type AssistEditTarget,\n} from '../model/assist-edit'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n runValidatedGeneration,\n validateStreamedGeneration,\n type AiCustomGenerationInput,\n} from '../runtime/ai-doctrine'\nimport { AI_PALETTE } from '../runtime/ai-palette.generated'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport {\n assistEditTool,\n checkAssistEditAnswer,\n editCanvasBlock,\n editSelectionBlock,\n parseAssistEditContext,\n} from '../server/assist-edit'\nimport { registerAiJobAdmission, type AiJobAdmission, type AiJobAdmissionRefusal } from './ai-job-admission'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiOriginJobId } from './ai-job-draft-ids'\nimport { aiGenerationSpent, aiLimitReview, aiUnspentOutcome } from './ai-job-generation'\nimport { AI_JOB_BRIEF_MAX_CHARS, type AiJobStepOutcome, type AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The `edit` step (AGL-3616): a change to a page or a layout the site already\n * has, from a description, made on the server with no editor open.\n *\n * It is the Assist edit rung, run as a job. The model is shown the stored\n * document as the rung's outline and proposes through the rung's own tool;\n * its answer is held to the rung's closed world and palette validators\n * (`checkAssistEditAnswer`) and re-asked once, naming what was left out; and\n * what survives lands through the canvas's own guarded mutators\n * (`applyAssistEdit`), on a `CanvasManager` built headless from the stored\n * nodes, inside the transaction that writes it.\n *\n * ── Where the change lands, decided before anything is spent ────────────\n *\n * Never on a version a visitor sees. With version history, a NEW version\n * beside the one it started from — a copy of that version's stored fields with\n * the changed nodes — which becomes the page's current version only when the\n * page is not published; a published page's current version, and a layout's,\n * never move. Without it, in place, on a version no visitor reaches: an\n * unpublished page's, or one that is not the current version and no schedule\n * will publish. Anything else is refused as a plan limit, charged nothing.\n *\n * ── What a job cannot do that the editor can ────────────────────────────\n *\n * The rung's chat door never offers saving part of a page as a reusable\n * component, and neither does this. A page's search title and description\n * live on the page itself, not on a version, so they are written only for a\n * page that is not published; on a published page they are left out, and the\n * output's note says so, as it says every change it left out.\n *\n * ── One version per job ──────────────────────────────────────────────────\n *\n * The new version is named by the job's id — a build's unit is named by its\n * plan item's id — so a run asked again finds what it wrote and spends\n * nothing. A change made in place stamps the version with the job's id for\n * the same reason.\n */\n\n/** The longest the step may take reading the page and writing it. */\nexport const AI_EDIT_READS_MS = 2_000\n\n/** The step's time: its answer and its re-ask at the `job.edit` ceiling, with its own reads. */\nexport const AI_JOB_EDIT_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.edit'],\n maxTokens: AI_ROUTING_TABLE['job.edit'].maxTokens,\n lookups: 0,\n ownReadsMs: AI_EDIT_READS_MS,\n})\n\nexport const AI_JOB_EDIT_STEP_MINIMUM_MS = AI_JOB_EDIT_STEP_BUDGET.minimumMs\n\nexport function aiJobEditModel(): string {\n return aiModelForStep('job.edit')\n}\n\n/**\n * The job's own rules, byte-identical on every request, ahead of the rung's\n * protocol and catalog for the document's kind, which carries the breakpoint:\n * both are the same for every site, so a kind's whole prefix caches once.\n */\nexport const AI_JOB_EDIT_RULES: AiSystemBlock = {\n text: [\n 'You change a page or a layout the person already has, as a job that runs on its own. No editor is open, nothing is selected, and nobody can answer a question before you finish.',\n `- Make exactly the change the request asks for, and keep everything else as it is: other elements, their text, their styles and their order. Never rewrite, restyle, move or remove what the request does not ask about.`,\n '- Find the elements the request means in the canvas outline the request carries, by what they say and what they are. Name only ids that outline lists.',\n `- If the outline does not show an element the request is about, change nothing for that part: never guess at an element.`,\n `- Call ${ASSIST_EDIT_TOOL_NAME} exactly once, with every operation. Its summary is one short line saying what changed, in the language of the request; it names the version the change is saved in.`,\n \"- Fill in a page's search title or description only when the request asks for it.\",\n '- What you change is saved where no visitor sees it until the person publishes it, so never say the site or the page has changed.',\n '- The protocol below was written for Assist’s chat. In this job there is no user to ask or to press Apply, and no selection: what it says about those does not apply. Everything else in it — the operations, their fields, the elements and the style tokens — does.',\n ].join('\\n'),\n}\n\n/** The system blocks for one kind of document. */\nexport function aiJobEditInstructions(kind: AiEditTargetKind): AiSystemBlock[] {\n return [AI_JOB_EDIT_RULES, { text: editCanvasBlock(kind), cacheBreakpoint: true }]\n}\n\n/** What the change is asked from: the document's outline, then the request. */\nexport function aiJobEditPrompt(input: { brief: string; context: AssistEditCanvasContext }): string {\n return [editSelectionBlock(input.context), '', `Request: ${input.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)}`].join('\\n')\n}\n\n/** The edit as the doctrine's loop runs it: the rung's tool, held to the rung's check. */\nexport function aiJobEditGeneration(request: {\n brief: string\n context: AssistEditCanvasContext\n target: AssistEditTarget\n model: string\n signal?: AbortSignal\n}): AiCustomGenerationInput<AssistEditProposal> {\n const row = AI_ROUTING_TABLE['job.edit']\n const kind = request.target.kind as AiEditTargetKind\n return {\n step: 'job.edit',\n model: request.model,\n instructions: aiJobEditInstructions(kind),\n messages: [{ role: 'user', content: aiJobEditPrompt(request) }],\n tool: assistEditTool(kind),\n maxTokens: AI_JOB_EDIT_STEP_BUDGET.maxTokens(request.model),\n ...(row.thinking ? { thinking: row.thinking } : {}),\n ...(row.effort ? { effort: row.effort } : {}),\n ...(request.signal ? { signal: request.signal } : {}),\n check: (answer) => checkAssistEditAnswer(answer, { context: request.context, target: request.target }),\n }\n}\n\n// ── The document, headless ────────────────────────────────────────────────\n\n/**\n * The slice of the platform a headless canvas asks: whether an element takes\n * children, answered from the AI palette the edit validator reads, so the\n * canvas refuses exactly what the validator refuses.\n */\nconst AI_EDIT_CANVAS_PLATFORM = {\n components: {\n getSchema: (componentId: string) => {\n const entry = AI_PALETTE[componentId]\n if (!entry) return undefined\n return entry.acceptsChildren ? {} : { flags: { dropping: FEATURE_FLAG.DISABLED } }\n },\n getLabel: (componentId: string) => AI_PALETTE[componentId]?.displayName ?? componentId,\n },\n}\n\n/** A canvas over a stored node map, with the editor's own guarded mutators and no editor. */\nexport function aiEditCanvas(nodes: NodesMap): CanvasManager {\n const canvas = new CanvasManager(AI_EDIT_CANVAS_PLATFORM as never)\n canvas.setNodes(nodes)\n return canvas\n}\n\n/**\n * Lands a proposal on a stored node map, as the editor lands one on its open\n * canvas: checked whole first, then one batch, all or nothing. The session is\n * the version being written, which is never one a visitor sees; a page's\n * search fields are captured into `seo` only where `seo` is given.\n */\nexport function aiApplyEditToNodes(\n nodes: NodesMap,\n proposal: AssistEditProposal,\n seo?: Partial<Record<(typeof SCREEN_SEO_TEXT_FIELDS)[number], string>>,\n): { ok: true; nodes: NodesMap } | { ok: false; reason: string } {\n const canvas = aiEditCanvas(nodes)\n const fields: Record<string, (value: string) => void> = {}\n if (seo) {\n for (const field of SCREEN_SEO_TEXT_FIELDS) fields[screenSeoStageKey(field)] = (value) => (seo[field] = value)\n }\n const session: EditorSession = {\n documentKind: proposal.target.kind,\n documentId: proposal.target.documentId,\n versionId: proposal.target.versionId,\n isLiveVersion: () => false,\n ...(seo ? { fields } : {}),\n }\n const applied = applyAssistEdit(canvas, proposal, session)\n if (applied.ok === false) return { ok: false, reason: applied.reason }\n return { ok: true, nodes: canvas.toJSON().nodes as NodesMap }\n}\n\n// ── Reading the document ──────────────────────────────────────────────────\n\ntype Firestore = FirebaseFirestore.Firestore\n\nconst COLLECTIONS: Readonly<Record<AiEditTargetKind, 'screens' | 'layouts'>> = {\n screen: 'screens',\n layout: 'layouts',\n}\n\n/** The page or layout an edit job changes, as it stands before anything is spent. */\nexport interface AiEditTargetRead {\n kind: AiEditTargetKind\n id: string\n name: string\n hostSubdomain: string | null\n /** The document's current version. */\n pointerVersionId: string | null\n /** The version the change starts from. */\n sourceVersionId: string\n nodes: NodesMap\n /** Whether visitors see the document's current version: a published page, a collection template, any layout. */\n served: boolean\n /** The version a pending publish schedule will make current, if any. */\n scheduledVersionId: string | null\n /** What this job already wrote, when a run of it got that far. */\n written: { versionId: string; placement: AiEditPlacement } | null\n}\n\nexport interface AiEditTargetRefusal {\n status: 400 | 404\n error: string\n}\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value : '')\n\n/** Whether visitors see a page's current version: it is routed, or it renders a collection's entries. */\nfunction screenServed(screen: FirebaseFirestore.DocumentSnapshot, host: FirebaseFirestore.DocumentSnapshot): boolean {\n if (screen.get('kind') === SCREEN_KIND_TEMPLATE) return true\n const routing = host.get('screens') as Record<string, unknown> | null | undefined\n return typeof routing?.[screen.id] === 'string'\n}\n\nfunction pendingScheduleVersion(doc: FirebaseFirestore.DocumentSnapshot): string | null {\n const schedule = doc.get('publishSchedule') as Record<string, unknown> | null | undefined\n if (!schedule || schedule['status'] !== 'pending' || schedule['action'] === 'unpublish') return null\n return text(schedule['versionId']) || null\n}\n\n/**\n * The document an edit job changes, on the job's own site: the page or\n * layout named, not deleted, not an email design, and the version it starts\n * from with a document in it. A refusal names what is wrong.\n */\nexport async function readAiEditTarget(\n firestore: Firestore,\n input: {\n orgId: string\n hostId: string\n inputs: AiEditJobInputs\n /** The job's own version id, to find what a run of it wrote; `null` at the door, before there is one. */\n jobId: string | null\n },\n): Promise<AiEditTargetRead | AiEditTargetRefusal> {\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n const host = await hostRef.get()\n if (!host.exists || host.get('orgId') !== input.orgId) return { status: 404, error: AI_EDIT_NO_SITE_COPY }\n const kinds: AiEditTargetKind[] = input.inputs.targetKind ? [input.inputs.targetKind] : ['screen', 'layout']\n for (const kind of kinds) {\n const docRef = hostRef.collection(COLLECTIONS[kind]).doc(input.inputs.target)\n const doc = await docRef.get()\n if (!doc.exists) continue\n if (doc.get('deletedAt') != null) return { status: 404, error: AI_EDIT_GONE_COPY }\n if (kind === 'screen' && doc.get('kind') === 'email') return { status: 400, error: AI_EDIT_EMAIL_COPY }\n const pointerVersionId = text(doc.get('versionId')) || null\n const versions = docRef.collection('versions')\n const hostSubdomain = text(host.get('subdomain')) || null\n const name = text(doc.get('displayName')) || (kind === 'screen' ? 'Page' : 'Layout')\n // A run of this job that got as far as its write.\n const own = input.jobId ? await versions.doc(input.jobId).get() : null\n const sourceVersionId = input.inputs.versionId ?? pointerVersionId\n const base = {\n kind,\n id: doc.id,\n name,\n hostSubdomain,\n pointerVersionId,\n served: kind === 'layout' || screenServed(doc, host),\n scheduledVersionId: pendingScheduleVersion(doc),\n }\n if (own?.exists && input.jobId) {\n const placement: AiEditPlacement = pointerVersionId === input.jobId ? 'new-current' : 'new-beside'\n return { ...base, sourceVersionId: sourceVersionId ?? input.jobId, nodes: {}, written: { versionId: input.jobId, placement } }\n }\n if (!sourceVersionId) return { status: 404, error: AI_EDIT_VERSION_GONE_COPY }\n const source = await versions.doc(sourceVersionId).get()\n if (!source.exists) return { status: 404, error: AI_EDIT_VERSION_GONE_COPY }\n if (input.jobId && source.get('aiEditJobId') === input.jobId) {\n return { ...base, sourceVersionId, nodes: {}, written: { versionId: sourceVersionId, placement: 'in-place' } }\n }\n const nodes = decodeStoredNodes<NodesMap>(source.get('nodes'))\n if (!nodes || !Object.keys(nodes).length) return { status: 400, error: AI_EDIT_NO_NODES_COPY }\n return { ...base, sourceVersionId, nodes, written: null }\n }\n return { status: 404, error: AI_EDIT_GONE_COPY }\n}\n\n/**\n * Where a change may land (see the module head), or `refused` when the only\n * place is a version visitors see and the plan keeps no version history.\n */\nexport function aiEditPlacement(input: {\n versioning: boolean\n served: boolean\n sourceIsPointer: boolean\n sourceScheduled: boolean\n}): AiEditPlacement | 'refused' {\n if (input.versioning) return !input.served && input.sourceIsPointer ? 'new-current' : 'new-beside'\n const seen = input.sourceScheduled || (input.sourceIsPointer && input.served)\n return seen ? 'refused' : 'in-place'\n}\n\n// ── Writing it ────────────────────────────────────────────────────────────\n\nexport interface AiEditWriteInput {\n hostId: string\n kind: AiEditTargetKind\n id: string\n sourceVersionId: string\n /** The new version's id, which is the job's; also the stamp a change in place carries. */\n jobVersionId: string\n versioning: boolean\n proposal: AssistEditProposal\n /** Whether the page's search fields may be written: a page no visitor reaches. */\n writeSeo: boolean\n versionName: string\n uid: string\n /** The job the member started, stamped as `aiJobId` on a new version. */\n aiJobId: string\n now: Date\n}\n\nexport type AiEditWrite =\n | { ok: true; versionId: string; placement: AiEditPlacement; seoWritten: boolean }\n | { ok: false; error: string }\n\n/**\n * The change written, in one transaction that reads the document as it is\n * stored NOW — a member may have saved, published or changed plan's history\n * since the read — decides where it lands again on that, and applies the\n * proposal to the stored nodes through the canvas's guards. An element that\n * changed since the model saw it refuses the whole change, as the editor does.\n */\nexport async function writeAiEdit(firestore: Firestore, input: AiEditWriteInput): Promise<AiEditWrite> {\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n const docRef = hostRef.collection(COLLECTIONS[input.kind]).doc(input.id)\n const versions = docRef.collection('versions')\n const sourceRef = versions.doc(input.sourceVersionId)\n const newRef = versions.doc(input.jobVersionId)\n return firestore.runTransaction(async (tx): Promise<AiEditWrite> => {\n const [host, doc, source, already] = await Promise.all([tx.get(hostRef), tx.get(docRef), tx.get(sourceRef), tx.get(newRef)])\n const pointer = text(doc.get('versionId')) || null\n if (already.exists) {\n return { ok: true, versionId: input.jobVersionId, placement: pointer === input.jobVersionId ? 'new-current' : 'new-beside', seoWritten: false }\n }\n if (!doc.exists || doc.get('deletedAt') != null) return { ok: false, error: AI_EDIT_GONE_COPY }\n if (!source.exists) return { ok: false, error: AI_EDIT_VERSION_GONE_COPY }\n const served = input.kind === 'layout' || screenServed(doc, host)\n const placement = aiEditPlacement({\n versioning: input.versioning,\n served,\n sourceIsPointer: pointer === input.sourceVersionId,\n sourceScheduled: pendingScheduleVersion(doc) === input.sourceVersionId,\n })\n if (placement === 'refused') return { ok: false, error: AI_EDIT_NEEDS_VERSIONING_COPY }\n const nodes = decodeStoredNodes<NodesMap>(source.get('nodes'))\n if (!nodes) return { ok: false, error: AI_EDIT_NO_NODES_COPY }\n const versionId = placement === 'in-place' ? input.sourceVersionId : input.jobVersionId\n const seo: Partial<Record<(typeof SCREEN_SEO_TEXT_FIELDS)[number], string>> = {}\n const writeSeo = input.writeSeo && input.kind === 'screen' && !served\n const proposal: AssistEditProposal = {\n ...input.proposal,\n ops: writeSeo ? input.proposal.ops : input.proposal.ops.filter((op) => op.op !== 'setSeo'),\n target: { ...input.proposal.target, versionId },\n }\n const applied = aiApplyEditToNodes(nodes, proposal, writeSeo ? seo : undefined)\n if (applied.ok === false) return { ok: false, error: applied.reason === 'stale' ? AI_EDIT_STALE_COPY : AI_EDIT_REFUSED_COPY }\n const packed = encodeStoredNodes(applied.nodes)\n if (!packed) return { ok: false, error: AI_EDIT_NO_NODES_COPY }\n const stored = Buffer.from(packed)\n const docPatch: Record<string, unknown> = {}\n if (placement === 'in-place') {\n tx.update(sourceRef, { nodes: stored, updatedAt: input.now, aiEditJobId: input.jobVersionId })\n } else {\n // A copy of the version it started from — its layout binding, its\n // declared properties, everything the editor stored — with the changed\n // tree, as the versions route copies one.\n const copy = { ...(source.data() as Record<string, unknown>) }\n delete copy['aiEditJobId']\n tx.create(newRef, {\n ...copy,\n nodes: stored,\n displayName: input.versionName,\n createdAt: input.now,\n updatedAt: input.now,\n createdBy: input.uid,\n aiJobId: input.aiJobId,\n })\n if (placement === 'new-current') docPatch['versionId'] = input.jobVersionId\n }\n for (const [field, value] of Object.entries(seo)) {\n if (value) docPatch[`seo.${field}`] = value\n }\n if (Object.keys(docPatch).length) tx.update(docRef, { ...docPatch, updatedAt: input.now })\n return { ok: true, versionId, placement, seoWritten: Object.keys(seo).length > 0 }\n })\n}\n\n// ── The runner ────────────────────────────────────────────────────────────\n\nexport interface AiJobEditStepDeps {\n readTarget?: typeof readAiEditTarget\n writeEdit?: typeof writeAiEdit\n}\n\n/** What an edit wrote, as the job's output: a link into the Besigner on the version written. */\nexport function aiEditOutput(\n read: Pick<AiEditTargetRead, 'kind' | 'id' | 'name' | 'hostSubdomain'>,\n hostId: string,\n written: { versionId: string; placement: AiEditPlacement },\n leftOut: readonly string[],\n): AiJobOutput {\n return {\n resource: read.kind,\n id: read.id,\n versionId: written.versionId,\n hostId,\n hostSubdomain: read.hostSubdomain,\n label: read.name,\n note: aiEditOutputNote({ kind: read.kind, placement: written.placement, leftOut }),\n }\n}\n\n/** The new version's id: the job's own, which a build's unit takes from its plan item. */\nexport function aiEditVersionId(job: Pick<AiJob, '$id'>): string {\n return job.$id.slice(0, 64)\n}\n\n/**\n * The ops a job may save, and why it left the rest out: never a component\n * save, and a page's search fields only where no visitor reaches the page.\n */\nexport function aiEditJobOps(\n ops: readonly AssistEditOp[],\n options: { seo: boolean },\n): { ops: AssistEditOp[]; leftOut: string[] } {\n const kept: AssistEditOp[] = []\n const leftOut = new Set<string>()\n for (const op of ops) {\n if (op.op === 'saveAsComponent') leftOut.add(AI_EDIT_COMPONENT_LEFT_OUT)\n else if (op.op === 'setSeo' && !options.seo) leftOut.add(AI_EDIT_SEO_LEFT_OUT)\n else kept.push(op)\n }\n return { ops: kept, leftOut: [...leftOut] }\n}\n\nexport function createAiJobEditStep(deps: AiJobEditStepDeps = {}): AiJobStepRunner {\n const readTarget = deps.readTarget ?? readAiEditTarget\n const writeEdit = deps.writeEdit ?? writeAiEdit\n return async ({ job, now, signal, firestore, org, modelFor }): Promise<AiJobStepOutcome> => {\n const model = modelFor?.('job.edit') ?? aiJobEditModel()\n const unspent = (failure: string): AiJobStepOutcome => ({ ...aiUnspentOutcome(model), failure })\n const inputs = parseAiEditJobInputs(job.inputs)\n if (typeof inputs === 'string') return unspent(inputs)\n const hostId = job.hostId\n if (!hostId) return unspent(AI_EDIT_NO_SITE_COPY)\n const read = await readTarget(firestore, { orgId: job.orgId, hostId, inputs, jobId: aiEditVersionId(job) })\n if ('error' in read) return unspent(read.error)\n if (read.written) return aiUnspentOutcome(model, { outputs: [aiEditOutput(read, hostId, read.written, [])] })\n\n const orgData = org ?? ((await firestore.collection('orgs').doc(job.orgId).get()).data() ?? null)\n const versioning = checkEntitlement(orgData as never, 'versioning')\n const placement = aiEditPlacement({\n versioning,\n served: read.served,\n sourceIsPointer: read.sourceVersionId === read.pointerVersionId,\n sourceScheduled: read.scheduledVersionId === read.sourceVersionId,\n })\n if (placement === 'refused') return aiUnspentOutcome(model, { review: aiLimitReview(AI_EDIT_NEEDS_VERSIONING_COPY) })\n const outline = aiEditJobOutline(read.nodes)\n const context = outline ? parseAssistEditContext(outline) : null\n if (!context) return unspent(AI_EDIT_NO_NODES_COPY)\n const target: AssistEditTarget = {\n kind: read.kind,\n documentId: read.id,\n versionId: placement === 'in-place' ? read.sourceVersionId : aiEditVersionId(job),\n hostId,\n }\n\n const generation = await runValidatedGeneration(\n 'edit',\n aiJobEditGeneration({ brief: job.brief, context, target, model, ...(signal ? { signal } : {}) }),\n )\n const spent = { ...aiGenerationSpent(generation), ...(generation.effort ? { effort: generation.effort } : {}) }\n if (generation.status === 'refused') return { ...spent, refused: true }\n // An answer its re-ask still could not make whole keeps the changes that\n // survived, as the chat door's card does, and says what it left out; one\n // with none, or one that broke a numbered rule, is nothing to save.\n let proposal: AssistEditProposal | null = generation.status === 'ok' ? generation.value : null\n const refused = generation.status === 'needs_input' ? generation.answer : null\n if (!proposal && refused) {\n const salvaged = validateStreamedGeneration('edit', {\n answer: refused,\n check: (answer) => checkAssistEditAnswer(answer, { context, target }),\n })\n proposal = salvaged.status === 'ok' ? salvaged.value : null\n }\n if (!proposal) return { ...spent, failure: AI_EDIT_NOTHING_COPY }\n\n const seo = read.kind === 'screen' && !read.served\n const kept = aiEditJobOps(proposal.ops, { seo })\n const leftOut = [...proposal.dropped, ...kept.leftOut]\n if (!kept.ops.length) {\n return { ...spent, failure: `Nothing was saved. Left out: ${leftOut.join('; ') || 'every change'}.` }\n }\n const write = await writeEdit(firestore, {\n hostId,\n kind: read.kind,\n id: read.id,\n sourceVersionId: read.sourceVersionId,\n jobVersionId: aiEditVersionId(job),\n versioning,\n proposal: { ...proposal, ops: kept.ops },\n writeSeo: seo,\n versionName: aiEditVersionName(proposal.summary),\n uid: job.createdBy,\n aiJobId: aiOriginJobId(job),\n now,\n })\n if (write.ok === false) return { ...spent, failure: write.error }\n return { ...spent, outputs: [aiEditOutput(read, hostId, write, leftOut)] }\n }\n}\n\nexport const runAiJobEditStep = createAiJobEditStep()\n\n// ── Admission ─────────────────────────────────────────────────────────────\n\nexport interface AiEditJobAdmissionDeps {\n readTarget?: typeof readAiEditTarget\n ownerOf?: typeof resolveOrgIdForHost\n}\n\n/**\n * An edit job is admitted for a page or layout of a site of its own org,\n * and only where the change has somewhere to land that no visitor sees on\n * this workspace's plan — refused in the plan's words before anything exists.\n */\nexport function createAiEditJobAdmission(deps: AiEditJobAdmissionDeps = {}): AiJobAdmission {\n const readTarget = deps.readTarget ?? readAiEditTarget\n const ownerOf = deps.ownerOf ?? resolveOrgIdForHost\n return async (context): Promise<AiJobAdmissionRefusal | null> => {\n const inputs = parseAiEditJobInputs(context.inputs)\n if (typeof inputs === 'string') return { status: 400, error: inputs }\n if (!context.hostId) return { status: 400, error: AI_EDIT_NO_SITE_COPY }\n const owner = await ownerOf(context.hostId)\n if (!owner || owner !== context.orgId) return { status: 404, error: 'Unknown site' }\n // A job asked about at its door has no run to find yet.\n const read = await readTarget(context.firestore, { orgId: context.orgId, hostId: context.hostId, inputs, jobId: null })\n if ('error' in read) return read\n const placement = aiEditPlacement({\n versioning: checkEntitlement(context.org as never, 'versioning'),\n served: read.served,\n sourceIsPointer: read.sourceVersionId === read.pointerVersionId,\n sourceScheduled: read.scheduledVersionId === read.sourceVersionId,\n })\n return placement === 'refused' ? { status: 403, error: AI_EDIT_NEEDS_VERSIONING_COPY } : null\n }\n}\n\nexport const aiEditJobAdmission = createAiEditJobAdmission()\n\n/** Registers the edit step and the check an edit job passes before it is created or resumed. */\nexport function registerAiEditJob(): void {\n registerAiJobStep('edit', runAiJobEditStep, { minimumMs: AI_JOB_EDIT_STEP_MINIMUM_MS })\n registerAiJobAdmission('edit', aiEditJobAdmission)\n}\n"],"names":["checkEntitlement","SCREEN_KIND_TEMPLATE","screenSeoStageKey","SCREEN_SEO_TEXT_FIELDS","decodeStoredNodes","encodeStoredNodes","CanvasManager","FEATURE_FLAG","resolveOrgIdForHost","applyAssistEdit","AI_EDIT_COMPONENT_LEFT_OUT","AI_EDIT_EMAIL_COPY","AI_EDIT_GONE_COPY","AI_EDIT_NEEDS_VERSIONING_COPY","AI_EDIT_NO_NODES_COPY","AI_EDIT_NO_SITE_COPY","AI_EDIT_NOTHING_COPY","AI_EDIT_REFUSED_COPY","AI_EDIT_SEO_LEFT_OUT","AI_EDIT_STALE_COPY","AI_EDIT_VERSION_GONE_COPY","aiEditJobOutline","aiEditOutputNote","aiEditVersionName","parseAiEditJobInputs","ASSIST_EDIT_TOOL_NAME","AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","runValidatedGeneration","validateStreamedGeneration","AI_PALETTE","assistEditTool","checkAssistEditAnswer","editCanvasBlock","editSelectionBlock","parseAssistEditContext","registerAiJobAdmission","aiJobStepBudget","aiOriginJobId","aiGenerationSpent","aiLimitReview","aiUnspentOutcome","AI_JOB_BRIEF_MAX_CHARS","registerAiJobStep","AI_EDIT_READS_MS","AI_JOB_EDIT_STEP_BUDGET","tier","maxTokens","lookups","ownReadsMs","AI_JOB_EDIT_STEP_MINIMUM_MS","minimumMs","aiJobEditModel","AI_JOB_EDIT_RULES","text","join","aiJobEditInstructions","kind","cacheBreakpoint","aiJobEditPrompt","input","context","brief","slice","aiJobEditGeneration","request","row","target","step","model","instructions","messages","role","content","tool","thinking","effort","signal","check","answer","AI_EDIT_CANVAS_PLATFORM","components","getSchema","componentId","entry","undefined","acceptsChildren","flags","dropping","DISABLED","getLabel","displayName","aiEditCanvas","nodes","canvas","setNodes","aiApplyEditToNodes","proposal","seo","fields","field","value","session","documentKind","documentId","versionId","isLiveVersion","applied","ok","reason","toJSON","COLLECTIONS","screen","layout","screenServed","host","get","routing","id","pendingScheduleVersion","doc","schedule","readAiEditTarget","firestore","hostRef","collection","hostId","exists","orgId","status","error","kinds","inputs","targetKind","docRef","pointerVersionId","versions","hostSubdomain","name","own","jobId","sourceVersionId","base","served","scheduledVersionId","placement","written","source","Object","keys","length","aiEditPlacement","versioning","sourceIsPointer","seen","sourceScheduled","writeAiEdit","sourceRef","newRef","jobVersionId","runTransaction","tx","already","Promise","all","pointer","seoWritten","writeSeo","ops","filter","op","packed","stored","Buffer","from","docPatch","update","updatedAt","now","aiEditJobId","copy","data","create","versionName","createdAt","createdBy","uid","aiJobId","entries","aiEditOutput","read","leftOut","resource","label","note","aiEditVersionId","job","$id","aiEditJobOps","options","kept","Set","add","push","createAiJobEditStep","deps","readTarget","writeEdit","org","modelFor","unspent","failure","outputs","orgData","review","outline","generation","spent","refused","salvaged","dropped","write","summary","runAiJobEditStep","createAiEditJobAdmission","ownerOf","owner","aiEditJobAdmission","registerAiEditJob"],"mappings":"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"}
@@ -16,6 +16,8 @@
16
16
  */ import { _ as _extends } from "@swc/helpers/_/_extends";
17
17
  import { AI_INSIGHT_ASK_SURFACES, AI_INSIGHT_DIGEST_DAYS, AI_INSIGHT_DIGEST_READERS, AI_INSIGHT_QUESTION_MAX_CHARS, AI_INSIGHTS_COLLECTION, aiInsightDays, aiInsightOutput, aiInsightSurface, aiInsightSurfaceNeedsSite } from "../model/ai-insight.js";
18
18
  import { aiHostPublishContext, aiSiteStatusTable } from "../model/ai-host-publish-context.js";
19
+ import { aiInstructionsWithSiteContext } from "../model/ai-site-context.js";
20
+ import { readAiSiteContext } from "../runtime/site-context.js";
19
21
  import { AI_ROUTING_TABLE, aiModelForStep } from "../providers/routing.js";
20
22
  import { AI_ACCEPTABLE_USE_BLOCK, runAiRequest } from "../runtime/ai-runtime.js";
21
23
  import { AI_GENERATION_MAX_ATTEMPTS } from "../runtime/ai-generation-bounds.js";
@@ -145,8 +147,8 @@ async function readHost(firestore, orgId, hostId) {
145
147
  };
146
148
  }
147
149
  export const runAiJobInsightStep = async (context)=>{
148
- var _ref, _job_hostId, _context_org, _job_expiresAt;
149
- var _job_inputs, _job_inputs1, _site_host;
150
+ var _ref, _job_hostId, _context_org, _site_host, _job_expiresAt;
151
+ var _job_inputs, _job_inputs1, _site_host1;
150
152
  const { job, firestore, signal, now } = context;
151
153
  const model = (_ref = context.modelFor == null ? void 0 : context.modelFor.call(context, 'job.insight')) != null ? _ref : aiModelForStep('job.insight');
152
154
  const spend = new InsightSpend(model);
@@ -177,9 +179,24 @@ export const runAiJobInsightStep = async (context)=>{
177
179
  const maxTokens = AI_JOB_INSIGHT_STEP_BUDGET.maxTokens(model);
178
180
  const allowance = AI_GENERATION_MAX_ATTEMPTS * maxTokens;
179
181
  const left = ()=>allowance - spend.usage.outputTokens;
182
+ // What the business is and what the owner's edits showed (AGL-3661), so an
183
+ // answer reads the numbers for THIS business. No contact details: an
184
+ // insight names nobody. No publish state either: that is the Site status
185
+ // table's to say, and the rules send the model there.
186
+ const siteContext = await readAiSiteContext(firestore, {
187
+ orgId: job.orgId,
188
+ hostId,
189
+ host: (_site_host = site.host) != null ? _site_host : null
190
+ });
191
+ const system = [
192
+ ...aiInstructionsWithSiteContext(AI_JOB_INSIGHT_SYSTEM, siteContext ? _extends({}, siteContext, {
193
+ publish: null,
194
+ contact: false
195
+ }) : null)
196
+ ];
180
197
  const ask = (messages, tool, ceiling)=>runAiRequest(_extends({
181
198
  model,
182
- system: AI_JOB_INSIGHT_SYSTEM,
199
+ system,
183
200
  messages,
184
201
  tools: [
185
202
  tool === 'read' ? aiInsightReadTool() : aiInsightAnswerTool()
@@ -303,7 +320,7 @@ export const runAiJobInsightStep = async (context)=>{
303
320
  }));
304
321
  return _extends({
305
322
  outputs: [
306
- aiInsightOutput(job, record, str((_site_host = site.host) == null ? void 0 : _site_host['subdomain']) || null)
323
+ aiInsightOutput(job, record, str((_site_host1 = site.host) == null ? void 0 : _site_host1['subdomain']) || null)
307
324
  ]
308
325
  }, spend.outcome());
309
326
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-insight-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { ResolvedPluginFigureReader } from '@aglyn/aglyn/plugin-manager/plugin-figures'\nimport {\n AI_INSIGHT_ASK_SURFACES,\n AI_INSIGHT_DIGEST_DAYS,\n AI_INSIGHT_DIGEST_READERS,\n AI_INSIGHT_QUESTION_MAX_CHARS,\n AI_INSIGHTS_COLLECTION,\n aiInsightDays,\n aiInsightOutput,\n aiInsightSurface,\n aiInsightSurfaceNeedsSite,\n type AiInsightRecord,\n type AiInsightSurface,\n type AiInsightTable,\n} from '../model/ai-insight'\nimport { aiHostPublishContext, aiSiteStatusTable } from '../model/ai-host-publish-context'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n AI_ACCEPTABLE_USE_BLOCK,\n runAiRequest,\n type AiMessage,\n type AiResult,\n type AiSystemBlock,\n type AiUsage,\n} from '../runtime/ai-runtime'\nimport { AI_GENERATION_MAX_ATTEMPTS } from '../runtime/ai-generation-bounds'\nimport { checkAiInsightAnswer, type AiInsightCheck } from '../runtime/ai-insight-check'\nimport {\n AI_INSIGHT_ANSWER_TOOL_NAME,\n AI_INSIGHT_READ_TOOL_NAME,\n aiInsightAnswerTool,\n aiInsightReadTool,\n parseAiInsightAnswer,\n parseAiInsightReads,\n type AiInsightReadRequest,\n} from '../tools/ai-insight-tool'\nimport { aiDatasetCatalog } from '../insights/ai-dataset-catalog'\nimport {\n aiInsightDatasetCatalog,\n aiInsightReaderCatalog,\n aiInsightReaderDays,\n aiInsightReaders,\n aiInsightTableText,\n readAiInsightTables,\n} from '../insights/ai-insight-readers'\nimport { assistExchangeExpiry } from '../usage/assist-usage'\nimport { registerAiJobAdmission, type AiJobAdmissionContext, type AiJobAdmissionRefusal } from './ai-job-admission'\nimport { AI_INSIGHT_READ_MAX_TOKENS, AI_JOB_INSIGHT_STEP_BUDGET, AI_JOB_INSIGHT_STEP_MINIMUM_MS } from './ai-job-insight-budget'\nimport type { AiJobStepContext, AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The `insight` step (AGL-2915): a question about a site's figures, answered\n * from tables code read, with every insight traced to the rows it cites.\n *\n * ## Two exchanges, and the model never writes a query\n *\n * 1. **The reads.** The model is shown the question and the readers this job\n * may read — each an id, what it answers, its windows and parameters — and\n * answers `read_figures` with a choice among them. It names nothing else: no\n * collection, no field path, no filter. A choice that names no reader this\n * job may read is dropped, and a job left with none reads the surface's\n * defaults. A weekly digest makes no read call: code reads the digest's\n * readers.\n * 2. **The answer.** Code reads the tables through the owners' readers, and the\n * model is shown them and answers `submit_insights`. The trace\n * (`runtime/ai-insight-check.ts`) keeps each insight whose every number is\n * in a row it cites and leaves out the rest. An answer that is not a call,\n * or whose every insight was left out, is asked for once more with the\n * reasons; what the second answer keeps is the answer.\n *\n * Both calls share one allowance of output, twice the routing ceiling, which\n * is what the step's least time is planned against.\n *\n * ## What reaches the model\n *\n * The question, the names and descriptions of the readers, the tables, the\n * site's publish state (published or not, its address, its pages' paths), and\n * for a datasets question the names of the datasets the member may see with\n * their fields' names and types. A table holds counts, sums and rates labeled\n * by a page path, a referring site, a form's, a service's, a product's or a\n * campaign's name, or the values of a dataset field with at most sixty\n * different values that at least three records share. No\n * record, visitor, contact, order, booking, submission or recipient is read\n * into a prompt, and every text cell is masked of email addresses and phone\n * numbers on its way into a table.\n *\n * ## What it writes\n *\n * The answer, at `orgs/{orgId}/aiInsights/{jobId}` under the job's own\n * expiry, and an output that names it and says how many insights it holds.\n * Nothing else: no draft, no setting, no message.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport const AI_INSIGHT_NO_SURFACE_COPY = 'This question does not say which page it was asked from.'\nexport const AI_INSIGHT_NO_SITE_COPY = 'Open the site whose numbers you are asking about.'\nexport const AI_INSIGHT_NO_READERS_COPY =\n 'There are no figures to answer from here yet: the features this page reports on are not on for this workspace.'\nexport const AI_INSIGHT_NO_TABLES_COPY = 'The figures could not be read. Try again in a moment.'\nexport const AI_INSIGHT_UNREADABLE_COPY = 'The AI did not answer with insights. Try asking in different words.'\n\n/** The digest's question: what a week's insights cover, in the order a person reads them. */\nexport const AI_INSIGHT_DIGEST_QUESTION =\n 'What changed on this site this week? Write the week’s insights: the change in traffic, the most viewed page, the form that turned the most views into submissions, a campaign that did better than the rest, and any day that stood out — only those the tables support.'\n\n/** The rules both calls are sent: static, byte-identical for every workspace. */\nexport const AI_INSIGHT_RULES = [\n 'You answer a workspace owner’s questions about their own figures: page views, orders, bookings, forms, campaigns, A/B tests and datasets. You never see a record, and you never write a query: code reads figures through fixed readers and gives you their tables.',\n '',\n `When you are given a question and a list of readers, call ${AI_INSIGHT_READ_TOOL_NAME} once with the readers whose tables answer it, most useful first, and the window each should cover. Choose only readers from the list, with their parameters' values taken from what the list and the question give you.`,\n '',\n `When you are given tables, call ${AI_INSIGHT_ANSWER_TOOL_NAME} once. Each table has a handle (t1, t2, …) and numbered rows. Rules for the insights:`,\n '- Say what matters most to the question first. Leave out what the tables do not support.',\n '- Every insight cites the table and the rows its figures come from.',\n '- Every number you write appears in a row you cite, as the table shows it or rounded. Never add, subtract, average or otherwise compute a figure yourself. The only other numbers you may write are the length of a table’s window, a date in it, and how many rows you cite.',\n '- Say a figure rose or fell only when a change you quote shows it.',\n '- Name nobody, and write no email address or phone number.',\n '- Plain sentences: no markdown, no headings, no lists.',\n '- When the tables cannot answer part of the question, say so in gap in one sentence with no numbers; otherwise gap is null.',\n '- Whether the site is published, and its address, come only from the Site status table. Page views and visitors never show whether a site is live: never say or suggest a site is not live, not launched or not published because of its traffic.',\n '- Write in the language of the question.',\n].join('\\n')\n\n/** The system blocks both calls send: the rules and the platform's acceptable-use rules. */\nexport const AI_JOB_INSIGHT_SYSTEM: AiSystemBlock[] = [\n { text: `${AI_INSIGHT_RULES}\\n\\n${AI_ACCEPTABLE_USE_BLOCK}`, cacheBreakpoint: true },\n]\n\nconst ZERO_USAGE: AiUsage = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** What the step's calls have spent so far, in the outcome's shape. */\nclass InsightSpend {\n usage: AiUsage = { ...ZERO_USAGE }\n estCostUsd = 0\n stopReason: string | null = null\n\n constructor(readonly model: string) {}\n\n add(result: AiResult): void {\n this.usage = {\n inputTokens: this.usage.inputTokens + result.usage.inputTokens,\n outputTokens: this.usage.outputTokens + result.usage.outputTokens,\n cacheReadTokens: this.usage.cacheReadTokens + result.usage.cacheReadTokens,\n cacheWriteTokens: this.usage.cacheWriteTokens + result.usage.cacheWriteTokens,\n }\n this.estCostUsd = Math.round((this.estCostUsd + result.estCostUsd) * 1_000_000) / 1_000_000\n this.stopReason = result.stopReason\n }\n\n outcome(): Pick<AiJobStepOutcome, 'usage' | 'estCostUsd' | 'model' | 'stopReason' | 'effort'> {\n return { usage: this.usage, estCostUsd: this.estCostUsd, model: this.model, stopReason: this.stopReason, effort: null }\n }\n}\n\n/** The read call's user turn: the question, the window asked for, the readers and, for datasets, the datasets. */\nexport function aiInsightReadPrompt(input: {\n question: string\n days: number\n readers: readonly ResolvedPluginFigureReader[]\n datasets: string | null\n}): string {\n return [\n `Question: ${input.question}`,\n `The window picked on the page: ${input.days} days. Use it unless the question names another window a reader lists.`,\n 'Readers you may read here:',\n aiInsightReaderCatalog(input.readers),\n ...(input.datasets === null ? [] : ['', 'Datasets you may read, with their fields:', input.datasets]),\n ].join('\\n')\n}\n\n/** The answer call's user turn: the question and every table. */\nexport function aiInsightAnswerPrompt(question: string, tables: readonly AiInsightTable[]): string {\n return [`Question: ${question}`, '', 'Tables:', '', tables.map(aiInsightTableText).join('\\n\\n')].join('\\n')\n}\n\n/** What a re-ask tells the model: why nothing it wrote could be kept. */\nexport function aiInsightReaskMessage(check: AiInsightCheck | null): string {\n const reasons = check?.findings.length\n ? `None of those insights could be kept: ${[...new Set(check.findings)].slice(0, 8).join('; ')}.`\n : `That answer did not come through ${AI_INSIGHT_ANSWER_TOOL_NAME}.`\n return `${reasons} Answer again with ${AI_INSIGHT_ANSWER_TOOL_NAME}: cite the rows each insight's figures are in, and write every number as a row you cite shows it.`\n}\n\n/** The reads a surface falls back to when the model named none it may read: the digest's own readers. */\nfunction defaultReads(readers: readonly ResolvedPluginFigureReader[], days: number): AiInsightReadRequest[] {\n const ids = readers.map(({ reader }) => reader.id)\n const preferred = AI_INSIGHT_DIGEST_READERS.filter((id) => ids.includes(id))\n const chosen = preferred.length ? preferred : ids\n return chosen.map((id) => ({\n reader: id,\n days: aiInsightReaderDays(readers.find(({ reader }) => reader.id === id)?.reader.windows ?? [], days),\n params: {},\n }))\n}\n\nasync function readHost(\n firestore: Firestore,\n orgId: string,\n hostId: string | null,\n): Promise<{ host: Record<string, unknown> | null; ok: boolean }> {\n if (!hostId) return { host: null, ok: true }\n const snapshot = await firestore.collection('hosts').doc(hostId).get()\n const host = snapshot.exists ? ((snapshot.data() ?? {}) as Record<string, unknown>) : null\n return { host, ok: Boolean(host) && host?.['orgId'] === orgId }\n}\n\nexport const runAiJobInsightStep: AiJobStepRunner = async (context: AiJobStepContext) => {\n const { job, firestore, signal, now } = context\n const model = context.modelFor?.('job.insight') ?? aiModelForStep('job.insight')\n const spend = new InsightSpend(model)\n const stop = (failure: string): AiJobStepOutcome => ({ outputs: [], ...spend.outcome(), failure })\n\n const surface = aiInsightSurface(job.inputs)\n if (!surface) return stop(AI_INSIGHT_NO_SURFACE_COPY)\n const hostId = job.hostId ?? null\n if (aiInsightSurfaceNeedsSite(surface) && !hostId) return stop(AI_INSIGHT_NO_SITE_COPY)\n // The door checks the caller belongs to the org; that the site does is\n // checked here, before anything about it is read.\n const site = await readHost(firestore, job.orgId, hostId)\n if (!site.ok) return stop(AI_INSIGHT_NO_SITE_COPY)\n const readers = await aiInsightReaders({\n orgId: job.orgId,\n hostId,\n org: context.org ?? null,\n host: site.host,\n surface,\n })\n if (!readers.length) return stop(AI_INSIGHT_NO_READERS_COPY)\n\n const digest = surface === 'digest'\n const days = digest ? AI_INSIGHT_DIGEST_DAYS : aiInsightDays(job.inputs?.['days'])\n const question = digest ? AI_INSIGHT_DIGEST_QUESTION : job.brief.slice(0, AI_INSIGHT_QUESTION_MAX_CHARS)\n const maxTokens = AI_JOB_INSIGHT_STEP_BUDGET.maxTokens(model)\n const allowance = AI_GENERATION_MAX_ATTEMPTS * maxTokens\n const left = () => allowance - spend.usage.outputTokens\n const ask = (messages: AiMessage[], tool: 'read' | 'answer', ceiling: number) =>\n runAiRequest({\n model,\n system: AI_JOB_INSIGHT_SYSTEM,\n messages,\n tools: [tool === 'read' ? aiInsightReadTool() : aiInsightAnswerTool()],\n maxTokens: Math.max(1, Math.min(ceiling, left())),\n ...(AI_ROUTING_TABLE['job.insight'].thinking ? { thinking: AI_ROUTING_TABLE['job.insight'].thinking } : {}),\n stream: false,\n ...(signal ? { signal } : {}),\n })\n\n // 1. The reads.\n const base = { orgId: job.orgId, hostId, uid: digest ? null : job.createdBy, now }\n let requests: AiInsightReadRequest[]\n if (digest) {\n requests = defaultReads(readers, days)\n } else {\n const datasets =\n surface === 'datasets'\n ? aiInsightDatasetCatalog(await aiDatasetCatalog(firestore, { orgId: job.orgId, hostId, uid: job.createdBy }))\n : null\n const result = await ask(\n [{ role: 'user', content: aiInsightReadPrompt({ question, days, readers, datasets }) }],\n 'read',\n AI_INSIGHT_READ_MAX_TOKENS,\n )\n spend.add(result)\n if (result.kind === 'refusal') return { outputs: [], ...spend.outcome(), refused: true }\n requests = parseAiInsightReads(result.toolUse.find((use) => use.name === AI_INSIGHT_READ_TOOL_NAME)?.input)\n }\n const maxTables = digest ? AI_INSIGHT_DIGEST_READERS.length : undefined\n let read = await readAiInsightTables(readers, requests, base, maxTables)\n if (!read.tables.length && !digest) read = await readAiInsightTables(readers, defaultReads(readers, days), base)\n if (read.refusals.length) console.warn('ai insight reads refused', { jobId: job.$id, refusals: read.refusals })\n if (!read.tables.length) return stop(AI_INSIGHT_NO_TABLES_COPY)\n // A site's question is told whether the site is live, so traffic is never\n // read as launch status. Appended, so the readers' refs stay as they were.\n const tables =\n !digest && site.host\n ? [...read.tables, aiSiteStatusTable(aiHostPublishContext(site.host), `t${read.tables.length + 1}`)]\n : read.tables\n\n // 2. The answer, and at most one re-ask.\n let messages: AiMessage[] = [{ role: 'user', content: aiInsightAnswerPrompt(question, tables) }]\n let checked: AiInsightCheck | null = null\n for (let attempt = 0; attempt < AI_GENERATION_MAX_ATTEMPTS && left() > 0; attempt += 1) {\n const result = await ask(messages, 'answer', maxTokens)\n spend.add(result)\n if (result.kind === 'refusal') return { outputs: [], ...spend.outcome(), refused: true }\n const answer = parseAiInsightAnswer(result.toolUse.find((use) => use.name === AI_INSIGHT_ANSWER_TOOL_NAME)?.input)\n const check = answer ? checkAiInsightAnswer(answer, tables) : null\n if (check) checked = check\n if (check && (check.kept.length || !answer?.insights.length)) break\n messages = [\n ...messages,\n { role: 'assistant', content: `Answered with ${AI_INSIGHT_ANSWER_TOOL_NAME}.` },\n { role: 'user', content: aiInsightReaskMessage(check) },\n ]\n }\n if (!checked) return stop(AI_INSIGHT_UNREADABLE_COPY)\n\n const record: AiInsightRecord = {\n jobId: job.$id,\n orgId: job.orgId,\n hostId,\n surface,\n createdBy: job.createdBy,\n question: digest ? str(job.brief) || 'Weekly insights' : question,\n days,\n insights: checked.kept,\n gap: checked.gap,\n tables,\n left: checked.left.length,\n week: digest ? str(job.inputs?.['week']) || null : null,\n createdAtMs: now.getTime(),\n }\n if (checked.left.length) {\n console.info('ai insight left out untraced insights', { jobId: job.$id, findings: checked.findings })\n }\n // Keyed by the job, so a step run again replaces its own answer.\n await firestore\n .collection('orgs')\n .doc(job.orgId)\n .collection(AI_INSIGHTS_COLLECTION)\n .doc(job.$id)\n .set({ ...(JSON.parse(JSON.stringify(record)) as AiInsightRecord), expiresAt: job.expiresAt ?? assistExchangeExpiry(now) })\n return {\n outputs: [aiInsightOutput(job, record, str(site.host?.['subdomain']) || null)],\n ...spend.outcome(),\n }\n}\n\n/**\n * Whether a question may be asked here: a surface a person asks from, a site\n * of the job's own org where the surface needs one, and at least one reader\n * the workspace may read. A digest is made by the weekly sweep and never at a\n * door.\n */\nexport async function aiInsightAdmissionRefusal(\n context: AiJobAdmissionContext,\n): Promise<AiJobAdmissionRefusal | null> {\n const surface: AiInsightSurface | null = aiInsightSurface(context.inputs)\n if (!surface || !AI_INSIGHT_ASK_SURFACES.includes(surface)) {\n return { status: 400, error: 'Ask about your numbers from a site’s Analytics, Data or CRM Reports page.' }\n }\n if (aiInsightSurfaceNeedsSite(surface) && !context.hostId) {\n return { status: 400, error: AI_INSIGHT_NO_SITE_COPY }\n }\n const site = await readHost(context.firestore, context.orgId, context.hostId)\n if (!site.ok) return { status: 404, error: 'Unknown site' }\n const readers = await aiInsightReaders({\n orgId: context.orgId,\n hostId: context.hostId,\n org: context.org,\n host: site.host,\n surface,\n })\n return readers.length ? null : { status: 403, error: AI_INSIGHT_NO_READERS_COPY }\n}\n\n/**\n * The insight kind's registrations, called by the console surface\n * (`registerAiJobKinds` in `server.ts`), never made by importing this module\n * (AGL-3025).\n */\nexport function registerAiInsightJob(): void {\n registerAiJobStep('insight', runAiJobInsightStep, { minimumMs: AI_JOB_INSIGHT_STEP_MINIMUM_MS })\n registerAiJobAdmission('insight', aiInsightAdmissionRefusal)\n}\n"],"names":["AI_INSIGHT_ASK_SURFACES","AI_INSIGHT_DIGEST_DAYS","AI_INSIGHT_DIGEST_READERS","AI_INSIGHT_QUESTION_MAX_CHARS","AI_INSIGHTS_COLLECTION","aiInsightDays","aiInsightOutput","aiInsightSurface","aiInsightSurfaceNeedsSite","aiHostPublishContext","aiSiteStatusTable","AI_ROUTING_TABLE","aiModelForStep","AI_ACCEPTABLE_USE_BLOCK","runAiRequest","AI_GENERATION_MAX_ATTEMPTS","checkAiInsightAnswer","AI_INSIGHT_ANSWER_TOOL_NAME","AI_INSIGHT_READ_TOOL_NAME","aiInsightAnswerTool","aiInsightReadTool","parseAiInsightAnswer","parseAiInsightReads","aiDatasetCatalog","aiInsightDatasetCatalog","aiInsightReaderCatalog","aiInsightReaderDays","aiInsightReaders","aiInsightTableText","readAiInsightTables","assistExchangeExpiry","registerAiJobAdmission","AI_INSIGHT_READ_MAX_TOKENS","AI_JOB_INSIGHT_STEP_BUDGET","AI_JOB_INSIGHT_STEP_MINIMUM_MS","registerAiJobStep","AI_INSIGHT_NO_SURFACE_COPY","AI_INSIGHT_NO_SITE_COPY","AI_INSIGHT_NO_READERS_COPY","AI_INSIGHT_NO_TABLES_COPY","AI_INSIGHT_UNREADABLE_COPY","AI_INSIGHT_DIGEST_QUESTION","AI_INSIGHT_RULES","join","AI_JOB_INSIGHT_SYSTEM","text","cacheBreakpoint","ZERO_USAGE","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","str","value","trim","InsightSpend","add","result","usage","estCostUsd","Math","round","stopReason","outcome","model","effort","aiInsightReadPrompt","input","question","days","readers","datasets","aiInsightAnswerPrompt","tables","map","aiInsightReaskMessage","check","reasons","findings","length","Set","slice","defaultReads","ids","reader","id","preferred","filter","includes","chosen","find","windows","params","readHost","firestore","orgId","hostId","snapshot","host","ok","collection","doc","get","exists","data","Boolean","runAiJobInsightStep","context","job","site","signal","now","modelFor","spend","stop","failure","outputs","surface","inputs","org","digest","brief","maxTokens","allowance","left","ask","messages","tool","ceiling","system","tools","max","min","thinking","stream","base","uid","createdBy","requests","role","content","kind","refused","toolUse","use","name","maxTables","undefined","read","refusals","console","warn","jobId","$id","checked","attempt","answer","kept","insights","record","gap","week","createdAtMs","getTime","info","set","JSON","parse","stringify","expiresAt","aiInsightAdmissionRefusal","status","error","registerAiInsightJob","minimumMs"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-insight-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { ResolvedPluginFigureReader } from '@aglyn/aglyn/plugin-manager/plugin-figures'\nimport {\n AI_INSIGHT_ASK_SURFACES,\n AI_INSIGHT_DIGEST_DAYS,\n AI_INSIGHT_DIGEST_READERS,\n AI_INSIGHT_QUESTION_MAX_CHARS,\n AI_INSIGHTS_COLLECTION,\n aiInsightDays,\n aiInsightOutput,\n aiInsightSurface,\n aiInsightSurfaceNeedsSite,\n type AiInsightRecord,\n type AiInsightSurface,\n type AiInsightTable,\n} from '../model/ai-insight'\nimport { aiHostPublishContext, aiSiteStatusTable } from '../model/ai-host-publish-context'\nimport { aiInstructionsWithSiteContext } from '../model/ai-site-context'\nimport { readAiSiteContext } from '../runtime/site-context'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n AI_ACCEPTABLE_USE_BLOCK,\n runAiRequest,\n type AiMessage,\n type AiResult,\n type AiSystemBlock,\n type AiUsage,\n} from '../runtime/ai-runtime'\nimport { AI_GENERATION_MAX_ATTEMPTS } from '../runtime/ai-generation-bounds'\nimport { checkAiInsightAnswer, type AiInsightCheck } from '../runtime/ai-insight-check'\nimport {\n AI_INSIGHT_ANSWER_TOOL_NAME,\n AI_INSIGHT_READ_TOOL_NAME,\n aiInsightAnswerTool,\n aiInsightReadTool,\n parseAiInsightAnswer,\n parseAiInsightReads,\n type AiInsightReadRequest,\n} from '../tools/ai-insight-tool'\nimport { aiDatasetCatalog } from '../insights/ai-dataset-catalog'\nimport {\n aiInsightDatasetCatalog,\n aiInsightReaderCatalog,\n aiInsightReaderDays,\n aiInsightReaders,\n aiInsightTableText,\n readAiInsightTables,\n} from '../insights/ai-insight-readers'\nimport { assistExchangeExpiry } from '../usage/assist-usage'\nimport { registerAiJobAdmission, type AiJobAdmissionContext, type AiJobAdmissionRefusal } from './ai-job-admission'\nimport { AI_INSIGHT_READ_MAX_TOKENS, AI_JOB_INSIGHT_STEP_BUDGET, AI_JOB_INSIGHT_STEP_MINIMUM_MS } from './ai-job-insight-budget'\nimport type { AiJobStepContext, AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The `insight` step (AGL-2915): a question about a site's figures, answered\n * from tables code read, with every insight traced to the rows it cites.\n *\n * ## Two exchanges, and the model never writes a query\n *\n * 1. **The reads.** The model is shown the question and the readers this job\n * may read — each an id, what it answers, its windows and parameters — and\n * answers `read_figures` with a choice among them. It names nothing else: no\n * collection, no field path, no filter. A choice that names no reader this\n * job may read is dropped, and a job left with none reads the surface's\n * defaults. A weekly digest makes no read call: code reads the digest's\n * readers.\n * 2. **The answer.** Code reads the tables through the owners' readers, and the\n * model is shown them and answers `submit_insights`. The trace\n * (`runtime/ai-insight-check.ts`) keeps each insight whose every number is\n * in a row it cites and leaves out the rest. An answer that is not a call,\n * or whose every insight was left out, is asked for once more with the\n * reasons; what the second answer keeps is the answer.\n *\n * Both calls share one allowance of output, twice the routing ceiling, which\n * is what the step's least time is planned against.\n *\n * ## What reaches the model\n *\n * The question, the names and descriptions of the readers, the tables, the\n * site's publish state (published or not, its address, its pages' paths), and\n * for a datasets question the names of the datasets the member may see with\n * their fields' names and types. A table holds counts, sums and rates labeled\n * by a page path, a referring site, a form's, a service's, a product's or a\n * campaign's name, or the values of a dataset field with at most sixty\n * different values that at least three records share. No\n * record, visitor, contact, order, booking, submission or recipient is read\n * into a prompt, and every text cell is masked of email addresses and phone\n * numbers on its way into a table.\n *\n * ## What it writes\n *\n * The answer, at `orgs/{orgId}/aiInsights/{jobId}` under the job's own\n * expiry, and an output that names it and says how many insights it holds.\n * Nothing else: no draft, no setting, no message.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport const AI_INSIGHT_NO_SURFACE_COPY = 'This question does not say which page it was asked from.'\nexport const AI_INSIGHT_NO_SITE_COPY = 'Open the site whose numbers you are asking about.'\nexport const AI_INSIGHT_NO_READERS_COPY =\n 'There are no figures to answer from here yet: the features this page reports on are not on for this workspace.'\nexport const AI_INSIGHT_NO_TABLES_COPY = 'The figures could not be read. Try again in a moment.'\nexport const AI_INSIGHT_UNREADABLE_COPY = 'The AI did not answer with insights. Try asking in different words.'\n\n/** The digest's question: what a week's insights cover, in the order a person reads them. */\nexport const AI_INSIGHT_DIGEST_QUESTION =\n 'What changed on this site this week? Write the week’s insights: the change in traffic, the most viewed page, the form that turned the most views into submissions, a campaign that did better than the rest, and any day that stood out — only those the tables support.'\n\n/** The rules both calls are sent: static, byte-identical for every workspace. */\nexport const AI_INSIGHT_RULES = [\n 'You answer a workspace owner’s questions about their own figures: page views, orders, bookings, forms, campaigns, A/B tests and datasets. You never see a record, and you never write a query: code reads figures through fixed readers and gives you their tables.',\n '',\n `When you are given a question and a list of readers, call ${AI_INSIGHT_READ_TOOL_NAME} once with the readers whose tables answer it, most useful first, and the window each should cover. Choose only readers from the list, with their parameters' values taken from what the list and the question give you.`,\n '',\n `When you are given tables, call ${AI_INSIGHT_ANSWER_TOOL_NAME} once. Each table has a handle (t1, t2, …) and numbered rows. Rules for the insights:`,\n '- Say what matters most to the question first. Leave out what the tables do not support.',\n '- Every insight cites the table and the rows its figures come from.',\n '- Every number you write appears in a row you cite, as the table shows it or rounded. Never add, subtract, average or otherwise compute a figure yourself. The only other numbers you may write are the length of a table’s window, a date in it, and how many rows you cite.',\n '- Say a figure rose or fell only when a change you quote shows it.',\n '- Name nobody, and write no email address or phone number.',\n '- Plain sentences: no markdown, no headings, no lists.',\n '- When the tables cannot answer part of the question, say so in gap in one sentence with no numbers; otherwise gap is null.',\n '- Whether the site is published, and its address, come only from the Site status table. Page views and visitors never show whether a site is live: never say or suggest a site is not live, not launched or not published because of its traffic.',\n '- Write in the language of the question.',\n].join('\\n')\n\n/** The system blocks both calls send: the rules and the platform's acceptable-use rules. */\nexport const AI_JOB_INSIGHT_SYSTEM: AiSystemBlock[] = [\n { text: `${AI_INSIGHT_RULES}\\n\\n${AI_ACCEPTABLE_USE_BLOCK}`, cacheBreakpoint: true },\n]\n\nconst ZERO_USAGE: AiUsage = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** What the step's calls have spent so far, in the outcome's shape. */\nclass InsightSpend {\n usage: AiUsage = { ...ZERO_USAGE }\n estCostUsd = 0\n stopReason: string | null = null\n\n constructor(readonly model: string) {}\n\n add(result: AiResult): void {\n this.usage = {\n inputTokens: this.usage.inputTokens + result.usage.inputTokens,\n outputTokens: this.usage.outputTokens + result.usage.outputTokens,\n cacheReadTokens: this.usage.cacheReadTokens + result.usage.cacheReadTokens,\n cacheWriteTokens: this.usage.cacheWriteTokens + result.usage.cacheWriteTokens,\n }\n this.estCostUsd = Math.round((this.estCostUsd + result.estCostUsd) * 1_000_000) / 1_000_000\n this.stopReason = result.stopReason\n }\n\n outcome(): Pick<AiJobStepOutcome, 'usage' | 'estCostUsd' | 'model' | 'stopReason' | 'effort'> {\n return { usage: this.usage, estCostUsd: this.estCostUsd, model: this.model, stopReason: this.stopReason, effort: null }\n }\n}\n\n/** The read call's user turn: the question, the window asked for, the readers and, for datasets, the datasets. */\nexport function aiInsightReadPrompt(input: {\n question: string\n days: number\n readers: readonly ResolvedPluginFigureReader[]\n datasets: string | null\n}): string {\n return [\n `Question: ${input.question}`,\n `The window picked on the page: ${input.days} days. Use it unless the question names another window a reader lists.`,\n 'Readers you may read here:',\n aiInsightReaderCatalog(input.readers),\n ...(input.datasets === null ? [] : ['', 'Datasets you may read, with their fields:', input.datasets]),\n ].join('\\n')\n}\n\n/** The answer call's user turn: the question and every table. */\nexport function aiInsightAnswerPrompt(question: string, tables: readonly AiInsightTable[]): string {\n return [`Question: ${question}`, '', 'Tables:', '', tables.map(aiInsightTableText).join('\\n\\n')].join('\\n')\n}\n\n/** What a re-ask tells the model: why nothing it wrote could be kept. */\nexport function aiInsightReaskMessage(check: AiInsightCheck | null): string {\n const reasons = check?.findings.length\n ? `None of those insights could be kept: ${[...new Set(check.findings)].slice(0, 8).join('; ')}.`\n : `That answer did not come through ${AI_INSIGHT_ANSWER_TOOL_NAME}.`\n return `${reasons} Answer again with ${AI_INSIGHT_ANSWER_TOOL_NAME}: cite the rows each insight's figures are in, and write every number as a row you cite shows it.`\n}\n\n/** The reads a surface falls back to when the model named none it may read: the digest's own readers. */\nfunction defaultReads(readers: readonly ResolvedPluginFigureReader[], days: number): AiInsightReadRequest[] {\n const ids = readers.map(({ reader }) => reader.id)\n const preferred = AI_INSIGHT_DIGEST_READERS.filter((id) => ids.includes(id))\n const chosen = preferred.length ? preferred : ids\n return chosen.map((id) => ({\n reader: id,\n days: aiInsightReaderDays(readers.find(({ reader }) => reader.id === id)?.reader.windows ?? [], days),\n params: {},\n }))\n}\n\nasync function readHost(\n firestore: Firestore,\n orgId: string,\n hostId: string | null,\n): Promise<{ host: Record<string, unknown> | null; ok: boolean }> {\n if (!hostId) return { host: null, ok: true }\n const snapshot = await firestore.collection('hosts').doc(hostId).get()\n const host = snapshot.exists ? ((snapshot.data() ?? {}) as Record<string, unknown>) : null\n return { host, ok: Boolean(host) && host?.['orgId'] === orgId }\n}\n\nexport const runAiJobInsightStep: AiJobStepRunner = async (context: AiJobStepContext) => {\n const { job, firestore, signal, now } = context\n const model = context.modelFor?.('job.insight') ?? aiModelForStep('job.insight')\n const spend = new InsightSpend(model)\n const stop = (failure: string): AiJobStepOutcome => ({ outputs: [], ...spend.outcome(), failure })\n\n const surface = aiInsightSurface(job.inputs)\n if (!surface) return stop(AI_INSIGHT_NO_SURFACE_COPY)\n const hostId = job.hostId ?? null\n if (aiInsightSurfaceNeedsSite(surface) && !hostId) return stop(AI_INSIGHT_NO_SITE_COPY)\n // The door checks the caller belongs to the org; that the site does is\n // checked here, before anything about it is read.\n const site = await readHost(firestore, job.orgId, hostId)\n if (!site.ok) return stop(AI_INSIGHT_NO_SITE_COPY)\n const readers = await aiInsightReaders({\n orgId: job.orgId,\n hostId,\n org: context.org ?? null,\n host: site.host,\n surface,\n })\n if (!readers.length) return stop(AI_INSIGHT_NO_READERS_COPY)\n\n const digest = surface === 'digest'\n const days = digest ? AI_INSIGHT_DIGEST_DAYS : aiInsightDays(job.inputs?.['days'])\n const question = digest ? AI_INSIGHT_DIGEST_QUESTION : job.brief.slice(0, AI_INSIGHT_QUESTION_MAX_CHARS)\n const maxTokens = AI_JOB_INSIGHT_STEP_BUDGET.maxTokens(model)\n const allowance = AI_GENERATION_MAX_ATTEMPTS * maxTokens\n const left = () => allowance - spend.usage.outputTokens\n // What the business is and what the owner's edits showed (AGL-3661), so an\n // answer reads the numbers for THIS business. No contact details: an\n // insight names nobody. No publish state either: that is the Site status\n // table's to say, and the rules send the model there.\n const siteContext = await readAiSiteContext(firestore, { orgId: job.orgId, hostId, host: site.host ?? null })\n const system = [\n ...aiInstructionsWithSiteContext(\n AI_JOB_INSIGHT_SYSTEM,\n siteContext ? { ...siteContext, publish: null, contact: false } : null,\n ),\n ]\n const ask = (messages: AiMessage[], tool: 'read' | 'answer', ceiling: number) =>\n runAiRequest({\n model,\n system,\n messages,\n tools: [tool === 'read' ? aiInsightReadTool() : aiInsightAnswerTool()],\n maxTokens: Math.max(1, Math.min(ceiling, left())),\n ...(AI_ROUTING_TABLE['job.insight'].thinking ? { thinking: AI_ROUTING_TABLE['job.insight'].thinking } : {}),\n stream: false,\n ...(signal ? { signal } : {}),\n })\n\n // 1. The reads.\n const base = { orgId: job.orgId, hostId, uid: digest ? null : job.createdBy, now }\n let requests: AiInsightReadRequest[]\n if (digest) {\n requests = defaultReads(readers, days)\n } else {\n const datasets =\n surface === 'datasets'\n ? aiInsightDatasetCatalog(await aiDatasetCatalog(firestore, { orgId: job.orgId, hostId, uid: job.createdBy }))\n : null\n const result = await ask(\n [{ role: 'user', content: aiInsightReadPrompt({ question, days, readers, datasets }) }],\n 'read',\n AI_INSIGHT_READ_MAX_TOKENS,\n )\n spend.add(result)\n if (result.kind === 'refusal') return { outputs: [], ...spend.outcome(), refused: true }\n requests = parseAiInsightReads(result.toolUse.find((use) => use.name === AI_INSIGHT_READ_TOOL_NAME)?.input)\n }\n const maxTables = digest ? AI_INSIGHT_DIGEST_READERS.length : undefined\n let read = await readAiInsightTables(readers, requests, base, maxTables)\n if (!read.tables.length && !digest) read = await readAiInsightTables(readers, defaultReads(readers, days), base)\n if (read.refusals.length) console.warn('ai insight reads refused', { jobId: job.$id, refusals: read.refusals })\n if (!read.tables.length) return stop(AI_INSIGHT_NO_TABLES_COPY)\n // A site's question is told whether the site is live, so traffic is never\n // read as launch status. Appended, so the readers' refs stay as they were.\n const tables =\n !digest && site.host\n ? [...read.tables, aiSiteStatusTable(aiHostPublishContext(site.host), `t${read.tables.length + 1}`)]\n : read.tables\n\n // 2. The answer, and at most one re-ask.\n let messages: AiMessage[] = [{ role: 'user', content: aiInsightAnswerPrompt(question, tables) }]\n let checked: AiInsightCheck | null = null\n for (let attempt = 0; attempt < AI_GENERATION_MAX_ATTEMPTS && left() > 0; attempt += 1) {\n const result = await ask(messages, 'answer', maxTokens)\n spend.add(result)\n if (result.kind === 'refusal') return { outputs: [], ...spend.outcome(), refused: true }\n const answer = parseAiInsightAnswer(result.toolUse.find((use) => use.name === AI_INSIGHT_ANSWER_TOOL_NAME)?.input)\n const check = answer ? checkAiInsightAnswer(answer, tables) : null\n if (check) checked = check\n if (check && (check.kept.length || !answer?.insights.length)) break\n messages = [\n ...messages,\n { role: 'assistant', content: `Answered with ${AI_INSIGHT_ANSWER_TOOL_NAME}.` },\n { role: 'user', content: aiInsightReaskMessage(check) },\n ]\n }\n if (!checked) return stop(AI_INSIGHT_UNREADABLE_COPY)\n\n const record: AiInsightRecord = {\n jobId: job.$id,\n orgId: job.orgId,\n hostId,\n surface,\n createdBy: job.createdBy,\n question: digest ? str(job.brief) || 'Weekly insights' : question,\n days,\n insights: checked.kept,\n gap: checked.gap,\n tables,\n left: checked.left.length,\n week: digest ? str(job.inputs?.['week']) || null : null,\n createdAtMs: now.getTime(),\n }\n if (checked.left.length) {\n console.info('ai insight left out untraced insights', { jobId: job.$id, findings: checked.findings })\n }\n // Keyed by the job, so a step run again replaces its own answer.\n await firestore\n .collection('orgs')\n .doc(job.orgId)\n .collection(AI_INSIGHTS_COLLECTION)\n .doc(job.$id)\n .set({ ...(JSON.parse(JSON.stringify(record)) as AiInsightRecord), expiresAt: job.expiresAt ?? assistExchangeExpiry(now) })\n return {\n outputs: [aiInsightOutput(job, record, str(site.host?.['subdomain']) || null)],\n ...spend.outcome(),\n }\n}\n\n/**\n * Whether a question may be asked here: a surface a person asks from, a site\n * of the job's own org where the surface needs one, and at least one reader\n * the workspace may read. A digest is made by the weekly sweep and never at a\n * door.\n */\nexport async function aiInsightAdmissionRefusal(\n context: AiJobAdmissionContext,\n): Promise<AiJobAdmissionRefusal | null> {\n const surface: AiInsightSurface | null = aiInsightSurface(context.inputs)\n if (!surface || !AI_INSIGHT_ASK_SURFACES.includes(surface)) {\n return { status: 400, error: 'Ask about your numbers from a site’s Analytics, Data or CRM Reports page.' }\n }\n if (aiInsightSurfaceNeedsSite(surface) && !context.hostId) {\n return { status: 400, error: AI_INSIGHT_NO_SITE_COPY }\n }\n const site = await readHost(context.firestore, context.orgId, context.hostId)\n if (!site.ok) return { status: 404, error: 'Unknown site' }\n const readers = await aiInsightReaders({\n orgId: context.orgId,\n hostId: context.hostId,\n org: context.org,\n host: site.host,\n surface,\n })\n return readers.length ? null : { status: 403, error: AI_INSIGHT_NO_READERS_COPY }\n}\n\n/**\n * The insight kind's registrations, called by the console surface\n * (`registerAiJobKinds` in `server.ts`), never made by importing this module\n * (AGL-3025).\n */\nexport function registerAiInsightJob(): void {\n registerAiJobStep('insight', runAiJobInsightStep, { minimumMs: AI_JOB_INSIGHT_STEP_MINIMUM_MS })\n registerAiJobAdmission('insight', aiInsightAdmissionRefusal)\n}\n"],"names":["AI_INSIGHT_ASK_SURFACES","AI_INSIGHT_DIGEST_DAYS","AI_INSIGHT_DIGEST_READERS","AI_INSIGHT_QUESTION_MAX_CHARS","AI_INSIGHTS_COLLECTION","aiInsightDays","aiInsightOutput","aiInsightSurface","aiInsightSurfaceNeedsSite","aiHostPublishContext","aiSiteStatusTable","aiInstructionsWithSiteContext","readAiSiteContext","AI_ROUTING_TABLE","aiModelForStep","AI_ACCEPTABLE_USE_BLOCK","runAiRequest","AI_GENERATION_MAX_ATTEMPTS","checkAiInsightAnswer","AI_INSIGHT_ANSWER_TOOL_NAME","AI_INSIGHT_READ_TOOL_NAME","aiInsightAnswerTool","aiInsightReadTool","parseAiInsightAnswer","parseAiInsightReads","aiDatasetCatalog","aiInsightDatasetCatalog","aiInsightReaderCatalog","aiInsightReaderDays","aiInsightReaders","aiInsightTableText","readAiInsightTables","assistExchangeExpiry","registerAiJobAdmission","AI_INSIGHT_READ_MAX_TOKENS","AI_JOB_INSIGHT_STEP_BUDGET","AI_JOB_INSIGHT_STEP_MINIMUM_MS","registerAiJobStep","AI_INSIGHT_NO_SURFACE_COPY","AI_INSIGHT_NO_SITE_COPY","AI_INSIGHT_NO_READERS_COPY","AI_INSIGHT_NO_TABLES_COPY","AI_INSIGHT_UNREADABLE_COPY","AI_INSIGHT_DIGEST_QUESTION","AI_INSIGHT_RULES","join","AI_JOB_INSIGHT_SYSTEM","text","cacheBreakpoint","ZERO_USAGE","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","str","value","trim","InsightSpend","add","result","usage","estCostUsd","Math","round","stopReason","outcome","model","effort","aiInsightReadPrompt","input","question","days","readers","datasets","aiInsightAnswerPrompt","tables","map","aiInsightReaskMessage","check","reasons","findings","length","Set","slice","defaultReads","ids","reader","id","preferred","filter","includes","chosen","find","windows","params","readHost","firestore","orgId","hostId","snapshot","host","ok","collection","doc","get","exists","data","Boolean","runAiJobInsightStep","context","job","site","signal","now","modelFor","spend","stop","failure","outputs","surface","inputs","org","digest","brief","maxTokens","allowance","left","siteContext","system","publish","contact","ask","messages","tool","ceiling","tools","max","min","thinking","stream","base","uid","createdBy","requests","role","content","kind","refused","toolUse","use","name","maxTables","undefined","read","refusals","console","warn","jobId","$id","checked","attempt","answer","kept","insights","record","gap","week","createdAtMs","getTime","info","set","JSON","parse","stringify","expiresAt","aiInsightAdmissionRefusal","status","error","registerAiInsightJob","minimumMs"],"mappings":"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"}
@@ -32,8 +32,11 @@ import type { AiSystemBlock } from '../runtime/ai-runtime';
32
32
  * menu, the Layout Slot, and the footer at the bottom of a short page
33
33
  * (`ai-layout-frame.ts`).
34
34
  */
35
- /** The most a frame's answer asks for: two sections of a few blocks each. */
36
- export declare const AI_JOB_LAYOUT_LANGUAGE_TOKENS = 1200;
35
+ /**
36
+ * The most a frame's answer asks for: two sections of a few blocks each (the
37
+ * live runs of 2026-10-07 wrote 442 to 583), and the thinking before them.
38
+ */
39
+ export declare const AI_JOB_LAYOUT_LANGUAGE_TOKENS: number;
37
40
  /** A language layout pass's time, on the tier the layout step is served from. */
38
41
  export declare const AI_JOB_LAYOUT_LANGUAGE_BUDGET: import("./ai-job-budget").AiJobStepBudget;
39
42
  /** The generation kind a frame is asked under, which the doctrine scopes. */
@@ -48,7 +51,7 @@ export declare function aiLayoutNavPages(job: Pick<AiJob, 'inputs'>, inventory:
48
51
  export declare function aiLayoutHomeId(pages: readonly AiLayoutPage[], inventory: AiSiteInventory | null): string | null;
49
52
  /** The frame's user turn: the site, its brief, its pages and its form. */
50
53
  export declare function aiLayoutFramePrompt(input: {
51
- job: Pick<AiJob, 'brief' | '$id'>;
54
+ job: Pick<AiJob, 'brief' | '$id'> & Partial<Pick<AiJob, 'inputs'>>;
52
55
  siteName: string;
53
56
  pages: readonly AiLayoutPage[];
54
57
  targets: AiLayoutTargets;
@@ -63,6 +66,8 @@ export interface AiLayoutFrameBuilt {
63
66
  }
64
67
  /** The check a frame answer is held to: read, compiled, stored and checked as a layout, with the layout door's own checks. */
65
68
  export declare function aiLayoutFrameCheck(input: {
69
+ /** The header arrangement the site's look chose (AGL-3660); the answer's otherwise. */
70
+ headerAlign?: 'start' | 'center';
66
71
  siteName: string;
67
72
  homeId: string | null;
68
73
  pages: readonly AiLayoutPage[];
@@ -22,10 +22,11 @@ import { AI_GENERATION_MAX_ATTEMPTS } from "../runtime/ai-generation-bounds.js";
22
22
  import { aiHomeScreenIds } from "../model/ai-site-inventory.js";
23
23
  import { AI_STEP_TIERS } from "../providers/catalog.js";
24
24
  import { validateAiDoctrineTree } from "../runtime/ai-doctrine-validators.js";
25
+ import { aiSiteKindDesignLines } from "../model/ai-site-kinds.js";
25
26
  import { aiJobStepBudget } from "./ai-job-budget.js";
26
27
  import { aiJobBriefLine } from "./ai-job-generation.js";
27
28
  import { AI_LAYOUT_SITE_PAGES_MAX, aiLayoutIsHomeSlug, aiLayoutSitePages } from "./ai-job-layout-site-pages.js";
28
- import { aiLayoutPageTargets } from "./ai-job-page-language.js";
29
+ import { AI_LAYOUT_FRAME_THINKING_TOKENS, aiLayoutPageTargets } from "./ai-job-page-language.js";
29
30
  import { AI_SITE_NAME_TOKEN } from "./ai-layout-site-facts.js";
30
31
  /**
31
32
  * A site's layout built in the compact layout language (AGL-3660): the layout
@@ -36,7 +37,10 @@ import { AI_SITE_NAME_TOKEN } from "./ai-layout-site-facts.js";
36
37
  * owes it: its name as the brand, its pages as the navigation and the phone
37
38
  * menu, the Layout Slot, and the footer at the bottom of a short page
38
39
  * (`ai-layout-frame.ts`).
39
- */ /** The most a frame's answer asks for: two sections of a few blocks each. */ export const AI_JOB_LAYOUT_LANGUAGE_TOKENS = 1200;
40
+ */ /**
41
+ * The most a frame's answer asks for: two sections of a few blocks each (the
42
+ * live runs of 2026-10-07 wrote 442 to 583), and the thinking before them.
43
+ */ export const AI_JOB_LAYOUT_LANGUAGE_TOKENS = 1200 + AI_LAYOUT_FRAME_THINKING_TOKENS;
40
44
  /** A language layout pass's time, on the tier the layout step is served from. */ export const AI_JOB_LAYOUT_LANGUAGE_BUDGET = aiJobStepBudget({
41
45
  tier: AI_STEP_TIERS['job.layout'],
42
46
  maxTokens: AI_JOB_LAYOUT_LANGUAGE_TOKENS
@@ -95,6 +99,7 @@ import { AI_SITE_NAME_TOKEN } from "./ai-layout-site-facts.js";
95
99
  return [
96
100
  `Site: ${input.siteName === AI_SITE_NAME_TOKEN ? 'the business the brief describes' : `"${input.siteName}"`}`,
97
101
  aiJobBriefLine(job),
102
+ ...aiSiteKindDesignLines(job.inputs),
98
103
  pages.length ? `Its pages, which the navigation links: ${pages.map((page)=>`${page.label} (page:${page.id})`).join(', ')}.` : 'It has no pages yet.',
99
104
  targets.forms.length || targets.formPageId ? 'A call to action that asks a visitor to get in touch goes to form.' : 'The site has no form yet.',
100
105
  `Design seed: ${job.$id.slice(-6)}.`
@@ -107,6 +112,7 @@ import { AI_SITE_NAME_TOKEN } from "./ai-layout-site-facts.js";
107
112
  answers += 1;
108
113
  const last = answers >= AI_GENERATION_MAX_ATTEMPTS;
109
114
  const frame = aiReadLayoutFrame(answer);
115
+ if (input.headerAlign && frame.header) frame.header.align = input.headerAlign;
110
116
  const compiled = aiCompileLayoutFrame(frame, {
111
117
  siteName: input.siteName,
112
118
  homeId: input.homeId,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-layout-language.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { aiCompileLayoutFrame } from '../layout-language/ai-layout-frame'\nimport {\n AI_LAYOUT_FRAME_TOOL,\n AI_LAYOUT_LANGUAGE_TEXT,\n aiReadLayoutFrame,\n} from '../layout-language/ai-layout-language'\nimport type {\n AiLayoutPage,\n AiLayoutTargets,\n} from '../layout-language/ai-layout-links'\nimport { aiLayoutCopyCheck } from '../layout-language/ai-layout-gaps'\nimport { aiLayoutStoredTree } from '../layout-language/ai-layout-store'\nimport { AI_GENERATION_MAX_ATTEMPTS } from '../runtime/ai-generation-bounds'\nimport type { AiJob } from '../model/ai-jobs.types'\nimport {\n aiHomeScreenIds,\n type AiSiteInventory,\n} from '../model/ai-site-inventory'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport type { AiGenerationCheck, AiValidatedTree } from '../runtime/ai-doctrine'\nimport {\n validateAiDoctrineTree,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiJobBriefLine } from './ai-job-generation'\nimport {\n AI_LAYOUT_SITE_PAGES_MAX,\n aiLayoutIsHomeSlug,\n aiLayoutSitePages,\n} from './ai-job-layout-site-pages'\nimport { aiLayoutPageTargets } from './ai-job-page-language'\nimport { AI_SITE_NAME_TOKEN } from './ai-layout-site-facts'\n\n/**\n * A site's layout built in the compact layout language (AGL-3660): the layout\n * step's pass for a guided start's and a build's layout unit. The model\n * designs the header and the footer — their bands, the call to action, the\n * footer's columns and every word — in one short answer through\n * `submit_frame`, and the compiler writes the layout around what the site\n * owes it: its name as the brand, its pages as the navigation and the phone\n * menu, the Layout Slot, and the footer at the bottom of a short page\n * (`ai-layout-frame.ts`).\n */\n\n/** The most a frame's answer asks for: two sections of a few blocks each. */\nexport const AI_JOB_LAYOUT_LANGUAGE_TOKENS = 1_200\n\n/** A language layout pass's time, on the tier the layout step is served from. */\nexport const AI_JOB_LAYOUT_LANGUAGE_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.layout'],\n maxTokens: AI_JOB_LAYOUT_LANGUAGE_TOKENS,\n})\n\n/** The generation kind a frame is asked under, which the doctrine scopes. */\nexport const AI_LAYOUT_FRAME_KIND = 'layout-frame'\n\n/** The language, as the frame door's cached instructions. */\nexport const AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n AI_LAYOUT_LANGUAGE_TEXT,\n '',\n \"Now you design a site's header and footer, each one section of the language, answered with submit_frame.\",\n \"- header: the platform writes the site's name and its navigation itself. Choose its band and align (start, or center for a centered navigation), and give it at most one button: the call to action every page shows.\",\n '- footer: a section like any other, usually a row of two or three columns: a few words about the business, the details the brief gives, and a list of links (items with to). Put no contact detail, opening hours or address in it that the brief does not give, and no gap in square brackets for one.',\n ].join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\n/** The site's name as the brand: the name the job was given, else the token the site's own name fills. */\nexport function aiLayoutSiteName(job: Pick<AiJob, 'inputs'>): string {\n const given = job.inputs?.['businessName']\n return typeof given === 'string' && given.trim()\n ? given.trim()\n : AI_SITE_NAME_TOKEN\n}\n\n/** The pages a frame's navigation links: the planned pages, else the site's own, in order. */\nexport function aiLayoutNavPages(\n job: Pick<AiJob, 'inputs'>,\n inventory: AiSiteInventory | null,\n): AiLayoutPage[] {\n // The header and the footer link the pages the job was told (AGL-3660),\n // else the site's own, the home page always and first, labelled Home. The\n // model draws neither list, so it cannot drop a page.\n const planned = aiLayoutSitePages(job.inputs)\n const own = (inventory?.screens ?? [])\n .filter((row) => !row.template)\n .map((row) => ({ id: row.id, label: row.name, slug: row.slug }))\n const siteHome = planned.some((page) => aiLayoutIsHomeSlug(page.slug))\n ? []\n : own.filter((page) => aiLayoutIsHomeSlug(page.slug)).slice(0, 1)\n const pages = [...siteHome, ...(planned.length ? planned : own)].filter((page) => page.label.trim())\n const home = pages.find((page) => aiLayoutIsHomeSlug(page.slug))\n return [\n ...(home ? [{ ...home, label: 'Home' }] : []),\n ...pages.filter((page) => page !== home && !aiLayoutIsHomeSlug(page.slug)),\n ].slice(0, AI_LAYOUT_SITE_PAGES_MAX)\n}\n\n/** The home page the brand links: the planned home, else the site's. */\nexport function aiLayoutHomeId(\n pages: readonly AiLayoutPage[],\n inventory: AiSiteInventory | null,\n): string | null {\n const planned = pages.find((page) => aiLayoutIsHomeSlug(page.slug))\n return planned?.id ?? aiHomeScreenIds(inventory)[0] ?? null\n}\n\n/** The frame's user turn: the site, its brief, its pages and its form. */\nexport function aiLayoutFramePrompt(input: {\n job: Pick<AiJob, 'brief' | '$id'>\n siteName: string\n pages: readonly AiLayoutPage[]\n targets: AiLayoutTargets\n}): string {\n const { job, pages, targets } = input\n return [\n `Site: ${input.siteName === AI_SITE_NAME_TOKEN ? 'the business the brief describes' : `\"${input.siteName}\"`}`,\n aiJobBriefLine(job),\n pages.length\n ? `Its pages, which the navigation links: ${pages.map((page) => `${page.label} (page:${page.id})`).join(', ')}.`\n : 'It has no pages yet.',\n targets.forms.length || targets.formPageId\n ? 'A call to action that asks a visitor to get in touch goes to form.'\n : 'The site has no form yet.',\n `Design seed: ${job.$id.slice(-6)}.`,\n ].join('\\n')\n}\n\n/** A validated language layout. */\nexport interface AiLayoutFrameBuilt {\n rootId: string\n nodes: NodesMap\n load: AiLoadEstimate | null\n /** What the last answer's gaps took out of the frame (`ai-layout-gaps.ts`). */\n dropped: string[]\n}\n\n/** The check a frame answer is held to: read, compiled, stored and checked as a layout, with the layout door's own checks. */\nexport function aiLayoutFrameCheck(input: {\n siteName: string\n homeId: string | null\n pages: readonly AiLayoutPage[]\n targets: AiLayoutTargets\n extend: (tree: AiValidatedTree) => AiDoctrineViolation[]\n}): AiGenerationCheck<AiLayoutFrameBuilt> {\n let answers = 0\n return (answer) => {\n // The last answer a generation takes has its gaps taken out rather than asked about again.\n answers += 1\n const last = answers >= AI_GENERATION_MAX_ATTEMPTS\n const frame = aiReadLayoutFrame(answer)\n const compiled = aiCompileLayoutFrame(\n frame,\n { siteName: input.siteName, homeId: input.homeId, navPages: input.pages },\n input.targets,\n )\n const context = {\n screenIds: [\n ...new Set([\n ...input.targets.pages.map((page) => page.id),\n ...input.pages.map((page) => page.id),\n ...(input.homeId ? [input.homeId] : []),\n ]),\n ],\n formIds: input.targets.forms.map((form) => form.id),\n homeScreenIds: input.targets.homeIds,\n codeBuilt: true,\n }\n const stored = aiLayoutStoredTree(compiled.tree, 'layout', context)\n if (stored.ok === false) {\n console.error('ai layout frame: a compiled layout did not store', {\n error: stored.error,\n })\n return {\n value: null,\n violations: [\n {\n rule: null,\n code: 'layout-compile',\n message: 'This layout could not be built. Try again.',\n detail: stored.error,\n },\n ],\n }\n }\n // No gap and no internal reference in the words of a frame every published page shows.\n const navPages = [...input.targets.pages, ...input.pages]\n const copy = aiLayoutCopyCheck(\n stored.nodes,\n stored.rootId,\n {\n pages: navPages,\n ids: [...navPages.map((page) => page.id), ...input.targets.forms.map((form) => form.id)],\n },\n last,\n )\n const { nodes, dropped } = copy\n const report = validateAiDoctrineTree(\n { rootId: stored.rootId, nodes },\n 'layout',\n context,\n )\n const violations = [\n ...report.violations,\n ...copy.violations,\n ...input.extend({\n rootId: stored.rootId,\n nodes,\n repairs: [],\n sourceIds: {},\n score: report.score as AiValidatedTree['score'],\n load: report.load,\n }),\n ]\n return {\n value: violations.length\n ? null\n : { rootId: stored.rootId, nodes, load: report.load, dropped },\n violations,\n }\n }\n}\n\n/** What a frame's links resolve against: the site's pages and its form, as a page's do. */\nexport function aiLayoutFrameTargets(\n job: Pick<AiJob, 'brief' | 'inputs'>,\n inventory: AiSiteInventory | null,\n): AiLayoutTargets {\n return aiLayoutPageTargets({ job, inventory, own: [] })\n}\n"],"names":["aiCompileLayoutFrame","AI_LAYOUT_LANGUAGE_TEXT","aiReadLayoutFrame","aiLayoutCopyCheck","aiLayoutStoredTree","AI_GENERATION_MAX_ATTEMPTS","aiHomeScreenIds","AI_STEP_TIERS","validateAiDoctrineTree","aiJobStepBudget","aiJobBriefLine","AI_LAYOUT_SITE_PAGES_MAX","aiLayoutIsHomeSlug","aiLayoutSitePages","aiLayoutPageTargets","AI_SITE_NAME_TOKEN","AI_JOB_LAYOUT_LANGUAGE_TOKENS","AI_JOB_LAYOUT_LANGUAGE_BUDGET","tier","maxTokens","AI_LAYOUT_FRAME_KIND","AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS","text","join","cacheBreakpoint","aiLayoutSiteName","job","given","inputs","trim","aiLayoutNavPages","inventory","planned","own","screens","filter","row","template","map","id","label","name","slug","siteHome","some","page","slice","pages","length","home","find","aiLayoutHomeId","aiLayoutFramePrompt","input","targets","siteName","forms","formPageId","$id","aiLayoutFrameCheck","answers","answer","last","frame","compiled","homeId","navPages","context","screenIds","Set","formIds","form","homeScreenIds","homeIds","codeBuilt","stored","tree","ok","console","error","value","violations","rule","code","message","detail","copy","nodes","rootId","ids","dropped","report","extend","repairs","sourceIds","score","load","aiLayoutFrameTargets"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-layout-language.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { aiCompileLayoutFrame } from '../layout-language/ai-layout-frame'\nimport {\n AI_LAYOUT_FRAME_TOOL,\n AI_LAYOUT_LANGUAGE_TEXT,\n aiReadLayoutFrame,\n} from '../layout-language/ai-layout-language'\nimport type {\n AiLayoutPage,\n AiLayoutTargets,\n} from '../layout-language/ai-layout-links'\nimport { aiLayoutCopyCheck } from '../layout-language/ai-layout-gaps'\nimport { aiLayoutStoredTree } from '../layout-language/ai-layout-store'\nimport { AI_GENERATION_MAX_ATTEMPTS } from '../runtime/ai-generation-bounds'\nimport type { AiJob } from '../model/ai-jobs.types'\nimport {\n aiHomeScreenIds,\n type AiSiteInventory,\n} from '../model/ai-site-inventory'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport type { AiGenerationCheck, AiValidatedTree } from '../runtime/ai-doctrine'\nimport {\n validateAiDoctrineTree,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport { aiSiteKindDesignLines } from '../model/ai-site-kinds'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiJobBriefLine } from './ai-job-generation'\nimport {\n AI_LAYOUT_SITE_PAGES_MAX,\n aiLayoutIsHomeSlug,\n aiLayoutSitePages,\n} from './ai-job-layout-site-pages'\nimport {\n AI_LAYOUT_FRAME_THINKING_TOKENS,\n aiLayoutPageTargets,\n} from './ai-job-page-language'\nimport { AI_SITE_NAME_TOKEN } from './ai-layout-site-facts'\n\n/**\n * A site's layout built in the compact layout language (AGL-3660): the layout\n * step's pass for a guided start's and a build's layout unit. The model\n * designs the header and the footer — their bands, the call to action, the\n * footer's columns and every word — in one short answer through\n * `submit_frame`, and the compiler writes the layout around what the site\n * owes it: its name as the brand, its pages as the navigation and the phone\n * menu, the Layout Slot, and the footer at the bottom of a short page\n * (`ai-layout-frame.ts`).\n */\n\n/**\n * The most a frame's answer asks for: two sections of a few blocks each (the\n * live runs of 2026-10-07 wrote 442 to 583), and the thinking before them.\n */\nexport const AI_JOB_LAYOUT_LANGUAGE_TOKENS = 1_200 + AI_LAYOUT_FRAME_THINKING_TOKENS\n\n/** A language layout pass's time, on the tier the layout step is served from. */\nexport const AI_JOB_LAYOUT_LANGUAGE_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.layout'],\n maxTokens: AI_JOB_LAYOUT_LANGUAGE_TOKENS,\n})\n\n/** The generation kind a frame is asked under, which the doctrine scopes. */\nexport const AI_LAYOUT_FRAME_KIND = 'layout-frame'\n\n/** The language, as the frame door's cached instructions. */\nexport const AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n AI_LAYOUT_LANGUAGE_TEXT,\n '',\n \"Now you design a site's header and footer, each one section of the language, answered with submit_frame.\",\n \"- header: the platform writes the site's name and its navigation itself. Choose its band and align (start, or center for a centered navigation), and give it at most one button: the call to action every page shows.\",\n '- footer: a section like any other, usually a row of two or three columns: a few words about the business, the details the brief gives, and a list of links (items with to). Put no contact detail, opening hours or address in it that the brief does not give, and no gap in square brackets for one.',\n ].join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\n/** The site's name as the brand: the name the job was given, else the token the site's own name fills. */\nexport function aiLayoutSiteName(job: Pick<AiJob, 'inputs'>): string {\n const given = job.inputs?.['businessName']\n return typeof given === 'string' && given.trim()\n ? given.trim()\n : AI_SITE_NAME_TOKEN\n}\n\n/** The pages a frame's navigation links: the planned pages, else the site's own, in order. */\nexport function aiLayoutNavPages(\n job: Pick<AiJob, 'inputs'>,\n inventory: AiSiteInventory | null,\n): AiLayoutPage[] {\n // The header and the footer link the pages the job was told (AGL-3660),\n // else the site's own, the home page always and first, labelled Home. The\n // model draws neither list, so it cannot drop a page.\n const planned = aiLayoutSitePages(job.inputs)\n const own = (inventory?.screens ?? [])\n .filter((row) => !row.template)\n .map((row) => ({ id: row.id, label: row.name, slug: row.slug }))\n const siteHome = planned.some((page) => aiLayoutIsHomeSlug(page.slug))\n ? []\n : own.filter((page) => aiLayoutIsHomeSlug(page.slug)).slice(0, 1)\n const pages = [...siteHome, ...(planned.length ? planned : own)].filter((page) => page.label.trim())\n const home = pages.find((page) => aiLayoutIsHomeSlug(page.slug))\n return [\n ...(home ? [{ ...home, label: 'Home' }] : []),\n ...pages.filter((page) => page !== home && !aiLayoutIsHomeSlug(page.slug)),\n ].slice(0, AI_LAYOUT_SITE_PAGES_MAX)\n}\n\n/** The home page the brand links: the planned home, else the site's. */\nexport function aiLayoutHomeId(\n pages: readonly AiLayoutPage[],\n inventory: AiSiteInventory | null,\n): string | null {\n const planned = pages.find((page) => aiLayoutIsHomeSlug(page.slug))\n return planned?.id ?? aiHomeScreenIds(inventory)[0] ?? null\n}\n\n/** The frame's user turn: the site, its brief, its pages and its form. */\nexport function aiLayoutFramePrompt(input: {\n job: Pick<AiJob, 'brief' | '$id'> & Partial<Pick<AiJob, 'inputs'>>\n siteName: string\n pages: readonly AiLayoutPage[]\n targets: AiLayoutTargets\n}): string {\n const { job, pages, targets } = input\n return [\n `Site: ${input.siteName === AI_SITE_NAME_TOKEN ? 'the business the brief describes' : `\"${input.siteName}\"`}`,\n aiJobBriefLine(job),\n ...aiSiteKindDesignLines(job.inputs),\n pages.length\n ? `Its pages, which the navigation links: ${pages.map((page) => `${page.label} (page:${page.id})`).join(', ')}.`\n : 'It has no pages yet.',\n targets.forms.length || targets.formPageId\n ? 'A call to action that asks a visitor to get in touch goes to form.'\n : 'The site has no form yet.',\n `Design seed: ${job.$id.slice(-6)}.`,\n ].join('\\n')\n}\n\n/** A validated language layout. */\nexport interface AiLayoutFrameBuilt {\n rootId: string\n nodes: NodesMap\n load: AiLoadEstimate | null\n /** What the last answer's gaps took out of the frame (`ai-layout-gaps.ts`). */\n dropped: string[]\n}\n\n/** The check a frame answer is held to: read, compiled, stored and checked as a layout, with the layout door's own checks. */\nexport function aiLayoutFrameCheck(input: {\n /** The header arrangement the site's look chose (AGL-3660); the answer's otherwise. */\n headerAlign?: 'start' | 'center'\n siteName: string\n homeId: string | null\n pages: readonly AiLayoutPage[]\n targets: AiLayoutTargets\n extend: (tree: AiValidatedTree) => AiDoctrineViolation[]\n}): AiGenerationCheck<AiLayoutFrameBuilt> {\n let answers = 0\n return (answer) => {\n // The last answer a generation takes has its gaps taken out rather than asked about again.\n answers += 1\n const last = answers >= AI_GENERATION_MAX_ATTEMPTS\n const frame = aiReadLayoutFrame(answer)\n if (input.headerAlign && frame.header) frame.header.align = input.headerAlign\n const compiled = aiCompileLayoutFrame(\n frame,\n { siteName: input.siteName, homeId: input.homeId, navPages: input.pages },\n input.targets,\n )\n const context = {\n screenIds: [\n ...new Set([\n ...input.targets.pages.map((page) => page.id),\n ...input.pages.map((page) => page.id),\n ...(input.homeId ? [input.homeId] : []),\n ]),\n ],\n formIds: input.targets.forms.map((form) => form.id),\n homeScreenIds: input.targets.homeIds,\n codeBuilt: true,\n }\n const stored = aiLayoutStoredTree(compiled.tree, 'layout', context)\n if (stored.ok === false) {\n console.error('ai layout frame: a compiled layout did not store', {\n error: stored.error,\n })\n return {\n value: null,\n violations: [\n {\n rule: null,\n code: 'layout-compile',\n message: 'This layout could not be built. Try again.',\n detail: stored.error,\n },\n ],\n }\n }\n // No gap and no internal reference in the words of a frame every published page shows.\n const navPages = [...input.targets.pages, ...input.pages]\n const copy = aiLayoutCopyCheck(\n stored.nodes,\n stored.rootId,\n {\n pages: navPages,\n ids: [...navPages.map((page) => page.id), ...input.targets.forms.map((form) => form.id)],\n },\n last,\n )\n const { nodes, dropped } = copy\n const report = validateAiDoctrineTree(\n { rootId: stored.rootId, nodes },\n 'layout',\n context,\n )\n const violations = [\n ...report.violations,\n ...copy.violations,\n ...input.extend({\n rootId: stored.rootId,\n nodes,\n repairs: [],\n sourceIds: {},\n score: report.score as AiValidatedTree['score'],\n load: report.load,\n }),\n ]\n return {\n value: violations.length\n ? null\n : { rootId: stored.rootId, nodes, load: report.load, dropped },\n violations,\n }\n }\n}\n\n/** What a frame's links resolve against: the site's pages and its form, as a page's do. */\nexport function aiLayoutFrameTargets(\n job: Pick<AiJob, 'brief' | 'inputs'>,\n inventory: AiSiteInventory | null,\n): AiLayoutTargets {\n return aiLayoutPageTargets({ job, inventory, own: [] })\n}\n"],"names":["aiCompileLayoutFrame","AI_LAYOUT_LANGUAGE_TEXT","aiReadLayoutFrame","aiLayoutCopyCheck","aiLayoutStoredTree","AI_GENERATION_MAX_ATTEMPTS","aiHomeScreenIds","AI_STEP_TIERS","validateAiDoctrineTree","aiSiteKindDesignLines","aiJobStepBudget","aiJobBriefLine","AI_LAYOUT_SITE_PAGES_MAX","aiLayoutIsHomeSlug","aiLayoutSitePages","AI_LAYOUT_FRAME_THINKING_TOKENS","aiLayoutPageTargets","AI_SITE_NAME_TOKEN","AI_JOB_LAYOUT_LANGUAGE_TOKENS","AI_JOB_LAYOUT_LANGUAGE_BUDGET","tier","maxTokens","AI_LAYOUT_FRAME_KIND","AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS","text","join","cacheBreakpoint","aiLayoutSiteName","job","given","inputs","trim","aiLayoutNavPages","inventory","planned","own","screens","filter","row","template","map","id","label","name","slug","siteHome","some","page","slice","pages","length","home","find","aiLayoutHomeId","aiLayoutFramePrompt","input","targets","siteName","forms","formPageId","$id","aiLayoutFrameCheck","answers","answer","last","frame","headerAlign","header","align","compiled","homeId","navPages","context","screenIds","Set","formIds","form","homeScreenIds","homeIds","codeBuilt","stored","tree","ok","console","error","value","violations","rule","code","message","detail","copy","nodes","rootId","ids","dropped","report","extend","repairs","sourceIds","score","load","aiLayoutFrameTargets"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAGD,SAASA,oBAAoB,QAAQ,wCAAoC;AACzE,SAEEC,uBAAuB,EACvBC,iBAAiB,QACZ,2CAAuC;AAK9C,SAASC,iBAAiB,QAAQ,uCAAmC;AACrE,SAASC,kBAAkB,QAAQ,wCAAoC;AACvE,SAASC,0BAA0B,QAAQ,qCAAiC;AAE5E,SACEC,eAAe,QAEV,gCAA4B;AACnC,SAASC,aAAa,QAAQ,0BAAsB;AAEpD,SACEC,sBAAsB,QAEjB,uCAAmC;AAE1C,SAASC,qBAAqB,QAAQ,4BAAwB;AAE9D,SAASC,eAAe,QAAQ,qBAAiB;AACjD,SAASC,cAAc,QAAQ,yBAAqB;AACpD,SACEC,wBAAwB,EACxBC,kBAAkB,EAClBC,iBAAiB,QACZ,gCAA4B;AACnC,SACEC,+BAA+B,EAC/BC,mBAAmB,QACd,4BAAwB;AAC/B,SAASC,kBAAkB,QAAQ,4BAAwB;AAE3D;;;;;;;;;CASC,GAED;;;CAGC,GACD,OAAO,MAAMC,gCAAgC,OAAQH,gCAA+B;AAEpF,+EAA+E,GAC/E,OAAO,MAAMI,gCAAgCT,gBAAgB;IAC3DU,MAAMb,aAAa,CAAC,aAAa;IACjCc,WAAWH;AACb,GAAE;AAEF,2EAA2E,GAC3E,OAAO,MAAMI,uBAAuB,eAAc;AAElD,2DAA2D,GAC3D,OAAO,MAAMC,sCAAgE;IAC3E;QACEC,MAAM;YACJvB;YACA;YACA;YACA;YACA;SACD,CAACwB,IAAI,CAAC;QACPC,iBAAiB;IACnB;CACD,CAAA;AAED,wGAAwG,GACxG,OAAO,SAASC,iBAAiBC,GAA0B;QAC3CA;IAAd,MAAMC,SAAQD,cAAAA,IAAIE,MAAM,qBAAVF,WAAY,CAAC,eAAe;IAC1C,OAAO,OAAOC,UAAU,YAAYA,MAAME,IAAI,KAC1CF,MAAME,IAAI,KACVd;AACN;AAEA,4FAA4F,GAC5F,OAAO,SAASe,iBACdJ,GAA0B,EAC1BK,SAAiC;;IAEjC,wEAAwE;IACxE,0EAA0E;IAC1E,sDAAsD;IACtD,MAAMC,UAAUpB,kBAAkBc,IAAIE,MAAM;IAC5C,MAAMK,MAAM,SAACF,6BAAAA,UAAWG,OAAO,mBAAI,EAAE,EAClCC,MAAM,CAAC,CAACC,MAAQ,CAACA,IAAIC,QAAQ,EAC7BC,GAAG,CAAC,CAACF,MAAS,CAAA;YAAEG,IAAIH,IAAIG,EAAE;YAAEC,OAAOJ,IAAIK,IAAI;YAAEC,MAAMN,IAAIM,IAAI;QAAC,CAAA;IAC/D,MAAMC,WAAWX,QAAQY,IAAI,CAAC,CAACC,OAASlC,mBAAmBkC,KAAKH,IAAI,KAChE,EAAE,GACFT,IAAIE,MAAM,CAAC,CAACU,OAASlC,mBAAmBkC,KAAKH,IAAI,GAAGI,KAAK,CAAC,GAAG;IACjE,MAAMC,QAAQ;WAAIJ;WAAcX,QAAQgB,MAAM,GAAGhB,UAAUC;KAAK,CAACE,MAAM,CAAC,CAACU,OAASA,KAAKL,KAAK,CAACX,IAAI;IACjG,MAAMoB,OAAOF,MAAMG,IAAI,CAAC,CAACL,OAASlC,mBAAmBkC,KAAKH,IAAI;IAC9D,OAAO;WACDO,OAAO;YAAC,aAAKA;gBAAMT,OAAO;;SAAS,GAAG,EAAE;WACzCO,MAAMZ,MAAM,CAAC,CAACU,OAASA,SAASI,QAAQ,CAACtC,mBAAmBkC,KAAKH,IAAI;KACzE,CAACI,KAAK,CAAC,GAAGpC;AACb;AAEA,sEAAsE,GACtE,OAAO,SAASyC,eACdJ,KAA8B,EAC9BhB,SAAiC;QAG1BC;IADP,MAAMA,UAAUe,MAAMG,IAAI,CAAC,CAACL,OAASlC,mBAAmBkC,KAAKH,IAAI;IACjE,QAAOV,gBAAAA,2BAAAA,QAASO,EAAE,oBAAInC,gBAAgB2B,UAAU,CAAC,EAAE,YAA5CC,OAAgD;AACzD;AAEA,wEAAwE,GACxE,OAAO,SAASoB,oBAAoBC,KAKnC;IACC,MAAM,EAAE3B,GAAG,EAAEqB,KAAK,EAAEO,OAAO,EAAE,GAAGD;IAChC,OAAO;QACL,CAAC,MAAM,EAAEA,MAAME,QAAQ,KAAKxC,qBAAqB,qCAAqC,CAAC,CAAC,EAAEsC,MAAME,QAAQ,CAAC,CAAC,CAAC,EAAE;QAC7G9C,eAAeiB;WACZnB,sBAAsBmB,IAAIE,MAAM;QACnCmB,MAAMC,MAAM,GACR,CAAC,uCAAuC,EAAED,MAAMT,GAAG,CAAC,CAACO,OAAS,GAAGA,KAAKL,KAAK,CAAC,OAAO,EAAEK,KAAKN,EAAE,CAAC,CAAC,CAAC,EAAEhB,IAAI,CAAC,MAAM,CAAC,CAAC,GAC9G;QACJ+B,QAAQE,KAAK,CAACR,MAAM,IAAIM,QAAQG,UAAU,GACtC,uEACA;QACJ,CAAC,aAAa,EAAE/B,IAAIgC,GAAG,CAACZ,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC;KACrC,CAACvB,IAAI,CAAC;AACT;AAWA,4HAA4H,GAC5H,OAAO,SAASoC,mBAAmBN,KAQlC;IACC,IAAIO,UAAU;IACd,OAAO,CAACC;QACN,2FAA2F;QAC3FD,WAAW;QACX,MAAME,OAAOF,WAAWzD;QACxB,MAAM4D,QAAQ/D,kBAAkB6D;QAChC,IAAIR,MAAMW,WAAW,IAAID,MAAME,MAAM,EAAEF,MAAME,MAAM,CAACC,KAAK,GAAGb,MAAMW,WAAW;QAC7E,MAAMG,WAAWrE,qBACfiE,OACA;YAAER,UAAUF,MAAME,QAAQ;YAAEa,QAAQf,MAAMe,MAAM;YAAEC,UAAUhB,MAAMN,KAAK;QAAC,GACxEM,MAAMC,OAAO;QAEf,MAAMgB,UAAU;YACdC,WAAW;mBACN,IAAIC,IAAI;uBACNnB,MAAMC,OAAO,CAACP,KAAK,CAACT,GAAG,CAAC,CAACO,OAASA,KAAKN,EAAE;uBACzCc,MAAMN,KAAK,CAACT,GAAG,CAAC,CAACO,OAASA,KAAKN,EAAE;uBAChCc,MAAMe,MAAM,GAAG;wBAACf,MAAMe,MAAM;qBAAC,GAAG,EAAE;iBACvC;aACF;YACDK,SAASpB,MAAMC,OAAO,CAACE,KAAK,CAAClB,GAAG,CAAC,CAACoC,OAASA,KAAKnC,EAAE;YAClDoC,eAAetB,MAAMC,OAAO,CAACsB,OAAO;YACpCC,WAAW;QACb;QACA,MAAMC,SAAS5E,mBAAmBiE,SAASY,IAAI,EAAE,UAAUT;QAC3D,IAAIQ,OAAOE,EAAE,KAAK,OAAO;YACvBC,QAAQC,KAAK,CAAC,oDAAoD;gBAChEA,OAAOJ,OAAOI,KAAK;YACrB;YACA,OAAO;gBACLC,OAAO;gBACPC,YAAY;oBACV;wBACEC,MAAM;wBACNC,MAAM;wBACNC,SAAS;wBACTC,QAAQV,OAAOI,KAAK;oBACtB;iBACD;YACH;QACF;QACA,uFAAuF;QACvF,MAAMb,WAAW;eAAIhB,MAAMC,OAAO,CAACP,KAAK;eAAKM,MAAMN,KAAK;SAAC;QACzD,MAAM0C,OAAOxF,kBACX6E,OAAOY,KAAK,EACZZ,OAAOa,MAAM,EACb;YACE5C,OAAOsB;YACPuB,KAAK;mBAAIvB,SAAS/B,GAAG,CAAC,CAACO,OAASA,KAAKN,EAAE;mBAAMc,MAAMC,OAAO,CAACE,KAAK,CAAClB,GAAG,CAAC,CAACoC,OAASA,KAAKnC,EAAE;aAAE;QAC1F,GACAuB;QAEF,MAAM,EAAE4B,KAAK,EAAEG,OAAO,EAAE,GAAGJ;QAC3B,MAAMK,SAASxF,uBACb;YAAEqF,QAAQb,OAAOa,MAAM;YAAED;QAAM,GAC/B,UACApB;QAEF,MAAMc,aAAa;eACdU,OAAOV,UAAU;eACjBK,KAAKL,UAAU;eACf/B,MAAM0C,MAAM,CAAC;gBACdJ,QAAQb,OAAOa,MAAM;gBACrBD;gBACAM,SAAS,EAAE;gBACXC,WAAW,CAAC;gBACZC,OAAOJ,OAAOI,KAAK;gBACnBC,MAAML,OAAOK,IAAI;YACnB;SACD;QACD,OAAO;YACLhB,OAAOC,WAAWpC,MAAM,GACpB,OACA;gBAAE2C,QAAQb,OAAOa,MAAM;gBAAED;gBAAOS,MAAML,OAAOK,IAAI;gBAAEN;YAAQ;YAC/DT;QACF;IACF;AACF;AAEA,yFAAyF,GACzF,OAAO,SAASgB,qBACd1E,GAAoC,EACpCK,SAAiC;IAEjC,OAAOjB,oBAAoB;QAAEY;QAAKK;QAAWE,KAAK,EAAE;IAAC;AACvD"}
@@ -14,7 +14,8 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
14
14
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
- */ import { CANVAS_ROOT_ELEMENT_ID } from "@aglyn/aglyn/foundation/constants/canvas";
17
+ */ import { aiSiteStyleTokens } from "../model/ai-site-kinds.js";
18
+ import { CANVAS_ROOT_ELEMENT_ID } from "@aglyn/aglyn/foundation/constants/canvas";
18
19
  import { duplicateResource } from "@aglyn/tenant-data-admin/server/duplicate-resource";
19
20
  import { aiHomeScreenIds } from "../model/ai-site-inventory.js";
20
21
  import { AI_STEP_TIERS } from "../providers/catalog.js";
@@ -33,7 +34,7 @@ import { aiLayoutInventedContactViolations, aiLayoutWithSiteName } from "./ai-la
33
34
  import { aiLayoutWithFullHeight } from "./ai-layout-full-height.js";
34
35
  import { AI_LAYOUT_FRAME_TOOL } from "../layout-language/ai-layout-language.js";
35
36
  import { AI_JOB_LAYOUT_LANGUAGE_BUDGET, AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS, AI_LAYOUT_FRAME_KIND, aiLayoutFrameCheck, aiLayoutFramePrompt, aiLayoutFrameTargets, aiLayoutHomeId, aiLayoutNavPages, aiLayoutSiteName } from "./ai-job-layout-language.js";
36
- import { aiJobUsesLayoutLanguage } from "./ai-job-page-language.js";
37
+ import { AI_LAYOUT_LANGUAGE_THINKING, aiJobUsesLayoutLanguage } from "./ai-job-page-language.js";
37
38
  import { aiJobStepBudget } from "./ai-job-budget.js";
38
39
  import { registerAiJobStep } from "./ai-jobs.js";
39
40
  /**
@@ -355,15 +356,15 @@ export function createAiJobLayoutStep(deps = {}) {
355
356
  }
356
357
  ],
357
358
  tool: AI_LAYOUT_FRAME_TOOL,
358
- maxTokens: AI_JOB_LAYOUT_LANGUAGE_BUDGET.maxTokens(model),
359
- thinking: 'off',
360
- check: aiLayoutFrameCheck({
359
+ maxTokens: AI_JOB_LAYOUT_LANGUAGE_BUDGET.maxTokens(model)
360
+ }, AI_LAYOUT_LANGUAGE_THINKING, {
361
+ check: aiLayoutFrameCheck(_extends({}, aiSiteStyleTokens(job.inputs), {
361
362
  siteName,
362
363
  homeId: aiLayoutHomeId(pages, inventory),
363
364
  pages,
364
365
  targets,
365
366
  extend: aiLayoutChecks(inventory, plan, job.brief)
366
- })
367
+ }))
367
368
  }, signal ? {
368
369
  signal
369
370
  } : {}));