@aglyn/plugins-ai 1.0.0-beta.230 → 1.0.0-beta.232

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 (305) 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-assist-provider-on-host.component.d.ts +66 -0
  5. package/src/lib/components/ai-assist-provider-on-host.component.js +78 -0
  6. package/src/lib/components/ai-assist-provider-on-host.component.js.map +1 -0
  7. package/src/lib/components/ai-assist-provider.component.js +9 -10
  8. package/src/lib/components/ai-assist-provider.component.js.map +1 -1
  9. package/src/lib/components/ai-job-follow.component.js +1 -0
  10. package/src/lib/components/ai-job-follow.component.js.map +1 -1
  11. package/src/lib/components/ai-job-links.js +13 -0
  12. package/src/lib/components/ai-job-links.js.map +1 -1
  13. package/src/lib/components/ai-job-plan.component.js +3 -1
  14. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  15. package/src/lib/components/ai-jobs-list-page.component.js +1 -0
  16. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  17. package/src/lib/components/ai-media-create.component.d.ts +20 -7
  18. package/src/lib/components/ai-media-create.component.js +107 -55
  19. package/src/lib/components/ai-media-create.component.js.map +1 -1
  20. package/src/lib/components/ai-permissions-on-host.component.d.ts +3 -46
  21. package/src/lib/components/ai-permissions-on-host.component.js +5 -53
  22. package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
  23. package/src/lib/components/ai-seo-fields-card.component.js +1 -0
  24. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  25. package/src/lib/components/ai-site-build-page.component.js +20 -8
  26. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  27. package/src/lib/components/ai-site-memory-card.component.d.ts +32 -0
  28. package/src/lib/components/ai-site-memory-card.component.js +187 -0
  29. package/src/lib/components/ai-site-memory-card.component.js.map +1 -0
  30. package/src/lib/components/ai-site-start-card.component.js +80 -77
  31. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  32. package/src/lib/components/ai-theme-proposal-card.component.js +4 -0
  33. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  34. package/src/lib/components/ai-usage-strip.component.js +1 -0
  35. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  36. package/src/lib/components/assist-panel.component.d.ts +10 -0
  37. package/src/lib/components/assist-panel.component.js +119 -9
  38. package/src/lib/components/assist-panel.component.js.map +1 -1
  39. package/src/lib/components/assist-signals-page.component.js +2 -0
  40. package/src/lib/components/assist-signals-page.component.js.map +1 -1
  41. package/src/lib/components/billing-ai-allotments.component.js +3 -0
  42. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  43. package/src/lib/components/billing-ai-top-users.component.js +1 -0
  44. package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
  45. package/src/lib/components/billing-assist-overage-card.component.js +2 -0
  46. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  47. package/src/lib/components/member-ai-usage-card.component.js +10 -0
  48. package/src/lib/components/member-ai-usage-card.component.js.map +1 -1
  49. package/src/lib/components/staff-org-ai-card.component.js +1 -0
  50. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  51. package/src/lib/components/staff-org-ai-conversations.component.d.ts +23 -0
  52. package/src/lib/components/staff-org-ai-conversations.component.js +346 -0
  53. package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -0
  54. package/src/lib/jobs/ai-build-capabilities.d.ts +13 -1
  55. package/src/lib/jobs/ai-build-capabilities.js +101 -7
  56. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  57. package/src/lib/jobs/ai-build-unit-outcome.js +4 -2
  58. package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -1
  59. package/src/lib/jobs/ai-business-profile-prefill.d.ts +29 -0
  60. package/src/lib/jobs/ai-business-profile-prefill.js +85 -0
  61. package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -0
  62. package/src/lib/jobs/ai-job-auto-confirm.d.ts +8 -0
  63. package/src/lib/jobs/ai-job-auto-confirm.js +30 -6
  64. package/src/lib/jobs/ai-job-auto-confirm.js.map +1 -1
  65. package/src/lib/jobs/ai-job-build-step.js +14 -1
  66. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  67. package/src/lib/jobs/ai-job-edit-step.d.ts +225 -0
  68. package/src/lib/jobs/ai-job-edit-step.js +631 -0
  69. package/src/lib/jobs/ai-job-edit-step.js.map +1 -0
  70. package/src/lib/jobs/ai-job-generation.d.ts +8 -0
  71. package/src/lib/jobs/ai-job-generation.js +13 -2
  72. package/src/lib/jobs/ai-job-generation.js.map +1 -1
  73. package/src/lib/jobs/ai-job-insight-step.js +29 -5
  74. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  75. package/src/lib/jobs/ai-job-layout-language.d.ts +78 -0
  76. package/src/lib/jobs/ai-job-layout-language.js +200 -0
  77. package/src/lib/jobs/ai-job-layout-language.js.map +1 -0
  78. package/src/lib/jobs/ai-job-layout-site-pages.d.ts +13 -2
  79. package/src/lib/jobs/ai-job-layout-site-pages.js +18 -2
  80. package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
  81. package/src/lib/jobs/ai-job-layout-step.d.ts +5 -0
  82. package/src/lib/jobs/ai-job-layout-step.js +107 -6
  83. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  84. package/src/lib/jobs/ai-job-logic-step.d.ts +20 -0
  85. package/src/lib/jobs/ai-job-logic-step.js +87 -6
  86. package/src/lib/jobs/ai-job-logic-step.js.map +1 -1
  87. package/src/lib/jobs/ai-job-page-language.d.ts +194 -0
  88. package/src/lib/jobs/ai-job-page-language.js +418 -0
  89. package/src/lib/jobs/ai-job-page-language.js.map +1 -0
  90. package/src/lib/jobs/ai-job-page-sections.d.ts +37 -1
  91. package/src/lib/jobs/ai-job-page-sections.js +93 -11
  92. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  93. package/src/lib/jobs/ai-job-page-step.d.ts +4 -4
  94. package/src/lib/jobs/ai-job-page-step.js +115 -14
  95. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  96. package/src/lib/jobs/ai-job-plan-step.d.ts +20 -6
  97. package/src/lib/jobs/ai-job-plan-step.js +62 -22
  98. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  99. package/src/lib/jobs/ai-job-site-content.d.ts +163 -0
  100. package/src/lib/jobs/ai-job-site-content.js +443 -0
  101. package/src/lib/jobs/ai-job-site-content.js.map +1 -0
  102. package/src/lib/jobs/ai-job-site-look.d.ts +112 -0
  103. package/src/lib/jobs/ai-job-site-look.js +322 -0
  104. package/src/lib/jobs/ai-job-site-look.js.map +1 -0
  105. package/src/lib/jobs/ai-job-site-step.d.ts +30 -5
  106. package/src/lib/jobs/ai-job-site-step.js +183 -27
  107. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  108. package/src/lib/jobs/ai-job-workflow-step.js +2 -2
  109. package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
  110. package/src/lib/jobs/ai-jobs-notify.d.ts +6 -1
  111. package/src/lib/jobs/ai-jobs-notify.js +15 -3
  112. package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
  113. package/src/lib/jobs/ai-jobs.d.ts +1 -1
  114. package/src/lib/jobs/ai-jobs.js +17 -8
  115. package/src/lib/jobs/ai-jobs.js.map +1 -1
  116. package/src/lib/jobs/ai-layout-full-height.d.ts +40 -0
  117. package/src/lib/jobs/ai-layout-full-height.js +82 -0
  118. package/src/lib/jobs/ai-layout-full-height.js.map +1 -0
  119. package/src/lib/jobs/ai-layout-site-facts.d.ts +54 -0
  120. package/src/lib/jobs/ai-layout-site-facts.js +153 -0
  121. package/src/lib/jobs/ai-layout-site-facts.js.map +1 -0
  122. package/src/lib/layout-language/ai-layout-compiler.d.ts +154 -0
  123. package/src/lib/layout-language/ai-layout-compiler.js +1235 -0
  124. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -0
  125. package/src/lib/layout-language/ai-layout-copy.d.ts +73 -0
  126. package/src/lib/layout-language/ai-layout-copy.js +137 -0
  127. package/src/lib/layout-language/ai-layout-copy.js.map +1 -0
  128. package/src/lib/layout-language/ai-layout-frame.d.ts +52 -0
  129. package/src/lib/layout-language/ai-layout-frame.js +458 -0
  130. package/src/lib/layout-language/ai-layout-frame.js.map +1 -0
  131. package/src/lib/layout-language/ai-layout-gaps.d.ts +63 -0
  132. package/src/lib/layout-language/ai-layout-gaps.js +345 -0
  133. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -0
  134. package/src/lib/layout-language/ai-layout-language.d.ts +168 -0
  135. package/src/lib/layout-language/ai-layout-language.js +529 -0
  136. package/src/lib/layout-language/ai-layout-language.js.map +1 -0
  137. package/src/lib/layout-language/ai-layout-links.d.ts +80 -0
  138. package/src/lib/layout-language/ai-layout-links.js +109 -0
  139. package/src/lib/layout-language/ai-layout-links.js.map +1 -0
  140. package/src/lib/layout-language/ai-layout-pictures.d.ts +144 -0
  141. package/src/lib/layout-language/ai-layout-pictures.js +214 -0
  142. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -0
  143. package/src/lib/layout-language/ai-layout-store.d.ts +41 -0
  144. package/src/lib/layout-language/ai-layout-store.js +75 -0
  145. package/src/lib/layout-language/ai-layout-store.js.map +1 -0
  146. package/src/lib/layout-language/ai-layout-tree.d.ts +59 -0
  147. package/src/lib/layout-language/ai-layout-tree.js +84 -0
  148. package/src/lib/layout-language/ai-layout-tree.js.map +1 -0
  149. package/src/lib/lazy-widget.d.ts +45 -0
  150. package/src/lib/lazy-widget.js +53 -0
  151. package/src/lib/lazy-widget.js.map +1 -0
  152. package/src/lib/model/ai-build-job.js +2 -1
  153. package/src/lib/model/ai-build-job.js.map +1 -1
  154. package/src/lib/model/ai-build-progress.d.ts +20 -3
  155. package/src/lib/model/ai-build-progress.js +52 -7
  156. package/src/lib/model/ai-build-progress.js.map +1 -1
  157. package/src/lib/model/ai-edit-job.d.ts +109 -0
  158. package/src/lib/model/ai-edit-job.js +157 -0
  159. package/src/lib/model/ai-edit-job.js.map +1 -0
  160. package/src/lib/model/ai-host-publish-context.d.ts +51 -0
  161. package/src/lib/model/ai-host-publish-context.js +104 -0
  162. package/src/lib/model/ai-host-publish-context.js.map +1 -0
  163. package/src/lib/model/ai-job-activity.d.ts +22 -0
  164. package/src/lib/model/ai-job-activity.js +23 -1
  165. package/src/lib/model/ai-job-activity.js.map +1 -1
  166. package/src/lib/model/ai-job-failure-copy.d.ts +38 -1
  167. package/src/lib/model/ai-job-failure-copy.js +56 -1
  168. package/src/lib/model/ai-job-failure-copy.js.map +1 -1
  169. package/src/lib/model/ai-job-notice.d.ts +3 -1
  170. package/src/lib/model/ai-job-notice.js +31 -9
  171. package/src/lib/model/ai-job-notice.js.map +1 -1
  172. package/src/lib/model/ai-jobs.types.d.ts +22 -1
  173. package/src/lib/model/ai-jobs.types.js.map +1 -1
  174. package/src/lib/model/ai-logic-job.d.ts +18 -1
  175. package/src/lib/model/ai-logic-job.js +24 -3
  176. package/src/lib/model/ai-logic-job.js.map +1 -1
  177. package/src/lib/model/ai-media-credits.d.ts +8 -4
  178. package/src/lib/model/ai-media-credits.js +11 -4
  179. package/src/lib/model/ai-media-credits.js.map +1 -1
  180. package/src/lib/model/ai-media-kinds.d.ts +68 -0
  181. package/src/lib/model/ai-media-kinds.js +98 -0
  182. package/src/lib/model/ai-media-kinds.js.map +1 -0
  183. package/src/lib/model/ai-site-build-progress.d.ts +9 -1
  184. package/src/lib/model/ai-site-build-progress.js +54 -14
  185. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  186. package/src/lib/model/ai-site-context.d.ts +111 -0
  187. package/src/lib/model/ai-site-context.js +222 -0
  188. package/src/lib/model/ai-site-context.js.map +1 -0
  189. package/src/lib/model/ai-site-job.d.ts +18 -2
  190. package/src/lib/model/ai-site-job.js +21 -5
  191. package/src/lib/model/ai-site-job.js.map +1 -1
  192. package/src/lib/model/ai-site-kinds.d.ts +164 -0
  193. package/src/lib/model/ai-site-kinds.js +1942 -0
  194. package/src/lib/model/ai-site-kinds.js.map +1 -0
  195. package/src/lib/model/ai-site-look.d.ts +150 -0
  196. package/src/lib/model/ai-site-look.js +1236 -0
  197. package/src/lib/model/ai-site-look.js.map +1 -0
  198. package/src/lib/model/ai-site-memory.d.ts +80 -0
  199. package/src/lib/model/ai-site-memory.js +171 -0
  200. package/src/lib/model/ai-site-memory.js.map +1 -0
  201. package/src/lib/model/ai-site-start-seo.d.ts +1 -1
  202. package/src/lib/model/ai-site-start.d.ts +15 -3
  203. package/src/lib/model/ai-site-start.js +21 -4
  204. package/src/lib/model/ai-site-start.js.map +1 -1
  205. package/src/lib/model/assist-follow-up.d.ts +92 -0
  206. package/src/lib/model/assist-follow-up.js +128 -0
  207. package/src/lib/model/assist-follow-up.js.map +1 -0
  208. package/src/lib/plugin-config.js +2 -0
  209. package/src/lib/plugin-config.js.map +1 -1
  210. package/src/lib/plugin.js +99 -48
  211. package/src/lib/plugin.js.map +1 -1
  212. package/src/lib/providers/anthropic.d.ts +9 -1
  213. package/src/lib/providers/anthropic.js +143 -9
  214. package/src/lib/providers/anthropic.js.map +1 -1
  215. package/src/lib/providers/catalog.d.ts +38 -8
  216. package/src/lib/providers/catalog.js +106 -14
  217. package/src/lib/providers/catalog.js.map +1 -1
  218. package/src/lib/providers/contract.d.ts +29 -0
  219. package/src/lib/providers/contract.js.map +1 -1
  220. package/src/lib/providers/image-contract.d.ts +22 -1
  221. package/src/lib/providers/image-contract.js +40 -1
  222. package/src/lib/providers/image-contract.js.map +1 -1
  223. package/src/lib/providers/image-endpoints.d.ts +24 -0
  224. package/src/lib/providers/image-endpoints.js +28 -0
  225. package/src/lib/providers/image-endpoints.js.map +1 -0
  226. package/src/lib/providers/model-choice.js +23 -2
  227. package/src/lib/providers/model-choice.js.map +1 -1
  228. package/src/lib/providers/routing.js +18 -0
  229. package/src/lib/providers/routing.js.map +1 -1
  230. package/src/lib/providers/vertex-image-endpoint.d.ts +48 -0
  231. package/src/lib/providers/vertex-image-endpoint.js +80 -0
  232. package/src/lib/providers/vertex-image-endpoint.js.map +1 -0
  233. package/src/lib/providers/vertex-image.d.ts +13 -27
  234. package/src/lib/providers/vertex-image.js +15 -42
  235. package/src/lib/providers/vertex-image.js.map +1 -1
  236. package/src/lib/runtime/ai-blog-post-generation.d.ts +62 -0
  237. package/src/lib/runtime/ai-blog-post-generation.js +89 -0
  238. package/src/lib/runtime/ai-blog-post-generation.js.map +1 -0
  239. package/src/lib/runtime/ai-dev-env.d.ts +53 -0
  240. package/src/lib/runtime/ai-dev-env.js +72 -0
  241. package/src/lib/runtime/ai-dev-env.js.map +1 -0
  242. package/src/lib/runtime/ai-dev-replay.d.ts +116 -0
  243. package/src/lib/runtime/ai-dev-replay.js +313 -0
  244. package/src/lib/runtime/ai-dev-replay.js.map +1 -0
  245. package/src/lib/runtime/ai-doctrine-validators.d.ts +82 -0
  246. package/src/lib/runtime/ai-doctrine-validators.js +436 -4
  247. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  248. package/src/lib/runtime/ai-doctrine.js +16 -3
  249. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  250. package/src/lib/runtime/ai-eval-live.js +4 -0
  251. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  252. package/src/lib/runtime/ai-eval.d.ts +13 -1
  253. package/src/lib/runtime/ai-eval.js +84 -1
  254. package/src/lib/runtime/ai-eval.js.map +1 -1
  255. package/src/lib/runtime/ai-icon-library.d.ts +257 -0
  256. package/src/lib/runtime/ai-icon-library.js +254 -0
  257. package/src/lib/runtime/ai-icon-library.js.map +1 -0
  258. package/src/lib/runtime/ai-live-batch.d.ts +45 -0
  259. package/src/lib/runtime/ai-live-batch.js +89 -0
  260. package/src/lib/runtime/ai-live-batch.js.map +1 -0
  261. package/src/lib/runtime/ai-node-tree.d.ts +9 -0
  262. package/src/lib/runtime/ai-node-tree.js +30 -4
  263. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  264. package/src/lib/runtime/ai-palette.d.ts +6 -0
  265. package/src/lib/runtime/ai-palette.generated.js +70 -0
  266. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  267. package/src/lib/runtime/ai-palette.js.map +1 -1
  268. package/src/lib/runtime/ai-runtime.js +46 -2
  269. package/src/lib/runtime/ai-runtime.js.map +1 -1
  270. package/src/lib/runtime/site-context.d.ts +58 -0
  271. package/src/lib/runtime/site-context.js +127 -0
  272. package/src/lib/runtime/site-context.js.map +1 -0
  273. package/src/lib/server/ai-admin-conversations.d.ts +22 -0
  274. package/src/lib/server/ai-admin-conversations.js +197 -0
  275. package/src/lib/server/ai-admin-conversations.js.map +1 -0
  276. package/src/lib/server/ai-media-image.d.ts +5 -5
  277. package/src/lib/server/ai-media-image.js +25 -11
  278. package/src/lib/server/ai-media-image.js.map +1 -1
  279. package/src/lib/server/ai-media-raster-prompt.d.ts +34 -0
  280. package/src/lib/server/ai-media-raster-prompt.js +53 -0
  281. package/src/lib/server/ai-media-raster-prompt.js.map +1 -0
  282. package/src/lib/server/assist-chat.d.ts +11 -0
  283. package/src/lib/server/assist-chat.js +57 -18
  284. package/src/lib/server/assist-chat.js.map +1 -1
  285. package/src/lib/server/assist-edit-applied.js +16 -3
  286. package/src/lib/server/assist-edit-applied.js.map +1 -1
  287. package/src/lib/server/assist-view-context.d.ts +19 -1
  288. package/src/lib/server/assist-view-context.js +38 -1
  289. package/src/lib/server/assist-view-context.js.map +1 -1
  290. package/src/lib/server.js +10 -0
  291. package/src/lib/server.js.map +1 -1
  292. package/src/lib/subprocessors.js +17 -1
  293. package/src/lib/subprocessors.js.map +1 -1
  294. package/src/lib/tools/ai-blog-post-tool.d.ts +60 -0
  295. package/src/lib/tools/ai-blog-post-tool.js +175 -0
  296. package/src/lib/tools/ai-blog-post-tool.js.map +1 -0
  297. package/src/lib/tools/ai-workflow-tool.d.ts +6 -2
  298. package/src/lib/tools/ai-workflow-tool.js +16 -14
  299. package/src/lib/tools/ai-workflow-tool.js.map +1 -1
  300. package/src/lib/usage/assist-usage.d.ts +3 -0
  301. package/src/lib/usage/assist-usage.js +4 -2
  302. package/src/lib/usage/assist-usage.js.map +1 -1
  303. package/src/lib/usage/staff-org-ai-conversations.d.ts +103 -0
  304. package/src/lib/usage/staff-org-ai-conversations.js +152 -0
  305. 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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"}
