@aglyn/plugins-ai 1.0.0-beta.269 → 1.0.0-beta.271

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 (237) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.d.ts +1 -1
  3. package/src/lib/activity/ai-activity-actions.js +1 -0
  4. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  5. package/src/lib/assets/site-styles/beauty.webp +0 -0
  6. package/src/lib/assets/site-styles/blog.webp +0 -0
  7. package/src/lib/assets/site-styles/business.webp +0 -0
  8. package/src/lib/assets/site-styles/coming-soon.webp +0 -0
  9. package/src/lib/assets/site-styles/education.webp +0 -0
  10. package/src/lib/assets/site-styles/events.webp +0 -0
  11. package/src/lib/assets/site-styles/fitness.webp +0 -0
  12. package/src/lib/assets/site-styles/landing.webp +0 -0
  13. package/src/lib/assets/site-styles/music.webp +0 -0
  14. package/src/lib/assets/site-styles/nonprofit.webp +0 -0
  15. package/src/lib/assets/site-styles/personal.webp +0 -0
  16. package/src/lib/assets/site-styles/photography.webp +0 -0
  17. package/src/lib/assets/site-styles/portfolio.webp +0 -0
  18. package/src/lib/assets/site-styles/professional.webp +0 -0
  19. package/src/lib/assets/site-styles/realestate.webp +0 -0
  20. package/src/lib/assets/site-styles/restaurant.webp +0 -0
  21. package/src/lib/assets/site-styles/store.webp +0 -0
  22. package/src/lib/assets/site-styles/studio.webp +0 -0
  23. package/src/lib/assets/site-styles/trades.webp +0 -0
  24. package/src/lib/assets/site-styles/wellness.webp +0 -0
  25. package/src/lib/assets/site-styles/yoga.webp +0 -0
  26. package/src/lib/components/ai-credits-card.component.d.ts +8 -6
  27. package/src/lib/components/ai-credits-card.component.js +39 -22
  28. package/src/lib/components/ai-credits-card.component.js.map +1 -1
  29. package/src/lib/components/ai-job-input-form.component.d.ts +36 -0
  30. package/src/lib/components/ai-job-input-form.component.js +100 -0
  31. package/src/lib/components/ai-job-input-form.component.js.map +1 -0
  32. package/src/lib/components/ai-job-links.d.ts +6 -0
  33. package/src/lib/components/ai-job-links.js +9 -0
  34. package/src/lib/components/ai-job-links.js.map +1 -1
  35. package/src/lib/components/ai-job-requests.d.ts +4 -0
  36. package/src/lib/components/ai-job-requests.js +4 -0
  37. package/src/lib/components/ai-job-requests.js.map +1 -1
  38. package/src/lib/components/ai-products-hub-card.component.d.ts +6 -0
  39. package/src/lib/components/ai-products-hub-card.component.js +21 -6
  40. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  41. package/src/lib/components/ai-site-build-page.component.js +3 -2
  42. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  43. package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
  44. package/src/lib/components/ai-site-start-card.component.js +99 -31
  45. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  46. package/src/lib/components/ai-site-start-steps.component.d.ts +43 -2
  47. package/src/lib/components/ai-site-start-steps.component.js +651 -15
  48. package/src/lib/components/ai-site-start-steps.component.js.map +1 -1
  49. package/src/lib/components/ai-site-start-tracking.d.ts +10 -0
  50. package/src/lib/components/ai-site-start-tracking.js +14 -0
  51. package/src/lib/components/ai-site-start-tracking.js.map +1 -1
  52. package/src/lib/components/ai-site-style-previews.js +26 -22
  53. package/src/lib/components/ai-site-style-previews.js.map +1 -1
  54. package/src/lib/components/ai-store-finish-card.component.d.ts +14 -3
  55. package/src/lib/components/ai-store-finish-card.component.js +67 -9
  56. package/src/lib/components/ai-store-finish-card.component.js.map +1 -1
  57. package/src/lib/components/ai-upsell-dialog.component.d.ts +7 -5
  58. package/src/lib/components/ai-upsell-dialog.component.js +8 -6
  59. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  60. package/src/lib/components/assist-build-card.component.js +14 -3
  61. package/src/lib/components/assist-build-card.component.js.map +1 -1
  62. package/src/lib/components/assist-jobs-drawer.component.js +9 -1
  63. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  64. package/src/lib/components/assist-panel.component.js +8 -4
  65. package/src/lib/components/assist-panel.component.js.map +1 -1
  66. package/src/lib/components/staff-org-ai-card.component.js +4 -7
  67. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  68. package/src/lib/declarations.d.ts +2 -2
  69. package/src/lib/declarations.js +9 -5
  70. package/src/lib/declarations.js.map +1 -1
  71. package/src/lib/jobs/ai-build-capabilities.js +3 -1
  72. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  73. package/src/lib/jobs/ai-business-profile-prefill.d.ts +1 -1
  74. package/src/lib/jobs/ai-business-profile-prefill.js +14 -3
  75. package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -1
  76. package/src/lib/jobs/ai-job-build-step.d.ts +3 -0
  77. package/src/lib/jobs/ai-job-build-step.js +13 -5
  78. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  79. package/src/lib/jobs/ai-job-form-step.d.ts +24 -1
  80. package/src/lib/jobs/ai-job-form-step.js +93 -7
  81. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  82. package/src/lib/jobs/ai-job-page-language.d.ts +35 -11
  83. package/src/lib/jobs/ai-job-page-language.js +68 -2
  84. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  85. package/src/lib/jobs/ai-job-page-step.d.ts +17 -0
  86. package/src/lib/jobs/ai-job-page-step.js +40 -17
  87. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  88. package/src/lib/jobs/ai-job-plan-step.d.ts +25 -2
  89. package/src/lib/jobs/ai-job-plan-step.js +103 -29
  90. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  91. package/src/lib/jobs/ai-job-settings-step.js +4 -3
  92. package/src/lib/jobs/ai-job-settings-step.js.map +1 -1
  93. package/src/lib/jobs/ai-job-site-datasets.d.ts +27 -0
  94. package/src/lib/jobs/ai-job-site-datasets.js +140 -14
  95. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  96. package/src/lib/jobs/ai-job-site-look.js +18 -5
  97. package/src/lib/jobs/ai-job-site-look.js.map +1 -1
  98. package/src/lib/jobs/ai-job-site-step.d.ts +16 -1
  99. package/src/lib/jobs/ai-job-site-step.js +95 -22
  100. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  101. package/src/lib/jobs/ai-job-text-step.d.ts +8 -1
  102. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  103. package/src/lib/jobs/ai-job-tracking-step.d.ts +14 -3
  104. package/src/lib/jobs/ai-job-tracking-step.js +28 -5
  105. package/src/lib/jobs/ai-job-tracking-step.js.map +1 -1
  106. package/src/lib/jobs/ai-jobs.d.ts +8 -1
  107. package/src/lib/jobs/ai-jobs.js +63 -8
  108. package/src/lib/jobs/ai-jobs.js.map +1 -1
  109. package/src/lib/jobs/ai-layout-owner-photos.d.ts +57 -0
  110. package/src/lib/jobs/ai-layout-owner-photos.js +110 -0
  111. package/src/lib/jobs/ai-layout-owner-photos.js.map +1 -0
  112. package/src/lib/jobs/ai-layout-stock-photos.d.ts +62 -4
  113. package/src/lib/jobs/ai-layout-stock-photos.js +523 -72
  114. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  115. package/src/lib/jobs/ai-site-inspiration-signals.d.ts +98 -0
  116. package/src/lib/jobs/ai-site-inspiration-signals.js +585 -0
  117. package/src/lib/jobs/ai-site-inspiration-signals.js.map +1 -0
  118. package/src/lib/jobs/ai-site-inspiration.d.ts +157 -0
  119. package/src/lib/jobs/ai-site-inspiration.js +346 -0
  120. package/src/lib/jobs/ai-site-inspiration.js.map +1 -0
  121. package/src/lib/jobs/ai-site-publish.d.ts +26 -1
  122. package/src/lib/jobs/ai-site-publish.js +162 -13
  123. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  124. package/src/lib/jobs/ai-theme-brand-inputs.d.ts +16 -0
  125. package/src/lib/jobs/ai-theme-brand-inputs.js +18 -4
  126. package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
  127. package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.d.ts +97 -0
  128. package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.js +137 -0
  129. package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.js.map +1 -0
  130. package/src/lib/jobs/fixtures/ai-site-inspiration-pages.d.ts +38 -0
  131. package/src/lib/jobs/fixtures/ai-site-inspiration-pages.js +103 -0
  132. package/src/lib/jobs/fixtures/ai-site-inspiration-pages.js.map +1 -0
  133. package/src/lib/jobs/fixtures/ai-stock-photo-agl3942-recordings.d.ts +29 -0
  134. package/src/lib/jobs/fixtures/ai-stock-photo-agl3942-recordings.js +291 -0
  135. package/src/lib/jobs/fixtures/ai-stock-photo-agl3942-recordings.js.map +1 -0
  136. package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.d.ts +25 -0
  137. package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.js +174 -0
  138. package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.js.map +1 -0
  139. package/src/lib/layout-language/ai-layout-compiler.d.ts +1 -1
  140. package/src/lib/layout-language/ai-layout-compiler.js +438 -25
  141. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  142. package/src/lib/layout-language/ai-layout-contact.d.ts +57 -0
  143. package/src/lib/layout-language/ai-layout-contact.js +137 -0
  144. package/src/lib/layout-language/ai-layout-contact.js.map +1 -0
  145. package/src/lib/layout-language/ai-layout-copy.d.ts +12 -0
  146. package/src/lib/layout-language/ai-layout-copy.js +34 -2
  147. package/src/lib/layout-language/ai-layout-copy.js.map +1 -1
  148. package/src/lib/layout-language/ai-layout-design.d.ts +65 -8
  149. package/src/lib/layout-language/ai-layout-design.js +411 -97
  150. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  151. package/src/lib/layout-language/ai-layout-frame.js +23 -0
  152. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  153. package/src/lib/layout-language/ai-layout-labels.d.ts +2 -0
  154. package/src/lib/layout-language/ai-layout-labels.js +12 -6
  155. package/src/lib/layout-language/ai-layout-labels.js.map +1 -1
  156. package/src/lib/layout-language/ai-layout-language.d.ts +3 -2
  157. package/src/lib/layout-language/ai-layout-language.js +4 -3
  158. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  159. package/src/lib/layout-language/ai-layout-links.d.ts +13 -0
  160. package/src/lib/layout-language/ai-layout-links.js +30 -0
  161. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  162. package/src/lib/layout-language/ai-layout-pictures.d.ts +49 -6
  163. package/src/lib/layout-language/ai-layout-pictures.js +145 -22
  164. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  165. package/src/lib/model/ai-build-job.d.ts +16 -1
  166. package/src/lib/model/ai-build-job.js +40 -0
  167. package/src/lib/model/ai-build-job.js.map +1 -1
  168. package/src/lib/model/ai-job-input.d.ts +33 -0
  169. package/src/lib/model/ai-job-input.js +59 -0
  170. package/src/lib/model/ai-job-input.js.map +1 -0
  171. package/src/lib/model/ai-job-notice.d.ts +4 -0
  172. package/src/lib/model/ai-job-notice.js +11 -1
  173. package/src/lib/model/ai-job-notice.js.map +1 -1
  174. package/src/lib/model/ai-jobs.types.d.ts +40 -1
  175. package/src/lib/model/ai-jobs.types.js.map +1 -1
  176. package/src/lib/model/ai-site-details.d.ts +122 -0
  177. package/src/lib/model/ai-site-details.js +300 -0
  178. package/src/lib/model/ai-site-details.js.map +1 -0
  179. package/src/lib/model/ai-site-entity.d.ts +181 -0
  180. package/src/lib/model/ai-site-entity.js +390 -0
  181. package/src/lib/model/ai-site-entity.js.map +1 -0
  182. package/src/lib/model/ai-site-inspiration.d.ts +72 -0
  183. package/src/lib/model/ai-site-inspiration.js +109 -0
  184. package/src/lib/model/ai-site-inspiration.js.map +1 -0
  185. package/src/lib/model/ai-site-job.d.ts +13 -1
  186. package/src/lib/model/ai-site-job.js +15 -2
  187. package/src/lib/model/ai-site-job.js.map +1 -1
  188. package/src/lib/model/ai-site-kinds.js +109 -21
  189. package/src/lib/model/ai-site-kinds.js.map +1 -1
  190. package/src/lib/model/ai-site-look.d.ts +10 -0
  191. package/src/lib/model/ai-site-look.js +24 -0
  192. package/src/lib/model/ai-site-look.js.map +1 -1
  193. package/src/lib/model/ai-site-start-seo.d.ts +19 -5
  194. package/src/lib/model/ai-site-start-seo.js +25 -7
  195. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  196. package/src/lib/model/ai-site-start-steps.d.ts +9 -3
  197. package/src/lib/model/ai-site-start-steps.js +67 -5
  198. package/src/lib/model/ai-site-start-steps.js.map +1 -1
  199. package/src/lib/model/ai-site-start.d.ts +68 -4
  200. package/src/lib/model/ai-site-start.js +166 -13
  201. package/src/lib/model/ai-site-start.js.map +1 -1
  202. package/src/lib/model/ai-upsell-copy.js +4 -1
  203. package/src/lib/model/ai-upsell-copy.js.map +1 -1
  204. package/src/lib/native-nav.d.ts +26 -0
  205. package/src/lib/native-nav.js +58 -0
  206. package/src/lib/native-nav.js.map +1 -0
  207. package/src/lib/plan-entitlements.d.ts +4 -3
  208. package/src/lib/plan-entitlements.js +3 -8
  209. package/src/lib/plan-entitlements.js.map +1 -1
  210. package/src/lib/runtime/ai-dataset-generation.d.ts +8 -1
  211. package/src/lib/runtime/ai-dataset-generation.js +12 -2
  212. package/src/lib/runtime/ai-dataset-generation.js.map +1 -1
  213. package/src/lib/runtime/ai-doctrine-validators.d.ts +26 -33
  214. package/src/lib/runtime/ai-doctrine-validators.js +54 -44
  215. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  216. package/src/lib/runtime/ai-doctrine.js +1 -1
  217. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  218. package/src/lib/runtime/ai-node-tree.d.ts +7 -0
  219. package/src/lib/runtime/ai-node-tree.js +9 -1
  220. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  221. package/src/lib/server/ai-create-route.d.ts +1 -1
  222. package/src/lib/server/ai-create-route.js +3 -4
  223. package/src/lib/server/ai-create-route.js.map +1 -1
  224. package/src/lib/server/ai-jobs-resume.js +27 -3
  225. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  226. package/src/lib/server/ai-jobs-route.js +7 -0
  227. package/src/lib/server/ai-jobs-route.js.map +1 -1
  228. package/src/lib/usage/assist-credits.d.ts +4 -4
  229. package/src/lib/usage/assist-credits.js +4 -4
  230. package/src/lib/usage/assist-credits.js.map +1 -1
  231. package/src/lib/usage/staff-org-ai-view.js +6 -3
  232. package/src/lib/usage/staff-org-ai-view.js.map +1 -1
  233. package/src/lib/usage/staff-org-ai.d.ts +4 -2
  234. package/src/lib/usage/staff-org-ai.js +7 -7
  235. package/src/lib/usage/staff-org-ai.js.map +1 -1
  236. package/src/lib/usage/staff-org-usage-ai.js +2 -2
  237. package/src/lib/usage/staff-org-usage-ai.js.map +1 -1
@@ -15,7 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  import type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types';
18
- import type { AiJob, AiJobOutput, AiJobPlan, AiJobReview, AiJobItemFailure, AiJobItemLedger, AiJobItemStatus, AiJobSitePublish } from '../model/ai-jobs.types';
18
+ import type { AiJob, AiJobOutput, AiJobPlan, AiJobReview, AiJobInputRequest, AiJobItemFailure, AiJobItemLedger, AiJobItemStatus, AiJobSitePublish } from '../model/ai-jobs.types';
19
19
  import { type AiStepKind } from '../providers/catalog';
20
20
  import { type AiEffort, type AiSystemBlock } from '../runtime/ai-runtime';
21
21
  import type { AssistTokenUsage } from '../usage/assist-usage';
