@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,163 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { pluginResourceDraftWriter } from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts';
18
+ import { publishContentEntries, writeContentCollection, writeContentEntryDraft } from '@aglyn/tenant-data-admin/server/content-entry-drafts';
19
+ import type { AiJob, AiJobOutput } from '../model/ai-jobs.types';
20
+ import { generateAiBlogPost } from '../runtime/ai-blog-post-generation';
21
+ import { aiJobAdmissionFor } from './ai-job-admission';
22
+ import type { AiJobStepContext, AiJobStepRunner } from './ai-job-text-step';
23
+ /**
24
+ * A GUIDED START'S FIRST POSTS AND FIRST PRODUCTS (AGL-3676).
25
+ *
26
+ * A site of the kind "Blog & writing" or "Online store" got the right look
27
+ * and pages, and nothing real in them: the writing page's cards and the
28
+ * shop's grid named posts and products nobody had written. On a paid plan,
29
+ * the scaffold now builds one more part for those two kinds, before the
30
+ * pages, each its own row with its own timer and credits:
31
+ *
32
+ * - WRITING YOUR FIRST POSTS: a content collection "Blog" — the platform's
33
+ * own blog, served at `/{slug}` with an entry page per post — and
34
+ * `AI_SITE_POSTS` posts in it, one generation a pass. Each is written as a
35
+ * draft through the same rules the console's routes hold
36
+ * (`content-entry-drafts.ts`), bylined with the business's name the person
37
+ * gave, and given a starter photo as its cover. They go live with the
38
+ * site: the guided start's publish publishes them with its pages
39
+ * (`aiPublishSitePosts`), as a person's Publish would.
40
+ * - ADDING YOUR FIRST PRODUCTS: the `products` step's catalog, asked for
41
+ * `AI_SITE_PRODUCTS` products, each written through the commerce plugin's
42
+ * `product` draft writer — a DRAFT with no price, off the storefront until
43
+ * the owner prices it, since no price is ever invented. No photo either:
44
+ * the writer takes none, and a stock picture of something else would show
45
+ * a shopper a product that is not the one for sale.
46
+ *
47
+ * The pages built after them are told their titles or names, so the writing
48
+ * page and the shop feature what exists rather than inventing their own.
49
+ *
50
+ * ── Not on the Free taste ────────────────────────────────────────────────
51
+ *
52
+ * A Free site's sections cap is DERIVED to spend the whole taste less one
53
+ * retried section (`aiFreeSiteSectionsWithin`), so there is no slack a post
54
+ * could use without taking a section away; and a Free plan includes neither
55
+ * the `commerce` feature nor a product (`productsPerHost` is 0).
56
+ * `ai-job-free-site.spec.ts` shows the arithmetic. Free sites keep the pages
57
+ * their kind already describes; the owner adds posts and products after.
58
+ */
59
+ /** How many first posts a paid blog gets. */
60
+ export declare const AI_SITE_POSTS = 3;
61
+ /** How many first products a paid store gets: asked for, and the most written. */
62
+ export declare const AI_SITE_PRODUCTS: {
63
+ readonly min: 3;
64
+ readonly max: 6;
65
+ };
66
+ /** What each part's row reads. */
67
+ export declare const AI_SITE_POSTS_LABEL = "Writing your first posts";
68
+ export declare const AI_SITE_PRODUCTS_LABEL = "Adding your first products";
69
+ /** The blog the posts go in, and the addresses it may answer at, in order of preference. */
70
+ export declare const AI_SITE_BLOG_NAME = "Blog";
71
+ export declare const AI_SITE_BLOG_SLUGS: readonly ["blog", "posts", "journal", "articles", "writing", "stories"];
72
+ /** The input a site unit's own job carries what the part needs in. */
73
+ export declare const AI_SITE_CONTENT_INPUT = "siteContent";
74
+ /** Which part a site of these answers gets; `null` for the Free taste and every other kind. */
75
+ export declare function aiSiteContentPart(inputs: Readonly<Record<string, unknown>> | null | undefined, freeTaste: boolean): 'posts' | 'products' | null;
76
+ /**
77
+ * ASSUMED: what one post pass reads and writes beside its generation: the
78
+ * host, the collection's slug claim and the entry's transaction.
79
+ */
80
+ export declare const AI_SITE_POST_WRITES_MS = 1500;
81
+ /** One post's pass: its generation and re-ask at the `copy.blog` ceiling, and its writes. */
82
+ export declare const AI_SITE_POST_BUDGET: import("./ai-job-budget").AiJobStepBudget;
83
+ /** What a posts unit's job is told: the posts already written and the addresses the blog must not take. */
84
+ export interface AiSitePostsInput {
85
+ written: Array<{
86
+ id: string;
87
+ title: string;
88
+ }>;
89
+ total: number;
90
+ /** Addresses the site's own pages answer at, which the blog must not shadow. */
91
+ avoidSlugs: string[];
92
+ /** Who the posts are by: the business's name as given, else empty. */
93
+ byline: string;
94
+ }
95
+ /** What a products unit's job is told. */
96
+ export interface AiSiteProductsInput {
97
+ min: number;
98
+ max: number;
99
+ }
100
+ /** The id of the `index`th post, of the blog its unit writes under `unitJobId`. */
101
+ export declare function aiSitePostId(unitJobId: string, index: number): string;
102
+ /** The id of the `index`th product a products unit writes. */
103
+ export declare function aiSiteProductId(unitJobId: string, index: number): string;
104
+ export interface AiSitePostsRunnerDeps {
105
+ generate?: typeof generateAiBlogPost;
106
+ writeCollection?: typeof writeContentCollection;
107
+ writeEntry?: typeof writeContentEntryDraft;
108
+ }
109
+ /**
110
+ * The posts unit's runner, on the job the scaffold derived for it: one post a
111
+ * pass, continuing while posts remain. Its first pass makes the blog; every
112
+ * pass finds it again under the unit's own id, and a post under its own id,
113
+ * so a pass repeated after its write reports the post it already wrote.
114
+ */
115
+ export declare function createAiSitePostsRunner(deps?: AiSitePostsRunnerDeps): AiJobStepRunner;
116
+ export declare const runAiSitePostsUnit: AiJobStepRunner;
117
+ export interface AiSiteProductsRunnerDeps {
118
+ /** The catalog's runner: the `products` step's. */
119
+ catalog: AiJobStepRunner;
120
+ writerFor?: typeof pluginResourceDraftWriter;
121
+ }
122
+ /** The sentence the products unit's brief ends with, which the catalog's rules read as how many. */
123
+ export declare function aiSiteProductsBriefLine(input?: AiSiteProductsInput): string;
124
+ /**
125
+ * The products unit's runner: the `products` step's catalog for the store's
126
+ * brief, then each proposed product, up to the most a site starts with,
127
+ * written as an unpriced draft by the plugin that keeps products. A product
128
+ * past the plan's allowance stops the writing there, and the row says how
129
+ * many were added; none at all is the allowance's review.
130
+ */
131
+ export declare function createAiSiteProductsRunner(deps: AiSiteProductsRunnerDeps): AiJobStepRunner;
132
+ /**
133
+ * Whether a part may be built for this member on this site, asked before its
134
+ * first pass so a refusal spends nothing; `null` admits. Products ask what a
135
+ * `products` job asks (the plan's commerce feature, Commerce on and released
136
+ * for the site) and the writer's own refusal (role, allowance); posts ask
137
+ * that the member may write the site's content.
138
+ */
139
+ export declare function aiSiteContentRefusal(part: 'posts' | 'products', context: Pick<AiJobStepContext, 'firestore' | 'org' | 'now'> & {
140
+ job: Pick<AiJob, 'orgId' | 'hostId' | 'createdBy' | 'inputs'>;
141
+ }, deps?: {
142
+ writerFor?: typeof pluginResourceDraftWriter;
143
+ admissionFor?: typeof aiJobAdmissionFor;
144
+ }): Promise<string | null>;
145
+ /** The guided start's posts, published with its pages (AGL-3676). */
146
+ export interface AiSitePostsPublish {
147
+ published: number;
148
+ kept: number;
149
+ /** The blog's own addresses, for the publish's cache drop. */
150
+ paths: string[];
151
+ }
152
+ /**
153
+ * Publishes the posts a guided start wrote, as the entry editor's Publish
154
+ * does, once its pages are live. Never throws: a post that stays a draft is
155
+ * the owner's to publish from Content.
156
+ */
157
+ export declare function aiPublishSitePosts(firestore: FirebaseFirestore.Firestore, input: {
158
+ job: Pick<AiJob, '$id' | 'orgId' | 'hostId' | 'createdBy'>;
159
+ outputs: readonly AiJobOutput[];
160
+ now: Date;
161
+ }, publish?: typeof publishContentEntries): Promise<AiSitePostsPublish | null>;
162
+ /** What a page of a blog or a store is told about the posts or products built before it. */
163
+ export declare function aiSiteContentBriefLines(outputs: readonly AiJobOutput[]): string[];
@@ -0,0 +1,443 @@
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 { hostRoleCanWrite } from "@aglyn/aglyn/app-utils/organizations";
18
+ import { pluginResourceDraftWriter } from "@aglyn/aglyn/plugin-manager/plugin-resource-drafts";
19
+ import { publishContentEntries, writeContentCollection, writeContentEntryDraft } from "@aglyn/tenant-data-admin/server/content-entry-drafts";
20
+ import { aiProductsProposalOf } from "../model/ai-products.js";
21
+ import { aiSiteKindOfInputs } from "../model/ai-site-kinds.js";
22
+ import { AI_STEP_TIERS } from "../providers/catalog.js";
23
+ import { aiModelForStep } from "../providers/routing.js";
24
+ import { AI_BLOG_POST_MAX_TOKENS, AI_BLOG_POST_STEP, generateAiBlogPost } from "../runtime/ai-blog-post-generation.js";
25
+ import { aiLayoutStarterPhotos } from "../layout-language/ai-layout-pictures.js";
26
+ import { aiJobAdmissionFor } from "./ai-job-admission.js";
27
+ import { aiJobStepBudget } from "./ai-job-budget.js";
28
+ import { aiGenerationSpent, aiUnspentOutcome } from "./ai-job-generation.js";
29
+ /**
30
+ * A GUIDED START'S FIRST POSTS AND FIRST PRODUCTS (AGL-3676).
31
+ *
32
+ * A site of the kind "Blog & writing" or "Online store" got the right look
33
+ * and pages, and nothing real in them: the writing page's cards and the
34
+ * shop's grid named posts and products nobody had written. On a paid plan,
35
+ * the scaffold now builds one more part for those two kinds, before the
36
+ * pages, each its own row with its own timer and credits:
37
+ *
38
+ * - WRITING YOUR FIRST POSTS: a content collection "Blog" — the platform's
39
+ * own blog, served at `/{slug}` with an entry page per post — and
40
+ * `AI_SITE_POSTS` posts in it, one generation a pass. Each is written as a
41
+ * draft through the same rules the console's routes hold
42
+ * (`content-entry-drafts.ts`), bylined with the business's name the person
43
+ * gave, and given a starter photo as its cover. They go live with the
44
+ * site: the guided start's publish publishes them with its pages
45
+ * (`aiPublishSitePosts`), as a person's Publish would.
46
+ * - ADDING YOUR FIRST PRODUCTS: the `products` step's catalog, asked for
47
+ * `AI_SITE_PRODUCTS` products, each written through the commerce plugin's
48
+ * `product` draft writer — a DRAFT with no price, off the storefront until
49
+ * the owner prices it, since no price is ever invented. No photo either:
50
+ * the writer takes none, and a stock picture of something else would show
51
+ * a shopper a product that is not the one for sale.
52
+ *
53
+ * The pages built after them are told their titles or names, so the writing
54
+ * page and the shop feature what exists rather than inventing their own.
55
+ *
56
+ * ── Not on the Free taste ────────────────────────────────────────────────
57
+ *
58
+ * A Free site's sections cap is DERIVED to spend the whole taste less one
59
+ * retried section (`aiFreeSiteSectionsWithin`), so there is no slack a post
60
+ * could use without taking a section away; and a Free plan includes neither
61
+ * the `commerce` feature nor a product (`productsPerHost` is 0).
62
+ * `ai-job-free-site.spec.ts` shows the arithmetic. Free sites keep the pages
63
+ * their kind already describes; the owner adds posts and products after.
64
+ */ /** How many first posts a paid blog gets. */ export const AI_SITE_POSTS = 3;
65
+ /** How many first products a paid store gets: asked for, and the most written. */ export const AI_SITE_PRODUCTS = {
66
+ min: 3,
67
+ max: 6
68
+ };
69
+ /** What each part's row reads. */ export const AI_SITE_POSTS_LABEL = 'Writing your first posts';
70
+ export const AI_SITE_PRODUCTS_LABEL = 'Adding your first products';
71
+ /** The blog the posts go in, and the addresses it may answer at, in order of preference. */ export const AI_SITE_BLOG_NAME = 'Blog';
72
+ export const AI_SITE_BLOG_SLUGS = [
73
+ 'blog',
74
+ 'posts',
75
+ 'journal',
76
+ 'articles',
77
+ 'writing',
78
+ 'stories'
79
+ ];
80
+ /** The input a site unit's own job carries what the part needs in. */ export const AI_SITE_CONTENT_INPUT = 'siteContent';
81
+ /** Which part a site of these answers gets; `null` for the Free taste and every other kind. */ export function aiSiteContentPart(inputs, freeTaste) {
82
+ var _aiSiteKindOfInputs;
83
+ if (freeTaste) return null;
84
+ const kind = (_aiSiteKindOfInputs = aiSiteKindOfInputs(inputs)) == null ? void 0 : _aiSiteKindOfInputs.id;
85
+ if (kind === 'blog') return 'posts';
86
+ if (kind === 'store') return 'products';
87
+ return null;
88
+ }
89
+ /**
90
+ * ASSUMED: what one post pass reads and writes beside its generation: the
91
+ * host, the collection's slug claim and the entry's transaction.
92
+ */ export const AI_SITE_POST_WRITES_MS = 1500;
93
+ /** One post's pass: its generation and re-ask at the `copy.blog` ceiling, and its writes. */ export const AI_SITE_POST_BUDGET = aiJobStepBudget({
94
+ tier: AI_STEP_TIERS[AI_BLOG_POST_STEP],
95
+ maxTokens: AI_BLOG_POST_MAX_TOKENS,
96
+ lookups: 0,
97
+ ownReadsMs: AI_SITE_POST_WRITES_MS
98
+ });
99
+ const str = (value)=>typeof value === 'string' ? value.trim() : '';
100
+ function postsInputOf(job) {
101
+ var _ref;
102
+ var _job_inputs;
103
+ const raw = (_ref = (_job_inputs = job.inputs) == null ? void 0 : _job_inputs[AI_SITE_CONTENT_INPUT]) != null ? _ref : {};
104
+ return {
105
+ written: Array.isArray(raw.written) ? raw.written.filter((one)=>one && typeof one.id === 'string') : [],
106
+ total: typeof raw.total === 'number' && raw.total > 0 ? Math.floor(raw.total) : AI_SITE_POSTS,
107
+ avoidSlugs: Array.isArray(raw.avoidSlugs) ? raw.avoidSlugs.map(str).filter(Boolean) : [],
108
+ byline: str(raw.byline)
109
+ };
110
+ }
111
+ /** The id of the `index`th post, of the blog its unit writes under `unitJobId`. */ export function aiSitePostId(unitJobId, index) {
112
+ return `${unitJobId}-${index}`;
113
+ }
114
+ /** The id of the `index`th product a products unit writes. */ export function aiSiteProductId(unitJobId, index) {
115
+ return `${unitJobId}-${index}`;
116
+ }
117
+ /** A site's subdomain and name, as its outputs and its posts' byline read them. */ async function readSite(firestore, hostId) {
118
+ var _host_get;
119
+ const host = await firestore.collection('hosts').doc(hostId).get();
120
+ if (!host.exists) return null;
121
+ const seo = (_host_get = host.get('seo')) != null ? _host_get : {};
122
+ const subdomain = str(host.get('subdomain')) || null;
123
+ return {
124
+ subdomain,
125
+ name: str(host.get('displayName')) || str(seo['title'])
126
+ };
127
+ }
128
+ const limitReview = (message)=>({
129
+ reason: 'limit',
130
+ message,
131
+ findings: []
132
+ });
133
+ /**
134
+ * The posts unit's runner, on the job the scaffold derived for it: one post a
135
+ * pass, continuing while posts remain. Its first pass makes the blog; every
136
+ * pass finds it again under the unit's own id, and a post under its own id,
137
+ * so a pass repeated after its write reports the post it already wrote.
138
+ */ export function createAiSitePostsRunner(deps = {}) {
139
+ var _deps_generate, _deps_writeCollection, _deps_writeEntry;
140
+ const generate = (_deps_generate = deps.generate) != null ? _deps_generate : generateAiBlogPost;
141
+ const writeCollection = (_deps_writeCollection = deps.writeCollection) != null ? _deps_writeCollection : writeContentCollection;
142
+ const writeEntry = (_deps_writeEntry = deps.writeEntry) != null ? _deps_writeEntry : writeContentEntryDraft;
143
+ return async (context)=>{
144
+ var _ref;
145
+ var _covers_index;
146
+ const { job, firestore, signal } = context;
147
+ const model = (_ref = context.modelFor == null ? void 0 : context.modelFor.call(context, AI_BLOG_POST_STEP)) != null ? _ref : aiModelForStep(AI_BLOG_POST_STEP);
148
+ if (!job.hostId) return _extends({}, aiUnspentOutcome(model), {
149
+ failure: 'Open the site before writing its posts.'
150
+ });
151
+ const input = postsInputOf(job);
152
+ const index = input.written.length;
153
+ if (index >= input.total) return aiUnspentOutcome(model);
154
+ const site = await readSite(firestore, job.hostId);
155
+ if (!site) return _extends({}, aiUnspentOutcome(model), {
156
+ failure: 'This site no longer exists.'
157
+ });
158
+ const avoid = new Set(input.avoidSlugs.map((slug)=>slug.toLowerCase()));
159
+ const blog = await writeCollection(firestore, {
160
+ hostId: job.hostId,
161
+ uid: job.createdBy,
162
+ id: job.$id,
163
+ displayName: AI_SITE_BLOG_NAME,
164
+ slugs: AI_SITE_BLOG_SLUGS.filter((slug)=>!avoid.has(slug)),
165
+ now: context.now
166
+ });
167
+ if (blog.ok === false) return aiUnspentOutcome(model, {
168
+ review: limitReview(blog.error)
169
+ });
170
+ const generation = await generate(_extends({
171
+ brief: job.brief,
172
+ merchantWords: job.brief,
173
+ earlierTitles: input.written.map((post)=>post.title),
174
+ index: index + 1,
175
+ total: input.total,
176
+ model,
177
+ maxTokens: AI_SITE_POST_BUDGET.maxTokens(model)
178
+ }, signal ? {
179
+ signal
180
+ } : {}));
181
+ const spent = aiGenerationSpent(generation);
182
+ if (generation.status === 'refused') return _extends({}, spent, {
183
+ refused: true
184
+ });
185
+ if (generation.status === 'needs_input') return _extends({}, spent, {
186
+ failure: generation.message
187
+ });
188
+ const post = generation.value;
189
+ // A starter photo as its cover, turned by the job so blogs differ.
190
+ const covers = aiLayoutStarterPhotos(Array.from({
191
+ length: input.total
192
+ }, (_, slot)=>({
193
+ imageId: `cover${slot}`,
194
+ frameId: null,
195
+ iconId: null,
196
+ alt: '',
197
+ aspect: 16 / 9,
198
+ sectionIndex: slot + 1,
199
+ role: 'gallery'
200
+ })), job.$id);
201
+ const byline = input.byline || site.name;
202
+ const written = await writeEntry(firestore, {
203
+ hostId: job.hostId,
204
+ collectionId: blog.id,
205
+ uid: job.createdBy,
206
+ id: aiSitePostId(job.$id, index),
207
+ content: _extends({
208
+ title: post.title,
209
+ excerpt: post.excerpt,
210
+ body: post.body,
211
+ seoDescription: post.seoDescription,
212
+ coverImage: (_covers_index = covers[index]) == null ? void 0 : _covers_index.src
213
+ }, byline ? {
214
+ authorName: byline
215
+ } : {}),
216
+ now: context.now
217
+ });
218
+ if (written.ok === false) return _extends({}, spent, {
219
+ review: limitReview(written.error)
220
+ });
221
+ const output = {
222
+ resource: 'entry',
223
+ id: written.id,
224
+ hostId: job.hostId,
225
+ hostSubdomain: site.subdomain,
226
+ label: written.title,
227
+ note: `A post in ${blog.displayName}, at /${blog.slug}/${written.slug}. It goes live with the site.`,
228
+ proposal: {
229
+ collectionId: blog.id,
230
+ collectionSlug: blog.slug,
231
+ slug: written.slug
232
+ }
233
+ };
234
+ return _extends({}, spent, {
235
+ outputs: [
236
+ output
237
+ ]
238
+ }, index + 1 < input.total ? {
239
+ continue: true
240
+ } : {});
241
+ };
242
+ }
243
+ export const runAiSitePostsUnit = createAiSitePostsRunner();
244
+ /** The sentence the products unit's brief ends with, which the catalog's rules read as how many. */ export function aiSiteProductsBriefLine(input = AI_SITE_PRODUCTS) {
245
+ return `Propose between ${input.min} and ${input.max} products: the store's first ones.`;
246
+ }
247
+ /**
248
+ * The products unit's runner: the `products` step's catalog for the store's
249
+ * brief, then each proposed product, up to the most a site starts with,
250
+ * written as an unpriced draft by the plugin that keeps products. A product
251
+ * past the plan's allowance stops the writing there, and the row says how
252
+ * many were added; none at all is the allowance's review.
253
+ */ export function createAiSiteProductsRunner(deps) {
254
+ var _deps_writerFor;
255
+ const writerFor = (_deps_writerFor = deps.writerFor) != null ? _deps_writerFor : pluginResourceDraftWriter;
256
+ return async (context)=>{
257
+ var _ref;
258
+ var _catalog_proposal;
259
+ const { job } = context;
260
+ const keeper = writerFor('product');
261
+ const model = (_ref = context.modelFor == null ? void 0 : context.modelFor.call(context, 'job.products')) != null ? _ref : aiModelForStep('job.products');
262
+ if (!keeper || !job.hostId) return _extends({}, aiUnspentOutcome(model), {
263
+ failure: 'Products are not available on this site.'
264
+ });
265
+ const outcome = await deps.catalog(_extends({}, context, {
266
+ job: _extends({}, job, {
267
+ kind: 'products',
268
+ inputs: _extends({}, job.inputs, {
269
+ target: 'catalog'
270
+ })
271
+ })
272
+ }));
273
+ if (outcome.refused || outcome.failure || outcome.review) return outcome;
274
+ const catalog = outcome.outputs.map((output)=>({
275
+ output,
276
+ proposal: aiProductsProposalOf(output)
277
+ })).find((entry)=>{
278
+ var _entry_proposal;
279
+ return ((_entry_proposal = entry.proposal) == null ? void 0 : _entry_proposal.kind) === 'catalog';
280
+ });
281
+ if (!catalog || ((_catalog_proposal = catalog.proposal) == null ? void 0 : _catalog_proposal.kind) !== 'catalog') return _extends({}, outcome, {
282
+ outputs: []
283
+ });
284
+ const proposed = catalog.proposal.products.slice(0, AI_SITE_PRODUCTS.max);
285
+ const outputs = [];
286
+ let stopped = null;
287
+ for (const [index, product] of proposed.entries()){
288
+ var _context_org, _catalog_output_hostSubdomain;
289
+ const written = await keeper.writer.write({
290
+ orgId: job.orgId,
291
+ hostId: job.hostId,
292
+ uid: job.createdBy,
293
+ org: (_context_org = context.org) != null ? _context_org : null,
294
+ now: context.now,
295
+ id: aiSiteProductId(job.$id, index),
296
+ name: product.name,
297
+ // No price: the owner sets it. No photo: the writer takes none.
298
+ content: {
299
+ name: product.name,
300
+ type: product.type,
301
+ description: product.description,
302
+ tags: product.tags,
303
+ options: product.options,
304
+ seoTitle: product.seoTitle,
305
+ seoDescription: product.seoDescription
306
+ }
307
+ });
308
+ if (written.ok === false) {
309
+ stopped = written.error;
310
+ break;
311
+ }
312
+ outputs.push({
313
+ resource: 'product',
314
+ id: written.id,
315
+ hostId: job.hostId,
316
+ hostSubdomain: (_catalog_output_hostSubdomain = catalog.output.hostSubdomain) != null ? _catalog_output_hostSubdomain : null,
317
+ label: written.name,
318
+ note: 'A draft with no price. Set its price and photo in Products to put it on sale.'
319
+ });
320
+ }
321
+ if (!outputs.length) {
322
+ return _extends({}, outcome, {
323
+ outputs: [],
324
+ review: limitReview(stopped != null ? stopped : 'No product could be added.')
325
+ });
326
+ }
327
+ // The allowance that stopped the rest is said where the last one added is.
328
+ if (stopped) {
329
+ const last = outputs[outputs.length - 1];
330
+ last.note = `${last.note} ${outputs.length} of ${proposed.length} added: ${stopped}.`;
331
+ }
332
+ return _extends({}, outcome, {
333
+ outputs
334
+ });
335
+ };
336
+ }
337
+ /**
338
+ * Whether a part may be built for this member on this site, asked before its
339
+ * first pass so a refusal spends nothing; `null` admits. Products ask what a
340
+ * `products` job asks (the plan's commerce feature, Commerce on and released
341
+ * for the site) and the writer's own refusal (role, allowance); posts ask
342
+ * that the member may write the site's content.
343
+ */ export async function aiSiteContentRefusal(part, context, deps = {}) {
344
+ var _deps_admissionFor, _context_org, _deps_writerFor, _context_org1, _ref;
345
+ const { job, firestore } = context;
346
+ if (!job.hostId) return 'Open the site first.';
347
+ if (part === 'posts') {
348
+ var _host_get;
349
+ const host = await firestore.collection('hosts').doc(job.hostId).get();
350
+ return hostRoleCanWrite(((_host_get = host.get('memberRoles')) != null ? _host_get : {})[job.createdBy]) ? null : 'Editing requires the editor role';
351
+ }
352
+ const admission = ((_deps_admissionFor = deps.admissionFor) != null ? _deps_admissionFor : aiJobAdmissionFor)('products');
353
+ const refused = admission ? await admission({
354
+ firestore,
355
+ orgId: job.orgId,
356
+ hostId: job.hostId,
357
+ inputs: {
358
+ target: 'catalog'
359
+ },
360
+ org: (_context_org = context.org) != null ? _context_org : null,
361
+ uid: job.createdBy
362
+ }) : {
363
+ status: 403,
364
+ error: 'Products are not available on this site.'
365
+ };
366
+ if (refused) return refused.error;
367
+ const keeper = ((_deps_writerFor = deps.writerFor) != null ? _deps_writerFor : pluginResourceDraftWriter)('product');
368
+ if (!keeper) return 'Products are not available on this site.';
369
+ const room = await keeper.writer.refusal({
370
+ orgId: job.orgId,
371
+ hostId: job.hostId,
372
+ uid: job.createdBy,
373
+ org: (_context_org1 = context.org) != null ? _context_org1 : null,
374
+ now: context.now
375
+ });
376
+ return (_ref = room == null ? void 0 : room.error) != null ? _ref : null;
377
+ }
378
+ /**
379
+ * Publishes the posts a guided start wrote, as the entry editor's Publish
380
+ * does, once its pages are live. Never throws: a post that stays a draft is
381
+ * the owner's to publish from Content.
382
+ */ export async function aiPublishSitePosts(firestore, input, publish = publishContentEntries) {
383
+ var _posts__proposal, _posts_, _posts__proposal1, _posts_1;
384
+ const posts = input.outputs.filter((output)=>output.resource === 'entry');
385
+ const collectionId = str((_posts_ = posts[0]) == null ? void 0 : (_posts__proposal = _posts_.proposal) == null ? void 0 : _posts__proposal['collectionId']);
386
+ const collectionSlug = str((_posts_1 = posts[0]) == null ? void 0 : (_posts__proposal1 = _posts_1.proposal) == null ? void 0 : _posts__proposal1['collectionSlug']);
387
+ if (!input.job.hostId || !posts.length || !collectionId) return null;
388
+ try {
389
+ const result = await publish(firestore, {
390
+ hostId: input.job.hostId,
391
+ collectionId,
392
+ uid: input.job.createdBy,
393
+ ids: posts.map((post)=>post.id),
394
+ now: input.now
395
+ });
396
+ if ('ok' in result) {
397
+ console.warn('ai site posts not published', {
398
+ jobId: input.job.$id,
399
+ error: result.error
400
+ });
401
+ return {
402
+ published: 0,
403
+ kept: posts.length,
404
+ paths: []
405
+ };
406
+ }
407
+ const slugs = posts.filter((post)=>result.published.includes(post.id)).map((post)=>{
408
+ var _post_proposal;
409
+ return str((_post_proposal = post.proposal) == null ? void 0 : _post_proposal['slug']);
410
+ }).filter(Boolean);
411
+ return {
412
+ published: result.published.length,
413
+ kept: result.kept.length,
414
+ paths: collectionSlug ? [
415
+ `/${collectionSlug}`,
416
+ ...slugs.map((slug)=>`/${collectionSlug}/${slug}`)
417
+ ] : []
418
+ };
419
+ } catch (error) {
420
+ console.error('ai site posts publish threw', {
421
+ orgId: input.job.orgId,
422
+ jobId: input.job.$id,
423
+ error
424
+ });
425
+ return null;
426
+ }
427
+ }
428
+ /** What a page of a blog or a store is told about the posts or products built before it. */ export function aiSiteContentBriefLines(outputs) {
429
+ const posts = outputs.filter((output)=>output.resource === 'entry');
430
+ const products = outputs.filter((output)=>output.resource === 'product' && !output.proposal);
431
+ const lines = [];
432
+ if (posts.length) {
433
+ var _posts__proposal;
434
+ const slug = str((_posts__proposal = posts[0].proposal) == null ? void 0 : _posts__proposal['collectionSlug']);
435
+ lines.push(`This site's blog${slug ? ` at /${slug}` : ''} has these posts: ${posts.map((post)=>`“${post.label}”`).join(', ')}.`, 'Where a page features writing, feature these posts by their titles; never name a post that is not in this list.');
436
+ }
437
+ if (products.length) {
438
+ lines.push(`This store's products are: ${products.map((product)=>`“${product.label}”`).join(', ')}.`, 'Where a page features products, feature these by their names; never name another product, and never state a price.');
439
+ }
440
+ return lines;
441
+ }
442
+
443
+ //# sourceMappingURL=ai-job-site-content.js.map