@@ -100,12 +100,20 @@ export declare function aiDoctrineReviewOutline(answer: Record<string, unknown>
100
100
  * entries it names, and an outline of those parts of the answer it was found
101
101
  * in (AGL-3078), so why a job stopped can be read back from the job once the
102
102
  * answer itself is gone.
103
+ *
104
+ * The doctrine's own sentence ("Rule 12 (Responsive by the theme's
105
+ * breakpoints): ...") is never what the customer reads (AGL-3596): it rides
106
+ * as `detail`, for staff, and `message` is the plain refusal, a page's when
107
+ * `page` says the refused thing is one, followed by `then` when given.
103
108
  */
104
109
  export declare function aiDoctrineReview(result: {
105
110
  message: string;
106
111
  violations: readonly AiDoctrineViolation[];
107
112
  /** The refused answer, whose nodes the findings name by id; outlined, never kept. */
108
113
  answer?: Record<string, unknown> | null;
114
+ }, options?: {
115
+ page?: boolean;
116
+ then?: string;
109
117
  }): AiJobReview;
110
118
  /** The review a step stops its job for when the site cannot take the draft. */
111
119
  export declare function aiLimitReview(message: string): AiJobReview;
@@ -17,6 +17,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
17
17
  */ import { REUSABLE_INSTANCE_COMPONENT_ID, REUSABLE_INSTANCE_PROP_VALUES_KEY } from "@aglyn/aglyn/app-utils/reusable-component-keys";
