@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,631 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { checkEntitlement } from "@aglyn/aglyn/app-utils/plan-entitlements";
18
+ import { SCREEN_KIND_TEMPLATE } from "@aglyn/aglyn/app-utils/screen-route";
19
+ import { screenSeoStageKey, SCREEN_SEO_TEXT_FIELDS } from "@aglyn/aglyn/app-utils/screen-seo-fields";
20
+ import { decodeStoredNodes, encodeStoredNodes } from "@aglyn/aglyn/app-utils/stored-nodes";
21
+ import { CanvasManager } from "@aglyn/aglyn/canvas-manager/canvas-manager";
22
+ import { FEATURE_FLAG } from "@aglyn/aglyn/foundation/constants/shared";
23
+ import { resolveOrgIdForHost } from "@aglyn/tenant-data-admin/server/organizations";
24
+ import { applyAssistEdit } from "../components/assist-edit-canvas.js";
25
+ import { 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 } from "../model/ai-edit-job.js";
26
+ import { ASSIST_EDIT_TOOL_NAME } from "../model/assist-edit.js";
27
+ import { AI_STEP_TIERS } from "../providers/catalog.js";
28
+ import { AI_ROUTING_TABLE, aiModelForStep } from "../providers/routing.js";
29
+ import { runValidatedGeneration, validateStreamedGeneration } from "../runtime/ai-doctrine.js";
30
+ import { AI_PALETTE } from "../runtime/ai-palette.generated.js";
31
+ import { assistEditTool, checkAssistEditAnswer, editCanvasBlock, editSelectionBlock, parseAssistEditContext } from "../server/assist-edit.js";
32
+ import { registerAiJobAdmission } from "./ai-job-admission.js";
33
+ import { aiJobStepBudget } from "./ai-job-budget.js";
34
+ import { aiOriginJobId } from "./ai-job-draft-ids.js";
35
+ import { aiGenerationSpent, aiLimitReview, aiUnspentOutcome } from "./ai-job-generation.js";
36
+ import { AI_JOB_BRIEF_MAX_CHARS } from "./ai-job-text-step.js";
37
+ import { registerAiJobStep } from "./ai-jobs.js";
38
+ /**
39
+ * The `edit` step (AGL-3616): a change to a page or a layout the site already
40
+ * has, from a description, made on the server with no editor open.
41
+ *
42
+ * It is the Assist edit rung, run as a job. The model is shown the stored
43
+ * document as the rung's outline and proposes through the rung's own tool;
44
+ * its answer is held to the rung's closed world and palette validators
45
+ * (`checkAssistEditAnswer`) and re-asked once, naming what was left out; and
46
+ * what survives lands through the canvas's own guarded mutators
47
+ * (`applyAssistEdit`), on a `CanvasManager` built headless from the stored
48
+ * nodes, inside the transaction that writes it.
49
+ *
50
+ * ── Where the change lands, decided before anything is spent ────────────
51
+ *
52
+ * Never on a version a visitor sees. With version history, a NEW version
53
+ * beside the one it started from — a copy of that version's stored fields with
54
+ * the changed nodes — which becomes the page's current version only when the
55
+ * page is not published; a published page's current version, and a layout's,
56
+ * never move. Without it, in place, on a version no visitor reaches: an
57
+ * unpublished page's, or one that is not the current version and no schedule
58
+ * will publish. Anything else is refused as a plan limit, charged nothing.
59
+ *
60
+ * ── What a job cannot do that the editor can ────────────────────────────
61
+ *
62
+ * The rung's chat door never offers saving part of a page as a reusable
63
+ * component, and neither does this. A page's search title and description
64
+ * live on the page itself, not on a version, so they are written only for a
65
+ * page that is not published; on a published page they are left out, and the
66
+ * output's note says so, as it says every change it left out.
67
+ *
68
+ * ── One version per job ──────────────────────────────────────────────────
69
+ *
70
+ * The new version is named by the job's id — a build's unit is named by its
71
+ * plan item's id — so a run asked again finds what it wrote and spends
72
+ * nothing. A change made in place stamps the version with the job's id for
73
+ * the same reason.
74
+ */ /** The longest the step may take reading the page and writing it. */ export const AI_EDIT_READS_MS = 2000;
75
+ /** The step's time: its answer and its re-ask at the `job.edit` ceiling, with its own reads. */ export const AI_JOB_EDIT_STEP_BUDGET = aiJobStepBudget({
76
+ tier: AI_STEP_TIERS['job.edit'],
77
+ maxTokens: AI_ROUTING_TABLE['job.edit'].maxTokens,
78
+ lookups: 0,
79
+ ownReadsMs: AI_EDIT_READS_MS
80
+ });
81
+ export const AI_JOB_EDIT_STEP_MINIMUM_MS = AI_JOB_EDIT_STEP_BUDGET.minimumMs;
82
+ export function aiJobEditModel() {
83
+ return aiModelForStep('job.edit');
84
+ }
85
+ /**
86
+ * The job's own rules, byte-identical on every request, ahead of the rung's
87
+ * protocol and catalog for the document's kind, which carries the breakpoint:
88
+ * both are the same for every site, so a kind's whole prefix caches once.
89
+ */ export const AI_JOB_EDIT_RULES = {
90
+ text: [
91
+ '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.',
92
+ `- 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.`,
93
+ '- 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.',
94
+ `- If the outline does not show an element the request is about, change nothing for that part: never guess at an element.`,
95
+ `- 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.`,
96
+ "- Fill in a page's search title or description only when the request asks for it.",
97
+ '- 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.',
98
+ '- 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.'
99
+ ].join('\n')
100
+ };
101
+ /** The system blocks for one kind of document. */ export function aiJobEditInstructions(kind) {
102
+ return [
103
+ AI_JOB_EDIT_RULES,
104
+ {
105
+ text: editCanvasBlock(kind),
106
+ cacheBreakpoint: true
107
+ }
108
+ ];
109
+ }
110
+ /** What the change is asked from: the document's outline, then the request. */ export function aiJobEditPrompt(input) {
111
+ return [
112
+ editSelectionBlock(input.context),
113
+ '',
114
+ `Request: ${input.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)}`
115
+ ].join('\n');
116
+ }
117
+ /** The edit as the doctrine's loop runs it: the rung's tool, held to the rung's check. */ export function aiJobEditGeneration(request) {
118
+ const row = AI_ROUTING_TABLE['job.edit'];
119
+ const kind = request.target.kind;
120
+ return _extends({
121
+ step: 'job.edit',
122
+ model: request.model,
123
+ instructions: aiJobEditInstructions(kind),
124
+ messages: [
125
+ {
126
+ role: 'user',
127
+ content: aiJobEditPrompt(request)
128
+ }
129
+ ],
130
+ tool: assistEditTool(kind),
131
+ maxTokens: AI_JOB_EDIT_STEP_BUDGET.maxTokens(request.model)
132
+ }, row.thinking ? {
133
+ thinking: row.thinking
134
+ } : {}, row.effort ? {
135
+ effort: row.effort
136
+ } : {}, request.signal ? {
137
+ signal: request.signal
138
+ } : {}, {
139
+ check: (answer)=>checkAssistEditAnswer(answer, {
140
+ context: request.context,
141
+ target: request.target
142
+ })
143
+ });
144
+ }
145
+ // ── The document, headless ────────────────────────────────────────────────
146
+ /**
147
+ * The slice of the platform a headless canvas asks: whether an element takes
148
+ * children, answered from the AI palette the edit validator reads, so the
149
+ * canvas refuses exactly what the validator refuses.
150
+ */ const AI_EDIT_CANVAS_PLATFORM = {
151
+ components: {
152
+ getSchema: (componentId)=>{
153
+ const entry = AI_PALETTE[componentId];
154
+ if (!entry) return undefined;
155
+ return entry.acceptsChildren ? {} : {
156
+ flags: {
157
+ dropping: FEATURE_FLAG.DISABLED
158
+ }
159
+ };
160
+ },
161
+ getLabel: (componentId)=>{
162
+ var _ref;
163
+ var _AI_PALETTE_componentId;
164
+ return (_ref = (_AI_PALETTE_componentId = AI_PALETTE[componentId]) == null ? void 0 : _AI_PALETTE_componentId.displayName) != null ? _ref : componentId;
165
+ }
166
+ }
167
+ };
168
+ /** A canvas over a stored node map, with the editor's own guarded mutators and no editor. */ export function aiEditCanvas(nodes) {
169
+ const canvas = new CanvasManager(AI_EDIT_CANVAS_PLATFORM);
170
+ canvas.setNodes(nodes);
171
+ return canvas;
172
+ }
173
+ /**
174
+ * Lands a proposal on a stored node map, as the editor lands one on its open
175
+ * canvas: checked whole first, then one batch, all or nothing. The session is
176
+ * the version being written, which is never one a visitor sees; a page's
177
+ * search fields are captured into `seo` only where `seo` is given.
178
+ */ export function aiApplyEditToNodes(nodes, proposal, seo) {
179
+ const canvas = aiEditCanvas(nodes);
180
+ const fields = {};
181
+ if (seo) {
182
+ for (const field of SCREEN_SEO_TEXT_FIELDS)fields[screenSeoStageKey(field)] = (value)=>seo[field] = value;
183
+ }
184
+ const session = _extends({
185
+ documentKind: proposal.target.kind,
186
+ documentId: proposal.target.documentId,
187
+ versionId: proposal.target.versionId,
188
+ isLiveVersion: ()=>false
189
+ }, seo ? {
190
+ fields
191
+ } : {});
192
+ const applied = applyAssistEdit(canvas, proposal, session);
193
+ if (applied.ok === false) return {
194
+ ok: false,
195
+ reason: applied.reason
196
+ };
197
+ return {
198
+ ok: true,
199
+ nodes: canvas.toJSON().nodes
200
+ };
201
+ }
202
+ const COLLECTIONS = {
203
+ screen: 'screens',
204
+ layout: 'layouts'
205
+ };
206
+ const text = (value)=>typeof value === 'string' ? value : '';
207
+ /** Whether visitors see a page's current version: it is routed, or it renders a collection's entries. */ function screenServed(screen, host) {
208
+ if (screen.get('kind') === SCREEN_KIND_TEMPLATE) return true;
209
+ const routing = host.get('screens');
210
+ return typeof (routing == null ? void 0 : routing[screen.id]) === 'string';
211
+ }
212
+ function pendingScheduleVersion(doc) {
213
+ const schedule = doc.get('publishSchedule');
214
+ if (!schedule || schedule['status'] !== 'pending' || schedule['action'] === 'unpublish') return null;
215
+ return text(schedule['versionId']) || null;
216
+ }
217
+ /**
218
+ * The document an edit job changes, on the job's own site: the page or
219
+ * layout named, not deleted, not an email design, and the version it starts
220
+ * from with a document in it. A refusal names what is wrong.
221
+ */ export async function readAiEditTarget(firestore, input) {
222
+ const hostRef = firestore.collection('hosts').doc(input.hostId);
223
+ const host = await hostRef.get();
224
+ if (!host.exists || host.get('orgId') !== input.orgId) return {
225
+ status: 404,
226
+ error: AI_EDIT_NO_SITE_COPY
227
+ };
228
+ const kinds = input.inputs.targetKind ? [
229
+ input.inputs.targetKind
230
+ ] : [
231
+ 'screen',
232
+ 'layout'
233
+ ];
234
+ for (const kind of kinds){
235
+ var _input_inputs_versionId;
236
+ const docRef = hostRef.collection(COLLECTIONS[kind]).doc(input.inputs.target);
237
+ const doc = await docRef.get();
238
+ if (!doc.exists) continue;
239
+ if (doc.get('deletedAt') != null) return {
240
+ status: 404,
241
+ error: AI_EDIT_GONE_COPY
242
+ };
243
+ if (kind === 'screen' && doc.get('kind') === 'email') return {
244
+ status: 400,
245
+ error: AI_EDIT_EMAIL_COPY
246
+ };
247
+ const pointerVersionId = text(doc.get('versionId')) || null;
248
+ const versions = docRef.collection('versions');
249
+ const hostSubdomain = text(host.get('subdomain')) || null;
250
+ const name = text(doc.get('displayName')) || (kind === 'screen' ? 'Page' : 'Layout');
251
+ // A run of this job that got as far as its write.
252
+ const own = input.jobId ? await versions.doc(input.jobId).get() : null;
253
+ const sourceVersionId = (_input_inputs_versionId = input.inputs.versionId) != null ? _input_inputs_versionId : pointerVersionId;
254
+ const base = {
255
+ kind,
256
+ id: doc.id,
257
+ name,
258
+ hostSubdomain,
259
+ pointerVersionId,
260
+ served: kind === 'layout' || screenServed(doc, host),
261
+ scheduledVersionId: pendingScheduleVersion(doc)
262
+ };
263
+ if ((own == null ? void 0 : own.exists) && input.jobId) {
264
+ const placement = pointerVersionId === input.jobId ? 'new-current' : 'new-beside';
265
+ return _extends({}, base, {
266
+ sourceVersionId: sourceVersionId != null ? sourceVersionId : input.jobId,
267
+ nodes: {},
268
+ written: {
269
+ versionId: input.jobId,
270
+ placement
271
+ }
272
+ });
273
+ }
274
+ if (!sourceVersionId) return {
275
+ status: 404,
276
+ error: AI_EDIT_VERSION_GONE_COPY
277
+ };
278
+ const source = await versions.doc(sourceVersionId).get();
279
+ if (!source.exists) return {
280
+ status: 404,
281
+ error: AI_EDIT_VERSION_GONE_COPY
282
+ };
283
+ if (input.jobId && source.get('aiEditJobId') === input.jobId) {
284
+ return _extends({}, base, {
285
+ sourceVersionId,
286
+ nodes: {},
287
+ written: {
288
+ versionId: sourceVersionId,
289
+ placement: 'in-place'
290
+ }
291
+ });
292
+ }
293
+ const nodes = decodeStoredNodes(source.get('nodes'));
294
+ if (!nodes || !Object.keys(nodes).length) return {
295
+ status: 400,
296
+ error: AI_EDIT_NO_NODES_COPY
297
+ };
298
+ return _extends({}, base, {
299
+ sourceVersionId,
300
+ nodes,
301
+ written: null
302
+ });
303
+ }
304
+ return {
305
+ status: 404,
306
+ error: AI_EDIT_GONE_COPY
307
+ };
308
+ }
309
+ /**
310
+ * Where a change may land (see the module head), or `refused` when the only
311
+ * place is a version visitors see and the plan keeps no version history.
312
+ */ export function aiEditPlacement(input) {
313
+ if (input.versioning) return !input.served && input.sourceIsPointer ? 'new-current' : 'new-beside';
314
+ const seen = input.sourceScheduled || input.sourceIsPointer && input.served;
315
+ return seen ? 'refused' : 'in-place';
316
+ }
317
+ /**
318
+ * The change written, in one transaction that reads the document as it is
319
+ * stored NOW — a member may have saved, published or changed plan's history
320
+ * since the read — decides where it lands again on that, and applies the
321
+ * proposal to the stored nodes through the canvas's guards. An element that
322
+ * changed since the model saw it refuses the whole change, as the editor does.
323
+ */ export async function writeAiEdit(firestore, input) {
324
+ const hostRef = firestore.collection('hosts').doc(input.hostId);
325
+ const docRef = hostRef.collection(COLLECTIONS[input.kind]).doc(input.id);
326
+ const versions = docRef.collection('versions');
327
+ const sourceRef = versions.doc(input.sourceVersionId);
328
+ const newRef = versions.doc(input.jobVersionId);
329
+ return firestore.runTransaction(async (tx)=>{
330
+ const [host, doc, source, already] = await Promise.all([
331
+ tx.get(hostRef),
332
+ tx.get(docRef),
333
+ tx.get(sourceRef),
334
+ tx.get(newRef)
335
+ ]);
336
+ const pointer = text(doc.get('versionId')) || null;
337
+ if (already.exists) {
338
+ return {
339
+ ok: true,
340
+ versionId: input.jobVersionId,
341
+ placement: pointer === input.jobVersionId ? 'new-current' : 'new-beside',
342
+ seoWritten: false
343
+ };
344
+ }
345
+ if (!doc.exists || doc.get('deletedAt') != null) return {
346
+ ok: false,
347
+ error: AI_EDIT_GONE_COPY
348
+ };
349
+ if (!source.exists) return {
350
+ ok: false,
351
+ error: AI_EDIT_VERSION_GONE_COPY
352
+ };
353
+ const served = input.kind === 'layout' || screenServed(doc, host);
354
+ const placement = aiEditPlacement({
355
+ versioning: input.versioning,
356
+ served,
357
+ sourceIsPointer: pointer === input.sourceVersionId,
358
+ sourceScheduled: pendingScheduleVersion(doc) === input.sourceVersionId
359
+ });
360
+ if (placement === 'refused') return {
361
+ ok: false,
362
+ error: AI_EDIT_NEEDS_VERSIONING_COPY
363
+ };
364
+ const nodes = decodeStoredNodes(source.get('nodes'));
365
+ if (!nodes) return {
366
+ ok: false,
367
+ error: AI_EDIT_NO_NODES_COPY
368
+ };
369
+ const versionId = placement === 'in-place' ? input.sourceVersionId : input.jobVersionId;
370
+ const seo = {};
371
+ const writeSeo = input.writeSeo && input.kind === 'screen' && !served;
372
+ const proposal = _extends({}, input.proposal, {
373
+ ops: writeSeo ? input.proposal.ops : input.proposal.ops.filter((op)=>op.op !== 'setSeo'),
374
+ target: _extends({}, input.proposal.target, {
375
+ versionId
376
+ })
377
+ });
378
+ const applied = aiApplyEditToNodes(nodes, proposal, writeSeo ? seo : undefined);
379
+ if (applied.ok === false) return {
380
+ ok: false,
381
+ error: applied.reason === 'stale' ? AI_EDIT_STALE_COPY : AI_EDIT_REFUSED_COPY
382
+ };
383
+ const packed = encodeStoredNodes(applied.nodes);
384
+ if (!packed) return {
385
+ ok: false,
386
+ error: AI_EDIT_NO_NODES_COPY
387
+ };
388
+ const stored = Buffer.from(packed);
389
+ const docPatch = {};
390
+ if (placement === 'in-place') {
391
+ tx.update(sourceRef, {
392
+ nodes: stored,
393
+ updatedAt: input.now,
394
+ aiEditJobId: input.jobVersionId
395
+ });
396
+ } else {
397
+ // A copy of the version it started from — its layout binding, its
398
+ // declared properties, everything the editor stored — with the changed
399
+ // tree, as the versions route copies one.
400
+ const copy = _extends({}, source.data());
401
+ delete copy['aiEditJobId'];
402
+ tx.create(newRef, _extends({}, copy, {
403
+ nodes: stored,
404
+ displayName: input.versionName,
405
+ createdAt: input.now,
406
+ updatedAt: input.now,
407
+ createdBy: input.uid,
408
+ aiJobId: input.aiJobId
409
+ }));
410
+ if (placement === 'new-current') docPatch['versionId'] = input.jobVersionId;
411
+ }
412
+ for (const [field, value] of Object.entries(seo)){
413
+ if (value) docPatch[`seo.${field}`] = value;
414
+ }
415
+ if (Object.keys(docPatch).length) tx.update(docRef, _extends({}, docPatch, {
416
+ updatedAt: input.now
417
+ }));
418
+ return {
419
+ ok: true,
420
+ versionId,
421
+ placement,
422
+ seoWritten: Object.keys(seo).length > 0
423
+ };
424
+ });
425
+ }
426
+ /** What an edit wrote, as the job's output: a link into the Besigner on the version written. */ export function aiEditOutput(read, hostId, written, leftOut) {
427
+ return {
428
+ resource: read.kind,
429
+ id: read.id,
430
+ versionId: written.versionId,
431
+ hostId,
432
+ hostSubdomain: read.hostSubdomain,
433
+ label: read.name,
434
+ note: aiEditOutputNote({
435
+ kind: read.kind,
436
+ placement: written.placement,
437
+ leftOut
438
+ })
439
+ };
440
+ }
441
+ /** The new version's id: the job's own, which a build's unit takes from its plan item. */ export function aiEditVersionId(job) {
442
+ return job.$id.slice(0, 64);
443
+ }
444
+ /**
445
+ * The ops a job may save, and why it left the rest out: never a component
446
+ * save, and a page's search fields only where no visitor reaches the page.
447
+ */ export function aiEditJobOps(ops, options) {
448
+ const kept = [];
449
+ const leftOut = new Set();
450
+ for (const op of ops){
451
+ if (op.op === 'saveAsComponent') leftOut.add(AI_EDIT_COMPONENT_LEFT_OUT);
452
+ else if (op.op === 'setSeo' && !options.seo) leftOut.add(AI_EDIT_SEO_LEFT_OUT);
453
+ else kept.push(op);
454
+ }
455
+ return {
456
+ ops: kept,
457
+ leftOut: [
458
+ ...leftOut
459
+ ]
460
+ };
461
+ }
462
+ export function createAiJobEditStep(deps = {}) {
463
+ var _deps_readTarget, _deps_writeEdit;
464
+ const readTarget = (_deps_readTarget = deps.readTarget) != null ? _deps_readTarget : readAiEditTarget;
465
+ const writeEdit = (_deps_writeEdit = deps.writeEdit) != null ? _deps_writeEdit : writeAiEdit;
466
+ return async ({ job, now, signal, firestore, org, modelFor })=>{
467
+ var _ref, _data;
468
+ const model = (_ref = modelFor == null ? void 0 : modelFor('job.edit')) != null ? _ref : aiJobEditModel();
469
+ const unspent = (failure)=>_extends({}, aiUnspentOutcome(model), {
470
+ failure
471
+ });
472
+ const inputs = parseAiEditJobInputs(job.inputs);
473
+ if (typeof inputs === 'string') return unspent(inputs);
474
+ const hostId = job.hostId;
475
+ if (!hostId) return unspent(AI_EDIT_NO_SITE_COPY);
476
+ const read = await readTarget(firestore, {
477
+ orgId: job.orgId,
478
+ hostId,
479
+ inputs,
480
+ jobId: aiEditVersionId(job)
481
+ });
482
+ if ('error' in read) return unspent(read.error);
483
+ if (read.written) return aiUnspentOutcome(model, {
484
+ outputs: [
485
+ aiEditOutput(read, hostId, read.written, [])
486
+ ]
487
+ });
488
+ const orgData = org != null ? org : (_data = (await firestore.collection('orgs').doc(job.orgId).get()).data()) != null ? _data : null;
489
+ const versioning = checkEntitlement(orgData, 'versioning');
490
+ const placement = aiEditPlacement({
491
+ versioning,
492
+ served: read.served,
493
+ sourceIsPointer: read.sourceVersionId === read.pointerVersionId,
494
+ sourceScheduled: read.scheduledVersionId === read.sourceVersionId
495
+ });
496
+ if (placement === 'refused') return aiUnspentOutcome(model, {
497
+ review: aiLimitReview(AI_EDIT_NEEDS_VERSIONING_COPY)
498
+ });
499
+ const outline = aiEditJobOutline(read.nodes);
500
+ const context = outline ? parseAssistEditContext(outline) : null;
501
+ if (!context) return unspent(AI_EDIT_NO_NODES_COPY);
502
+ const target = {
503
+ kind: read.kind,
504
+ documentId: read.id,
505
+ versionId: placement === 'in-place' ? read.sourceVersionId : aiEditVersionId(job),
506
+ hostId
507
+ };
508
+ const generation = await runValidatedGeneration('edit', aiJobEditGeneration(_extends({
509
+ brief: job.brief,
510
+ context,
511
+ target,
512
+ model
513
+ }, signal ? {
514
+ signal
515
+ } : {})));
516
+ const spent = _extends({}, aiGenerationSpent(generation), generation.effort ? {
517
+ effort: generation.effort
518
+ } : {});
519
+ if (generation.status === 'refused') return _extends({}, spent, {
520
+ refused: true
521
+ });
522
+ // An answer its re-ask still could not make whole keeps the changes that
523
+ // survived, as the chat door's card does, and says what it left out; one
524
+ // with none, or one that broke a numbered rule, is nothing to save.
525
+ let proposal = generation.status === 'ok' ? generation.value : null;
526
+ const refused = generation.status === 'needs_input' ? generation.answer : null;
527
+ if (!proposal && refused) {
528
+ const salvaged = validateStreamedGeneration('edit', {
529
+ answer: refused,
530
+ check: (answer)=>checkAssistEditAnswer(answer, {
531
+ context,
532
+ target
533
+ })
534
+ });
535
+ proposal = salvaged.status === 'ok' ? salvaged.value : null;
536
+ }
537
+ if (!proposal) return _extends({}, spent, {
538
+ failure: AI_EDIT_NOTHING_COPY
539
+ });
540
+ const seo = read.kind === 'screen' && !read.served;
541
+ const kept = aiEditJobOps(proposal.ops, {
542
+ seo
543
+ });
544
+ const leftOut = [
545
+ ...proposal.dropped,
546
+ ...kept.leftOut
547
+ ];
548
+ if (!kept.ops.length) {
549
+ return _extends({}, spent, {
550
+ failure: `Nothing was saved. Left out: ${leftOut.join('; ') || 'every change'}.`
551
+ });
552
+ }
553
+ const write = await writeEdit(firestore, {
554
+ hostId,
555
+ kind: read.kind,
556
+ id: read.id,
557
+ sourceVersionId: read.sourceVersionId,
558
+ jobVersionId: aiEditVersionId(job),
559
+ versioning,
560
+ proposal: _extends({}, proposal, {
561
+ ops: kept.ops
562
+ }),
563
+ writeSeo: seo,
564
+ versionName: aiEditVersionName(proposal.summary),
565
+ uid: job.createdBy,
566
+ aiJobId: aiOriginJobId(job),
567
+ now
568
+ });
569
+ if (write.ok === false) return _extends({}, spent, {
570
+ failure: write.error
571
+ });
572
+ return _extends({}, spent, {
573
+ outputs: [
574
+ aiEditOutput(read, hostId, write, leftOut)
575
+ ]
576
+ });
577
+ };
578
+ }
579
+ export const runAiJobEditStep = createAiJobEditStep();
580
+ /**
581
+ * An edit job is admitted for a page or layout of a site of its own org,
582
+ * and only where the change has somewhere to land that no visitor sees on
583
+ * this workspace's plan — refused in the plan's words before anything exists.
584
+ */ export function createAiEditJobAdmission(deps = {}) {
585
+ var _deps_readTarget, _deps_ownerOf;
586
+ const readTarget = (_deps_readTarget = deps.readTarget) != null ? _deps_readTarget : readAiEditTarget;
587
+ const ownerOf = (_deps_ownerOf = deps.ownerOf) != null ? _deps_ownerOf : resolveOrgIdForHost;
588
+ return async (context)=>{
589
+ const inputs = parseAiEditJobInputs(context.inputs);
590
+ if (typeof inputs === 'string') return {
591
+ status: 400,
592
+ error: inputs
593
+ };
594
+ if (!context.hostId) return {
595
+ status: 400,
596
+ error: AI_EDIT_NO_SITE_COPY
597
+ };
598
+ const owner = await ownerOf(context.hostId);
599
+ if (!owner || owner !== context.orgId) return {
600
+ status: 404,
601
+ error: 'Unknown site'
602
+ };
603
+ // A job asked about at its door has no run to find yet.
604
+ const read = await readTarget(context.firestore, {
605
+ orgId: context.orgId,
606
+ hostId: context.hostId,
607
+ inputs,
608
+ jobId: null
609
+ });
610
+ if ('error' in read) return read;
611
+ const placement = aiEditPlacement({
612
+ versioning: checkEntitlement(context.org, 'versioning'),
613
+ served: read.served,
614
+ sourceIsPointer: read.sourceVersionId === read.pointerVersionId,
615
+ sourceScheduled: read.scheduledVersionId === read.sourceVersionId
616
+ });
617
+ return placement === 'refused' ? {
618
+ status: 403,
619
+ error: AI_EDIT_NEEDS_VERSIONING_COPY
620
+ } : null;
621
+ };
622
+ }
623
+ export const aiEditJobAdmission = createAiEditJobAdmission();
624
+ /** Registers the edit step and the check an edit job passes before it is created or resumed. */ export function registerAiEditJob() {
625
+ registerAiJobStep('edit', runAiJobEditStep, {
626
+ minimumMs: AI_JOB_EDIT_STEP_MINIMUM_MS
627
+ });
628
+ registerAiJobAdmission('edit', aiEditJobAdmission);
629
+ }
630
+
631
+ //# sourceMappingURL=ai-job-edit-step.js.map