@@ -92,6 +92,13 @@ export interface AiJobStepOutcome {
92
92
  * step back, so trying again runs the same one.
93
93
  */
94
94
  review?: AiJobReview;
95
+ /**
96
+ * With a `plan` review: the plan needs an answer only the person can give
97
+ * first (AGL-3978) — a tracking change with no pixel ID. The machine keeps
98
+ * the plan and its review and parks the job `needs_input` with these
99
+ * fields instead of `needs_review`; the answer returns it to the review.
100
+ */
101
+ input?: AiJobInputRequest;
95
102
  /**
96
103
  * The answer was refused by the platform's own checks, not the model's
97
104
  * (AGL-3594): the machine meters the step as any other and then gives the
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-text-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport type {\n AiJob,\n AiJobOutput,\n AiJobPlan,\n AiJobReview,\n AiJobItemFailure,\n AiJobItemLedger,\n AiJobItemStatus,\n AiJobSitePublish,\n} from '../model/ai-jobs.types'\nimport { AI_STEP_TIERS, type AiStepKind } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport { aiDevEffortFor } from '../runtime/ai-dev-env'\nimport { runAiRequest, type AiEffort, type AiSystemBlock } from '../runtime/ai-runtime'\nimport type { AssistTokenUsage } from '../usage/assist-usage'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiOverlayCopyRequested } from '../model/ai-overlay-copy'\nimport { runAiOverlayCopy } from './ai-job-overlay-copy'\n\n/**\n * The `text` step (AGL-2904): a brief in, a short piece of copy out.\n *\n * The one step kind this issue ships for real, so the state machine — the\n * lease, the reservation, the record, the beat — is exercised end to end by\n * something that spends tokens, rather than by a stub that could pass with\n * the meter unplugged. Every other kind refuses until its own issue lands.\n *\n * The balanced tier with adaptive thinking: a brief is open-ended in a way the copy\n * assistant's element mode is not, and the cost is one call per job. The\n * static system block is cached; the brief rides in the user turn, so no\n * per-org byte sits inside the cached prefix (AGL-2352, enforced by the\n * runtime before the request leaves).\n */\n\n/** The model the text step runs on: the routing table's answer for `job.text`. */\nexport function aiJobTextModel(): string {\n return aiModelForStep('job.text')\n}\n\n/** The text step's answer ceiling, from the routing table. */\nexport const AI_JOB_TEXT_MAX_TOKENS = AI_ROUTING_TABLE['job.text'].maxTokens\n\n/**\n * The text step's time (AGL-3035): ONE request — no re-ask, and no site\n * inventory to look anything up in — at the routing table's ceiling on the\n * tier `job.text` is served from, with the step's reads and writes. That fits\n * an inline door's budget, so a door runs a text job where it is asked for,\n * and the least time is the helper's worst case rather than a figure anyone\n * chose; a slower tier asks less so its worst case still fits.\n */\nexport const AI_JOB_TEXT_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.text'],\n maxTokens: AI_JOB_TEXT_MAX_TOKENS,\n attempts: 1,\n lookups: 0,\n})\n\n/** The least time one text step needs before it starts. */\nexport const AI_JOB_TEXT_STEP_MINIMUM_MS = AI_JOB_TEXT_STEP_BUDGET.minimumMs\n\n/** How much of a brief is sent. Past this a brief is a document, not a brief. */\nexport const AI_JOB_BRIEF_MAX_CHARS = 4_000\n\n/**\n * What a step hands back to the machine. The machine meters `usage` at\n * `model`'s rates, records the step and appends the outputs; a step never\n * touches the job document or the meter itself.\n */\nexport interface AiJobStepOutcome {\n outputs: AiJobOutput[]\n /**\n * The step made progress and has more of the same work to do (AGL-2910):\n * a site audit works through its pages a batch at a time. The machine\n * records this pass — its spend and its outputs — and hands the SAME step\n * back to run again, so every pass is one reservation and one provider\n * exchange, and the job document says how far it got.\n */\n continue?: boolean\n usage: AssistTokenUsage\n estCostUsd: number\n model: string\n stopReason: string | null\n /**\n * The thinking effort the request asked for, recorded on the step\n * (AGL-2937); absent or `null` when the request named none.\n */\n effort?: AiEffort | null\n /** The model declined the brief (`stop_reason: 'refusal'`). Tokens were spent. */\n refused?: boolean\n /**\n * The model answered and nothing usable came of it — no structured answer\n * even after the step's own re-ask, say (AGL-2938). Tokens were spent, so\n * the machine meters the step before it fails the job with this sentence,\n * which is customer-safe and the only thing a customer reads.\n */\n failure?: string\n /** The plan the plan step proposed, which the machine keeps on the job (AGL-2935). */\n plan?: AiJobPlan\n /**\n * The step stopped for a person (AGL-2935). The machine records what it\n * spent and parks the job `needs_review`: a `plan` review completes the\n * step, so confirming runs the next one; a `doctrine` review hands the\n * step back, so trying again runs the same one.\n */\n review?: AiJobReview\n /**\n * The answer was refused by the platform's own checks, not the model's\n * (AGL-3594): the machine meters the step as any other and then gives the\n * job's credits back to the meters it was charged to (`refundJobCredits`),\n * a bounded number of times a day.\n */\n uncredited?: boolean\n /**\n * The `failure` is the workspace's own limit, not a fault of ours: the\n * month's Free AI credits are spent (a 429 from the kind's admission, asked\n * when the plan is kept). The machine fails the job and gives the step's\n * credits back exactly as for our failure, but announces it as NOT ours, so\n * no staff alert is raised for a customer reaching their allowance.\n */\n customerLimit?: boolean\n /**\n * What a guided site start put live on its last pass (AGL-3596), kept on\n * the job by the machine in the write that records the pass: a step never\n * writes the job document itself.\n */\n sitePublish?: AiJobSitePublish\n /**\n * A `build` pass's unit and what it came to (AGL-3616), which the machine\n * writes to the job's item ledger in the transaction that records the\n * pass's spend. An item's failure is never the step's: it is a row, and\n * the build goes on.\n */\n item?: AiJobItemOutcome\n /** A `build`'s ledger to start from, written by the machine in the same transaction (AGL-3616). */\n items?: AiJobItemLedger[]\n}\n\n/** What one `build` pass came to for the unit it ran (AGL-3616). */\nexport interface AiJobItemOutcome {\n slot: string\n /** `running` while the unit asks for another pass of its own. */\n status: AiJobItemStatus\n failure?: AiJobItemFailure | null\n /** Customer-safe: what the person should know about the item. */\n note?: string | null\n degradedBy?: string[]\n /** The ids of the outputs this pass produced for the item. */\n outputs?: string[]\n}\n\nexport interface AiJobStepContext {\n job: AiJob\n stepIndex: number\n now: Date\n /** Aborts the provider call when the runner's budget ends. */\n signal?: AbortSignal\n /**\n * The Admin SDK handle the machine runs on, for a step that reads what it\n * builds from — a site's theme, say — or writes a draft. A step never\n * writes the job document, the meter or the lease through it.\n */\n firestore: FirebaseFirestore.Firestore\n /** The org document the machine read for the step's reservation. */\n org?: Partial<AglynOrgBilling> | null\n /**\n * The model a step of this kind runs on for THIS job (AGL-2942): the\n * creator's pick where the plan and the allotment allowlists allow it,\n * the routing table otherwise. Absent, a runner asks the table itself.\n */\n modelFor?: (kind: AiStepKind) => string | undefined\n}\n\nexport type AiJobStepRunner = (\n context: AiJobStepContext,\n) => Promise<AiJobStepOutcome>\n\n/**\n * The static block, byte-identical on every request so it caches. The\n * customer's brief and the org's inputs never enter it.\n */\nexport const AI_JOB_TEXT_SYSTEM: AiSystemBlock[] = [\n {\n text:\n 'You write short, publishable copy for a website builder. You are ' +\n 'given a brief from the person who owns the site. Answer with the ' +\n 'copy only: no preamble, no headings unless the brief asks for them, ' +\n 'no closing remarks, no markdown fences. Keep to the length the brief ' +\n 'implies; when it implies none, write under 150 words. Write in the ' +\n 'language of the brief. Never invent facts, prices, names or claims ' +\n 'the brief did not give you; where a detail is missing, leave a ' +\n 'clearly marked placeholder in square brackets.',\n cacheBreakpoint: true,\n },\n]\n\n/** The brief as the user turn, with the kind-specific inputs stated plainly. */\nexport function aiJobTextPrompt(job: Pick<AiJob, 'brief' | 'inputs'>): string {\n const brief = job.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)\n const tone = typeof job.inputs?.['tone'] === 'string' ? job.inputs['tone'] : ''\n const audience =\n typeof job.inputs?.['audience'] === 'string' ? job.inputs['audience'] : ''\n const lines = [`Brief: ${brief}`]\n if (tone) lines.push(`Tone: ${tone}`)\n if (audience) lines.push(`Audience: ${audience}`)\n return lines.join('\\n')\n}\n\nexport const runAiJobTextStep: AiJobStepRunner = async ({ job, signal, modelFor }) => {\n const model = modelFor?.('job.text') ?? aiJobTextModel()\n // Overlay copy (AGL-3603): the same one request, answered as fields.\n if (aiOverlayCopyRequested(job.inputs)) {\n return runAiOverlayCopy({\n job,\n model,\n maxTokens: AI_JOB_TEXT_STEP_BUDGET.maxTokens(model),\n ...(signal ? { signal } : {}),\n })\n }\n // A development run's effort A/B arm (AGL-3781), else the row's.\n const effort = aiDevEffortFor('job.text') ?? AI_ROUTING_TABLE['job.text'].effort\n const result = await runAiRequest({\n model,\n system: AI_JOB_TEXT_SYSTEM,\n messages: [{ role: 'user', content: aiJobTextPrompt(job) }],\n // The routing ceiling, lowered on a tier too slow to answer it inside the\n // least time the step registered.\n maxTokens: AI_JOB_TEXT_STEP_BUDGET.maxTokens(model),\n ...(AI_ROUTING_TABLE['job.text'].thinking ? { thinking: AI_ROUTING_TABLE['job.text'].thinking } : {}),\n ...(effort ? { effort } : {}),\n stream: false,\n ...(signal ? { signal } : {}),\n })\n if (result.kind === 'refusal') {\n return {\n outputs: [],\n usage: result.usage,\n estCostUsd: result.estCostUsd,\n model,\n stopReason: result.stopReason,\n refused: true,\n }\n }\n const text = result.text.trim()\n return {\n outputs: text\n ? [\n {\n resource: 'text',\n // A text output has no document of its own; the id names the\n // step within the job so a later step can address it.\n id: 'draft',\n hostId: job.hostId ?? null,\n label: 'Draft copy',\n text,\n },\n ]\n : [],\n usage: result.usage,\n estCostUsd: result.estCostUsd,\n model,\n stopReason: result.stopReason,\n }\n}\n"],"names":["AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","aiDevEffortFor","runAiRequest","aiJobStepBudget","aiOverlayCopyRequested","runAiOverlayCopy","aiJobTextModel","AI_JOB_TEXT_MAX_TOKENS","maxTokens","AI_JOB_TEXT_STEP_BUDGET","tier","attempts","lookups","AI_JOB_TEXT_STEP_MINIMUM_MS","minimumMs","AI_JOB_BRIEF_MAX_CHARS","AI_JOB_TEXT_SYSTEM","text","cacheBreakpoint","aiJobTextPrompt","job","brief","slice","tone","inputs","audience","lines","push","join","runAiJobTextStep","signal","modelFor","model","effort","result","system","messages","role","content","thinking","stream","kind","outputs","usage","estCostUsd","stopReason","refused","trim","resource","id","hostId","label"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-text-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport type {\n AiJob,\n AiJobOutput,\n AiJobPlan,\n AiJobReview,\n AiJobInputRequest,\n AiJobItemFailure,\n AiJobItemLedger,\n AiJobItemStatus,\n AiJobSitePublish,\n} from '../model/ai-jobs.types'\nimport { AI_STEP_TIERS, type AiStepKind } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport { aiDevEffortFor } from '../runtime/ai-dev-env'\nimport { runAiRequest, type AiEffort, type AiSystemBlock } from '../runtime/ai-runtime'\nimport type { AssistTokenUsage } from '../usage/assist-usage'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiOverlayCopyRequested } from '../model/ai-overlay-copy'\nimport { runAiOverlayCopy } from './ai-job-overlay-copy'\n\n/**\n * The `text` step (AGL-2904): a brief in, a short piece of copy out.\n *\n * The one step kind this issue ships for real, so the state machine — the\n * lease, the reservation, the record, the beat — is exercised end to end by\n * something that spends tokens, rather than by a stub that could pass with\n * the meter unplugged. Every other kind refuses until its own issue lands.\n *\n * The balanced tier with adaptive thinking: a brief is open-ended in a way the copy\n * assistant's element mode is not, and the cost is one call per job. The\n * static system block is cached; the brief rides in the user turn, so no\n * per-org byte sits inside the cached prefix (AGL-2352, enforced by the\n * runtime before the request leaves).\n */\n\n/** The model the text step runs on: the routing table's answer for `job.text`. */\nexport function aiJobTextModel(): string {\n return aiModelForStep('job.text')\n}\n\n/** The text step's answer ceiling, from the routing table. */\nexport const AI_JOB_TEXT_MAX_TOKENS = AI_ROUTING_TABLE['job.text'].maxTokens\n\n/**\n * The text step's time (AGL-3035): ONE request — no re-ask, and no site\n * inventory to look anything up in — at the routing table's ceiling on the\n * tier `job.text` is served from, with the step's reads and writes. That fits\n * an inline door's budget, so a door runs a text job where it is asked for,\n * and the least time is the helper's worst case rather than a figure anyone\n * chose; a slower tier asks less so its worst case still fits.\n */\nexport const AI_JOB_TEXT_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.text'],\n maxTokens: AI_JOB_TEXT_MAX_TOKENS,\n attempts: 1,\n lookups: 0,\n})\n\n/** The least time one text step needs before it starts. */\nexport const AI_JOB_TEXT_STEP_MINIMUM_MS = AI_JOB_TEXT_STEP_BUDGET.minimumMs\n\n/** How much of a brief is sent. Past this a brief is a document, not a brief. */\nexport const AI_JOB_BRIEF_MAX_CHARS = 4_000\n\n/**\n * What a step hands back to the machine. The machine meters `usage` at\n * `model`'s rates, records the step and appends the outputs; a step never\n * touches the job document or the meter itself.\n */\nexport interface AiJobStepOutcome {\n outputs: AiJobOutput[]\n /**\n * The step made progress and has more of the same work to do (AGL-2910):\n * a site audit works through its pages a batch at a time. The machine\n * records this pass — its spend and its outputs — and hands the SAME step\n * back to run again, so every pass is one reservation and one provider\n * exchange, and the job document says how far it got.\n */\n continue?: boolean\n usage: AssistTokenUsage\n estCostUsd: number\n model: string\n stopReason: string | null\n /**\n * The thinking effort the request asked for, recorded on the step\n * (AGL-2937); absent or `null` when the request named none.\n */\n effort?: AiEffort | null\n /** The model declined the brief (`stop_reason: 'refusal'`). Tokens were spent. */\n refused?: boolean\n /**\n * The model answered and nothing usable came of it — no structured answer\n * even after the step's own re-ask, say (AGL-2938). Tokens were spent, so\n * the machine meters the step before it fails the job with this sentence,\n * which is customer-safe and the only thing a customer reads.\n */\n failure?: string\n /** The plan the plan step proposed, which the machine keeps on the job (AGL-2935). */\n plan?: AiJobPlan\n /**\n * The step stopped for a person (AGL-2935). The machine records what it\n * spent and parks the job `needs_review`: a `plan` review completes the\n * step, so confirming runs the next one; a `doctrine` review hands the\n * step back, so trying again runs the same one.\n */\n review?: AiJobReview\n /**\n * With a `plan` review: the plan needs an answer only the person can give\n * first (AGL-3978) — a tracking change with no pixel ID. The machine keeps\n * the plan and its review and parks the job `needs_input` with these\n * fields instead of `needs_review`; the answer returns it to the review.\n */\n input?: AiJobInputRequest\n /**\n * The answer was refused by the platform's own checks, not the model's\n * (AGL-3594): the machine meters the step as any other and then gives the\n * job's credits back to the meters it was charged to (`refundJobCredits`),\n * a bounded number of times a day.\n */\n uncredited?: boolean\n /**\n * The `failure` is the workspace's own limit, not a fault of ours: the\n * month's Free AI credits are spent (a 429 from the kind's admission, asked\n * when the plan is kept). The machine fails the job and gives the step's\n * credits back exactly as for our failure, but announces it as NOT ours, so\n * no staff alert is raised for a customer reaching their allowance.\n */\n customerLimit?: boolean\n /**\n * What a guided site start put live on its last pass (AGL-3596), kept on\n * the job by the machine in the write that records the pass: a step never\n * writes the job document itself.\n */\n sitePublish?: AiJobSitePublish\n /**\n * A `build` pass's unit and what it came to (AGL-3616), which the machine\n * writes to the job's item ledger in the transaction that records the\n * pass's spend. An item's failure is never the step's: it is a row, and\n * the build goes on.\n */\n item?: AiJobItemOutcome\n /** A `build`'s ledger to start from, written by the machine in the same transaction (AGL-3616). */\n items?: AiJobItemLedger[]\n}\n\n/** What one `build` pass came to for the unit it ran (AGL-3616). */\nexport interface AiJobItemOutcome {\n slot: string\n /** `running` while the unit asks for another pass of its own. */\n status: AiJobItemStatus\n failure?: AiJobItemFailure | null\n /** Customer-safe: what the person should know about the item. */\n note?: string | null\n degradedBy?: string[]\n /** The ids of the outputs this pass produced for the item. */\n outputs?: string[]\n}\n\nexport interface AiJobStepContext {\n job: AiJob\n stepIndex: number\n now: Date\n /** Aborts the provider call when the runner's budget ends. */\n signal?: AbortSignal\n /**\n * The Admin SDK handle the machine runs on, for a step that reads what it\n * builds from — a site's theme, say — or writes a draft. A step never\n * writes the job document, the meter or the lease through it.\n */\n firestore: FirebaseFirestore.Firestore\n /** The org document the machine read for the step's reservation. */\n org?: Partial<AglynOrgBilling> | null\n /**\n * The model a step of this kind runs on for THIS job (AGL-2942): the\n * creator's pick where the plan and the allotment allowlists allow it,\n * the routing table otherwise. Absent, a runner asks the table itself.\n */\n modelFor?: (kind: AiStepKind) => string | undefined\n}\n\nexport type AiJobStepRunner = (\n context: AiJobStepContext,\n) => Promise<AiJobStepOutcome>\n\n/**\n * The static block, byte-identical on every request so it caches. The\n * customer's brief and the org's inputs never enter it.\n */\nexport const AI_JOB_TEXT_SYSTEM: AiSystemBlock[] = [\n {\n text:\n 'You write short, publishable copy for a website builder. You are ' +\n 'given a brief from the person who owns the site. Answer with the ' +\n 'copy only: no preamble, no headings unless the brief asks for them, ' +\n 'no closing remarks, no markdown fences. Keep to the length the brief ' +\n 'implies; when it implies none, write under 150 words. Write in the ' +\n 'language of the brief. Never invent facts, prices, names or claims ' +\n 'the brief did not give you; where a detail is missing, leave a ' +\n 'clearly marked placeholder in square brackets.',\n cacheBreakpoint: true,\n },\n]\n\n/** The brief as the user turn, with the kind-specific inputs stated plainly. */\nexport function aiJobTextPrompt(job: Pick<AiJob, 'brief' | 'inputs'>): string {\n const brief = job.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)\n const tone = typeof job.inputs?.['tone'] === 'string' ? job.inputs['tone'] : ''\n const audience =\n typeof job.inputs?.['audience'] === 'string' ? job.inputs['audience'] : ''\n const lines = [`Brief: ${brief}`]\n if (tone) lines.push(`Tone: ${tone}`)\n if (audience) lines.push(`Audience: ${audience}`)\n return lines.join('\\n')\n}\n\nexport const runAiJobTextStep: AiJobStepRunner = async ({ job, signal, modelFor }) => {\n const model = modelFor?.('job.text') ?? aiJobTextModel()\n // Overlay copy (AGL-3603): the same one request, answered as fields.\n if (aiOverlayCopyRequested(job.inputs)) {\n return runAiOverlayCopy({\n job,\n model,\n maxTokens: AI_JOB_TEXT_STEP_BUDGET.maxTokens(model),\n ...(signal ? { signal } : {}),\n })\n }\n // A development run's effort A/B arm (AGL-3781), else the row's.\n const effort = aiDevEffortFor('job.text') ?? AI_ROUTING_TABLE['job.text'].effort\n const result = await runAiRequest({\n model,\n system: AI_JOB_TEXT_SYSTEM,\n messages: [{ role: 'user', content: aiJobTextPrompt(job) }],\n // The routing ceiling, lowered on a tier too slow to answer it inside the\n // least time the step registered.\n maxTokens: AI_JOB_TEXT_STEP_BUDGET.maxTokens(model),\n ...(AI_ROUTING_TABLE['job.text'].thinking ? { thinking: AI_ROUTING_TABLE['job.text'].thinking } : {}),\n ...(effort ? { effort } : {}),\n stream: false,\n ...(signal ? { signal } : {}),\n })\n if (result.kind === 'refusal') {\n return {\n outputs: [],\n usage: result.usage,\n estCostUsd: result.estCostUsd,\n model,\n stopReason: result.stopReason,\n refused: true,\n }\n }\n const text = result.text.trim()\n return {\n outputs: text\n ? [\n {\n resource: 'text',\n // A text output has no document of its own; the id names the\n // step within the job so a later step can address it.\n id: 'draft',\n hostId: job.hostId ?? null,\n label: 'Draft copy',\n text,\n },\n ]\n : [],\n usage: result.usage,\n estCostUsd: result.estCostUsd,\n model,\n stopReason: result.stopReason,\n }\n}\n"],"names":["AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","aiDevEffortFor","runAiRequest","aiJobStepBudget","aiOverlayCopyRequested","runAiOverlayCopy","aiJobTextModel","AI_JOB_TEXT_MAX_TOKENS","maxTokens","AI_JOB_TEXT_STEP_BUDGET","tier","attempts","lookups","AI_JOB_TEXT_STEP_MINIMUM_MS","minimumMs","AI_JOB_BRIEF_MAX_CHARS","AI_JOB_TEXT_SYSTEM","text","cacheBreakpoint","aiJobTextPrompt","job","brief","slice","tone","inputs","audience","lines","push","join","runAiJobTextStep","signal","modelFor","model","effort","result","system","messages","role","content","thinking","stream","kind","outputs","usage","estCostUsd","stopReason","refused","trim","resource","id","hostId","label"],"mappings":"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"}
@@ -15,7 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  import { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache';
18
- import type { PluginAiCapabilityArgsSchema } from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities';
18
+ import type { PluginAiCapabilityArgsSchema, PluginAiCapabilityInputField } from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities';
19
19
  import { getLockdownVerdict } from '@aglyn/tenant-data-admin/server/lockdown';
20
20
  import { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations';
21
21
  import { type Firestore } from 'firebase-admin/firestore';
@@ -78,6 +78,8 @@ export interface AiTrackingTag {
78
78
  pattern: RegExp;
79
79
  /** What the card's field says the id looks like. */
80
80
  shape: string;
81
+ /** The id's name, as a field asking for it is labeled (AGL-3978): it names the provider. */
82
+ field: string;
81
83
  /** Asked when a request names this tag and no usable id, from the card's helper text. */
82
84
  ask: string;
83
85
  /** Words that name this tag in an item's name. */
@@ -108,11 +110,19 @@ export declare function aiTrackingRequestOf(inputs: Readonly<Record<string, unkn
108
110
  * is never built: it asks for the id of the tag its arguments or its name
109
111
  * name, in the card's own words, and says why an id given was not one.
110
112
  */
111
- export declare function aiTrackingMissingInput(item: {
113
+ export declare function aiTrackingMissingInput(item: AiTrackingItem): string | null;
114
+ type AiTrackingItem = {
112
115
  name?: string;
113
116
  why?: string;
114
117
  args: Readonly<Record<string, unknown>>;
115
- }): string | null;
118
+ };
119
+ /**
120
+ * The fields a planned tracking change with no usable id waits for (AGL-3978):
121
+ * one per tag it asks about, labeled with the provider's id and held to the
122
+ * card's pattern, so the build pauses in `needs_input` for the person to give
123
+ * it. Empty when the item can be built, or names no tag at all.
124
+ */
125
+ export declare function aiTrackingInputFields(item: AiTrackingItem): PluginAiCapabilityInputField[];
116
126
  /** Whether a read request asks for anything at all. */
117
127
  export declare function aiTrackingRequestEmpty(request: AiTrackingRequest): boolean;
118
128
  /** One change to the host document, with the sentence that says it was made. */
@@ -182,3 +192,4 @@ export declare function createAiTrackingJobAdmission(deps?: AiTrackingJobAdmissi
182
192
  export declare const aiTrackingJobAdmission: AiJobAdmission;
183
193
  /** Registers the tracking step and its door (AGL-3751). */
184
194
  export declare function registerAiTrackingJob(): void;
195
+ export {};
@@ -78,6 +78,7 @@ export const AI_TRACKING_NOTHING_COPY = 'The request named no tracking id this c
78
78
  label: 'Google Analytics',
79
79
  pattern: GA_MEASUREMENT_ID_PATTERN,
80
80
  shape: 'G-XXXXXXXXXX',
81
+ field: 'Google Analytics measurement ID',
81
82
  ask: 'What’s your Google Analytics measurement ID? It looks like G-XXXXXXXXXX.',
82
83
  named: /google analytics|\bga4?\b|\banalytics\b/i
83
84
  },
@@ -87,6 +88,7 @@ export const AI_TRACKING_NOTHING_COPY = 'The request named no tracking id this c
87
88
  label: 'Google Tag Manager',
88
89
  pattern: GTM_CONTAINER_ID_PATTERN,
89
90
  shape: 'GTM-XXXXXXX',
91
+ field: 'Google Tag Manager container ID',
90
92
  ask: 'What’s your Google Tag Manager container ID? It looks like GTM-XXXXXXX.',
91
93
  named: /tag manager|\bgtm\b/i
92
94
  },
@@ -96,6 +98,7 @@ export const AI_TRACKING_NOTHING_COPY = 'The request named no tracking id this c
96
98
  label: 'the Meta pixel',
97
99
  pattern: META_PIXEL_ID_PATTERN,
98
100
  shape: '8–20 digits',
101
+ field: 'Meta pixel ID',
99
102
  ask: 'What’s your Meta pixel ID? It’s the number (8–20 digits) shown for your pixel in Meta Events Manager.',
100
103
  named: /\bmeta\b|facebook|instagram/i
101
104
  },
@@ -105,6 +108,7 @@ export const AI_TRACKING_NOTHING_COPY = 'The request named no tracking id this c
105
108
  label: 'the Google Ads tag',
106
109
  pattern: GOOGLE_ADS_ID_PATTERN,
107
110
  shape: 'AW-123456789',
111
+ field: 'Google Ads conversion ID',
108
112
  ask: 'What’s your Google Ads conversion ID? It looks like AW-123456789, from Google Ads.',
109
113
  named: /google ads|adwords/i
110
114
  },
@@ -114,6 +118,7 @@ export const AI_TRACKING_NOTHING_COPY = 'The request named no tracking id this c
114
118
  label: 'the LinkedIn Insight Tag',
115
119
  pattern: LINKEDIN_PARTNER_ID_PATTERN,
116
120
  shape: '4–10 digits',
121
+ field: 'LinkedIn partner ID',
117
122
  ask: 'What’s your LinkedIn partner ID? It’s the number (4–10 digits) in LinkedIn Campaign Manager.',
118
123
  named: /linkedin/i
119
124
  },
@@ -123,6 +128,7 @@ export const AI_TRACKING_NOTHING_COPY = 'The request named no tracking id this c
123
128
  label: 'the TikTok pixel',
124
129
  pattern: TIKTOK_PIXEL_ID_PATTERN,
125
130
  shape: '16–24 capital letters and digits',
131
+ field: 'TikTok pixel code',
126
132
  ask: 'What’s your TikTok pixel code? It’s in TikTok Ads Manager → Events, like C4ABCDEFGH1234567890.',
127
133
  named: /tiktok/i
128
134
  },