18
18
  import { CANVAS_ROOT_ELEMENT_ID } from "@aglyn/aglyn/foundation/constants/canvas";
19
19
  import { AI_BUILD_PLAN_EMBED_HOST_NAMES, AI_PLAN_NEW_REF_PREFIX, isAiPlanNewRef } from "../model/ai-build-plan.js";
20
+ import { AI_DOCTRINE_REFUSED_COPY, AI_DOCTRINE_REFUSED_PAGE_COPY, aiIsDoctrineRuleText } from "../model/ai-job-failure-copy.js";
20
21
  import { isAiUnreadToken } from "../model/ai-template-subjects.js";
21
22
  import { aiAnswerTree } from "../runtime/ai-doctrine.js";
22
23
  import { aiBracketedFacts, aiTreeCopy, walkTree } from "../runtime/ai-doctrine-validators.js";
@@ -327,11 +328,21 @@ function elementOf(node) {
327
328
  * entries it names, and an outline of those parts of the answer it was found
328
329
  * in (AGL-3078), so why a job stopped can be read back from the job once the
329
330
  * answer itself is gone.
330
- */ export function aiDoctrineReview(result) {
331
+ *
332
+ * The doctrine's own sentence ("Rule 12 (Responsive by the theme's
333
+ * breakpoints): ...") is never what the customer reads (AGL-3596): it rides
334
+ * as `detail`, for staff, and `message` is the plain refusal, a page's when
335
+ * `page` says the refused thing is one, followed by `then` when given.
336
+ */ export function aiDoctrineReview(result, options = {}) {
331
337
  const outline = aiDoctrineReviewOutline(result.answer, result.violations);
338
+ const ruleText = aiIsDoctrineRuleText(result.message);
339
+ const plain = ruleText ? options.page ? AI_DOCTRINE_REFUSED_PAGE_COPY : AI_DOCTRINE_REFUSED_COPY : result.message;
332
340
  return _extends({
333
341
  reason: 'doctrine',
334
- message: result.message,
342
+ message: options.then ? `${plain} ${options.then}` : plain
343
+ }, ruleText ? {
344
+ detail: result.message
345
+ } : {}, {
335
346
  findings: result.violations.map(({ rule, code, message, nodeIds, paths })=>_extends({
336
347
  rule,
337
348
  code,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-generation.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n REUSABLE_INSTANCE_COMPONENT_ID,\n REUSABLE_INSTANCE_PROP_VALUES_KEY,\n} from '@aglyn/aglyn/app-utils/reusable-component-keys'\nimport { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport {\n AI_BUILD_PLAN_EMBED_HOST_NAMES,\n AI_PLAN_NEW_REF_PREFIX,\n isAiPlanNewRef,\n type AiBuildPlanCreate,\n type AiBuildPlanCreateKind,\n} from '../model/ai-build-plan'\nimport type {\n AiJob,\n AiJobOutput,\n AiJobPlan,\n AiJobReview,\n AiJobReviewOutlineNode,\n} from '../model/ai-jobs.types'\nimport type { AiSiteInventory } from '../model/ai-site-inventory'\nimport { isAiUnreadToken } from '../model/ai-template-subjects'\nimport { aiAnswerTree, type AiGenerationSpend, type AiValidatedTree } from '../runtime/ai-doctrine'\nimport {\n aiBracketedFacts,\n aiTreeCopy,\n walkTree,\n type AiDoctrineNode,\n type AiDoctrineTree,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport { AI_INSTANCE_REF_PROP } from '../runtime/ai-node-tree'\nimport type { AssistTokenUsage } from '../usage/assist-usage'\nimport { AI_JOB_BRIEF_MAX_CHARS, type AiJobStepOutcome } from './ai-job-text-step'\n\n/**\n * What the generation steps share (AGL-2909): reading the plan a member\n * confirmed, stating it to the model as references, and turning what a\n * generation spent or refused into the outcome the machine records.\n *\n * A generation step runs after its job's plan step, once a member confirmed\n * the plan, so the plan is part of what it executes: the records it reuses,\n * and the creation it builds. The plan travels as references — ids, names,\n * reasons — never as a document (AGL-2937).\n */\n\nexport const AI_JOB_ZERO_USAGE: AssistTokenUsage = {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n}\n\n/** The job's plan once a member confirmed it; `null` before that, or for a job that never planned. */\nexport function aiConfirmedPlan(job: Pick<AiJob, 'plan'>): AiJobPlan | null {\n return job.plan?.status === 'confirmed' ? job.plan : null\n}\n\n/** The creation of one kind the plan names; the first when it names several. */\nexport function aiPlanCreation(\n plan: AiJobPlan | null,\n kind: AiBuildPlanCreateKind,\n): AiBuildPlanCreate | null {\n return plan?.create.find((entry) => entry.kind === kind) ?? null\n}\n\n/** A plan reference as the model reads it: the id with the name it was planned under. */\nfunction referenceOf(plan: AiJobPlan, ref: string): string {\n const label = plan.labels?.[ref]\n return label ? `${ref} (\"${label}\")` : ref\n}\n\n/** The user turn's opening: the brief, cut at the ceiling every job step keeps. */\nexport function aiJobBriefLine(job: Pick<AiJob, 'brief'>): string {\n return `Brief: ${job.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)}`\n}\n\n/** The confirmed plan as reference lines; none when there is no confirmed plan. */\nexport function aiPlanReferenceLines(plan: AiJobPlan | null): string[] {\n if (!plan) return []\n const lines = ['Confirmed plan:']\n for (const entry of plan.reuse) {\n lines.push(`- reuse the ${entry.kind === 'screen' ? 'page' : entry.kind} ${referenceOf(plan, entry.id)}: ${entry.purpose}`)\n }\n for (const entry of plan.create) {\n const from = entry.duplicateOf ? `, from a copy of ${referenceOf(plan, entry.duplicateOf)}` : ''\n // A template's slug or timestamp is filled, never shown, so a build told\n // it is a field prints it (AGL-3143 §16).\n const shown = entry.kind === 'template' ? entry.fields.filter((field) => !isAiUnreadToken(field)) : entry.fields\n const fields = shown.length ? ` Fields: ${shown.join(', ')}.` : ''\n lines.push(`- create the ${entry.kind} \"${entry.name}\"${from}: ${entry.why}${fields}`)\n }\n for (const screen of plan.screens) {\n const sections = screen.sections.map((section) => section.name).join(', ')\n lines.push(`- the page \"${screen.title}\" at ${screen.slug}${sections ? `: ${sections}` : ''}`)\n }\n // The one place a Video embed is admitted (AGL-3433): where the plan lists it, at the brief's link or none.\n for (const embed of plan.embeds ?? []) {\n const where = isAiPlanNewRef(embed.where)\n ? `the component \"${embed.where.slice(AI_PLAN_NEW_REF_PREFIX.length)}\"`\n : embed.where\n const link = embed.url ? `, url ${embed.url}` : ', url left empty for the site owner to paste'\n lines.push(`- embed a ${AI_BUILD_PLAN_EMBED_HOST_NAMES[embed.host]} player (videoEmbed) on ${where}${link}`)\n }\n return lines.length > 1 ? lines : []\n}\n\n/**\n * The most of a source's tree a build is shown, as JSON characters: about\n * 3,000 tokens, a whole layout or page template, never a site (AGL-3143 §14).\n */\nexport const AI_COPY_SOURCE_MAX_CHARS = 12_000\n\n/**\n * The record a plan's copy starts from, as the build that replaces the copy\n * is shown it (AGL-3143 §14): its tree in the node-map shape the tool takes,\n * with the one instruction that makes it a start rather than an example.\n * Empty when there is no tree to show or it is too large to show whole, and\n * the build then starts from the brief and the plan.\n *\n * A copy whose source cannot keep what the plan promised is never minted,\n * because the review after it would refuse it with certainty and the member\n * could only try the same copy again. Measured on 2026-09-22 (job\n * `Pq3pgvMH0D`): \"copy the interior layout and add a sidebar\" could only end\n * in review. Building from the source keeps what the plan does not change —\n * the firm's header, its footer, their copy — which a build from the brief\n * alone would draw again from nothing.\n */\nexport function aiCopySourceLines(noun: string, label: string, nodes: Readonly<Record<string, unknown>> | null): string[] {\n if (!nodes || !isRecordNode(nodes[CANVAS_ROOT_ELEMENT_ID])) return []\n const shaped: Record<string, Record<string, unknown>> = {}\n for (const [id, node] of Object.entries(nodes)) {\n if (!isRecordNode(node)) continue\n const { componentId, props, sx, nodes: children } = node\n shaped[id] = {\n componentId,\n ...(isRecordNode(props) && Object.keys(props).length ? { props } : {}),\n ...(isRecordNode(sx) && Object.keys(sx).length ? { sx } : {}),\n ...(Array.isArray(children) && children.length ? { nodes: children } : {}),\n }\n }\n const tree = JSON.stringify({ rootId: CANVAS_ROOT_ELEMENT_ID, nodes: shaped })\n if (tree.length > AI_COPY_SOURCE_MAX_CHARS) return []\n return [\n `The plan starts from a copy of the ${noun} \"${label}\", which lacks part of what the plan gives it. Build from it: keep every part the plan does not change, and add what the plan adds. Its tree, in the node-map shape the tool takes:`,\n tree,\n ]\n}\n\nfunction isRecordNode(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** The reusable components a tree places, by the id each instance names. */\nexport function aiPlacedComponentIds(tree: Pick<AiValidatedTree, 'rootId' | 'nodes'>): Set<string> {\n const placed = new Set<string>()\n const nodes = tree.nodes as unknown as Record<string, AiDoctrineNode>\n for (const { node } of walkTree({ rootId: tree.rootId, nodes })) {\n const ref = node.props?.[AI_INSTANCE_REF_PROP]\n if (node.componentId === REUSABLE_INSTANCE_COMPONENT_ID && typeof ref === 'string') {\n placed.add(ref)\n }\n }\n return placed\n}\n\n/**\n * Rule 7 on a generated tree: every component the confirmed plan reuses is\n * placed. `noun` names what the tree builds, in the finding.\n */\nexport function aiPlanReuseViolations(\n inventory: AiSiteInventory | null,\n plan: AiJobPlan | null,\n placed: ReadonlySet<string>,\n noun: string,\n): AiDoctrineViolation[] {\n const names = new Map((inventory?.components ?? []).map((component) => [component.id, component.name]))\n const reused = [\n ...new Set(\n (plan?.reuse ?? []).filter((entry) => entry.kind === 'component').map((entry) => entry.id),\n ),\n ]\n const missing = reused.filter((id) => !placed.has(id))\n if (!missing.length) return []\n const listed = missing.map((id) => `\"${names.get(id) ?? id}\"`).join(', ')\n return [\n {\n rule: 7,\n code: 'plan-reuse-not-placed',\n message: `The confirmed plan reuses ${listed}, and the ${noun} does not place ${\n missing.length === 1 ? 'it' : 'them'\n }. Place every component the plan reuses.`,\n },\n ]\n}\n\n/** What a generation spent, as the step's outcome carries it for the meter. */\nexport function aiGenerationSpent(\n spend: AiGenerationSpend,\n): Pick<AiJobStepOutcome, 'outputs' | 'usage' | 'estCostUsd' | 'model' | 'stopReason'> {\n return {\n outputs: [],\n usage: spend.usage,\n estCostUsd: spend.estCostUsd,\n model: spend.model,\n stopReason: spend.stopReason,\n }\n}\n\n/** The most node ids, or plan paths, one finding keeps on its review (AGL-3078). */\nexport const AI_JOB_REVIEW_REFS_MAX = 24\n/** The longest node id or plan path a review keeps, in characters. */\nexport const AI_JOB_REVIEW_REF_MAX_CHARS = 64\n/** How far below a node a finding names its outline goes: the node, its children and theirs. */\nexport const AI_JOB_REVIEW_OUTLINE_MAX_DEPTH = 2\n/** The most nodes a review outlines, across all its findings. */\nexport const AI_JOB_REVIEW_OUTLINE_MAX_NODES = 40\n/** The most names an outlined node lists of its props, of its sx keys, or of its children. */\nexport const AI_JOB_REVIEW_OUTLINE_MAX_NAMES = 16\n/** The most bytes a review's outline takes as JSON, whatever the counts above allow. */\nexport const AI_JOB_REVIEW_OUTLINE_MAX_BYTES = 4_096\n\n/** A Grid's layout props, whose written values an outline keeps. */\nconst AI_JOB_REVIEW_GRID_PROPS = [\n 'container',\n 'size',\n 'offset',\n 'direction',\n 'wrap',\n 'spacing',\n 'rowSpacing',\n 'columnSpacing',\n 'columns',\n] as const\n\n/** A node id or plan path a review keeps: no whitespace, slash or quote, so never copy or an address. */\nconst REVIEW_REF = /^[^\\s/\\\\\"'<>]+$/\n/** An element id an outline names. */\nconst OUTLINE_ELEMENT = /^[\\w.-]{1,64}$/\n/** A prop or sx name an outline lists. */\nconst OUTLINE_NAME = /^[A-Za-z_$][\\w$-]{0,39}$/\n/** The longest layout value an outline keeps as written. */\nconst OUTLINE_VALUE_MAX_CHARS = 40\n/**\n * The words a Grid's layout is written in, besides numbers: breakpoints, span\n * words, switch words, directions and wraps. Nothing else is kept, so no copy is.\n */\nconst OUTLINE_LAYOUT_WORDS = new Set([\n 'xs',\n 'sm',\n 'md',\n 'lg',\n 'xl',\n 'auto',\n 'grow',\n 'true',\n 'false',\n 'yes',\n 'no',\n 'on',\n 'off',\n 'row',\n 'row-reverse',\n 'column',\n 'column-reverse',\n 'wrap',\n 'nowrap',\n 'wrap-reverse',\n])\n/** A number a layout value is written in, with a CSS length unit where it has one. */\nconst OUTLINE_LAYOUT_NUMBER = /^[-+]?(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|%)?$/i\n/** What stands between the words of a layout value, as in `xs:12 md:4` or `{ xs: 12, md: 4 }`. */\nconst OUTLINE_LAYOUT_SEPARATORS = /[\\s,:;=/{}()[\\]\"']+/\n/** A binding token, which a component's Grid may carry for a layout value. */\nconst OUTLINE_BINDING = /^\\{\\{\\s*[\\w.-]+\\s*\\}\\}$/\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** Whether text is a layout value: a binding token, or only layout words and numbers. */\nfunction isLayoutText(value: string): boolean {\n if (value.length > OUTLINE_VALUE_MAX_CHARS) return false\n if (OUTLINE_BINDING.test(value.trim())) return true\n return value\n .split(OUTLINE_LAYOUT_SEPARATORS)\n .every((word) => word === '' || OUTLINE_LAYOUT_WORDS.has(word.toLowerCase()) || OUTLINE_LAYOUT_NUMBER.test(word))\n}\n\n/** An id or a path as a review keeps it: cut to its ceiling, and `?` for one it cannot show. */\nfunction reviewRef(ref: string): string {\n const cut = ref.slice(0, AI_JOB_REVIEW_REF_MAX_CHARS)\n return REVIEW_REF.test(cut) ? cut : '?'\n}\n\nfunction reviewRefs(refs: readonly string[]): string[] {\n return refs.slice(0, AI_JOB_REVIEW_REFS_MAX).map(reviewRef)\n}\n\n/** A written layout value as an outline keeps it; text that is no layout value is kept as a marker. */\nfunction outlineValue(value: unknown): string | number | boolean | null {\n if (value === null) return null\n if (typeof value === 'boolean') return value\n if (typeof value === 'number') return Number.isFinite(value) ? value : null\n if (typeof value === 'string') return isLayoutText(value) ? value : '<text>'\n return Array.isArray(value) ? '<list>' : '<object>'\n}\n\nfunction childIdsOf(node: Record<string, unknown>): string[] {\n return Array.isArray(node['nodes']) ? node['nodes'].filter((child): child is string => typeof child === 'string') : []\n}\n\nfunction elementOf(node: unknown): string {\n const componentId = isRecord(node) ? node['componentId'] : undefined\n return typeof componentId === 'string' && OUTLINE_ELEMENT.test(componentId) ? componentId : '?'\n}\n\n/** One node of an outline: where it sits, what it is and the names of what it sets, never a copy value. */\nfunction outlineNodeOf(\n id: string,\n depth: number,\n node: Record<string, unknown>,\n nodes: Readonly<Record<string, unknown>>,\n): AiJobReviewOutlineNode {\n const props = isRecord(node['props']) ? node['props'] : {}\n const sx = isRecord(node['sx']) ? node['sx'] : isRecord(props['sx']) ? props['sx'] : {}\n const names = (keys: string[]) =>\n keys.filter((key) => OUTLINE_NAME.test(key)).slice(0, AI_JOB_REVIEW_OUTLINE_MAX_NAMES)\n const componentId = elementOf(node)\n const grid =\n componentId === 'muiGrid'\n ? Object.fromEntries(\n AI_JOB_REVIEW_GRID_PROPS.filter((name) => props[name] !== undefined).map((name) => [\n name,\n outlineValue(props[name]),\n ]),\n )\n : {}\n const sxNames = names(Object.keys(sx))\n return {\n id: reviewRef(id),\n depth,\n componentId,\n props: names(Object.keys(props).filter((key) => key !== 'sx')),\n ...(sxNames.length ? { sx: sxNames } : {}),\n ...(Object.keys(grid).length ? { grid } : {}),\n children: childIdsOf(node)\n .filter((child) => isRecord(nodes[child]))\n .slice(0, AI_JOB_REVIEW_OUTLINE_MAX_NAMES)\n .map((child) => elementOf(nodes[child])),\n }\n}\n\n/**\n * The parts of a refused answer its findings name, as a review keeps them\n * (AGL-3078): each node a finding names by id, with the nodes below it to\n * `AI_JOB_REVIEW_OUTLINE_MAX_DEPTH`, in document order and each once. A node\n * keeps its id, its element, the names of its props and sx keys and its\n * children's elements, and a Grid the values of its layout props as written;\n * no copy, and no address, is kept. The outline stops at\n * `AI_JOB_REVIEW_OUTLINE_MAX_NODES` nodes or `AI_JOB_REVIEW_OUTLINE_MAX_BYTES`\n * bytes, whichever comes first. An answer that is no node map, and findings\n * that name no node, outline nothing.\n */\nexport function aiDoctrineReviewOutline(\n answer: Record<string, unknown> | null | undefined,\n violations: readonly AiDoctrineViolation[],\n): AiJobReviewOutlineNode[] {\n const outline: AiJobReviewOutlineNode[] = []\n const tree = answer ? aiAnswerTree(answer) : null\n const nodes = isRecord(tree) && isRecord(tree['nodes']) ? tree['nodes'] : null\n if (!nodes) return outline\n const encoder = new TextEncoder()\n const seen = new Set<string>()\n let bytes = 2\n // False once the outline is full, which ends the walk.\n const add = (id: string, depth: number): boolean => {\n const node = nodes[id]\n if (seen.has(id) || !isRecord(node)) return true\n seen.add(id)\n const entry = outlineNodeOf(id, depth, node, nodes)\n const size = encoder.encode(JSON.stringify(entry)).length + 1\n if (outline.length === AI_JOB_REVIEW_OUTLINE_MAX_NODES || bytes + size > AI_JOB_REVIEW_OUTLINE_MAX_BYTES) {\n return false\n }\n outline.push(entry)\n bytes += size\n return depth === AI_JOB_REVIEW_OUTLINE_MAX_DEPTH || childIdsOf(node).every((child) => add(child, depth + 1))\n }\n const named = violations.flatMap((violation) => (violation.nodeIds ?? []).slice(0, AI_JOB_REVIEW_REFS_MAX))\n for (const id of new Set(named)) {\n if (!add(id, 0)) break\n }\n return outline\n}\n\n/**\n * The review a generation stops its job for when the re-ask still broke a\n * rule: the sentence a person reads, each finding with the nodes or plan\n * entries it names, and an outline of those parts of the answer it was found\n * in (AGL-3078), so why a job stopped can be read back from the job once the\n * answer itself is gone.\n */\nexport function aiDoctrineReview(result: {\n message: string\n violations: readonly AiDoctrineViolation[]\n /** The refused answer, whose nodes the findings name by id; outlined, never kept. */\n answer?: Record<string, unknown> | null\n}): AiJobReview {\n const outline = aiDoctrineReviewOutline(result.answer, result.violations)\n return {\n reason: 'doctrine',\n message: result.message,\n findings: result.violations.map(({ rule, code, message, nodeIds, paths }) => ({\n rule,\n code,\n message,\n ...(nodeIds?.length ? { nodeIds: reviewRefs(nodeIds) } : {}),\n ...(paths?.length ? { paths: reviewRefs(paths) } : {}),\n })),\n ...(outline.length ? { outline } : {}),\n }\n}\n\n/** The review a step stops its job for when the site cannot take the draft. */\nexport function aiLimitReview(message: string): AiJobReview {\n return { reason: 'limit', message, findings: [] }\n}\n\n/**\n * An outcome that reached no model: a draft an earlier run already wrote, a\n * copy the plan asked for, or a person asked before anything was spent.\n */\nexport function aiUnspentOutcome(\n model: string,\n rest: { outputs?: AiJobOutput[]; review?: AiJobReview } = {},\n): AiJobStepOutcome {\n return {\n outputs: rest.outputs ?? [],\n usage: AI_JOB_ZERO_USAGE,\n estCostUsd: 0,\n model,\n stopReason: null,\n ...(rest.review ? { review: rest.review } : {}),\n }\n}\n\n/** Node ids as the MODEL wrote them, from the minted ids a check reads. */\nexport function aiModelNodeIds(\n ids: readonly string[],\n sourceIds: Readonly<Record<string, string>>,\n): string[] {\n return ids.map((id) => sourceIds[id] ?? id)\n}\n\n/** The most facts a note names before it counts the rest. */\nconst AI_NOTE_MAX_FACTS = 8\n\n/** Facts as a sentence lists them: \"a, b and c\", the ones past the cap counted. */\nfunction factList(facts: readonly string[]): string {\n const shown = facts.slice(0, AI_NOTE_MAX_FACTS)\n const items = facts.length > shown.length ? [...shown, `${facts.length - shown.length} more`] : shown\n return items.length <= 1 ? items.join('') : `${items.slice(0, -1).join(', ')} and ${items[items.length - 1]}`\n}\n\n/**\n * What a draft asks the member to fill in before it is published (AGL-3056),\n * in the words the job's output note shows: the facts in square brackets its\n * copy holds, which the brief did not give (rule 14), each once. A page's\n * placed components show their own defaults wherever the page sets no value\n * of its own, so a component whose defaults hold such facts is named once,\n * however often the page places it. `copy` is what the draft shows beside its\n * tree, such as a component's defaults. `null` when there is nothing to fill.\n */\nexport function aiBracketedFactsNote(input: {\n tree: AiDoctrineTree\n inventory: AiSiteInventory | null\n copy?: readonly string[]\n}): string | null {\n const components = input.inventory?.components ?? []\n const componentProps = Object.fromEntries(components.map((component) => [component.id, component.props]))\n const facts = aiBracketedFacts([\n ...aiTreeCopy(input.tree, { componentProps }).map((sample) => sample.text),\n ...(input.copy ?? []),\n ])\n const listed = new Set(facts.map((fact) => fact.toLowerCase()))\n const byComponent = new Map<string, string[]>()\n for (const { node } of walkTree(input.tree)) {\n const component = components.find((row) => row.id === node.props?.[AI_INSTANCE_REF_PROP])\n if (node.componentId !== REUSABLE_INSTANCE_COMPONENT_ID || !component?.bracketedDefaults) continue\n const values = node.props?.[REUSABLE_INSTANCE_PROP_VALUES_KEY]\n const set = (name: string): boolean => {\n const value = values && typeof values === 'object' ? (values as Record<string, unknown>)[name] : undefined\n return value !== undefined && value !== null && String(value).trim() !== ''\n }\n const shown = byComponent.get(component.name) ?? []\n for (const [name, defaults] of Object.entries(component.bracketedDefaults)) {\n if (set(name)) continue\n for (const fact of defaults) {\n if (!listed.has(fact.toLowerCase()) && !shown.some((known) => known.toLowerCase() === fact.toLowerCase())) {\n shown.push(fact)\n }\n }\n }\n if (shown.length) byComponent.set(component.name, shown)\n }\n const sentences = [\n ...(facts.length\n ? [`Before you publish, replace the facts in square brackets, which the brief did not give: ${factList(facts)}.`]\n : []),\n ...[...byComponent.entries()].map(\n ([name, shown]) =>\n `The \"${name}\" component on this page shows ${factList(shown)} until you replace ${shown.length === 1 ? 'it' : 'them'}.`,\n ),\n ]\n return sentences.length ? sentences.join(' ') : null\n}\n"],"names":["REUSABLE_INSTANCE_COMPONENT_ID","REUSABLE_INSTANCE_PROP_VALUES_KEY","CANVAS_ROOT_ELEMENT_ID","AI_BUILD_PLAN_EMBED_HOST_NAMES","AI_PLAN_NEW_REF_PREFIX","isAiPlanNewRef","isAiUnreadToken","aiAnswerTree","aiBracketedFacts","aiTreeCopy","walkTree","AI_INSTANCE_REF_PROP","AI_JOB_BRIEF_MAX_CHARS","AI_JOB_ZERO_USAGE","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","aiConfirmedPlan","job","plan","status","aiPlanCreation","kind","create","find","entry","referenceOf","ref","label","labels","aiJobBriefLine","brief","slice","aiPlanReferenceLines","lines","reuse","push","id","purpose","from","duplicateOf","shown","fields","filter","field","length","join","name","why","screen","screens","sections","map","section","title","slug","embed","embeds","where","link","url","host","AI_COPY_SOURCE_MAX_CHARS","aiCopySourceLines","noun","nodes","isRecordNode","shaped","node","Object","entries","componentId","props","sx","children","keys","Array","isArray","tree","JSON","stringify","rootId","value","aiPlacedComponentIds","placed","Set","add","aiPlanReuseViolations","inventory","names","Map","components","component","reused","missing","has","listed","get","rule","code","message","aiGenerationSpent","spend","outputs","usage","estCostUsd","model","stopReason","AI_JOB_REVIEW_REFS_MAX","AI_JOB_REVIEW_REF_MAX_CHARS","AI_JOB_REVIEW_OUTLINE_MAX_DEPTH","AI_JOB_REVIEW_OUTLINE_MAX_NODES","AI_JOB_REVIEW_OUTLINE_MAX_NAMES","AI_JOB_REVIEW_OUTLINE_MAX_BYTES","AI_JOB_REVIEW_GRID_PROPS","REVIEW_REF","OUTLINE_ELEMENT","OUTLINE_NAME","OUTLINE_VALUE_MAX_CHARS","OUTLINE_LAYOUT_WORDS","OUTLINE_LAYOUT_NUMBER","OUTLINE_LAYOUT_SEPARATORS","OUTLINE_BINDING","isRecord","isLayoutText","test","trim","split","every","word","toLowerCase","reviewRef","cut","reviewRefs","refs","outlineValue","Number","isFinite","childIdsOf","child","elementOf","undefined","outlineNodeOf","depth","key","grid","fromEntries","sxNames","aiDoctrineReviewOutline","answer","violations","outline","encoder","TextEncoder","seen","bytes","size","encode","named","flatMap","violation","nodeIds","aiDoctrineReview","result","reason","findings","paths","aiLimitReview","aiUnspentOutcome","rest","review","aiModelNodeIds","ids","sourceIds","AI_NOTE_MAX_FACTS","factList","facts","items","aiBracketedFactsNote","input","componentProps","sample","text","copy","fact","byComponent","row","bracketedDefaults","values","set","String","defaults","some","known","sentences"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-generation.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n REUSABLE_INSTANCE_COMPONENT_ID,\n REUSABLE_INSTANCE_PROP_VALUES_KEY,\n} from '@aglyn/aglyn/app-utils/reusable-component-keys'\nimport { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport {\n AI_BUILD_PLAN_EMBED_HOST_NAMES,\n AI_PLAN_NEW_REF_PREFIX,\n isAiPlanNewRef,\n type AiBuildPlanCreate,\n type AiBuildPlanCreateKind,\n} from '../model/ai-build-plan'\nimport type {\n AiJob,\n AiJobOutput,\n AiJobPlan,\n AiJobReview,\n AiJobReviewOutlineNode,\n} from '../model/ai-jobs.types'\nimport { AI_DOCTRINE_REFUSED_COPY, AI_DOCTRINE_REFUSED_PAGE_COPY, aiIsDoctrineRuleText } from '../model/ai-job-failure-copy'\nimport type { AiSiteInventory } from '../model/ai-site-inventory'\nimport { isAiUnreadToken } from '../model/ai-template-subjects'\nimport { aiAnswerTree, type AiGenerationSpend, type AiValidatedTree } from '../runtime/ai-doctrine'\nimport {\n aiBracketedFacts,\n aiTreeCopy,\n walkTree,\n type AiDoctrineNode,\n type AiDoctrineTree,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport { AI_INSTANCE_REF_PROP } from '../runtime/ai-node-tree'\nimport type { AssistTokenUsage } from '../usage/assist-usage'\nimport { AI_JOB_BRIEF_MAX_CHARS, type AiJobStepOutcome } from './ai-job-text-step'\n\n/**\n * What the generation steps share (AGL-2909): reading the plan a member\n * confirmed, stating it to the model as references, and turning what a\n * generation spent or refused into the outcome the machine records.\n *\n * A generation step runs after its job's plan step, once a member confirmed\n * the plan, so the plan is part of what it executes: the records it reuses,\n * and the creation it builds. The plan travels as references — ids, names,\n * reasons — never as a document (AGL-2937).\n */\n\nexport const AI_JOB_ZERO_USAGE: AssistTokenUsage = {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n}\n\n/** The job's plan once a member confirmed it; `null` before that, or for a job that never planned. */\nexport function aiConfirmedPlan(job: Pick<AiJob, 'plan'>): AiJobPlan | null {\n return job.plan?.status === 'confirmed' ? job.plan : null\n}\n\n/** The creation of one kind the plan names; the first when it names several. */\nexport function aiPlanCreation(\n plan: AiJobPlan | null,\n kind: AiBuildPlanCreateKind,\n): AiBuildPlanCreate | null {\n return plan?.create.find((entry) => entry.kind === kind) ?? null\n}\n\n/** A plan reference as the model reads it: the id with the name it was planned under. */\nfunction referenceOf(plan: AiJobPlan, ref: string): string {\n const label = plan.labels?.[ref]\n return label ? `${ref} (\"${label}\")` : ref\n}\n\n/** The user turn's opening: the brief, cut at the ceiling every job step keeps. */\nexport function aiJobBriefLine(job: Pick<AiJob, 'brief'>): string {\n return `Brief: ${job.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)}`\n}\n\n/** The confirmed plan as reference lines; none when there is no confirmed plan. */\nexport function aiPlanReferenceLines(plan: AiJobPlan | null): string[] {\n if (!plan) return []\n const lines = ['Confirmed plan:']\n for (const entry of plan.reuse) {\n lines.push(`- reuse the ${entry.kind === 'screen' ? 'page' : entry.kind} ${referenceOf(plan, entry.id)}: ${entry.purpose}`)\n }\n for (const entry of plan.create) {\n const from = entry.duplicateOf ? `, from a copy of ${referenceOf(plan, entry.duplicateOf)}` : ''\n // A template's slug or timestamp is filled, never shown, so a build told\n // it is a field prints it (AGL-3143 §16).\n const shown = entry.kind === 'template' ? entry.fields.filter((field) => !isAiUnreadToken(field)) : entry.fields\n const fields = shown.length ? ` Fields: ${shown.join(', ')}.` : ''\n lines.push(`- create the ${entry.kind} \"${entry.name}\"${from}: ${entry.why}${fields}`)\n }\n for (const screen of plan.screens) {\n const sections = screen.sections.map((section) => section.name).join(', ')\n lines.push(`- the page \"${screen.title}\" at ${screen.slug}${sections ? `: ${sections}` : ''}`)\n }\n // The one place a Video embed is admitted (AGL-3433): where the plan lists it, at the brief's link or none.\n for (const embed of plan.embeds ?? []) {\n const where = isAiPlanNewRef(embed.where)\n ? `the component \"${embed.where.slice(AI_PLAN_NEW_REF_PREFIX.length)}\"`\n : embed.where\n const link = embed.url ? `, url ${embed.url}` : ', url left empty for the site owner to paste'\n lines.push(`- embed a ${AI_BUILD_PLAN_EMBED_HOST_NAMES[embed.host]} player (videoEmbed) on ${where}${link}`)\n }\n return lines.length > 1 ? lines : []\n}\n\n/**\n * The most of a source's tree a build is shown, as JSON characters: about\n * 3,000 tokens, a whole layout or page template, never a site (AGL-3143 §14).\n */\nexport const AI_COPY_SOURCE_MAX_CHARS = 12_000\n\n/**\n * The record a plan's copy starts from, as the build that replaces the copy\n * is shown it (AGL-3143 §14): its tree in the node-map shape the tool takes,\n * with the one instruction that makes it a start rather than an example.\n * Empty when there is no tree to show or it is too large to show whole, and\n * the build then starts from the brief and the plan.\n *\n * A copy whose source cannot keep what the plan promised is never minted,\n * because the review after it would refuse it with certainty and the member\n * could only try the same copy again. Measured on 2026-09-22 (job\n * `Pq3pgvMH0D`): \"copy the interior layout and add a sidebar\" could only end\n * in review. Building from the source keeps what the plan does not change —\n * the firm's header, its footer, their copy — which a build from the brief\n * alone would draw again from nothing.\n */\nexport function aiCopySourceLines(noun: string, label: string, nodes: Readonly<Record<string, unknown>> | null): string[] {\n if (!nodes || !isRecordNode(nodes[CANVAS_ROOT_ELEMENT_ID])) return []\n const shaped: Record<string, Record<string, unknown>> = {}\n for (const [id, node] of Object.entries(nodes)) {\n if (!isRecordNode(node)) continue\n const { componentId, props, sx, nodes: children } = node\n shaped[id] = {\n componentId,\n ...(isRecordNode(props) && Object.keys(props).length ? { props } : {}),\n ...(isRecordNode(sx) && Object.keys(sx).length ? { sx } : {}),\n ...(Array.isArray(children) && children.length ? { nodes: children } : {}),\n }\n }\n const tree = JSON.stringify({ rootId: CANVAS_ROOT_ELEMENT_ID, nodes: shaped })\n if (tree.length > AI_COPY_SOURCE_MAX_CHARS) return []\n return [\n `The plan starts from a copy of the ${noun} \"${label}\", which lacks part of what the plan gives it. Build from it: keep every part the plan does not change, and add what the plan adds. Its tree, in the node-map shape the tool takes:`,\n tree,\n ]\n}\n\nfunction isRecordNode(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** The reusable components a tree places, by the id each instance names. */\nexport function aiPlacedComponentIds(tree: Pick<AiValidatedTree, 'rootId' | 'nodes'>): Set<string> {\n const placed = new Set<string>()\n const nodes = tree.nodes as unknown as Record<string, AiDoctrineNode>\n for (const { node } of walkTree({ rootId: tree.rootId, nodes })) {\n const ref = node.props?.[AI_INSTANCE_REF_PROP]\n if (node.componentId === REUSABLE_INSTANCE_COMPONENT_ID && typeof ref === 'string') {\n placed.add(ref)\n }\n }\n return placed\n}\n\n/**\n * Rule 7 on a generated tree: every component the confirmed plan reuses is\n * placed. `noun` names what the tree builds, in the finding.\n */\nexport function aiPlanReuseViolations(\n inventory: AiSiteInventory | null,\n plan: AiJobPlan | null,\n placed: ReadonlySet<string>,\n noun: string,\n): AiDoctrineViolation[] {\n const names = new Map((inventory?.components ?? []).map((component) => [component.id, component.name]))\n const reused = [\n ...new Set(\n (plan?.reuse ?? []).filter((entry) => entry.kind === 'component').map((entry) => entry.id),\n ),\n ]\n const missing = reused.filter((id) => !placed.has(id))\n if (!missing.length) return []\n const listed = missing.map((id) => `\"${names.get(id) ?? id}\"`).join(', ')\n return [\n {\n rule: 7,\n code: 'plan-reuse-not-placed',\n message: `The confirmed plan reuses ${listed}, and the ${noun} does not place ${\n missing.length === 1 ? 'it' : 'them'\n }. Place every component the plan reuses.`,\n },\n ]\n}\n\n/** What a generation spent, as the step's outcome carries it for the meter. */\nexport function aiGenerationSpent(\n spend: AiGenerationSpend,\n): Pick<AiJobStepOutcome, 'outputs' | 'usage' | 'estCostUsd' | 'model' | 'stopReason'> {\n return {\n outputs: [],\n usage: spend.usage,\n estCostUsd: spend.estCostUsd,\n model: spend.model,\n stopReason: spend.stopReason,\n }\n}\n\n/** The most node ids, or plan paths, one finding keeps on its review (AGL-3078). */\nexport const AI_JOB_REVIEW_REFS_MAX = 24\n/** The longest node id or plan path a review keeps, in characters. */\nexport const AI_JOB_REVIEW_REF_MAX_CHARS = 64\n/** How far below a node a finding names its outline goes: the node, its children and theirs. */\nexport const AI_JOB_REVIEW_OUTLINE_MAX_DEPTH = 2\n/** The most nodes a review outlines, across all its findings. */\nexport const AI_JOB_REVIEW_OUTLINE_MAX_NODES = 40\n/** The most names an outlined node lists of its props, of its sx keys, or of its children. */\nexport const AI_JOB_REVIEW_OUTLINE_MAX_NAMES = 16\n/** The most bytes a review's outline takes as JSON, whatever the counts above allow. */\nexport const AI_JOB_REVIEW_OUTLINE_MAX_BYTES = 4_096\n\n/** A Grid's layout props, whose written values an outline keeps. */\nconst AI_JOB_REVIEW_GRID_PROPS = [\n 'container',\n 'size',\n 'offset',\n 'direction',\n 'wrap',\n 'spacing',\n 'rowSpacing',\n 'columnSpacing',\n 'columns',\n] as const\n\n/** A node id or plan path a review keeps: no whitespace, slash or quote, so never copy or an address. */\nconst REVIEW_REF = /^[^\\s/\\\\\"'<>]+$/\n/** An element id an outline names. */\nconst OUTLINE_ELEMENT = /^[\\w.-]{1,64}$/\n/** A prop or sx name an outline lists. */\nconst OUTLINE_NAME = /^[A-Za-z_$][\\w$-]{0,39}$/\n/** The longest layout value an outline keeps as written. */\nconst OUTLINE_VALUE_MAX_CHARS = 40\n/**\n * The words a Grid's layout is written in, besides numbers: breakpoints, span\n * words, switch words, directions and wraps. Nothing else is kept, so no copy is.\n */\nconst OUTLINE_LAYOUT_WORDS = new Set([\n 'xs',\n 'sm',\n 'md',\n 'lg',\n 'xl',\n 'auto',\n 'grow',\n 'true',\n 'false',\n 'yes',\n 'no',\n 'on',\n 'off',\n 'row',\n 'row-reverse',\n 'column',\n 'column-reverse',\n 'wrap',\n 'nowrap',\n 'wrap-reverse',\n])\n/** A number a layout value is written in, with a CSS length unit where it has one. */\nconst OUTLINE_LAYOUT_NUMBER = /^[-+]?(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|%)?$/i\n/** What stands between the words of a layout value, as in `xs:12 md:4` or `{ xs: 12, md: 4 }`. */\nconst OUTLINE_LAYOUT_SEPARATORS = /[\\s,:;=/{}()[\\]\"']+/\n/** A binding token, which a component's Grid may carry for a layout value. */\nconst OUTLINE_BINDING = /^\\{\\{\\s*[\\w.-]+\\s*\\}\\}$/\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** Whether text is a layout value: a binding token, or only layout words and numbers. */\nfunction isLayoutText(value: string): boolean {\n if (value.length > OUTLINE_VALUE_MAX_CHARS) return false\n if (OUTLINE_BINDING.test(value.trim())) return true\n return value\n .split(OUTLINE_LAYOUT_SEPARATORS)\n .every((word) => word === '' || OUTLINE_LAYOUT_WORDS.has(word.toLowerCase()) || OUTLINE_LAYOUT_NUMBER.test(word))\n}\n\n/** An id or a path as a review keeps it: cut to its ceiling, and `?` for one it cannot show. */\nfunction reviewRef(ref: string): string {\n const cut = ref.slice(0, AI_JOB_REVIEW_REF_MAX_CHARS)\n return REVIEW_REF.test(cut) ? cut : '?'\n}\n\nfunction reviewRefs(refs: readonly string[]): string[] {\n return refs.slice(0, AI_JOB_REVIEW_REFS_MAX).map(reviewRef)\n}\n\n/** A written layout value as an outline keeps it; text that is no layout value is kept as a marker. */\nfunction outlineValue(value: unknown): string | number | boolean | null {\n if (value === null) return null\n if (typeof value === 'boolean') return value\n if (typeof value === 'number') return Number.isFinite(value) ? value : null\n if (typeof value === 'string') return isLayoutText(value) ? value : '<text>'\n return Array.isArray(value) ? '<list>' : '<object>'\n}\n\nfunction childIdsOf(node: Record<string, unknown>): string[] {\n return Array.isArray(node['nodes']) ? node['nodes'].filter((child): child is string => typeof child === 'string') : []\n}\n\nfunction elementOf(node: unknown): string {\n const componentId = isRecord(node) ? node['componentId'] : undefined\n return typeof componentId === 'string' && OUTLINE_ELEMENT.test(componentId) ? componentId : '?'\n}\n\n/** One node of an outline: where it sits, what it is and the names of what it sets, never a copy value. */\nfunction outlineNodeOf(\n id: string,\n depth: number,\n node: Record<string, unknown>,\n nodes: Readonly<Record<string, unknown>>,\n): AiJobReviewOutlineNode {\n const props = isRecord(node['props']) ? node['props'] : {}\n const sx = isRecord(node['sx']) ? node['sx'] : isRecord(props['sx']) ? props['sx'] : {}\n const names = (keys: string[]) =>\n keys.filter((key) => OUTLINE_NAME.test(key)).slice(0, AI_JOB_REVIEW_OUTLINE_MAX_NAMES)\n const componentId = elementOf(node)\n const grid =\n componentId === 'muiGrid'\n ? Object.fromEntries(\n AI_JOB_REVIEW_GRID_PROPS.filter((name) => props[name] !== undefined).map((name) => [\n name,\n outlineValue(props[name]),\n ]),\n )\n : {}\n const sxNames = names(Object.keys(sx))\n return {\n id: reviewRef(id),\n depth,\n componentId,\n props: names(Object.keys(props).filter((key) => key !== 'sx')),\n ...(sxNames.length ? { sx: sxNames } : {}),\n ...(Object.keys(grid).length ? { grid } : {}),\n children: childIdsOf(node)\n .filter((child) => isRecord(nodes[child]))\n .slice(0, AI_JOB_REVIEW_OUTLINE_MAX_NAMES)\n .map((child) => elementOf(nodes[child])),\n }\n}\n\n/**\n * The parts of a refused answer its findings name, as a review keeps them\n * (AGL-3078): each node a finding names by id, with the nodes below it to\n * `AI_JOB_REVIEW_OUTLINE_MAX_DEPTH`, in document order and each once. A node\n * keeps its id, its element, the names of its props and sx keys and its\n * children's elements, and a Grid the values of its layout props as written;\n * no copy, and no address, is kept. The outline stops at\n * `AI_JOB_REVIEW_OUTLINE_MAX_NODES` nodes or `AI_JOB_REVIEW_OUTLINE_MAX_BYTES`\n * bytes, whichever comes first. An answer that is no node map, and findings\n * that name no node, outline nothing.\n */\nexport function aiDoctrineReviewOutline(\n answer: Record<string, unknown> | null | undefined,\n violations: readonly AiDoctrineViolation[],\n): AiJobReviewOutlineNode[] {\n const outline: AiJobReviewOutlineNode[] = []\n const tree = answer ? aiAnswerTree(answer) : null\n const nodes = isRecord(tree) && isRecord(tree['nodes']) ? tree['nodes'] : null\n if (!nodes) return outline\n const encoder = new TextEncoder()\n const seen = new Set<string>()\n let bytes = 2\n // False once the outline is full, which ends the walk.\n const add = (id: string, depth: number): boolean => {\n const node = nodes[id]\n if (seen.has(id) || !isRecord(node)) return true\n seen.add(id)\n const entry = outlineNodeOf(id, depth, node, nodes)\n const size = encoder.encode(JSON.stringify(entry)).length + 1\n if (outline.length === AI_JOB_REVIEW_OUTLINE_MAX_NODES || bytes + size > AI_JOB_REVIEW_OUTLINE_MAX_BYTES) {\n return false\n }\n outline.push(entry)\n bytes += size\n return depth === AI_JOB_REVIEW_OUTLINE_MAX_DEPTH || childIdsOf(node).every((child) => add(child, depth + 1))\n }\n const named = violations.flatMap((violation) => (violation.nodeIds ?? []).slice(0, AI_JOB_REVIEW_REFS_MAX))\n for (const id of new Set(named)) {\n if (!add(id, 0)) break\n }\n return outline\n}\n\n/**\n * The review a generation stops its job for when the re-ask still broke a\n * rule: the sentence a person reads, each finding with the nodes or plan\n * entries it names, and an outline of those parts of the answer it was found\n * in (AGL-3078), so why a job stopped can be read back from the job once the\n * answer itself is gone.\n *\n * The doctrine's own sentence (\"Rule 12 (Responsive by the theme's\n * breakpoints): ...\") is never what the customer reads (AGL-3596): it rides\n * as `detail`, for staff, and `message` is the plain refusal, a page's when\n * `page` says the refused thing is one, followed by `then` when given.\n */\nexport function aiDoctrineReview(\n result: {\n message: string\n violations: readonly AiDoctrineViolation[]\n /** The refused answer, whose nodes the findings name by id; outlined, never kept. */\n answer?: Record<string, unknown> | null\n },\n options: { page?: boolean; then?: string } = {},\n): AiJobReview {\n const outline = aiDoctrineReviewOutline(result.answer, result.violations)\n const ruleText = aiIsDoctrineRuleText(result.message)\n const plain = ruleText ? (options.page ? AI_DOCTRINE_REFUSED_PAGE_COPY : AI_DOCTRINE_REFUSED_COPY) : result.message\n return {\n reason: 'doctrine',\n message: options.then ? `${plain} ${options.then}` : plain,\n ...(ruleText ? { detail: result.message } : {}),\n findings: result.violations.map(({ rule, code, message, nodeIds, paths }) => ({\n rule,\n code,\n message,\n ...(nodeIds?.length ? { nodeIds: reviewRefs(nodeIds) } : {}),\n ...(paths?.length ? { paths: reviewRefs(paths) } : {}),\n })),\n ...(outline.length ? { outline } : {}),\n }\n}\n\n/** The review a step stops its job for when the site cannot take the draft. */\nexport function aiLimitReview(message: string): AiJobReview {\n return { reason: 'limit', message, findings: [] }\n}\n\n/**\n * An outcome that reached no model: a draft an earlier run already wrote, a\n * copy the plan asked for, or a person asked before anything was spent.\n */\nexport function aiUnspentOutcome(\n model: string,\n rest: { outputs?: AiJobOutput[]; review?: AiJobReview } = {},\n): AiJobStepOutcome {\n return {\n outputs: rest.outputs ?? [],\n usage: AI_JOB_ZERO_USAGE,\n estCostUsd: 0,\n model,\n stopReason: null,\n ...(rest.review ? { review: rest.review } : {}),\n }\n}\n\n/** Node ids as the MODEL wrote them, from the minted ids a check reads. */\nexport function aiModelNodeIds(\n ids: readonly string[],\n sourceIds: Readonly<Record<string, string>>,\n): string[] {\n return ids.map((id) => sourceIds[id] ?? id)\n}\n\n/** The most facts a note names before it counts the rest. */\nconst AI_NOTE_MAX_FACTS = 8\n\n/** Facts as a sentence lists them: \"a, b and c\", the ones past the cap counted. */\nfunction factList(facts: readonly string[]): string {\n const shown = facts.slice(0, AI_NOTE_MAX_FACTS)\n const items = facts.length > shown.length ? [...shown, `${facts.length - shown.length} more`] : shown\n return items.length <= 1 ? items.join('') : `${items.slice(0, -1).join(', ')} and ${items[items.length - 1]}`\n}\n\n/**\n * What a draft asks the member to fill in before it is published (AGL-3056),\n * in the words the job's output note shows: the facts in square brackets its\n * copy holds, which the brief did not give (rule 14), each once. A page's\n * placed components show their own defaults wherever the page sets no value\n * of its own, so a component whose defaults hold such facts is named once,\n * however often the page places it. `copy` is what the draft shows beside its\n * tree, such as a component's defaults. `null` when there is nothing to fill.\n */\nexport function aiBracketedFactsNote(input: {\n tree: AiDoctrineTree\n inventory: AiSiteInventory | null\n copy?: readonly string[]\n}): string | null {\n const components = input.inventory?.components ?? []\n const componentProps = Object.fromEntries(components.map((component) => [component.id, component.props]))\n const facts = aiBracketedFacts([\n ...aiTreeCopy(input.tree, { componentProps }).map((sample) => sample.text),\n ...(input.copy ?? []),\n ])\n const listed = new Set(facts.map((fact) => fact.toLowerCase()))\n const byComponent = new Map<string, string[]>()\n for (const { node } of walkTree(input.tree)) {\n const component = components.find((row) => row.id === node.props?.[AI_INSTANCE_REF_PROP])\n if (node.componentId !== REUSABLE_INSTANCE_COMPONENT_ID || !component?.bracketedDefaults) continue\n const values = node.props?.[REUSABLE_INSTANCE_PROP_VALUES_KEY]\n const set = (name: string): boolean => {\n const value = values && typeof values === 'object' ? (values as Record<string, unknown>)[name] : undefined\n return value !== undefined && value !== null && String(value).trim() !== ''\n }\n const shown = byComponent.get(component.name) ?? []\n for (const [name, defaults] of Object.entries(component.bracketedDefaults)) {\n if (set(name)) continue\n for (const fact of defaults) {\n if (!listed.has(fact.toLowerCase()) && !shown.some((known) => known.toLowerCase() === fact.toLowerCase())) {\n shown.push(fact)\n }\n }\n }\n if (shown.length) byComponent.set(component.name, shown)\n }\n const sentences = [\n ...(facts.length\n ? [`Before you publish, replace the facts in square brackets, which the brief did not give: ${factList(facts)}.`]\n : []),\n ...[...byComponent.entries()].map(\n ([name, shown]) =>\n `The \"${name}\" component on this page shows ${factList(shown)} until you replace ${shown.length === 1 ? 'it' : 'them'}.`,\n ),\n ]\n return sentences.length ? sentences.join(' ') : null\n}\n"],"names":["REUSABLE_INSTANCE_COMPONENT_ID","REUSABLE_INSTANCE_PROP_VALUES_KEY","CANVAS_ROOT_ELEMENT_ID","AI_BUILD_PLAN_EMBED_HOST_NAMES","AI_PLAN_NEW_REF_PREFIX","isAiPlanNewRef","AI_DOCTRINE_REFUSED_COPY","AI_DOCTRINE_REFUSED_PAGE_COPY","aiIsDoctrineRuleText","isAiUnreadToken","aiAnswerTree","aiBracketedFacts","aiTreeCopy","walkTree","AI_INSTANCE_REF_PROP","AI_JOB_BRIEF_MAX_CHARS","AI_JOB_ZERO_USAGE","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","aiConfirmedPlan","job","plan","status","aiPlanCreation","kind","create","find","entry","referenceOf","ref","label","labels","aiJobBriefLine","brief","slice","aiPlanReferenceLines","lines","reuse","push","id","purpose","from","duplicateOf","shown","fields","filter","field","length","join","name","why","screen","screens","sections","map","section","title","slug","embed","embeds","where","link","url","host","AI_COPY_SOURCE_MAX_CHARS","aiCopySourceLines","noun","nodes","isRecordNode","shaped","node","Object","entries","componentId","props","sx","children","keys","Array","isArray","tree","JSON","stringify","rootId","value","aiPlacedComponentIds","placed","Set","add","aiPlanReuseViolations","inventory","names","Map","components","component","reused","missing","has","listed","get","rule","code","message","aiGenerationSpent","spend","outputs","usage","estCostUsd","model","stopReason","AI_JOB_REVIEW_REFS_MAX","AI_JOB_REVIEW_REF_MAX_CHARS","AI_JOB_REVIEW_OUTLINE_MAX_DEPTH","AI_JOB_REVIEW_OUTLINE_MAX_NODES","AI_JOB_REVIEW_OUTLINE_MAX_NAMES","AI_JOB_REVIEW_OUTLINE_MAX_BYTES","AI_JOB_REVIEW_GRID_PROPS","REVIEW_REF","OUTLINE_ELEMENT","OUTLINE_NAME","OUTLINE_VALUE_MAX_CHARS","OUTLINE_LAYOUT_WORDS","OUTLINE_LAYOUT_NUMBER","OUTLINE_LAYOUT_SEPARATORS","OUTLINE_BINDING","isRecord","isLayoutText","test","trim","split","every","word","toLowerCase","reviewRef","cut","reviewRefs","refs","outlineValue","Number","isFinite","childIdsOf","child","elementOf","undefined","outlineNodeOf","depth","key","grid","fromEntries","sxNames","aiDoctrineReviewOutline","answer","violations","outline","encoder","TextEncoder","seen","bytes","size","encode","named","flatMap","violation","nodeIds","aiDoctrineReview","result","options","ruleText","plain","page","reason","then","detail","findings","paths","aiLimitReview","aiUnspentOutcome","rest","review","aiModelNodeIds","ids","sourceIds","AI_NOTE_MAX_FACTS","factList","facts","items","aiBracketedFactsNote","input","componentProps","sample","text","copy","fact","byComponent","row","bracketedDefaults","values","set","String","defaults","some","known","sentences"],"mappings":"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"}
@@ -15,6 +15,9 @@
15
15
  * limitations under the License.
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
+ 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";
18
21
  import { AI_ROUTING_TABLE, aiModelForStep } from "../providers/routing.js";
19
22
  import { AI_ACCEPTABLE_USE_BLOCK, runAiRequest } from "../runtime/ai-runtime.js";
20
23
  import { AI_GENERATION_MAX_ATTEMPTS } from "../runtime/ai-generation-bounds.js";
@@ -45,6 +48,7 @@ export const AI_INSIGHT_UNREADABLE_COPY = 'The AI did not answer with insights.
45
48
  '- Name nobody, and write no email address or phone number.',
46
49
  '- Plain sentences: no markdown, no headings, no lists.',
47
50
  '- When the tables cannot answer part of the question, say so in gap in one sentence with no numbers; otherwise gap is null.',
51
+ '- 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.',
48
52
  '- Write in the language of the question.'
49
53
  ].join('\n');
50
54
  /** The system blocks both calls send: the rules and the platform's acceptable-use rules. */ export const AI_JOB_INSIGHT_SYSTEM = [
@@ -143,8 +147,8 @@ async function readHost(firestore, orgId, hostId) {
143
147
  };
144
148
  }
145
149
  export const runAiJobInsightStep = async (context)=>{
146
- var _ref, _job_hostId, _context_org, _job_expiresAt;
147
- 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;
148
152
  const { job, firestore, signal, now } = context;
149
153
  const model = (_ref = context.modelFor == null ? void 0 : context.modelFor.call(context, 'job.insight')) != null ? _ref : aiModelForStep('job.insight');
150
154
  const spend = new InsightSpend(model);
@@ -175,9 +179,24 @@ export const runAiJobInsightStep = async (context)=>{
175
179
  const maxTokens = AI_JOB_INSIGHT_STEP_BUDGET.maxTokens(model);
176
180
  const allowance = AI_GENERATION_MAX_ATTEMPTS * maxTokens;
177
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
+ ];
178
197
  const ask = (messages, tool, ceiling)=>runAiRequest(_extends({
179
198
  model,
180
- system: AI_JOB_INSIGHT_SYSTEM,
199
+ system,
181
200
  messages,
182
201
  tools: [
183
202
  tool === 'read' ? aiInsightReadTool() : aiInsightAnswerTool()
@@ -234,7 +253,12 @@ export const runAiJobInsightStep = async (context)=>{
234
253
  refusals: read.refusals
235
254
  });
236
255
  if (!read.tables.length) return stop(AI_INSIGHT_NO_TABLES_COPY);
237
- const { tables } = read;
256
+ // A site's question is told whether the site is live, so traffic is never
257
+ // read as launch status. Appended, so the readers' refs stay as they were.
258
+ const tables = !digest && site.host ? [
259
+ ...read.tables,
260
+ aiSiteStatusTable(aiHostPublishContext(site.host), `t${read.tables.length + 1}`)
261
+ ] : read.tables;
238
262
  // 2. The answer, and at most one re-ask.
239
263
  let messages = [
240
264
  {
@@ -296,7 +320,7 @@ export const runAiJobInsightStep = async (context)=>{
296
320
  }));
297
321
  return _extends({
298
322
  outputs: [
299
- 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)
300
324
  ]
301
325
  }, spend.outcome());
302
326
  };