@@ -132,6 +138,7 @@ export const AI_TRACKING_NOTHING_COPY = 'The request named no tracking id this c
132
138
  label: 'the Pinterest tag',
133
139
  pattern: PINTEREST_TAG_ID_PATTERN,
134
140
  shape: '10–16 digits',
141
+ field: 'Pinterest tag ID',
135
142
  ask: 'What’s your Pinterest tag ID? It’s the number (10–16 digits) in Pinterest Ads → Conversions.',
136
143
  named: /pinterest/i
137
144
  }
@@ -231,11 +238,7 @@ function capitalized(label) {
231
238
  */ export function aiTrackingMissingInput(item) {
232
239
  const read = aiTrackingRequestOf(item.args);
233
240
  if (!aiTrackingRequestEmpty(read.request)) return null;
234
- // The tags its arguments tried to set, else the ones its name or reason names.
235
- const tried = AI_TRACKING_TAGS.filter((tag)=>text(item.args[tag.arg]));
236
- const naming = (words)=>AI_TRACKING_TAGS.filter((tag)=>tag.named.test(words != null ? words : ''));
237
- const byName = naming(item.name);
238
- const named = tried.length ? tried : byName.length ? byName : naming(item.why);
241
+ const named = aiTrackingTagsAsked(item);
239
242
  const asks = named.length ? named.map((tag)=>tag.ask) : [
240
243
  AI_TRACKING_WHICH_COPY
241
244
  ];
@@ -244,6 +247,26 @@ function capitalized(label) {
244
247
  ...asks
245
248
  ].join(' ');
246
249
  }
250
+ /** The tags an item asks about: those its arguments tried to set, else the ones its name or reason names. */ function aiTrackingTagsAsked(item) {
251
+ const tried = AI_TRACKING_TAGS.filter((tag)=>text(item.args[tag.arg]));
252
+ const naming = (words)=>AI_TRACKING_TAGS.filter((tag)=>tag.named.test(words != null ? words : ''));
253
+ const byName = naming(item.name);
254
+ return tried.length ? tried : byName.length ? byName : naming(item.why);
255
+ }
256
+ /**
257
+ * The fields a planned tracking change with no usable id waits for (AGL-3978):
258
+ * one per tag it asks about, labeled with the provider's id and held to the
259
+ * card's pattern, so the build pauses in `needs_input` for the person to give
260
+ * it. Empty when the item can be built, or names no tag at all.
261
+ */ export function aiTrackingInputFields(item) {
262
+ if (!aiTrackingMissingInput(item)) return [];
263
+ return aiTrackingTagsAsked(item).map((tag)=>({
264
+ arg: tag.arg,
265
+ label: tag.field,
266
+ shape: tag.shape,
267
+ pattern: tag.pattern
268
+ }));
269
+ }
247
270
  /** Whether a read request asks for anything at all. */ export function aiTrackingRequestEmpty(request) {
248
271
  return Object.keys(request.set).length === 0 && request.remove.length === 0;
249
272
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-tracking-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { hostRoleCanWrite } from '@aglyn/aglyn/app-utils/organizations'\nimport { SCREEN_ROOT_PATH } from '@aglyn/aglyn/app-utils/screen-route'\nimport {\n GA_MEASUREMENT_ID_PATTERN,\n GOOGLE_ADS_ID_PATTERN,\n GTM_CONTAINER_ID_PATTERN,\n LINKEDIN_PARTNER_ID_PATTERN,\n META_PIXEL_ID_PATTERN,\n PINTEREST_TAG_ID_PATTERN,\n TIKTOK_PIXEL_ID_PATTERN,\n} from '@aglyn/aglyn/app-utils/visitor-consent'\nimport { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache'\nimport type { PluginAiCapabilityArgsSchema } from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities'\nimport { getLockdownVerdict } from '@aglyn/tenant-data-admin/server/lockdown'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport { FieldValue, type Firestore } from 'firebase-admin/firestore'\nimport type { AiJobOutput } from '../model/ai-jobs.types'\nimport { registerAiJobAdmission, type AiJobAdmission, type AiJobAdmissionRefusal } from './ai-job-admission'\nimport { aiJobEditModel } from './ai-job-edit-step'\nimport { aiLimitReview, aiUnspentOutcome } from './ai-job-generation'\nimport type { AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The `tracking` step (AGL-3751): the site's analytics and advertising tags,\n * as the console's Setup → Tracking card sets them — Google Analytics, Google\n * Tag Manager, and the Meta, Google Ads, LinkedIn, TikTok and Pinterest tags —\n * as a first-class build operation, so \"connect Google Analytics G-4R7KX2PQ9Z\"\n * is one item on Assist's plan card.\n *\n * NO MODEL. The planner already wrote the ids as the item's arguments; this\n * holds each to the pattern the card's field (and the tenant's loader) holds\n * it to and writes it, so it costs no credits.\n *\n * ── Only the card's own fields, and only public ids ──────────────────────\n *\n * Every field written is one the Tracking card writes, under the same name:\n * `analytics.gaMeasurementId`, `analytics.gtmContainerId` and\n * `analytics.adTags.{meta,google-ads,linkedin,tiktok,pinterest}`, each tested\n * by the same expression (`GA_MEASUREMENT_ID_PATTERN` and its siblings in\n * `visitor-consent.ts`). Each is an id that is printed in every page's HTML\n * anyway, never a credential. Removing one deletes the field, as the card's\n * cleared box does (`CLEARABLE_TRACKING_PATHS`).\n *\n * NEVER: the consent banner (`consent.*` — turning it off, its mode, or the\n * advertising question), search indexing, verification tokens, and anything\n * that needs a secret the person pastes — the Ad conversions card's access\n * tokens, Zapier, order webhooks' signing secrets. Those are not arguments.\n * The tags load only where the consent banner lets them, exactly as a tag the\n * card saved does: the tenant gates on the stored ids, not on who wrote them.\n *\n * ── Live on confirm, who may, idempotent ─────────────────────────────────\n *\n * As the `settings` step (AGL-3748): a tag has no draft, so the plan card's\n * confirm is the agreement and the write is live. One transaction re-reads\n * the host, re-checks the role, writes only what differs with a site-wide\n * publish outbox entry, and the site's cached pages are dropped after. The\n * gate is the Firestore rules' for the host document (`canWriteHostContent`,\n * `hostWritesFrozen`): an admin, editor or author, while no lockdown freezes\n * the site, its org or the platform.\n */\n\nexport const AI_TRACKING_KIND = 'tracking' as const\n\n/** ASSUMED: the least time the step needs — the site, the org and one transaction. */\nexport const AI_JOB_TRACKING_STEP_MINIMUM_MS = 5_000\n\nexport const AI_TRACKING_NO_SITE_COPY = 'Open the site whose tracking should change.'\nexport const AI_TRACKING_ROLE_COPY = 'Changing the site’s tracking needs the admin, editor or author role on this site.'\nexport const AI_TRACKING_LOCKED_COPY = 'This site is not accepting changes right now.'\nexport const AI_TRACKING_NOTHING_COPY = 'The request named no tracking id this could set or remove.'\n\n/** Where a person finds what the op changed: the card that edits it. */\nexport const AI_TRACKING_WHERE = 'Setup → Tracking'\n\n/** The output's fixed id: one site's tracking, whatever changed. */\nexport const AI_TRACKING_OUTPUT_ID = 'tracking'\n\n/** One tag the card sets: its argument, its stored field, its name, and its pattern. */\nexport interface AiTrackingTag {\n arg: string\n path: string\n label: string\n pattern: RegExp\n /** What the card's field says the id looks like. */\n shape: string\n /** Asked when a request names this tag and no usable id, from the card's helper text. */\n ask: string\n /** Words that name this tag in an item's name. */\n named: RegExp\n}\n\n/** The Tracking card's fields, in its order (`trackingSchema`, host-settings-scope.tsx). */\nexport const AI_TRACKING_TAGS: readonly AiTrackingTag[] = [\n {\n arg: 'googleAnalytics',\n path: 'analytics.gaMeasurementId',\n label: 'Google Analytics',\n pattern: GA_MEASUREMENT_ID_PATTERN,\n shape: 'G-XXXXXXXXXX',\n ask: 'What’s your Google Analytics measurement ID? It looks like G-XXXXXXXXXX.',\n named: /google analytics|\\bga4?\\b|\\banalytics\\b/i,\n },\n {\n arg: 'googleTagManager',\n path: 'analytics.gtmContainerId',\n label: 'Google Tag Manager',\n pattern: GTM_CONTAINER_ID_PATTERN,\n shape: 'GTM-XXXXXXX',\n ask: 'What’s your Google Tag Manager container ID? It looks like GTM-XXXXXXX.',\n named: /tag manager|\\bgtm\\b/i,\n },\n {\n arg: 'metaPixel',\n path: 'analytics.adTags.meta',\n label: 'the Meta pixel',\n pattern: META_PIXEL_ID_PATTERN,\n shape: '8–20 digits',\n ask: 'What’s your Meta pixel ID? It’s the number (8–20 digits) shown for your pixel in Meta Events Manager.',\n named: /\\bmeta\\b|facebook|instagram/i,\n },\n {\n arg: 'googleAds',\n path: 'analytics.adTags.google-ads',\n label: 'the Google Ads tag',\n pattern: GOOGLE_ADS_ID_PATTERN,\n shape: 'AW-123456789',\n ask: 'What’s your Google Ads conversion ID? It looks like AW-123456789, from Google Ads.',\n named: /google ads|adwords/i,\n },\n {\n arg: 'linkedinInsight',\n path: 'analytics.adTags.linkedin',\n label: 'the LinkedIn Insight Tag',\n pattern: LINKEDIN_PARTNER_ID_PATTERN,\n shape: '4–10 digits',\n ask: 'What’s your LinkedIn partner ID? It’s the number (4–10 digits) in LinkedIn Campaign Manager.',\n named: /linkedin/i,\n },\n {\n arg: 'tiktokPixel',\n path: 'analytics.adTags.tiktok',\n label: 'the TikTok pixel',\n pattern: TIKTOK_PIXEL_ID_PATTERN,\n shape: '16–24 capital letters and digits',\n ask: 'What’s your TikTok pixel code? It’s in TikTok Ads Manager → Events, like C4ABCDEFGH1234567890.',\n named: /tiktok/i,\n },\n {\n arg: 'pinterestTag',\n path: 'analytics.adTags.pinterest',\n label: 'the Pinterest tag',\n pattern: PINTEREST_TAG_ID_PATTERN,\n shape: '10–16 digits',\n ask: 'What’s your Pinterest tag ID? It’s the number (10–16 digits) in Pinterest Ads → Conversions.',\n named: /pinterest/i,\n },\n]\n\n/** Asked when an item names no tag at all. */\nexport const AI_TRACKING_WHICH_COPY =\n 'Which tag should be connected, and what is its ID? Google Analytics, Google Tag Manager, the Meta pixel, Google Ads, LinkedIn, TikTok or Pinterest.'\n\nconst TAG_ARGS = AI_TRACKING_TAGS.map((tag) => tag.arg)\n\nconst ID_RULE = 'Exactly as the person gave it; leave it out unless the request states the id — never make one up.'\n\n/** The item's arguments, as the planner fills them in. Every one optional. */\nexport const AI_TRACKING_ARGS_SCHEMA: PluginAiCapabilityArgsSchema = {\n type: 'object',\n properties: {\n googleAnalytics: { type: 'string', description: `The Google Analytics 4 measurement ID, like G-XXXXXXXXXX. ${ID_RULE}`, maxLength: 24 },\n googleTagManager: { type: 'string', description: `The Google Tag Manager container ID, like GTM-XXXXXXX. ${ID_RULE}`, maxLength: 24 },\n metaPixel: { type: 'string', description: `The Meta (Facebook) pixel ID, 8–20 digits. ${ID_RULE}`, maxLength: 24 },\n googleAds: { type: 'string', description: `The Google Ads conversion ID, like AW-123456789. ${ID_RULE}`, maxLength: 24 },\n linkedinInsight: { type: 'string', description: `The LinkedIn Insight Tag partner ID, 4–10 digits. ${ID_RULE}`, maxLength: 24 },\n tiktokPixel: { type: 'string', description: `The TikTok pixel code, like C4ABCDEFGH1234567890. ${ID_RULE}`, maxLength: 32 },\n pinterestTag: { type: 'string', description: `The Pinterest tag ID, 10–16 digits. ${ID_RULE}`, maxLength: 24 },\n remove: {\n type: 'array',\n description: 'The tags the person asked to take off the site.',\n items: { type: 'string', enum: TAG_ARGS },\n maxItems: AI_TRACKING_TAGS.length,\n },\n },\n additionalProperties: false,\n}\n\n/** What an item asks: ids to set by field, and fields to clear. */\nexport interface AiTrackingRequest {\n set: Record<string, string>\n remove: string[]\n}\n\nexport interface AiTrackingRead {\n request: AiTrackingRequest\n /** A sentence per value that could not be used, so the rest still are. */\n problems: string[]\n}\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** The item's arguments read: what may be written, and a sentence for each that may not. */\nexport function aiTrackingRequestOf(inputs: Readonly<Record<string, unknown>> | null | undefined): AiTrackingRead {\n const set: Record<string, string> = {}\n const problems: string[] = []\n const removing = new Set(Array.isArray(inputs?.['remove']) ? (inputs?.['remove'] as unknown[]).map((one) => String(one)) : [])\n const remove: string[] = []\n for (const tag of AI_TRACKING_TAGS) {\n const value = text(inputs?.[tag.arg])\n if (value && removing.has(tag.arg)) {\n problems.push(`${capitalized(tag.label)} was both set and removed, so it was left as it is.`)\n continue\n }\n if (removing.has(tag.arg)) {\n remove.push(tag.path)\n continue\n }\n if (!value) continue\n // The card's own test, and the loader's: an id of any other shape is\n // saved by nobody, since it would never load.\n if (tag.pattern.test(value)) set[tag.path] = value\n else problems.push(`“${value.slice(0, 40)}” is not an ID for ${tag.label}, which looks like ${tag.shape}.`)\n }\n return { request: { set, remove }, problems }\n}\n\nfunction capitalized(label: string): string {\n return label.charAt(0).toUpperCase() + label.slice(1)\n}\n\n/**\n * The question a planned tracking change asks the person, or `null` when it\n * sets or removes something (AGL-3751). An item with no usable id — the\n * planner kept \"add my Meta pixel\" with no id, or an id of the wrong shape —\n * is never built: it asks for the id of the tag its arguments or its name\n * name, in the card's own words, and says why an id given was not one.\n */\nexport function aiTrackingMissingInput(item: { name?: string; why?: string; args: Readonly<Record<string, unknown>> }): string | null {\n const read = aiTrackingRequestOf(item.args)\n if (!aiTrackingRequestEmpty(read.request)) return null\n // The tags its arguments tried to set, else the ones its name or reason names.\n const tried = AI_TRACKING_TAGS.filter((tag) => text(item.args[tag.arg]))\n const naming = (words: string | undefined) => AI_TRACKING_TAGS.filter((tag) => tag.named.test(words ?? ''))\n const byName = naming(item.name)\n const named = tried.length ? tried : byName.length ? byName : naming(item.why)\n const asks = named.length ? named.map((tag) => tag.ask) : [AI_TRACKING_WHICH_COPY]\n return [...read.problems, ...asks].join(' ')\n}\n\n/** Whether a read request asks for anything at all. */\nexport function aiTrackingRequestEmpty(request: AiTrackingRequest): boolean {\n return Object.keys(request.set).length === 0 && request.remove.length === 0\n}\n\n// ── The write ─────────────────────────────────────────────────────────────\n\nconst getPath = (data: Record<string, unknown>, path: string): unknown =>\n path.split('.').reduce<unknown>((at, key) => (at && typeof at === 'object' ? (at as Record<string, unknown>)[key] : undefined), data)\n\n/** One change to the host document, with the sentence that says it was made. */\nexport interface AiTrackingChange {\n path: string\n /** The id to store, or `null` to delete the field. */\n value: string | null\n said: string\n}\n\n/** Every change the request asks, in the card's order, before comparing with what is stored. */\nexport function aiTrackingChanges(request: AiTrackingRequest): AiTrackingChange[] {\n const changes: AiTrackingChange[] = []\n for (const tag of AI_TRACKING_TAGS) {\n const value = request.set[tag.path]\n if (value) changes.push({ path: tag.path, value, said: `${capitalized(tag.label)} is connected (${value}).` })\n else if (request.remove.includes(tag.path)) changes.push({ path: tag.path, value: null, said: `${capitalized(tag.label)} is removed.` })\n }\n return changes\n}\n\n/**\n * What the person should know about the tags the site now carries, in the\n * card's own words: where the consent banner is off, nothing of ours gates\n * them; and a container beside a direct Google tag counts everything twice.\n */\nexport function aiTrackingWarnings(host: Record<string, unknown>): string[] {\n const warnings: string[] = []\n const has = (path: string) => {\n const value = getPath(host, path)\n return typeof value === 'string' && value !== ''\n }\n const any = AI_TRACKING_TAGS.some((tag) => has(tag.path))\n if (any && getPath(host, 'consent.disabled') === true) {\n warnings.push('This site’s cookie banner is turned off because it uses its own consent tool, so that tool decides when these load.')\n }\n if (has('analytics.gtmContainerId') && (has('analytics.gaMeasurementId') || has('analytics.adTags.google-ads'))) {\n warnings.push('Do not also put an Analytics or Google Ads tag in the Tag Manager container: that loads the same measurement twice and counts every visit and conversion twice.')\n }\n return warnings\n}\n\nexport type AiTrackingWrite =\n | { ok: true; done: string[]; changed: string[]; warnings: string[]; outboxId: string | null }\n | { ok: false; error: string }\n\n/**\n * The tags written, in one transaction that reads the host as it is stored\n * NOW: the member still holds a write role, and only the values that differ\n * from what is stored are written, with a site-wide publish outbox entry\n * beside them so the live site's cached pages are dropped.\n */\nexport async function writeAiSiteTracking(\n firestore: Firestore,\n input: { hostId: string; uid: string; request: AiTrackingRequest; now: Date },\n): Promise<AiTrackingWrite> {\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n return firestore.runTransaction(async (tx): Promise<AiTrackingWrite> => {\n const host = await tx.get(hostRef)\n if (!host.exists) return { ok: false, error: 'Unknown site' }\n const roles = (host.get('memberRoles') ?? {}) as Record<string, unknown>\n if (!hostRoleCanWrite(roles[input.uid])) return { ok: false, error: AI_TRACKING_ROLE_COPY }\n const data = (host.data() ?? {}) as Record<string, unknown>\n const changes = aiTrackingChanges(input.request)\n const changed = changes.filter((change) => {\n const stored = getPath(data, change.path)\n return change.value === null ? stored != null && stored !== '' : stored !== change.value\n })\n // What the site carries once this lands, for the warnings.\n // Only the two maps the warnings read; both are plain strings and flags.\n const after: Record<string, unknown> = JSON.parse(JSON.stringify({ analytics: data['analytics'] ?? {}, consent: data['consent'] ?? {} }))\n for (const change of changed) {\n const keys = change.path.split('.')\n const leaf = keys.pop() as string\n let node = after as Record<string, unknown>\n for (const key of keys) {\n if (!node[key] || typeof node[key] !== 'object') node[key] = {}\n node = node[key] as Record<string, unknown>\n }\n if (change.value === null) delete node[leaf]\n else node[leaf] = change.value\n }\n const done = changes.map((change) => change.said)\n const warnings = aiTrackingWarnings(after)\n if (!changed.length) return { ok: true, done, changed: [], warnings, outboxId: null }\n const updates: Record<string, unknown> = { updatedAt: input.now }\n for (const change of changed) updates[change.path] = change.value === null ? FieldValue.delete() : change.value\n tx.update(hostRef, updates)\n // The tags are in every page's head, so every cached page is stale.\n const outboxRef = firestore.collection('publishOutbox').doc(createResourceUid())\n tx.set(outboxRef, {\n hostId: input.hostId,\n paths: [SCREEN_ROOT_PATH],\n entireHost: true,\n createdAt: FieldValue.serverTimestamp(),\n attempts: 0,\n })\n return { ok: true, done, changed: changed.map((change) => change.path), warnings, outboxId: outboxRef.id }\n })\n}\n\n// ── The step ──────────────────────────────────────────────────────────────\n\n/** Why this member may not change this site's tracking now, or `null`. */\nexport async function aiTrackingAccessRefusal(\n input: { host: FirebaseFirestore.DocumentSnapshot; uid: string; org: object | null },\n deps: { lockdown?: typeof getLockdownVerdict } = {},\n): Promise<string | null> {\n if (!input.host.exists) return 'Unknown site'\n const roles = (input.host.get('memberRoles') ?? {}) as Record<string, unknown>\n if (!hostRoleCanWrite(roles[input.uid])) return AI_TRACKING_ROLE_COPY\n const locked = await (deps.lockdown ?? getLockdownVerdict)({\n staff: false,\n uid: input.uid,\n org: input.org as never,\n host: input.host.data() as never,\n intent: 'write',\n })\n return locked ? AI_TRACKING_LOCKED_COPY : null\n}\n\n/** The output's note: what changed, that it is live, how consent gates it, and what was not done. */\nexport function aiTrackingOutputNote(done: readonly string[], warnings: readonly string[], notDone: readonly string[], changedAny: boolean): string {\n const did = done.length\n ? `${done.join(' ')} Live now, in ${AI_TRACKING_WHERE}.${changedAny ? '' : ' It already was, so nothing was written.'}`\n : 'Nothing needed changing.'\n // The card's own promise, true of a tag whoever saved it.\n const consent = done.length ? ' Tags load only where a visitor’s cookie consent allows them.' : ''\n const warn = warnings.length ? ` ${warnings.join(' ')}` : ''\n const not = notDone.length ? ` Not done: ${notDone.join(' ')}` : ''\n return `${did}${consent}${warn}${not}`.slice(0, 1_000)\n}\n\nexport interface AiJobTrackingStepDeps {\n write?: typeof writeAiSiteTracking\n lockdown?: typeof getLockdownVerdict\n dropCache?: typeof dropPluginSiteCache\n}\n\nexport function createAiJobTrackingStep(deps: AiJobTrackingStepDeps = {}): AiJobStepRunner {\n const write = deps.write ?? writeAiSiteTracking\n const dropCache = deps.dropCache ?? dropPluginSiteCache\n return async ({ job, now, firestore, org, modelFor }): Promise<AiJobStepOutcome> => {\n // No model is asked: the outcome records the edit step's at no cost.\n const model = modelFor?.('job.edit') ?? aiJobEditModel()\n const unspent = (failure: string): AiJobStepOutcome => ({ ...aiUnspentOutcome(model), failure })\n const hostId = job.hostId\n if (!hostId) return unspent(AI_TRACKING_NO_SITE_COPY)\n const read = aiTrackingRequestOf(job.inputs)\n if (aiTrackingRequestEmpty(read.request)) return unspent([AI_TRACKING_NOTHING_COPY, ...read.problems].join(' '))\n const orgData = (org ?? ((await firestore.collection('orgs').doc(job.orgId).get()).data() ?? null)) as Record<string, unknown> | null\n const host = await firestore.collection('hosts').doc(hostId).get()\n const refused = await aiTrackingAccessRefusal({ host, uid: job.createdBy, org: orgData }, { lockdown: deps.lockdown })\n if (refused) return aiUnspentOutcome(model, { review: aiLimitReview(refused) })\n const written = await write(firestore, { hostId, uid: job.createdBy, request: read.request, now })\n if (written.ok === false) return unspent(written.error)\n if (written.outboxId) {\n // The live site's cached pages go now; the outbox entry stays for the\n // drain unless the drop certainly happened.\n const dropped = await dropCache({ hostIds: [hostId], reason: 'AI build changed site tracking' }).catch(() => ({ complete: false }))\n if (dropped.complete) await firestore.collection('publishOutbox').doc(written.outboxId).delete().catch(() => undefined)\n }\n const output: AiJobOutput = {\n resource: 'tracking',\n id: AI_TRACKING_OUTPUT_ID,\n hostId,\n hostSubdomain: (typeof host.get('subdomain') === 'string' && host.get('subdomain')) || null,\n label: 'Site tracking',\n note: aiTrackingOutputNote(written.done, written.warnings, read.problems, written.changed.length > 0),\n }\n return aiUnspentOutcome(model, { outputs: [output] })\n }\n}\n\nexport const runAiJobTrackingStep = createAiJobTrackingStep()\n\n// ── Admission ─────────────────────────────────────────────────────────────\n\nexport interface AiTrackingJobAdmissionDeps {\n ownerOf?: typeof resolveOrgIdForHost\n lockdown?: typeof getLockdownVerdict\n}\n\n/**\n * A tracking change is admitted on a site of the job's own org, for a member\n * who may write its settings now, where the request names at least one tag\n * this can set or remove. Refused in those words before anything is written.\n */\nexport function createAiTrackingJobAdmission(deps: AiTrackingJobAdmissionDeps = {}): AiJobAdmission {\n const ownerOf = deps.ownerOf ?? resolveOrgIdForHost\n return async (context): Promise<AiJobAdmissionRefusal | null> => {\n if (!context.hostId) return { status: 400, error: AI_TRACKING_NO_SITE_COPY }\n const owner = await ownerOf(context.hostId)\n if (!owner || owner !== context.orgId) return { status: 404, error: 'Unknown site' }\n const host = await context.firestore.collection('hosts').doc(context.hostId).get()\n const refused = await aiTrackingAccessRefusal({ host, uid: context.uid ?? '', org: context.org }, { lockdown: deps.lockdown })\n if (refused) return { status: 403, error: refused }\n const read = aiTrackingRequestOf(context.inputs as Record<string, unknown>)\n return aiTrackingRequestEmpty(read.request) ? { status: 400, error: [AI_TRACKING_NOTHING_COPY, ...read.problems].join(' ') } : null\n }\n}\n\nexport const aiTrackingJobAdmission = createAiTrackingJobAdmission()\n\n/** Registers the tracking step and its door (AGL-3751). */\nexport function registerAiTrackingJob(): void {\n registerAiJobStep(AI_TRACKING_KIND, runAiJobTrackingStep, { minimumMs: AI_JOB_TRACKING_STEP_MINIMUM_MS })\n registerAiJobAdmission(AI_TRACKING_KIND, aiTrackingJobAdmission)\n}\n"],"names":["createResourceUid","hostRoleCanWrite","SCREEN_ROOT_PATH","GA_MEASUREMENT_ID_PATTERN","GOOGLE_ADS_ID_PATTERN","GTM_CONTAINER_ID_PATTERN","LINKEDIN_PARTNER_ID_PATTERN","META_PIXEL_ID_PATTERN","PINTEREST_TAG_ID_PATTERN","TIKTOK_PIXEL_ID_PATTERN","dropPluginSiteCache","getLockdownVerdict","resolveOrgIdForHost","FieldValue","registerAiJobAdmission","aiJobEditModel","aiLimitReview","aiUnspentOutcome","registerAiJobStep","AI_TRACKING_KIND","AI_JOB_TRACKING_STEP_MINIMUM_MS","AI_TRACKING_NO_SITE_COPY","AI_TRACKING_ROLE_COPY","AI_TRACKING_LOCKED_COPY","AI_TRACKING_NOTHING_COPY","AI_TRACKING_WHERE","AI_TRACKING_OUTPUT_ID","AI_TRACKING_TAGS","arg","path","label","pattern","shape","ask","named","AI_TRACKING_WHICH_COPY","TAG_ARGS","map","tag","ID_RULE","AI_TRACKING_ARGS_SCHEMA","type","properties","googleAnalytics","description","maxLength","googleTagManager","metaPixel","googleAds","linkedinInsight","tiktokPixel","pinterestTag","remove","items","enum","maxItems","length","additionalProperties","text","value","trim","aiTrackingRequestOf","inputs","set","problems","removing","Set","Array","isArray","one","String","has","push","capitalized","test","slice","request","charAt","toUpperCase","aiTrackingMissingInput","item","read","args","aiTrackingRequestEmpty","tried","filter","naming","words","byName","name","why","asks","join","Object","keys","getPath","data","split","reduce","at","key","undefined","aiTrackingChanges","changes","said","includes","aiTrackingWarnings","host","warnings","any","some","writeAiSiteTracking","firestore","input","hostRef","collection","doc","hostId","runTransaction","tx","get","exists","ok","error","roles","uid","changed","change","stored","after","JSON","parse","stringify","analytics","consent","leaf","pop","node","done","outboxId","updates","updatedAt","now","delete","update","outboxRef","paths","entireHost","createdAt","serverTimestamp","attempts","id","aiTrackingAccessRefusal","deps","locked","lockdown","staff","org","intent","aiTrackingOutputNote","notDone","changedAny","did","warn","not","createAiJobTrackingStep","write","dropCache","job","modelFor","model","unspent","failure","orgData","orgId","refused","createdBy","review","written","dropped","hostIds","reason","catch","complete","output","resource","hostSubdomain","note","outputs","runAiJobTrackingStep","createAiTrackingJobAdmission","ownerOf","context","status","owner","aiTrackingJobAdmission","registerAiTrackingJob","minimumMs"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-tracking-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { hostRoleCanWrite } from '@aglyn/aglyn/app-utils/organizations'\nimport { SCREEN_ROOT_PATH } from '@aglyn/aglyn/app-utils/screen-route'\nimport {\n GA_MEASUREMENT_ID_PATTERN,\n GOOGLE_ADS_ID_PATTERN,\n GTM_CONTAINER_ID_PATTERN,\n LINKEDIN_PARTNER_ID_PATTERN,\n META_PIXEL_ID_PATTERN,\n PINTEREST_TAG_ID_PATTERN,\n TIKTOK_PIXEL_ID_PATTERN,\n} from '@aglyn/aglyn/app-utils/visitor-consent'\nimport { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache'\nimport type { PluginAiCapabilityArgsSchema, PluginAiCapabilityInputField } from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities'\nimport { getLockdownVerdict } from '@aglyn/tenant-data-admin/server/lockdown'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport { FieldValue, type Firestore } from 'firebase-admin/firestore'\nimport type { AiJobOutput } from '../model/ai-jobs.types'\nimport { registerAiJobAdmission, type AiJobAdmission, type AiJobAdmissionRefusal } from './ai-job-admission'\nimport { aiJobEditModel } from './ai-job-edit-step'\nimport { aiLimitReview, aiUnspentOutcome } from './ai-job-generation'\nimport type { AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The `tracking` step (AGL-3751): the site's analytics and advertising tags,\n * as the console's Setup → Tracking card sets them — Google Analytics, Google\n * Tag Manager, and the Meta, Google Ads, LinkedIn, TikTok and Pinterest tags —\n * as a first-class build operation, so \"connect Google Analytics G-4R7KX2PQ9Z\"\n * is one item on Assist's plan card.\n *\n * NO MODEL. The planner already wrote the ids as the item's arguments; this\n * holds each to the pattern the card's field (and the tenant's loader) holds\n * it to and writes it, so it costs no credits.\n *\n * ── Only the card's own fields, and only public ids ──────────────────────\n *\n * Every field written is one the Tracking card writes, under the same name:\n * `analytics.gaMeasurementId`, `analytics.gtmContainerId` and\n * `analytics.adTags.{meta,google-ads,linkedin,tiktok,pinterest}`, each tested\n * by the same expression (`GA_MEASUREMENT_ID_PATTERN` and its siblings in\n * `visitor-consent.ts`). Each is an id that is printed in every page's HTML\n * anyway, never a credential. Removing one deletes the field, as the card's\n * cleared box does (`CLEARABLE_TRACKING_PATHS`).\n *\n * NEVER: the consent banner (`consent.*` — turning it off, its mode, or the\n * advertising question), search indexing, verification tokens, and anything\n * that needs a secret the person pastes — the Ad conversions card's access\n * tokens, Zapier, order webhooks' signing secrets. Those are not arguments.\n * The tags load only where the consent banner lets them, exactly as a tag the\n * card saved does: the tenant gates on the stored ids, not on who wrote them.\n *\n * ── Live on confirm, who may, idempotent ─────────────────────────────────\n *\n * As the `settings` step (AGL-3748): a tag has no draft, so the plan card's\n * confirm is the agreement and the write is live. One transaction re-reads\n * the host, re-checks the role, writes only what differs with a site-wide\n * publish outbox entry, and the site's cached pages are dropped after. The\n * gate is the Firestore rules' for the host document (`canWriteHostContent`,\n * `hostWritesFrozen`): an admin, editor or author, while no lockdown freezes\n * the site, its org or the platform.\n */\n\nexport const AI_TRACKING_KIND = 'tracking' as const\n\n/** ASSUMED: the least time the step needs — the site, the org and one transaction. */\nexport const AI_JOB_TRACKING_STEP_MINIMUM_MS = 5_000\n\nexport const AI_TRACKING_NO_SITE_COPY = 'Open the site whose tracking should change.'\nexport const AI_TRACKING_ROLE_COPY = 'Changing the site’s tracking needs the admin, editor or author role on this site.'\nexport const AI_TRACKING_LOCKED_COPY = 'This site is not accepting changes right now.'\nexport const AI_TRACKING_NOTHING_COPY = 'The request named no tracking id this could set or remove.'\n\n/** Where a person finds what the op changed: the card that edits it. */\nexport const AI_TRACKING_WHERE = 'Setup → Tracking'\n\n/** The output's fixed id: one site's tracking, whatever changed. */\nexport const AI_TRACKING_OUTPUT_ID = 'tracking'\n\n/** One tag the card sets: its argument, its stored field, its name, and its pattern. */\nexport interface AiTrackingTag {\n arg: string\n path: string\n label: string\n pattern: RegExp\n /** What the card's field says the id looks like. */\n shape: string\n /** The id's name, as a field asking for it is labeled (AGL-3978): it names the provider. */\n field: string\n /** Asked when a request names this tag and no usable id, from the card's helper text. */\n ask: string\n /** Words that name this tag in an item's name. */\n named: RegExp\n}\n\n/** The Tracking card's fields, in its order (`trackingSchema`, host-settings-scope.tsx). */\nexport const AI_TRACKING_TAGS: readonly AiTrackingTag[] = [\n {\n arg: 'googleAnalytics',\n path: 'analytics.gaMeasurementId',\n label: 'Google Analytics',\n pattern: GA_MEASUREMENT_ID_PATTERN,\n shape: 'G-XXXXXXXXXX',\n field: 'Google Analytics measurement ID',\n ask: 'What’s your Google Analytics measurement ID? It looks like G-XXXXXXXXXX.',\n named: /google analytics|\\bga4?\\b|\\banalytics\\b/i,\n },\n {\n arg: 'googleTagManager',\n path: 'analytics.gtmContainerId',\n label: 'Google Tag Manager',\n pattern: GTM_CONTAINER_ID_PATTERN,\n shape: 'GTM-XXXXXXX',\n field: 'Google Tag Manager container ID',\n ask: 'What’s your Google Tag Manager container ID? It looks like GTM-XXXXXXX.',\n named: /tag manager|\\bgtm\\b/i,\n },\n {\n arg: 'metaPixel',\n path: 'analytics.adTags.meta',\n label: 'the Meta pixel',\n pattern: META_PIXEL_ID_PATTERN,\n shape: '8–20 digits',\n field: 'Meta pixel ID',\n ask: 'What’s your Meta pixel ID? It’s the number (8–20 digits) shown for your pixel in Meta Events Manager.',\n named: /\\bmeta\\b|facebook|instagram/i,\n },\n {\n arg: 'googleAds',\n path: 'analytics.adTags.google-ads',\n label: 'the Google Ads tag',\n pattern: GOOGLE_ADS_ID_PATTERN,\n shape: 'AW-123456789',\n field: 'Google Ads conversion ID',\n ask: 'What’s your Google Ads conversion ID? It looks like AW-123456789, from Google Ads.',\n named: /google ads|adwords/i,\n },\n {\n arg: 'linkedinInsight',\n path: 'analytics.adTags.linkedin',\n label: 'the LinkedIn Insight Tag',\n pattern: LINKEDIN_PARTNER_ID_PATTERN,\n shape: '4–10 digits',\n field: 'LinkedIn partner ID',\n ask: 'What’s your LinkedIn partner ID? It’s the number (4–10 digits) in LinkedIn Campaign Manager.',\n named: /linkedin/i,\n },\n {\n arg: 'tiktokPixel',\n path: 'analytics.adTags.tiktok',\n label: 'the TikTok pixel',\n pattern: TIKTOK_PIXEL_ID_PATTERN,\n shape: '16–24 capital letters and digits',\n field: 'TikTok pixel code',\n ask: 'What’s your TikTok pixel code? It’s in TikTok Ads Manager → Events, like C4ABCDEFGH1234567890.',\n named: /tiktok/i,\n },\n {\n arg: 'pinterestTag',\n path: 'analytics.adTags.pinterest',\n label: 'the Pinterest tag',\n pattern: PINTEREST_TAG_ID_PATTERN,\n shape: '10–16 digits',\n field: 'Pinterest tag ID',\n ask: 'What’s your Pinterest tag ID? It’s the number (10–16 digits) in Pinterest Ads → Conversions.',\n named: /pinterest/i,\n },\n]\n\n/** Asked when an item names no tag at all. */\nexport const AI_TRACKING_WHICH_COPY =\n 'Which tag should be connected, and what is its ID? Google Analytics, Google Tag Manager, the Meta pixel, Google Ads, LinkedIn, TikTok or Pinterest.'\n\nconst TAG_ARGS = AI_TRACKING_TAGS.map((tag) => tag.arg)\n\nconst ID_RULE = 'Exactly as the person gave it; leave it out unless the request states the id — never make one up.'\n\n/** The item's arguments, as the planner fills them in. Every one optional. */\nexport const AI_TRACKING_ARGS_SCHEMA: PluginAiCapabilityArgsSchema = {\n type: 'object',\n properties: {\n googleAnalytics: { type: 'string', description: `The Google Analytics 4 measurement ID, like G-XXXXXXXXXX. ${ID_RULE}`, maxLength: 24 },\n googleTagManager: { type: 'string', description: `The Google Tag Manager container ID, like GTM-XXXXXXX. ${ID_RULE}`, maxLength: 24 },\n metaPixel: { type: 'string', description: `The Meta (Facebook) pixel ID, 8–20 digits. ${ID_RULE}`, maxLength: 24 },\n googleAds: { type: 'string', description: `The Google Ads conversion ID, like AW-123456789. ${ID_RULE}`, maxLength: 24 },\n linkedinInsight: { type: 'string', description: `The LinkedIn Insight Tag partner ID, 4–10 digits. ${ID_RULE}`, maxLength: 24 },\n tiktokPixel: { type: 'string', description: `The TikTok pixel code, like C4ABCDEFGH1234567890. ${ID_RULE}`, maxLength: 32 },\n pinterestTag: { type: 'string', description: `The Pinterest tag ID, 10–16 digits. ${ID_RULE}`, maxLength: 24 },\n remove: {\n type: 'array',\n description: 'The tags the person asked to take off the site.',\n items: { type: 'string', enum: TAG_ARGS },\n maxItems: AI_TRACKING_TAGS.length,\n },\n },\n additionalProperties: false,\n}\n\n/** What an item asks: ids to set by field, and fields to clear. */\nexport interface AiTrackingRequest {\n set: Record<string, string>\n remove: string[]\n}\n\nexport interface AiTrackingRead {\n request: AiTrackingRequest\n /** A sentence per value that could not be used, so the rest still are. */\n problems: string[]\n}\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** The item's arguments read: what may be written, and a sentence for each that may not. */\nexport function aiTrackingRequestOf(inputs: Readonly<Record<string, unknown>> | null | undefined): AiTrackingRead {\n const set: Record<string, string> = {}\n const problems: string[] = []\n const removing = new Set(Array.isArray(inputs?.['remove']) ? (inputs?.['remove'] as unknown[]).map((one) => String(one)) : [])\n const remove: string[] = []\n for (const tag of AI_TRACKING_TAGS) {\n const value = text(inputs?.[tag.arg])\n if (value && removing.has(tag.arg)) {\n problems.push(`${capitalized(tag.label)} was both set and removed, so it was left as it is.`)\n continue\n }\n if (removing.has(tag.arg)) {\n remove.push(tag.path)\n continue\n }\n if (!value) continue\n // The card's own test, and the loader's: an id of any other shape is\n // saved by nobody, since it would never load.\n if (tag.pattern.test(value)) set[tag.path] = value\n else problems.push(`“${value.slice(0, 40)}” is not an ID for ${tag.label}, which looks like ${tag.shape}.`)\n }\n return { request: { set, remove }, problems }\n}\n\nfunction capitalized(label: string): string {\n return label.charAt(0).toUpperCase() + label.slice(1)\n}\n\n/**\n * The question a planned tracking change asks the person, or `null` when it\n * sets or removes something (AGL-3751). An item with no usable id — the\n * planner kept \"add my Meta pixel\" with no id, or an id of the wrong shape —\n * is never built: it asks for the id of the tag its arguments or its name\n * name, in the card's own words, and says why an id given was not one.\n */\nexport function aiTrackingMissingInput(item: AiTrackingItem): string | null {\n const read = aiTrackingRequestOf(item.args)\n if (!aiTrackingRequestEmpty(read.request)) return null\n const named = aiTrackingTagsAsked(item)\n const asks = named.length ? named.map((tag) => tag.ask) : [AI_TRACKING_WHICH_COPY]\n return [...read.problems, ...asks].join(' ')\n}\n\ntype AiTrackingItem = { name?: string; why?: string; args: Readonly<Record<string, unknown>> }\n\n/** The tags an item asks about: those its arguments tried to set, else the ones its name or reason names. */\nfunction aiTrackingTagsAsked(item: AiTrackingItem): AiTrackingTag[] {\n const tried = AI_TRACKING_TAGS.filter((tag) => text(item.args[tag.arg]))\n const naming = (words: string | undefined) => AI_TRACKING_TAGS.filter((tag) => tag.named.test(words ?? ''))\n const byName = naming(item.name)\n return tried.length ? tried : byName.length ? byName : naming(item.why)\n}\n\n/**\n * The fields a planned tracking change with no usable id waits for (AGL-3978):\n * one per tag it asks about, labeled with the provider's id and held to the\n * card's pattern, so the build pauses in `needs_input` for the person to give\n * it. Empty when the item can be built, or names no tag at all.\n */\nexport function aiTrackingInputFields(item: AiTrackingItem): PluginAiCapabilityInputField[] {\n if (!aiTrackingMissingInput(item)) return []\n return aiTrackingTagsAsked(item).map((tag) => ({ arg: tag.arg, label: tag.field, shape: tag.shape, pattern: tag.pattern }))\n}\n\n/** Whether a read request asks for anything at all. */\nexport function aiTrackingRequestEmpty(request: AiTrackingRequest): boolean {\n return Object.keys(request.set).length === 0 && request.remove.length === 0\n}\n\n// ── The write ─────────────────────────────────────────────────────────────\n\nconst getPath = (data: Record<string, unknown>, path: string): unknown =>\n path.split('.').reduce<unknown>((at, key) => (at && typeof at === 'object' ? (at as Record<string, unknown>)[key] : undefined), data)\n\n/** One change to the host document, with the sentence that says it was made. */\nexport interface AiTrackingChange {\n path: string\n /** The id to store, or `null` to delete the field. */\n value: string | null\n said: string\n}\n\n/** Every change the request asks, in the card's order, before comparing with what is stored. */\nexport function aiTrackingChanges(request: AiTrackingRequest): AiTrackingChange[] {\n const changes: AiTrackingChange[] = []\n for (const tag of AI_TRACKING_TAGS) {\n const value = request.set[tag.path]\n if (value) changes.push({ path: tag.path, value, said: `${capitalized(tag.label)} is connected (${value}).` })\n else if (request.remove.includes(tag.path)) changes.push({ path: tag.path, value: null, said: `${capitalized(tag.label)} is removed.` })\n }\n return changes\n}\n\n/**\n * What the person should know about the tags the site now carries, in the\n * card's own words: where the consent banner is off, nothing of ours gates\n * them; and a container beside a direct Google tag counts everything twice.\n */\nexport function aiTrackingWarnings(host: Record<string, unknown>): string[] {\n const warnings: string[] = []\n const has = (path: string) => {\n const value = getPath(host, path)\n return typeof value === 'string' && value !== ''\n }\n const any = AI_TRACKING_TAGS.some((tag) => has(tag.path))\n if (any && getPath(host, 'consent.disabled') === true) {\n warnings.push('This site’s cookie banner is turned off because it uses its own consent tool, so that tool decides when these load.')\n }\n if (has('analytics.gtmContainerId') && (has('analytics.gaMeasurementId') || has('analytics.adTags.google-ads'))) {\n warnings.push('Do not also put an Analytics or Google Ads tag in the Tag Manager container: that loads the same measurement twice and counts every visit and conversion twice.')\n }\n return warnings\n}\n\nexport type AiTrackingWrite =\n | { ok: true; done: string[]; changed: string[]; warnings: string[]; outboxId: string | null }\n | { ok: false; error: string }\n\n/**\n * The tags written, in one transaction that reads the host as it is stored\n * NOW: the member still holds a write role, and only the values that differ\n * from what is stored are written, with a site-wide publish outbox entry\n * beside them so the live site's cached pages are dropped.\n */\nexport async function writeAiSiteTracking(\n firestore: Firestore,\n input: { hostId: string; uid: string; request: AiTrackingRequest; now: Date },\n): Promise<AiTrackingWrite> {\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n return firestore.runTransaction(async (tx): Promise<AiTrackingWrite> => {\n const host = await tx.get(hostRef)\n if (!host.exists) return { ok: false, error: 'Unknown site' }\n const roles = (host.get('memberRoles') ?? {}) as Record<string, unknown>\n if (!hostRoleCanWrite(roles[input.uid])) return { ok: false, error: AI_TRACKING_ROLE_COPY }\n const data = (host.data() ?? {}) as Record<string, unknown>\n const changes = aiTrackingChanges(input.request)\n const changed = changes.filter((change) => {\n const stored = getPath(data, change.path)\n return change.value === null ? stored != null && stored !== '' : stored !== change.value\n })\n // What the site carries once this lands, for the warnings.\n // Only the two maps the warnings read; both are plain strings and flags.\n const after: Record<string, unknown> = JSON.parse(JSON.stringify({ analytics: data['analytics'] ?? {}, consent: data['consent'] ?? {} }))\n for (const change of changed) {\n const keys = change.path.split('.')\n const leaf = keys.pop() as string\n let node = after as Record<string, unknown>\n for (const key of keys) {\n if (!node[key] || typeof node[key] !== 'object') node[key] = {}\n node = node[key] as Record<string, unknown>\n }\n if (change.value === null) delete node[leaf]\n else node[leaf] = change.value\n }\n const done = changes.map((change) => change.said)\n const warnings = aiTrackingWarnings(after)\n if (!changed.length) return { ok: true, done, changed: [], warnings, outboxId: null }\n const updates: Record<string, unknown> = { updatedAt: input.now }\n for (const change of changed) updates[change.path] = change.value === null ? FieldValue.delete() : change.value\n tx.update(hostRef, updates)\n // The tags are in every page's head, so every cached page is stale.\n const outboxRef = firestore.collection('publishOutbox').doc(createResourceUid())\n tx.set(outboxRef, {\n hostId: input.hostId,\n paths: [SCREEN_ROOT_PATH],\n entireHost: true,\n createdAt: FieldValue.serverTimestamp(),\n attempts: 0,\n })\n return { ok: true, done, changed: changed.map((change) => change.path), warnings, outboxId: outboxRef.id }\n })\n}\n\n// ── The step ──────────────────────────────────────────────────────────────\n\n/** Why this member may not change this site's tracking now, or `null`. */\nexport async function aiTrackingAccessRefusal(\n input: { host: FirebaseFirestore.DocumentSnapshot; uid: string; org: object | null },\n deps: { lockdown?: typeof getLockdownVerdict } = {},\n): Promise<string | null> {\n if (!input.host.exists) return 'Unknown site'\n const roles = (input.host.get('memberRoles') ?? {}) as Record<string, unknown>\n if (!hostRoleCanWrite(roles[input.uid])) return AI_TRACKING_ROLE_COPY\n const locked = await (deps.lockdown ?? getLockdownVerdict)({\n staff: false,\n uid: input.uid,\n org: input.org as never,\n host: input.host.data() as never,\n intent: 'write',\n })\n return locked ? AI_TRACKING_LOCKED_COPY : null\n}\n\n/** The output's note: what changed, that it is live, how consent gates it, and what was not done. */\nexport function aiTrackingOutputNote(done: readonly string[], warnings: readonly string[], notDone: readonly string[], changedAny: boolean): string {\n const did = done.length\n ? `${done.join(' ')} Live now, in ${AI_TRACKING_WHERE}.${changedAny ? '' : ' It already was, so nothing was written.'}`\n : 'Nothing needed changing.'\n // The card's own promise, true of a tag whoever saved it.\n const consent = done.length ? ' Tags load only where a visitor’s cookie consent allows them.' : ''\n const warn = warnings.length ? ` ${warnings.join(' ')}` : ''\n const not = notDone.length ? ` Not done: ${notDone.join(' ')}` : ''\n return `${did}${consent}${warn}${not}`.slice(0, 1_000)\n}\n\nexport interface AiJobTrackingStepDeps {\n write?: typeof writeAiSiteTracking\n lockdown?: typeof getLockdownVerdict\n dropCache?: typeof dropPluginSiteCache\n}\n\nexport function createAiJobTrackingStep(deps: AiJobTrackingStepDeps = {}): AiJobStepRunner {\n const write = deps.write ?? writeAiSiteTracking\n const dropCache = deps.dropCache ?? dropPluginSiteCache\n return async ({ job, now, firestore, org, modelFor }): Promise<AiJobStepOutcome> => {\n // No model is asked: the outcome records the edit step's at no cost.\n const model = modelFor?.('job.edit') ?? aiJobEditModel()\n const unspent = (failure: string): AiJobStepOutcome => ({ ...aiUnspentOutcome(model), failure })\n const hostId = job.hostId\n if (!hostId) return unspent(AI_TRACKING_NO_SITE_COPY)\n const read = aiTrackingRequestOf(job.inputs)\n if (aiTrackingRequestEmpty(read.request)) return unspent([AI_TRACKING_NOTHING_COPY, ...read.problems].join(' '))\n const orgData = (org ?? ((await firestore.collection('orgs').doc(job.orgId).get()).data() ?? null)) as Record<string, unknown> | null\n const host = await firestore.collection('hosts').doc(hostId).get()\n const refused = await aiTrackingAccessRefusal({ host, uid: job.createdBy, org: orgData }, { lockdown: deps.lockdown })\n if (refused) return aiUnspentOutcome(model, { review: aiLimitReview(refused) })\n const written = await write(firestore, { hostId, uid: job.createdBy, request: read.request, now })\n if (written.ok === false) return unspent(written.error)\n if (written.outboxId) {\n // The live site's cached pages go now; the outbox entry stays for the\n // drain unless the drop certainly happened.\n const dropped = await dropCache({ hostIds: [hostId], reason: 'AI build changed site tracking' }).catch(() => ({ complete: false }))\n if (dropped.complete) await firestore.collection('publishOutbox').doc(written.outboxId).delete().catch(() => undefined)\n }\n const output: AiJobOutput = {\n resource: 'tracking',\n id: AI_TRACKING_OUTPUT_ID,\n hostId,\n hostSubdomain: (typeof host.get('subdomain') === 'string' && host.get('subdomain')) || null,\n label: 'Site tracking',\n note: aiTrackingOutputNote(written.done, written.warnings, read.problems, written.changed.length > 0),\n }\n return aiUnspentOutcome(model, { outputs: [output] })\n }\n}\n\nexport const runAiJobTrackingStep = createAiJobTrackingStep()\n\n// ── Admission ─────────────────────────────────────────────────────────────\n\nexport interface AiTrackingJobAdmissionDeps {\n ownerOf?: typeof resolveOrgIdForHost\n lockdown?: typeof getLockdownVerdict\n}\n\n/**\n * A tracking change is admitted on a site of the job's own org, for a member\n * who may write its settings now, where the request names at least one tag\n * this can set or remove. Refused in those words before anything is written.\n */\nexport function createAiTrackingJobAdmission(deps: AiTrackingJobAdmissionDeps = {}): AiJobAdmission {\n const ownerOf = deps.ownerOf ?? resolveOrgIdForHost\n return async (context): Promise<AiJobAdmissionRefusal | null> => {\n if (!context.hostId) return { status: 400, error: AI_TRACKING_NO_SITE_COPY }\n const owner = await ownerOf(context.hostId)\n if (!owner || owner !== context.orgId) return { status: 404, error: 'Unknown site' }\n const host = await context.firestore.collection('hosts').doc(context.hostId).get()\n const refused = await aiTrackingAccessRefusal({ host, uid: context.uid ?? '', org: context.org }, { lockdown: deps.lockdown })\n if (refused) return { status: 403, error: refused }\n const read = aiTrackingRequestOf(context.inputs as Record<string, unknown>)\n return aiTrackingRequestEmpty(read.request) ? { status: 400, error: [AI_TRACKING_NOTHING_COPY, ...read.problems].join(' ') } : null\n }\n}\n\nexport const aiTrackingJobAdmission = createAiTrackingJobAdmission()\n\n/** Registers the tracking step and its door (AGL-3751). */\nexport function registerAiTrackingJob(): void {\n registerAiJobStep(AI_TRACKING_KIND, runAiJobTrackingStep, { minimumMs: AI_JOB_TRACKING_STEP_MINIMUM_MS })\n registerAiJobAdmission(AI_TRACKING_KIND, aiTrackingJobAdmission)\n}\n"],"names":["createResourceUid","hostRoleCanWrite","SCREEN_ROOT_PATH","GA_MEASUREMENT_ID_PATTERN","GOOGLE_ADS_ID_PATTERN","GTM_CONTAINER_ID_PATTERN","LINKEDIN_PARTNER_ID_PATTERN","META_PIXEL_ID_PATTERN","PINTEREST_TAG_ID_PATTERN","TIKTOK_PIXEL_ID_PATTERN","dropPluginSiteCache","getLockdownVerdict","resolveOrgIdForHost","FieldValue","registerAiJobAdmission","aiJobEditModel","aiLimitReview","aiUnspentOutcome","registerAiJobStep","AI_TRACKING_KIND","AI_JOB_TRACKING_STEP_MINIMUM_MS","AI_TRACKING_NO_SITE_COPY","AI_TRACKING_ROLE_COPY","AI_TRACKING_LOCKED_COPY","AI_TRACKING_NOTHING_COPY","AI_TRACKING_WHERE","AI_TRACKING_OUTPUT_ID","AI_TRACKING_TAGS","arg","path","label","pattern","shape","field","ask","named","AI_TRACKING_WHICH_COPY","TAG_ARGS","map","tag","ID_RULE","AI_TRACKING_ARGS_SCHEMA","type","properties","googleAnalytics","description","maxLength","googleTagManager","metaPixel","googleAds","linkedinInsight","tiktokPixel","pinterestTag","remove","items","enum","maxItems","length","additionalProperties","text","value","trim","aiTrackingRequestOf","inputs","set","problems","removing","Set","Array","isArray","one","String","has","push","capitalized","test","slice","request","charAt","toUpperCase","aiTrackingMissingInput","item","read","args","aiTrackingRequestEmpty","aiTrackingTagsAsked","asks","join","tried","filter","naming","words","byName","name","why","aiTrackingInputFields","Object","keys","getPath","data","split","reduce","at","key","undefined","aiTrackingChanges","changes","said","includes","aiTrackingWarnings","host","warnings","any","some","writeAiSiteTracking","firestore","input","hostRef","collection","doc","hostId","runTransaction","tx","get","exists","ok","error","roles","uid","changed","change","stored","after","JSON","parse","stringify","analytics","consent","leaf","pop","node","done","outboxId","updates","updatedAt","now","delete","update","outboxRef","paths","entireHost","createdAt","serverTimestamp","attempts","id","aiTrackingAccessRefusal","deps","locked","lockdown","staff","org","intent","aiTrackingOutputNote","notDone","changedAny","did","warn","not","createAiJobTrackingStep","write","dropCache","job","modelFor","model","unspent","failure","orgData","orgId","refused","createdBy","review","written","dropped","hostIds","reason","catch","complete","output","resource","hostSubdomain","note","outputs","runAiJobTrackingStep","createAiTrackingJobAdmission","ownerOf","context","status","owner","aiTrackingJobAdmission","registerAiTrackingJob","minimumMs"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,iBAAiB,QAAQ,6CAA4C;AAC9E,SAASC,gBAAgB,QAAQ,uCAAsC;AACvE,SAASC,gBAAgB,QAAQ,sCAAqC;AACtE,SACEC,yBAAyB,EACzBC,qBAAqB,EACrBC,wBAAwB,EACxBC,2BAA2B,EAC3BC,qBAAqB,EACrBC,wBAAwB,EACxBC,uBAAuB,QAClB,yCAAwC;AAC/C,SAASC,mBAAmB,QAAQ,gDAA+C;AAEnF,SAASC,kBAAkB,QAAQ,2CAA0C;AAC7E,SAASC,mBAAmB,QAAQ,gDAA+C;AACnF,SAASC,UAAU,QAAwB,2BAA0B;AAErE,SAASC,sBAAsB,QAAyD,wBAAoB;AAC5G,SAASC,cAAc,QAAQ,wBAAoB;AACnD,SAASC,aAAa,EAAEC,gBAAgB,QAAQ,yBAAqB;AAErE,SAASC,iBAAiB,QAAQ,eAAW;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqCC,GAED,OAAO,MAAMC,mBAAmB,WAAmB;AAEnD,oFAAoF,GACpF,OAAO,MAAMC,kCAAkC,KAAK;AAEpD,OAAO,MAAMC,2BAA2B,8CAA6C;AACrF,OAAO,MAAMC,wBAAwB,oFAAmF;AACxH,OAAO,MAAMC,0BAA0B,gDAA+C;AACtF,OAAO,MAAMC,2BAA2B,6DAA4D;AAEpG,sEAAsE,GACtE,OAAO,MAAMC,oBAAoB,mBAAkB;AAEnD,kEAAkE,GAClE,OAAO,MAAMC,wBAAwB,WAAU;AAkB/C,0FAA0F,GAC1F,OAAO,MAAMC,mBAA6C;IACxD;QACEC,KAAK;QACLC,MAAM;QACNC,OAAO;QACPC,SAAS5B;QACT6B,OAAO;QACPC,OAAO;QACPC,KAAK;QACLC,OAAO;IACT;IACA;QACEP,KAAK;QACLC,MAAM;QACNC,OAAO;QACPC,SAAS1B;QACT2B,OAAO;QACPC,OAAO;QACPC,KAAK;QACLC,OAAO;IACT;IACA;QACEP,KAAK;QACLC,MAAM;QACNC,OAAO;QACPC,SAASxB;QACTyB,OAAO;QACPC,OAAO;QACPC,KAAK;QACLC,OAAO;IACT;IACA;QACEP,KAAK;QACLC,MAAM;QACNC,OAAO;QACPC,SAAS3B;QACT4B,OAAO;QACPC,OAAO;QACPC,KAAK;QACLC,OAAO;IACT;IACA;QACEP,KAAK;QACLC,MAAM;QACNC,OAAO;QACPC,SAASzB;QACT0B,OAAO;QACPC,OAAO;QACPC,KAAK;QACLC,OAAO;IACT;IACA;QACEP,KAAK;QACLC,MAAM;QACNC,OAAO;QACPC,SAAStB;QACTuB,OAAO;QACPC,OAAO;QACPC,KAAK;QACLC,OAAO;IACT;IACA;QACEP,KAAK;QACLC,MAAM;QACNC,OAAO;QACPC,SAASvB;QACTwB,OAAO;QACPC,OAAO;QACPC,KAAK;QACLC,OAAO;IACT;CACD,CAAA;AAED,4CAA4C,GAC5C,OAAO,MAAMC,yBACX,sJAAqJ;AAEvJ,MAAMC,WAAWV,iBAAiBW,GAAG,CAAC,CAACC,MAAQA,IAAIX,GAAG;AAEtD,MAAMY,UAAU;AAEhB,4EAA4E,GAC5E,OAAO,MAAMC,0BAAwD;IACnEC,MAAM;IACNC,YAAY;QACVC,iBAAiB;YAAEF,MAAM;YAAUG,aAAa,CAAC,0DAA0D,EAAEL,SAAS;YAAEM,WAAW;QAAG;QACtIC,kBAAkB;YAAEL,MAAM;YAAUG,aAAa,CAAC,uDAAuD,EAAEL,SAAS;YAAEM,WAAW;QAAG;QACpIE,WAAW;YAAEN,MAAM;YAAUG,aAAa,CAAC,2CAA2C,EAAEL,SAAS;YAAEM,WAAW;QAAG;QACjHG,WAAW;YAAEP,MAAM;YAAUG,aAAa,CAAC,iDAAiD,EAAEL,SAAS;YAAEM,WAAW;QAAG;QACvHI,iBAAiB;YAAER,MAAM;YAAUG,aAAa,CAAC,kDAAkD,EAAEL,SAAS;YAAEM,WAAW;QAAG;QAC9HK,aAAa;YAAET,MAAM;YAAUG,aAAa,CAAC,kDAAkD,EAAEL,SAAS;YAAEM,WAAW;QAAG;QAC1HM,cAAc;YAAEV,MAAM;YAAUG,aAAa,CAAC,oCAAoC,EAAEL,SAAS;YAAEM,WAAW;QAAG;QAC7GO,QAAQ;YACNX,MAAM;YACNG,aAAa;YACbS,OAAO;gBAAEZ,MAAM;gBAAUa,MAAMlB;YAAS;YACxCmB,UAAU7B,iBAAiB8B,MAAM;QACnC;IACF;IACAC,sBAAsB;AACxB,EAAC;AAcD,MAAMC,OAAO,CAACC,QAA4B,OAAOA,UAAU,WAAWA,MAAMC,IAAI,KAAK;AAErF,0FAA0F,GAC1F,OAAO,SAASC,oBAAoBC,MAA4D;IAC9F,MAAMC,MAA8B,CAAC;IACrC,MAAMC,WAAqB,EAAE;IAC7B,MAAMC,WAAW,IAAIC,IAAIC,MAAMC,OAAO,CAACN,0BAAAA,MAAQ,CAAC,SAAS,IAAI,CAACA,0BAAAA,MAAQ,CAAC,SAAS,EAAezB,GAAG,CAAC,CAACgC,MAAQC,OAAOD,QAAQ,EAAE;IAC7H,MAAMjB,SAAmB,EAAE;IAC3B,KAAK,MAAMd,OAAOZ,iBAAkB;QAClC,MAAMiC,QAAQD,KAAKI,0BAAAA,MAAQ,CAACxB,IAAIX,GAAG,CAAC;QACpC,IAAIgC,SAASM,SAASM,GAAG,CAACjC,IAAIX,GAAG,GAAG;YAClCqC,SAASQ,IAAI,CAAC,GAAGC,YAAYnC,IAAIT,KAAK,EAAE,mDAAmD,CAAC;YAC5F;QACF;QACA,IAAIoC,SAASM,GAAG,CAACjC,IAAIX,GAAG,GAAG;YACzByB,OAAOoB,IAAI,CAAClC,IAAIV,IAAI;YACpB;QACF;QACA,IAAI,CAAC+B,OAAO;QACZ,qEAAqE;QACrE,8CAA8C;QAC9C,IAAIrB,IAAIR,OAAO,CAAC4C,IAAI,CAACf,QAAQI,GAAG,CAACzB,IAAIV,IAAI,CAAC,GAAG+B;aACxCK,SAASQ,IAAI,CAAC,CAAC,CAAC,EAAEb,MAAMgB,KAAK,CAAC,GAAG,IAAI,mBAAmB,EAAErC,IAAIT,KAAK,CAAC,mBAAmB,EAAES,IAAIP,KAAK,CAAC,CAAC,CAAC;IAC5G;IACA,OAAO;QAAE6C,SAAS;YAAEb;YAAKX;QAAO;QAAGY;IAAS;AAC9C;AAEA,SAASS,YAAY5C,KAAa;IAChC,OAAOA,MAAMgD,MAAM,CAAC,GAAGC,WAAW,KAAKjD,MAAM8C,KAAK,CAAC;AACrD;AAEA;;;;;;CAMC,GACD,OAAO,SAASI,uBAAuBC,IAAoB;IACzD,MAAMC,OAAOpB,oBAAoBmB,KAAKE,IAAI;IAC1C,IAAI,CAACC,uBAAuBF,KAAKL,OAAO,GAAG,OAAO;IAClD,MAAM1C,QAAQkD,oBAAoBJ;IAClC,MAAMK,OAAOnD,MAAMsB,MAAM,GAAGtB,MAAMG,GAAG,CAAC,CAACC,MAAQA,IAAIL,GAAG,IAAI;QAACE;KAAuB;IAClF,OAAO;WAAI8C,KAAKjB,QAAQ;WAAKqB;KAAK,CAACC,IAAI,CAAC;AAC1C;AAIA,2GAA2G,GAC3G,SAASF,oBAAoBJ,IAAoB;IAC/C,MAAMO,QAAQ7D,iBAAiB8D,MAAM,CAAC,CAAClD,MAAQoB,KAAKsB,KAAKE,IAAI,CAAC5C,IAAIX,GAAG,CAAC;IACtE,MAAM8D,SAAS,CAACC,QAA8BhE,iBAAiB8D,MAAM,CAAC,CAAClD,MAAQA,IAAIJ,KAAK,CAACwC,IAAI,CAACgB,gBAAAA,QAAS;IACvG,MAAMC,SAASF,OAAOT,KAAKY,IAAI;IAC/B,OAAOL,MAAM/B,MAAM,GAAG+B,QAAQI,OAAOnC,MAAM,GAAGmC,SAASF,OAAOT,KAAKa,GAAG;AACxE;AAEA;;;;;CAKC,GACD,OAAO,SAASC,sBAAsBd,IAAoB;IACxD,IAAI,CAACD,uBAAuBC,OAAO,OAAO,EAAE;IAC5C,OAAOI,oBAAoBJ,MAAM3C,GAAG,CAAC,CAACC,MAAS,CAAA;YAAEX,KAAKW,IAAIX,GAAG;YAAEE,OAAOS,IAAIN,KAAK;YAAED,OAAOO,IAAIP,KAAK;YAAED,SAASQ,IAAIR,OAAO;QAAC,CAAA;AAC1H;AAEA,qDAAqD,GACrD,OAAO,SAASqD,uBAAuBP,OAA0B;IAC/D,OAAOmB,OAAOC,IAAI,CAACpB,QAAQb,GAAG,EAAEP,MAAM,KAAK,KAAKoB,QAAQxB,MAAM,CAACI,MAAM,KAAK;AAC5E;AAEA,6EAA6E;AAE7E,MAAMyC,UAAU,CAACC,MAA+BtE,OAC9CA,KAAKuE,KAAK,CAAC,KAAKC,MAAM,CAAU,CAACC,IAAIC,MAASD,MAAM,OAAOA,OAAO,WAAW,AAACA,EAA8B,CAACC,IAAI,GAAGC,WAAYL;AAUlI,8FAA8F,GAC9F,OAAO,SAASM,kBAAkB5B,OAA0B;IAC1D,MAAM6B,UAA8B,EAAE;IACtC,KAAK,MAAMnE,OAAOZ,iBAAkB;QAClC,MAAMiC,QAAQiB,QAAQb,GAAG,CAACzB,IAAIV,IAAI,CAAC;QACnC,IAAI+B,OAAO8C,QAAQjC,IAAI,CAAC;YAAE5C,MAAMU,IAAIV,IAAI;YAAE+B;YAAO+C,MAAM,GAAGjC,YAAYnC,IAAIT,KAAK,EAAE,eAAe,EAAE8B,MAAM,EAAE,CAAC;QAAC;aACvG,IAAIiB,QAAQxB,MAAM,CAACuD,QAAQ,CAACrE,IAAIV,IAAI,GAAG6E,QAAQjC,IAAI,CAAC;YAAE5C,MAAMU,IAAIV,IAAI;YAAE+B,OAAO;YAAM+C,MAAM,GAAGjC,YAAYnC,IAAIT,KAAK,EAAE,YAAY,CAAC;QAAC;IACxI;IACA,OAAO4E;AACT;AAEA;;;;CAIC,GACD,OAAO,SAASG,mBAAmBC,IAA6B;IAC9D,MAAMC,WAAqB,EAAE;IAC7B,MAAMvC,MAAM,CAAC3C;QACX,MAAM+B,QAAQsC,QAAQY,MAAMjF;QAC5B,OAAO,OAAO+B,UAAU,YAAYA,UAAU;IAChD;IACA,MAAMoD,MAAMrF,iBAAiBsF,IAAI,CAAC,CAAC1E,MAAQiC,IAAIjC,IAAIV,IAAI;IACvD,IAAImF,OAAOd,QAAQY,MAAM,wBAAwB,MAAM;QACrDC,SAAStC,IAAI,CAAC;IAChB;IACA,IAAID,IAAI,+BAAgCA,CAAAA,IAAI,gCAAgCA,IAAI,8BAA6B,GAAI;QAC/GuC,SAAStC,IAAI,CAAC;IAChB;IACA,OAAOsC;AACT;AAMA;;;;;CAKC,GACD,OAAO,eAAeG,oBACpBC,SAAoB,EACpBC,KAA6E;IAE7E,MAAMC,UAAUF,UAAUG,UAAU,CAAC,SAASC,GAAG,CAACH,MAAMI,MAAM;IAC9D,OAAOL,UAAUM,cAAc,CAAC,OAAOC;YAGtBZ,WAEDA,YAQgEX,iBAAkCA;QAZhH,MAAMW,OAAO,MAAMY,GAAGC,GAAG,CAACN;QAC1B,IAAI,CAACP,KAAKc,MAAM,EAAE,OAAO;YAAEC,IAAI;YAAOC,OAAO;QAAe;QAC5D,MAAMC,SAASjB,YAAAA,KAAKa,GAAG,CAAC,0BAATb,YAA2B,CAAC;QAC3C,IAAI,CAAC7G,iBAAiB8H,KAAK,CAACX,MAAMY,GAAG,CAAC,GAAG,OAAO;YAAEH,IAAI;YAAOC,OAAOxG;QAAsB;QAC1F,MAAM6E,QAAQW,aAAAA,KAAKX,IAAI,cAATW,aAAe,CAAC;QAC9B,MAAMJ,UAAUD,kBAAkBW,MAAMvC,OAAO;QAC/C,MAAMoD,UAAUvB,QAAQjB,MAAM,CAAC,CAACyC;YAC9B,MAAMC,SAASjC,QAAQC,MAAM+B,OAAOrG,IAAI;YACxC,OAAOqG,OAAOtE,KAAK,KAAK,OAAOuE,UAAU,QAAQA,WAAW,KAAKA,WAAWD,OAAOtE,KAAK;QAC1F;QACA,2DAA2D;QAC3D,yEAAyE;QACzE,MAAMwE,QAAiCC,KAAKC,KAAK,CAACD,KAAKE,SAAS,CAAC;YAAEC,SAAS,GAAErC,kBAAAA,IAAI,CAAC,YAAY,YAAjBA,kBAAqB,CAAC;YAAGsC,OAAO,GAAEtC,gBAAAA,IAAI,CAAC,UAAU,YAAfA,gBAAmB,CAAC;QAAE;QACtI,KAAK,MAAM+B,UAAUD,QAAS;YAC5B,MAAMhC,OAAOiC,OAAOrG,IAAI,CAACuE,KAAK,CAAC;YAC/B,MAAMsC,OAAOzC,KAAK0C,GAAG;YACrB,IAAIC,OAAOR;YACX,KAAK,MAAM7B,OAAON,KAAM;gBACtB,IAAI,CAAC2C,IAAI,CAACrC,IAAI,IAAI,OAAOqC,IAAI,CAACrC,IAAI,KAAK,UAAUqC,IAAI,CAACrC,IAAI,GAAG,CAAC;gBAC9DqC,OAAOA,IAAI,CAACrC,IAAI;YAClB;YACA,IAAI2B,OAAOtE,KAAK,KAAK,MAAM,OAAOgF,IAAI,CAACF,KAAK;iBACvCE,IAAI,CAACF,KAAK,GAAGR,OAAOtE,KAAK;QAChC;QACA,MAAMiF,OAAOnC,QAAQpE,GAAG,CAAC,CAAC4F,SAAWA,OAAOvB,IAAI;QAChD,MAAMI,WAAWF,mBAAmBuB;QACpC,IAAI,CAACH,QAAQxE,MAAM,EAAE,OAAO;YAAEoE,IAAI;YAAMgB;YAAMZ,SAAS,EAAE;YAAElB;YAAU+B,UAAU;QAAK;QACpF,MAAMC,UAAmC;YAAEC,WAAW5B,MAAM6B,GAAG;QAAC;QAChE,KAAK,MAAMf,UAAUD,QAASc,OAAO,CAACb,OAAOrG,IAAI,CAAC,GAAGqG,OAAOtE,KAAK,KAAK,OAAO/C,WAAWqI,MAAM,KAAKhB,OAAOtE,KAAK;QAC/G8D,GAAGyB,MAAM,CAAC9B,SAAS0B;QACnB,oEAAoE;QACpE,MAAMK,YAAYjC,UAAUG,UAAU,CAAC,iBAAiBC,GAAG,CAACvH;QAC5D0H,GAAG1D,GAAG,CAACoF,WAAW;YAChB5B,QAAQJ,MAAMI,MAAM;YACpB6B,OAAO;gBAACnJ;aAAiB;YACzBoJ,YAAY;YACZC,WAAW1I,WAAW2I,eAAe;YACrCC,UAAU;QACZ;QACA,OAAO;YAAE5B,IAAI;YAAMgB;YAAMZ,SAASA,QAAQ3F,GAAG,CAAC,CAAC4F,SAAWA,OAAOrG,IAAI;YAAGkF;YAAU+B,UAAUM,UAAUM,EAAE;QAAC;IAC3G;AACF;AAEA,6EAA6E;AAE7E,wEAAwE,GACxE,OAAO,eAAeC,wBACpBvC,KAAoF,EACpFwC,OAAiD,CAAC,CAAC;QAGpCxC,iBAEOwC;IAHtB,IAAI,CAACxC,MAAMN,IAAI,CAACc,MAAM,EAAE,OAAO;IAC/B,MAAMG,SAASX,kBAAAA,MAAMN,IAAI,CAACa,GAAG,CAAC,0BAAfP,kBAAiC,CAAC;IACjD,IAAI,CAACnH,iBAAiB8H,KAAK,CAACX,MAAMY,GAAG,CAAC,GAAG,OAAO1G;IAChD,MAAMuI,SAAS,MAAM,EAACD,iBAAAA,KAAKE,QAAQ,YAAbF,iBAAiBjJ,oBAAoB;QACzDoJ,OAAO;QACP/B,KAAKZ,MAAMY,GAAG;QACdgC,KAAK5C,MAAM4C,GAAG;QACdlD,MAAMM,MAAMN,IAAI,CAACX,IAAI;QACrB8D,QAAQ;IACV;IACA,OAAOJ,SAAStI,0BAA0B;AAC5C;AAEA,mGAAmG,GACnG,OAAO,SAAS2I,qBAAqBrB,IAAuB,EAAE9B,QAA2B,EAAEoD,OAA0B,EAAEC,UAAmB;IACxI,MAAMC,MAAMxB,KAAKpF,MAAM,GACnB,GAAGoF,KAAKtD,IAAI,CAAC,KAAK,cAAc,EAAE9D,kBAAkB,CAAC,EAAE2I,aAAa,KAAK,4CAA4C,GACrH;IACJ,0DAA0D;IAC1D,MAAM3B,UAAUI,KAAKpF,MAAM,GAAG,kEAAkE;IAChG,MAAM6G,OAAOvD,SAAStD,MAAM,GAAG,CAAC,CAAC,EAAEsD,SAASxB,IAAI,CAAC,MAAM,GAAG;IAC1D,MAAMgF,MAAMJ,QAAQ1G,MAAM,GAAG,CAAC,WAAW,EAAE0G,QAAQ5E,IAAI,CAAC,MAAM,GAAG;IACjE,OAAO,GAAG8E,MAAM5B,UAAU6B,OAAOC,KAAK,CAAC3F,KAAK,CAAC,GAAG;AAClD;AAQA,OAAO,SAAS4F,wBAAwBZ,OAA8B,CAAC,CAAC;QACxDA,aACIA;IADlB,MAAMa,SAAQb,cAAAA,KAAKa,KAAK,YAAVb,cAAc1C;IAC5B,MAAMwD,aAAYd,kBAAAA,KAAKc,SAAS,YAAdd,kBAAkBlJ;IACpC,OAAO,OAAO,EAAEiK,GAAG,EAAE1B,GAAG,EAAE9B,SAAS,EAAE6C,GAAG,EAAEY,QAAQ,EAAE;kBAQzB;QAPzB,qEAAqE;QACrE,MAAMC,gBAAQD,4BAAAA,SAAW,8BAAe7J;QACxC,MAAM+J,UAAU,CAACC,UAAuC,aAAK9J,iBAAiB4J;gBAAQE;;QACtF,MAAMvD,SAASmD,IAAInD,MAAM;QACzB,IAAI,CAACA,QAAQ,OAAOsD,QAAQzJ;QAC5B,MAAM6D,OAAOpB,oBAAoB6G,IAAI5G,MAAM;QAC3C,IAAIqB,uBAAuBF,KAAKL,OAAO,GAAG,OAAOiG,QAAQ;YAACtJ;eAA6B0D,KAAKjB,QAAQ;SAAC,CAACsB,IAAI,CAAC;QAC3G,MAAMyF,UAAWhB,cAAAA,OAAQ,QAAA,AAAC,CAAA,MAAM7C,UAAUG,UAAU,CAAC,QAAQC,GAAG,CAACoD,IAAIM,KAAK,EAAEtD,GAAG,EAAC,EAAGxB,IAAI,cAA9D,QAAoE;QAC7F,MAAMW,OAAO,MAAMK,UAAUG,UAAU,CAAC,SAASC,GAAG,CAACC,QAAQG,GAAG;QAChE,MAAMuD,UAAU,MAAMvB,wBAAwB;YAAE7C;YAAMkB,KAAK2C,IAAIQ,SAAS;YAAEnB,KAAKgB;QAAQ,GAAG;YAAElB,UAAUF,KAAKE,QAAQ;QAAC;QACpH,IAAIoB,SAAS,OAAOjK,iBAAiB4J,OAAO;YAAEO,QAAQpK,cAAckK;QAAS;QAC7E,MAAMG,UAAU,MAAMZ,MAAMtD,WAAW;YAAEK;YAAQQ,KAAK2C,IAAIQ,SAAS;YAAEtG,SAASK,KAAKL,OAAO;YAAEoE;QAAI;QAChG,IAAIoC,QAAQxD,EAAE,KAAK,OAAO,OAAOiD,QAAQO,QAAQvD,KAAK;QACtD,IAAIuD,QAAQvC,QAAQ,EAAE;YACpB,sEAAsE;YACtE,4CAA4C;YAC5C,MAAMwC,UAAU,MAAMZ,UAAU;gBAAEa,SAAS;oBAAC/D;iBAAO;gBAAEgE,QAAQ;YAAiC,GAAGC,KAAK,CAAC,IAAO,CAAA;oBAAEC,UAAU;gBAAM,CAAA;YAChI,IAAIJ,QAAQI,QAAQ,EAAE,MAAMvE,UAAUG,UAAU,CAAC,iBAAiBC,GAAG,CAAC8D,QAAQvC,QAAQ,EAAEI,MAAM,GAAGuC,KAAK,CAAC,IAAMjF;QAC/G;QACA,MAAMmF,SAAsB;YAC1BC,UAAU;YACVlC,IAAIhI;YACJ8F;YACAqE,eAAe,AAAC,OAAO/E,KAAKa,GAAG,CAAC,iBAAiB,YAAYb,KAAKa,GAAG,CAAC,gBAAiB;YACvF7F,OAAO;YACPgK,MAAM5B,qBAAqBmB,QAAQxC,IAAI,EAAEwC,QAAQtE,QAAQ,EAAE7B,KAAKjB,QAAQ,EAAEoH,QAAQpD,OAAO,CAACxE,MAAM,GAAG;QACrG;QACA,OAAOxC,iBAAiB4J,OAAO;YAAEkB,SAAS;gBAACJ;aAAO;QAAC;IACrD;AACF;AAEA,OAAO,MAAMK,uBAAuBxB,0BAAyB;AAS7D;;;;CAIC,GACD,OAAO,SAASyB,6BAA6BrC,OAAmC,CAAC,CAAC;QAChEA;IAAhB,MAAMsC,WAAUtC,gBAAAA,KAAKsC,OAAO,YAAZtC,gBAAgBhJ;IAChC,OAAO,OAAOuL;YAK+CA;QAJ3D,IAAI,CAACA,QAAQ3E,MAAM,EAAE,OAAO;YAAE4E,QAAQ;YAAKtE,OAAOzG;QAAyB;QAC3E,MAAMgL,QAAQ,MAAMH,QAAQC,QAAQ3E,MAAM;QAC1C,IAAI,CAAC6E,SAASA,UAAUF,QAAQlB,KAAK,EAAE,OAAO;YAAEmB,QAAQ;YAAKtE,OAAO;QAAe;QACnF,MAAMhB,OAAO,MAAMqF,QAAQhF,SAAS,CAACG,UAAU,CAAC,SAASC,GAAG,CAAC4E,QAAQ3E,MAAM,EAAEG,GAAG;QAChF,MAAMuD,UAAU,MAAMvB,wBAAwB;YAAE7C;YAAMkB,GAAG,GAAEmE,eAAAA,QAAQnE,GAAG,YAAXmE,eAAe;YAAInC,KAAKmC,QAAQnC,GAAG;QAAC,GAAG;YAAEF,UAAUF,KAAKE,QAAQ;QAAC;QAC5H,IAAIoB,SAAS,OAAO;YAAEkB,QAAQ;YAAKtE,OAAOoD;QAAQ;QAClD,MAAMhG,OAAOpB,oBAAoBqI,QAAQpI,MAAM;QAC/C,OAAOqB,uBAAuBF,KAAKL,OAAO,IAAI;YAAEuH,QAAQ;YAAKtE,OAAO;gBAACtG;mBAA6B0D,KAAKjB,QAAQ;aAAC,CAACsB,IAAI,CAAC;QAAK,IAAI;IACjI;AACF;AAEA,OAAO,MAAM+G,yBAAyBL,+BAA8B;AAEpE,yDAAyD,GACzD,OAAO,SAASM;IACdrL,kBAAkBC,kBAAkB6K,sBAAsB;QAAEQ,WAAWpL;IAAgC;IACvGN,uBAAuBK,kBAAkBmL;AAC3C"}
@@ -14,7 +14,7 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- import { AI_JOB_PLAN_STEP, type AiJob, type AiJobApplied, type AiJobItemLedger, type AiJobKind, type AiJobOutput, type AiJobPlan, type AiJobReview, type AiJobSitePublish, type AiJobStatus, type AiJobStepTokens, type AiJobSummary } from '../model/ai-jobs.types';
17
+ import { AI_JOB_PLAN_STEP, type AiJob, type AiJobApplied, type AiJobInputRequest, type AiJobItemLedger, type AiJobKind, type AiJobOutput, type AiJobPlan, type AiJobReview, type AiJobSitePublish, type AiJobStatus, type AiJobStepTokens, type AiJobSummary } from '../model/ai-jobs.types';
18
18
  import type { AiJobCreditsConfirmation } from '../model/ai-credit-estimate';
19
19
  import type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types';
20
20
  import { type AiActivityActor } from '../activity/ai-activity';
@@ -337,6 +337,11 @@ export interface RecordStepInput {
337
337
  plan?: AiJobPlan;
338
338
  /** The step stopped for a person: the job parks `needs_review` in this same write. */
339
339
  review?: AiJobReview;
340
+ /**
341
+ * With `review`: the person must answer these first (AGL-3978), so the job
342
+ * parks `needs_input` with them, the review kept for after the answer.
343
+ */
344
+ input?: AiJobInputRequest;
340
345
  /**
341
346
  * With `pending`: the step is handed back because it made progress and
342
347
  * asked to continue (AGL-2910), not because an attempt failed. Its
@@ -555,6 +560,8 @@ export declare function resumeAiJob(firestore: Firestore, orgId: string, jobId:
555
560
  * the plan the job proposed. Only while that plan waits for confirmation.
556
561
  */
557
562
  reducePlan?: (plan: AiJobPlan) => AiJobPlan | null;
563
+ /** The answers a job waiting in `needs_input` asked for (AGL-3978), by `aiJobInputKey`. */
564
+ input?: Readonly<Record<string, unknown>> | null;
558
565
  }): Promise<{
559
566
  job: AiJob;
560
567
  changed: boolean;