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

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 (274) hide show
  1. package/README.md +3 -3
  2. package/package.json +17 -17
  3. package/src/lib/activity/ai-activity-actions.js +7 -1
  4. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  5. package/src/lib/components/ai-automation-zones.d.ts +38 -0
  6. package/src/lib/components/ai-automation-zones.js +5 -1
  7. package/src/lib/components/ai-automation-zones.js.map +1 -1
  8. package/src/lib/components/ai-brief-dialog.component.d.ts +20 -2
  9. package/src/lib/components/ai-brief-dialog.component.js +87 -8
  10. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  11. package/src/lib/components/ai-campaign-create.component.d.ts +54 -0
  12. package/src/lib/components/ai-campaign-create.component.js +270 -0
  13. package/src/lib/components/ai-campaign-create.component.js.map +1 -0
  14. package/src/lib/components/ai-describe-automation.component.d.ts +8 -3
  15. package/src/lib/components/ai-describe-automation.component.js +15 -6
  16. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  17. package/src/lib/components/ai-describe-button.component.d.ts +20 -8
  18. package/src/lib/components/ai-describe-button.component.js +19 -36
  19. package/src/lib/components/ai-describe-button.component.js.map +1 -1
  20. package/src/lib/components/ai-describe-email.component.d.ts +31 -0
  21. package/src/lib/components/ai-describe-email.component.js +22 -0
  22. package/src/lib/components/ai-describe-email.component.js.map +1 -0
  23. package/src/lib/components/ai-describe-org-automation.component.d.ts +36 -0
  24. package/src/lib/components/ai-describe-org-automation.component.js +238 -0
  25. package/src/lib/components/ai-describe-org-automation.component.js.map +1 -0
  26. package/src/lib/components/ai-experiment-cards.component.d.ts +9 -1
  27. package/src/lib/components/ai-experiment-cards.component.js +104 -18
  28. package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
  29. package/src/lib/components/ai-explain-automation.component.js +1 -5
  30. package/src/lib/components/ai-explain-automation.component.js.map +1 -1
  31. package/src/lib/components/ai-funnel-zones.component.d.ts +61 -0
  32. package/src/lib/components/ai-funnel-zones.component.js +178 -0
  33. package/src/lib/components/ai-funnel-zones.component.js.map +1 -0
  34. package/src/lib/components/ai-insight-card.component.d.ts +42 -0
  35. package/src/lib/components/ai-insight-card.component.js +137 -0
  36. package/src/lib/components/ai-insight-card.component.js.map +1 -0
  37. package/src/lib/components/ai-insight-dialog.component.d.ts +6 -0
  38. package/src/lib/components/ai-insight-dialog.component.js +9 -3
  39. package/src/lib/components/ai-insight-dialog.component.js.map +1 -1
  40. package/src/lib/components/ai-job-plan.component.d.ts +7 -2
  41. package/src/lib/components/ai-job-plan.component.js +35 -5
  42. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  43. package/src/lib/components/ai-job-requests.d.ts +17 -1
  44. package/src/lib/components/ai-job-requests.js +39 -3
  45. package/src/lib/components/ai-job-requests.js.map +1 -1
  46. package/src/lib/components/ai-jobs-list-page.component.js +12 -1
  47. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  48. package/src/lib/components/ai-logic-zones.d.ts +64 -0
  49. package/src/lib/components/ai-logic-zones.js +18 -0
  50. package/src/lib/components/ai-logic-zones.js.map +1 -0
  51. package/src/lib/components/ai-logic.component.d.ts +77 -0
  52. package/src/lib/components/ai-logic.component.js +553 -0
  53. package/src/lib/components/ai-logic.component.js.map +1 -0
  54. package/src/lib/components/ai-marketing-insight.component.d.ts +30 -0
  55. package/src/lib/components/ai-marketing-insight.component.js +80 -0
  56. package/src/lib/components/ai-marketing-insight.component.js.map +1 -0
  57. package/src/lib/components/ai-media-create.component.d.ts +55 -0
  58. package/src/lib/components/ai-media-create.component.js +433 -0
  59. package/src/lib/components/ai-media-create.component.js.map +1 -0
  60. package/src/lib/components/ai-overlay-cards.component.d.ts +83 -0
  61. package/src/lib/components/ai-overlay-cards.component.js +505 -0
  62. package/src/lib/components/ai-overlay-cards.component.js.map +1 -0
  63. package/src/lib/components/ai-product-zones.d.ts +5 -0
  64. package/src/lib/components/ai-product-zones.js +1 -1
  65. package/src/lib/components/ai-product-zones.js.map +1 -1
  66. package/src/lib/components/ai-products-create-button.component.d.ts +42 -0
  67. package/src/lib/components/ai-products-create-button.component.js +67 -0
  68. package/src/lib/components/ai-products-create-button.component.js.map +1 -0
  69. package/src/lib/components/ai-products-hub-card.component.js +100 -81
  70. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  71. package/src/lib/components/ai-revise-automation.component.d.ts +16 -0
  72. package/src/lib/components/ai-revise-automation.component.js +207 -0
  73. package/src/lib/components/ai-revise-automation.component.js.map +1 -0
  74. package/src/lib/components/ai-site-build-page.component.d.ts +1 -1
  75. package/src/lib/components/ai-site-build-page.component.js +177 -20
  76. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  77. package/src/lib/components/ai-site-start-card.component.d.ts +10 -1
  78. package/src/lib/components/ai-site-start-card.component.js +7 -4
  79. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  80. package/src/lib/components/ai-upsell-dialog.component.d.ts +53 -0
  81. package/src/lib/components/ai-upsell-dialog.component.js +177 -0
  82. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -0
  83. package/src/lib/components/assist-build-card.component.d.ts +42 -0
  84. package/src/lib/components/assist-build-card.component.js +183 -0
  85. package/src/lib/components/assist-build-card.component.js.map +1 -0
  86. package/src/lib/components/assist-edit-canvas.d.ts +2 -0
  87. package/src/lib/components/assist-edit-canvas.js +56 -1
  88. package/src/lib/components/assist-edit-canvas.js.map +1 -1
  89. package/src/lib/components/assist-jobs-drawer.component.d.ts +11 -0
  90. package/src/lib/components/assist-jobs-drawer.component.js +85 -4
  91. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  92. package/src/lib/components/assist-panel.component.js +51 -2
  93. package/src/lib/components/assist-panel.component.js.map +1 -1
  94. package/src/lib/components/use-ai-job-run.d.ts +3 -14
  95. package/src/lib/components/use-ai-job-run.js +0 -50
  96. package/src/lib/components/use-ai-job-run.js.map +1 -1
  97. package/src/lib/jobs/ai-build-capabilities.d.ts +58 -0
  98. package/src/lib/jobs/ai-build-capabilities.js +256 -0
  99. package/src/lib/jobs/ai-build-capabilities.js.map +1 -0
  100. package/src/lib/jobs/ai-build-unit-outcome.d.ts +52 -0
  101. package/src/lib/jobs/ai-build-unit-outcome.js +138 -0
  102. package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -0
  103. package/src/lib/jobs/ai-job-build-step.d.ts +144 -0
  104. package/src/lib/jobs/ai-job-build-step.js +657 -0
  105. package/src/lib/jobs/ai-job-build-step.js.map +1 -0
  106. package/src/lib/jobs/ai-job-draft-ids.js +9 -1
  107. package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
  108. package/src/lib/jobs/ai-job-drafts.d.ts +14 -4
  109. package/src/lib/jobs/ai-job-drafts.js +25 -4
  110. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  111. package/src/lib/jobs/ai-job-layout-site-pages.d.ts +70 -0
  112. package/src/lib/jobs/ai-job-layout-site-pages.js +216 -0
  113. package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -0
  114. package/src/lib/jobs/ai-job-layout-step.d.ts +2 -2
  115. package/src/lib/jobs/ai-job-layout-step.js +19 -1
  116. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  117. package/src/lib/jobs/ai-job-logic-step.d.ts +107 -0
  118. package/src/lib/jobs/ai-job-logic-step.js +395 -0
  119. package/src/lib/jobs/ai-job-logic-step.js.map +1 -0
  120. package/src/lib/jobs/ai-job-overlay-copy.d.ts +70 -0
  121. package/src/lib/jobs/ai-job-overlay-copy.js +178 -0
  122. package/src/lib/jobs/ai-job-overlay-copy.js.map +1 -0
  123. package/src/lib/jobs/ai-job-page-step.js +6 -6
  124. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  125. package/src/lib/jobs/ai-job-plan-step.d.ts +26 -1
  126. package/src/lib/jobs/ai-job-plan-step.js +79 -11
  127. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  128. package/src/lib/jobs/ai-job-site-step.d.ts +15 -1
  129. package/src/lib/jobs/ai-job-site-step.js +207 -58
  130. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  131. package/src/lib/jobs/ai-job-text-step.d.ts +22 -1
  132. package/src/lib/jobs/ai-job-text-step.js +12 -0
  133. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  134. package/src/lib/jobs/ai-job-workflow-step.d.ts +54 -4
  135. package/src/lib/jobs/ai-job-workflow-step.js +318 -13
  136. package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
  137. package/src/lib/jobs/ai-jobs.d.ts +90 -2
  138. package/src/lib/jobs/ai-jobs.js +495 -61
  139. package/src/lib/jobs/ai-jobs.js.map +1 -1
  140. package/src/lib/jobs/ai-logic-records.d.ts +40 -0
  141. package/src/lib/jobs/ai-logic-records.js +55 -0
  142. package/src/lib/jobs/ai-logic-records.js.map +1 -0
  143. package/src/lib/jobs/ai-site-publish.d.ts +7 -0
  144. package/src/lib/jobs/ai-site-publish.js +9 -0
  145. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  146. package/src/lib/jobs/ai-workflow-records.d.ts +4 -0
  147. package/src/lib/jobs/ai-workflow-records.js +40 -1
  148. package/src/lib/jobs/ai-workflow-records.js.map +1 -1
  149. package/src/lib/model/ai-automation-draft.d.ts +18 -0
  150. package/src/lib/model/ai-automation-draft.js +86 -0
  151. package/src/lib/model/ai-automation-draft.js.map +1 -1
  152. package/src/lib/model/ai-build-job.d.ts +201 -0
  153. package/src/lib/model/ai-build-job.js +471 -0
  154. package/src/lib/model/ai-build-job.js.map +1 -0
  155. package/src/lib/model/ai-build-plan.d.ts +61 -0
  156. package/src/lib/model/ai-build-plan.js +154 -9
  157. package/src/lib/model/ai-build-plan.js.map +1 -1
  158. package/src/lib/model/ai-build-progress.d.ts +56 -0
  159. package/src/lib/model/ai-build-progress.js +128 -0
  160. package/src/lib/model/ai-build-progress.js.map +1 -0
  161. package/src/lib/model/ai-insight.d.ts +20 -10
  162. package/src/lib/model/ai-insight.js +72 -9
  163. package/src/lib/model/ai-insight.js.map +1 -1
  164. package/src/lib/model/ai-job-activity.js +3 -1
  165. package/src/lib/model/ai-job-activity.js.map +1 -1
  166. package/src/lib/model/ai-job-failure-copy.d.ts +20 -1
  167. package/src/lib/model/ai-job-failure-copy.js +28 -1
  168. package/src/lib/model/ai-job-failure-copy.js.map +1 -1
  169. package/src/lib/model/ai-job-notice.d.ts +3 -0
  170. package/src/lib/model/ai-job-notice.js +11 -2
  171. package/src/lib/model/ai-job-notice.js.map +1 -1
  172. package/src/lib/model/ai-jobs.types.d.ts +87 -2
  173. package/src/lib/model/ai-jobs.types.js +3 -1
  174. package/src/lib/model/ai-jobs.types.js.map +1 -1
  175. package/src/lib/model/ai-logic-job.d.ts +108 -0
  176. package/src/lib/model/ai-logic-job.js +327 -0
  177. package/src/lib/model/ai-logic-job.js.map +1 -0
  178. package/src/lib/model/ai-media-credits.d.ts +52 -0
  179. package/src/lib/model/ai-media-credits.js +61 -0
  180. package/src/lib/model/ai-media-credits.js.map +1 -0
  181. package/src/lib/model/ai-overlay-copy.d.ts +114 -0
  182. package/src/lib/model/ai-overlay-copy.js +175 -0
  183. package/src/lib/model/ai-overlay-copy.js.map +1 -0
  184. package/src/lib/model/ai-site-build-progress.d.ts +28 -3
  185. package/src/lib/model/ai-site-build-progress.js +231 -17
  186. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  187. package/src/lib/model/ai-site-start-seo.d.ts +16 -0
  188. package/src/lib/model/ai-site-start-seo.js +54 -4
  189. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  190. package/src/lib/model/ai-site-start.d.ts +14 -0
  191. package/src/lib/model/ai-site-start.js +43 -0
  192. package/src/lib/model/ai-site-start.js.map +1 -1
  193. package/src/lib/model/ai-svg.d.ts +69 -0
  194. package/src/lib/model/ai-svg.js +265 -0
  195. package/src/lib/model/ai-svg.js.map +1 -0
  196. package/src/lib/model/ai-usage-by-user.d.ts +3 -2
  197. package/src/lib/model/ai-usage-by-user.js.map +1 -1
  198. package/src/lib/model/ai-workflow-job.d.ts +92 -4
  199. package/src/lib/model/ai-workflow-job.js +124 -7
  200. package/src/lib/model/ai-workflow-job.js.map +1 -1
  201. package/src/lib/model/assist-build.d.ts +58 -0
  202. package/src/lib/model/assist-build.js +52 -0
  203. package/src/lib/model/assist-build.js.map +1 -0
  204. package/src/lib/model/assist-edit.d.ts +48 -3
  205. package/src/lib/model/assist-edit.js +48 -4
  206. package/src/lib/model/assist-edit.js.map +1 -1
  207. package/src/lib/plugin-config.js +2 -0
  208. package/src/lib/plugin-config.js.map +1 -1
  209. package/src/lib/plugin.js +240 -11
  210. package/src/lib/plugin.js.map +1 -1
  211. package/src/lib/providers/catalog.d.ts +81 -4
  212. package/src/lib/providers/catalog.js +80 -9
  213. package/src/lib/providers/catalog.js.map +1 -1
  214. package/src/lib/providers/image-contract.d.ts +116 -0
  215. package/src/lib/providers/image-contract.js +74 -0
  216. package/src/lib/providers/image-contract.js.map +1 -0
  217. package/src/lib/providers/model-choice.js +11 -0
  218. package/src/lib/providers/model-choice.js.map +1 -1
  219. package/src/lib/providers/routing.js +18 -0
  220. package/src/lib/providers/routing.js.map +1 -1
  221. package/src/lib/providers/vertex-image.d.ts +121 -0
  222. package/src/lib/providers/vertex-image.js +312 -0
  223. package/src/lib/providers/vertex-image.js.map +1 -0
  224. package/src/lib/runtime/ai-doctrine-validators.d.ts +19 -0
  225. package/src/lib/runtime/ai-doctrine-validators.js +89 -0
  226. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  227. package/src/lib/runtime/ai-doctrine.d.ts +6 -0
  228. package/src/lib/runtime/ai-doctrine.js +16 -6
  229. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  230. package/src/lib/runtime/ai-eval-live.js +4 -0
  231. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  232. package/src/lib/runtime/ai-eval.d.ts +15 -1
  233. package/src/lib/runtime/ai-eval.js +52 -1
  234. package/src/lib/runtime/ai-eval.js.map +1 -1
  235. package/src/lib/runtime/experiment-variant-copy.d.ts +34 -0
  236. package/src/lib/runtime/experiment-variant-copy.js +102 -0
  237. package/src/lib/runtime/experiment-variant-copy.js.map +1 -0
  238. package/src/lib/server/ai-experiment-versions.d.ts +51 -0
  239. package/src/lib/server/ai-experiment-versions.js +270 -0
  240. package/src/lib/server/ai-experiment-versions.js.map +1 -0
  241. package/src/lib/server/ai-jobs-resume.js +74 -4
  242. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  243. package/src/lib/server/ai-media-image.d.ts +66 -0
  244. package/src/lib/server/ai-media-image.js +521 -0
  245. package/src/lib/server/ai-media-image.js.map +1 -0
  246. package/src/lib/server/ai-media-svg.d.ts +91 -0
  247. package/src/lib/server/ai-media-svg.js +186 -0
  248. package/src/lib/server/ai-media-svg.js.map +1 -0
  249. package/src/lib/server/assist-build.d.ts +54 -0
  250. package/src/lib/server/assist-build.js +121 -0
  251. package/src/lib/server/assist-build.js.map +1 -0
  252. package/src/lib/server/assist-chat.js +108 -12
  253. package/src/lib/server/assist-chat.js.map +1 -1
  254. package/src/lib/server/assist-edit.js +213 -3
  255. package/src/lib/server/assist-edit.js.map +1 -1
  256. package/src/lib/server/assist-view-context.js +242 -4
  257. package/src/lib/server/assist-view-context.js.map +1 -1
  258. package/src/lib/server.js +26 -0
  259. package/src/lib/server.js.map +1 -1
  260. package/src/lib/tools/ai-logic-tool.d.ts +27 -0
  261. package/src/lib/tools/ai-logic-tool.js +207 -0
  262. package/src/lib/tools/ai-logic-tool.js.map +1 -0
  263. package/src/lib/tools/ai-overlay-tool.d.ts +35 -0
  264. package/src/lib/tools/ai-overlay-tool.js +107 -0
  265. package/src/lib/tools/ai-overlay-tool.js.map +1 -0
  266. package/src/lib/tools/ai-workflow-tool.d.ts +11 -0
  267. package/src/lib/tools/ai-workflow-tool.js +31 -0
  268. package/src/lib/tools/ai-workflow-tool.js.map +1 -1
  269. package/src/lib/usage/assist-job-refund.d.ts +31 -3
  270. package/src/lib/usage/assist-job-refund.js +46 -10
  271. package/src/lib/usage/assist-job-refund.js.map +1 -1
  272. package/src/lib/usage/assist-usage.d.ts +5 -0
  273. package/src/lib/usage/assist-usage.js +2 -0
  274. package/src/lib/usage/assist-usage.js.map +1 -1
@@ -865,6 +865,37 @@ function readStep(reader, raw, index, event, capabilities) {
865
865
  unsupported: null
866
866
  });
867
867
  }
868
+ /**
869
+ * An answer already read, held as well to an org automation's narrower
870
+ * vocabulary (AGL-3603): its trigger one the vocabulary names, and every step
871
+ * a type it names. A refusal names what the vocabulary holds, so the re-ask
872
+ * can pick from it. An answer that says the automation cannot be built is
873
+ * left as it is.
874
+ */ export function holdAiAutomationAnswerToVocabulary(result, vocabulary) {
875
+ var _result_offending;
876
+ const answer = result.value;
877
+ if (!answer || answer.unsupported) return result;
878
+ const reader = {
879
+ violations: [
880
+ ...result.violations
881
+ ],
882
+ offending: Object.entries((_result_offending = result.offending) != null ? _result_offending : {})
883
+ };
884
+ if (!vocabulary.triggers.includes(answer.trigger.event)) {
885
+ refuse(reader, 'automation-trigger', `An org automation starts only on one of: ${vocabulary.triggers.join(', ')}. Pick one of them, or answer with unsupported "no-trigger".`, 'trigger.event', answer.trigger.event);
886
+ }
887
+ answer.steps.forEach((step, index)=>{
888
+ if (vocabulary.steps.includes(step.type)) return;
889
+ refuse(reader, 'automation-step-type', `An org automation uses only these steps: ${vocabulary.steps.join(', ')}. Leave "${step.type}" out, or answer with unsupported "no-step".`, `steps[${index}]`, step.type);
890
+ });
891
+ if (reader.violations.length === result.violations.length) return result;
892
+ return _extends({
893
+ value: null,
894
+ violations: reader.violations
895
+ }, reader.offending.length ? {
896
+ offending: Object.fromEntries(reader.offending)
897
+ } : {});
898
+ }
868
899
  /** Whether a line carries an email address, which nothing an explanation was shown holds. */ const CARRIES_ADDRESS = /[^\s@]+@[^\s@]+\.[^\s@]+/;
869
900
  function lines(value, max) {
870
901
  return (Array.isArray(value) ? value : []).map(textOf).filter((line)=>line !== null).slice(0, max).map((line)=>line.slice(0, AI_WORKFLOW_LINE_MAX_CHARS));
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/tools/ai-workflow-tool.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n ACTION_MAX_CONDITIONS,\n ACTION_MAX_STEPS,\n TRIGGER_COMBINATORS,\n TRIGGER_CONDITION_OPS,\n type TriggerCombinator,\n type TriggerConditionOp,\n} from '@aglyn/aglyn/app-utils/site-interactions'\nimport {\n CONTACT_LIFECYCLE_STAGE_LABELS,\n CONTACT_LIFECYCLE_STAGES,\n CONTACT_TAG_MAX_LENGTH,\n CRM_ACTIVITY_DIRECTIONS,\n CRM_ACTIVITY_KINDS,\n CRM_TASK_KINDS,\n CRM_TASK_MAX_DUE_DAYS,\n type ContactLifecycleStage,\n type CrmActivityDirection,\n type CrmActivityKind,\n type CrmTaskKind,\n type CrmTaskPriority,\n} from '@aglyn/aglyn/app-utils/crm'\nimport {\n HOST_EVENT_PAYLOAD_KEYS,\n HOST_EVENT_TYPES,\n type HostEventType,\n} from '@aglyn/aglyn/app-utils/host-events'\nimport {\n AI_AUTOMATION_NEED_LABELS,\n AI_AUTOMATION_STEP_NEEDS,\n AI_AUTOMATION_STEP_TYPES,\n AI_AUTOMATION_TRIGGER_NEEDS,\n AI_AUTOMATION_UNSUPPORTED,\n AI_AUTOMATION_UNSUPPORTED_NEED,\n aiAutomationTriggerLabel,\n type AiAutomationCapabilities,\n type AiAutomationStepType,\n type AiAutomationUnsupported,\n} from '../model/ai-workflow-job'\nimport {\n AI_AUTOMATION_TIMED_OUT_FIELD,\n AI_AUTOMATION_WAIT_MINUTES,\n aiStepLabel,\n} from '../model/ai-automation-format'\nimport type { AiTool } from '../providers/contract'\nimport type { AiGenerationCheckResult } from '../runtime/ai-doctrine'\nimport type { AiDoctrineViolation } from '../runtime/ai-doctrine-validators'\nimport { HOSTILE_TEXT } from '../runtime/ai-node-tree'\n\n/**\n * The two tools a `workflow` job answers through (AGL-2919).\n *\n * `submit_automation` carries one automation in the Actions editor's own\n * vocabulary — a trigger from the host events, conditions over the fields the\n * trigger carries, and steps from the automation step types — with every\n * record it names written as WORDS. The step resolves those words against the\n * site after the answer, so the model is never shown the site's lists,\n * campaigns, workflows, webhooks or pipeline stages, and never writes an id it\n * could only have invented.\n *\n * `submit_explanation` carries a plain-words account of one automation, or of\n * why one of its runs failed.\n *\n * Both schemas are strict: every object forbids extra keys and requires every\n * key it has, and nothing in either is a `null`. Where a step may go without\n * something, the absence is an empty list or an empty string: a step that\n * always runs has an empty `when`, a notEmpty condition compares against\n * nothing, and an answer that builds an automation leaves `unsupported`\n * empty. Bounds a strict schema cannot state are stated in the descriptions\n * and held by the readers below, whose refusals name the field so the one\n * re-ask can fix it.\n *\n * A provider that constrains decoding compiles every strict schema of a\n * request into a grammar, and refuses one whose grammar grows past its size\n * (`AiToolSchemaLimits`). So the steps' union carries as little repetition as\n * a grammar can be made to hold:\n *\n * - a step is its guard and its action, `{ when, action }`, so the condition\n * is compiled once for the whole union instead of once per variant;\n * - a variant serves every step type that carries the same fields, named for\n * what they hold: the six steps that name a record share `reference`, and\n * the three that carry one line share `text`;\n * - every field stays required and every vocabulary but one stays an enum,\n * so the model is still decoded into the Actions editor's words. The one\n * is the event a waitForEvent step waits for: the trigger's `event` already\n * compiles the host events' enum, and a second copy of it is what a task's\n * priority and a logged call's direction cost (AGL-3538). It is a string\n * described as one of the trigger events, and the reader refuses any\n * other naming every event, so the one re-ask can fix it;\n * - a description is free: a compiler drops it (`aiToolSchemaCompiledBytes`),\n * so the words a field needs go there and never into the grammar.\n *\n * Ten variants carry all seventeen step types, and the reader below maps the\n * shared fields back onto the step's own key.\n */\n\nexport const AI_AUTOMATION_TOOL_NAME = 'submit_automation'\nexport const AI_WORKFLOW_EXPLANATION_TOOL_NAME = 'submit_explanation'\n\n/** The longest automation name the Actions editor stores. */\nexport const AI_AUTOMATION_NAME_MAX_CHARS = 60\n/** The longest subject the executor sends. */\nexport const AI_AUTOMATION_SUBJECT_MAX_CHARS = 200\n/** The longest email text the executor sends. */\nexport const AI_AUTOMATION_BODY_MAX_CHARS = 5_000\n/** The longest title, message or record name a step carries. */\nexport const AI_AUTOMATION_TEXT_MAX_CHARS = 300\n/** The longest words naming a record. */\nexport const AI_AUTOMATION_REFERENCE_MAX_CHARS = 100\n/** The longest condition value. */\nexport const AI_AUTOMATION_VALUE_MAX_CHARS = 200\n/** The most notes an answer keeps, and the longest. */\nexport const AI_AUTOMATION_NOTES_MAX = 3\nexport const AI_AUTOMATION_NOTE_MAX_CHARS = 300\n/**\n * The longest a whole answer may be, written out as JSON: what the routing\n * ceiling for `job.workflow` holds at three characters a token with as much\n * again to think in (`ai-job-workflow-step.spec.ts` measures it). Ten steps\n * with two emails of a paragraph or two each come to about 2,400, each step\n * carrying only its own fields.\n */\nexport const AI_AUTOMATION_ANSWER_MAX_CHARS = 6_000\n\n/** The explanation's bounds. */\nexport const AI_WORKFLOW_SUMMARY_MAX_CHARS = 400\nexport const AI_WORKFLOW_POINTS_MAX = 8\nexport const AI_WORKFLOW_SUGGESTIONS_MAX = 4\nexport const AI_WORKFLOW_LINE_MAX_CHARS = 300\n\nconst SEVERITIES = ['info', 'success', 'warning', 'error'] as const\ntype Severity = (typeof SEVERITIES)[number]\n\n/** A task's priorities, by meaning — the runner stores the org's label for each (AGL-3517). */\nconst TASK_PRIORITIES = ['low', 'normal', 'high'] as const satisfies readonly CrmTaskPriority[]\n\n/** Every direction any activity takes, and `''` for a kind that takes none (AGL-3538). */\nconst ACTIVITY_DIRECTIONS = [\n '',\n ...new Set(Object.values(CRM_ACTIVITY_DIRECTIONS).flatMap((directions) => [...(directions ?? [])])),\n] as const\n\n/** The events a waitForEvent step may wait for, as its refusal names them. */\nconst WAITABLE_EVENTS = HOST_EVENT_TYPES.join(', ')\n\ntype Schema = Record<string, unknown>\n\n/**\n * What the schema defines once and refers to where it is used: a condition,\n * which the trigger and every step's guard share, and the guard itself.\n */\nconst DEFINITIONS: Schema = {\n condition: {\n type: 'object',\n additionalProperties: false,\n required: ['field', 'op', 'value'],\n properties: {\n field: {\n type: 'string',\n description: 'The field of the event the condition reads, as the trigger lists its fields.',\n },\n op: { type: 'string', enum: [...TRIGGER_CONDITION_OPS] },\n value: {\n type: 'string',\n description: 'What equals or contains compares against; empty for notEmpty.',\n },\n },\n },\n event: {\n type: 'string',\n enum: [...HOST_EVENT_TYPES],\n description: 'One of the things that happen on a site.',\n },\n // A list rather than a condition or `null`: a step goes without one by an\n // empty list, which costs no union.\n when: {\n type: 'array',\n description: 'At most one condition: the step runs only when it holds. Empty when the step always runs.',\n items: { $ref: '#/$defs/condition' },\n },\n}\n\n/** The steps that name one of the site's records, which they all name the same way. */\nconst REFERENCE_STEPS = [\n 'enrollList',\n 'assignCampaign',\n 'runWorkflow',\n 'datasetAppend',\n 'updateDataset',\n 'webhookPost',\n] as const satisfies readonly AiAutomationStepType[]\n\n/** The steps whose only field is the one line they carry. */\nconst TEXT_STEPS = [\n 'notifyAdmins',\n 'addContactTag',\n 'assignContactOwner',\n] as const satisfies readonly AiAutomationStepType[]\n\n/** The key each `reference` step fills in the answer the reader gives back. */\nconst REFERENCE_KEY = {\n enrollList: 'list',\n assignCampaign: 'campaign',\n runWorkflow: 'workflow',\n datasetAppend: 'dataset',\n updateDataset: 'dataset',\n webhookPost: 'webhook',\n} as const satisfies Readonly<Record<(typeof REFERENCE_STEPS)[number], keyof AiAutomationAnswerStep>>\n\n/**\n * The key each step's one line fills in the answer the reader gives back. Six\n * step types carry one line between them, so the schema asks for all six\n * under `text` and the reader puts each back where the automation keeps it.\n */\nconst TEXT_KEY = {\n notifyAdmins: 'title',\n addContactTag: 'tag',\n assignContactOwner: 'owner',\n siteAlert: 'message',\n createCrmTask: 'title',\n logCrmActivity: 'body',\n} as const satisfies Readonly<Partial<Record<AiAutomationStepType, keyof AiAutomationAnswerStep>>>\n\nconst minutes = (what: string): Schema => ({\n type: 'integer',\n description: `${what}, in whole minutes from ${AI_AUTOMATION_WAIT_MINUTES.min} to ${AI_AUTOMATION_WAIT_MINUTES.max}; an hour is 60 and a day 1440.`,\n})\n\n/**\n * One variant of the steps' union: the step types it serves, and the fields\n * every one of them carries, each required and never nullable. A type belongs\n * to exactly one variant, so the type a step names settles which fields it\n * writes.\n */\nfunction stepVariant(\n types: readonly AiAutomationStepType[],\n fields: Readonly<Record<string, Schema>>,\n): Schema {\n return {\n type: 'object',\n additionalProperties: false,\n required: ['type', ...Object.keys(fields)],\n properties: { type: { type: 'string', enum: [...types] }, ...fields },\n }\n}\n\n/** Every step type an automation may carry, grouped by the fields it needs. */\nexport const AI_AUTOMATION_STEP_VARIANTS: readonly Schema[] = [\n stepVariant([...REFERENCE_STEPS], {\n reference: {\n type: 'string',\n description:\n 'The record the step acts on — the email list, campaign, workflow, dataset or outbound webhook — in the words of the description, such as \"newsletter\". Never an id.',\n },\n }),\n stepVariant([...TEXT_STEPS], {\n text: {\n type: 'string',\n description: `The one line the step carries: for notifyAdmins what the admins read, for addContactTag the tag (at most ${CONTACT_TAG_MAX_LENGTH} characters), and for assignContactOwner \"round robin\" or the teammate’s email address the description gives.`,\n },\n }),\n stepVariant(['sendEmail'], {\n subject: { type: 'string', description: 'The subject line.' },\n body: { type: 'string', description: 'The email, as plain text.' },\n toField: {\n type: 'string',\n description: 'The event field holding the address: \"email\" unless the description names another.',\n },\n }),\n stepVariant(['siteAlert'], {\n text: { type: 'string', description: 'What the visitor reads.' },\n severity: { type: 'string', enum: [...SEVERITIES] },\n }),\n stepVariant(['wait'], { minutes: minutes('How long to wait') }),\n stepVariant(['waitForEvent'], {\n // A string, not the events' enum again — see the header.\n event: { type: 'string', description: 'One of the trigger events.' },\n minutes: minutes('When to give up'),\n }),\n stepVariant(['exitFlow'], {}),\n stepVariant(['setContactStage'], { stage: { type: 'string', enum: [...CONTACT_LIFECYCLE_STAGES] } }),\n stepVariant(['createCrmTask'], {\n text: { type: 'string', description: 'The task.' },\n taskKind: { type: 'string', enum: [...CRM_TASK_KINDS] },\n priority: { type: 'string', enum: [...TASK_PRIORITIES] },\n dueInDays: {\n type: 'integer',\n description: `Days from the run, 0 for today, at most ${CRM_TASK_MAX_DUE_DAYS}.`,\n },\n }),\n stepVariant(['logCrmActivity'], {\n activityKind: { type: 'string', enum: [...CRM_ACTIVITY_KINDS] },\n direction: {\n type: 'string',\n enum: [...ACTIVITY_DIRECTIONS],\n description: 'A call: outbound, inbound or internal. An email: outbound or inbound. Else empty.',\n },\n text: { type: 'string', description: 'What happened.' },\n }),\n]\n\n/** The automation tool. The same bytes on every request, so it sits inside the cached prefix. */\nexport function aiAutomationTool(): AiTool {\n return {\n name: AI_AUTOMATION_TOOL_NAME,\n description: `Submit one automation: its trigger, the conditions on it, and its steps in order. Written out, the whole automation is at most ${AI_AUTOMATION_ANSWER_MAX_CHARS} characters, so keep its emails short.`,\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['name', 'trigger', 'steps', 'notes', 'unsupported'],\n properties: {\n name: {\n type: 'string',\n description: `A short name for the automation, at most ${AI_AUTOMATION_NAME_MAX_CHARS} characters.`,\n },\n trigger: {\n type: 'object',\n additionalProperties: false,\n required: ['event', 'conditions', 'combinator'],\n properties: {\n event: { $ref: '#/$defs/event' },\n conditions: {\n type: 'array',\n description: `What the event must meet: at most ${ACTION_MAX_CONDITIONS} conditions, empty when it runs every time.`,\n items: { $ref: '#/$defs/condition' },\n },\n combinator: { type: 'string', enum: [...TRIGGER_COMBINATORS] },\n },\n },\n steps: {\n type: 'array',\n description: `The steps in order, at most ${ACTION_MAX_STEPS}. Each is when it runs and what it does, and the action carries only the fields its type takes. Empty only with unsupported.`,\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['when', 'action'],\n properties: {\n when: { $ref: '#/$defs/when' },\n action: { anyOf: AI_AUTOMATION_STEP_VARIANTS },\n },\n },\n },\n notes: {\n type: 'array',\n description: `What the person must decide that the automation cannot hold; at most ${AI_AUTOMATION_NOTES_MAX} short sentences.`,\n items: { type: 'string' },\n },\n unsupported: {\n type: 'string',\n enum: ['', ...AI_AUTOMATION_UNSUPPORTED],\n description: 'Why no automation can be built from this description; empty when one is built.',\n },\n },\n $defs: DEFINITIONS,\n },\n }\n}\n\n/** The explanation tool. */\nexport function aiWorkflowExplanationTool(): AiTool {\n return {\n name: AI_WORKFLOW_EXPLANATION_TOOL_NAME,\n description: 'Submit the explanation, in plain words for someone who does not write code.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['summary', 'points', 'suggestions'],\n properties: {\n summary: {\n type: 'string',\n description: `One or two sentences, at most ${AI_WORKFLOW_SUMMARY_MAX_CHARS} characters.`,\n },\n points: {\n type: 'array',\n description: `At most ${AI_WORKFLOW_POINTS_MAX}, one sentence each.`,\n items: { type: 'string' },\n },\n suggestions: {\n type: 'array',\n description: `At most ${AI_WORKFLOW_SUGGESTIONS_MAX}, one sentence each; empty when there is nothing to suggest.`,\n items: { type: 'string' },\n },\n },\n },\n }\n}\n\n// ── Reading an automation ─────────────────────────────────────────────────\n\nexport interface AiAutomationCondition {\n field: string\n op: TriggerConditionOp\n /** `null` for `notEmpty`. */\n value: string | null\n}\n\n/** One step as the reader gives it back, every field the step does not use `null`. */\nexport interface AiAutomationAnswerStep {\n type: AiAutomationStepType\n when: AiAutomationCondition | null\n list: string | null\n campaign: string | null\n workflow: string | null\n webhook: string | null\n dataset: string | null\n subject: string | null\n body: string | null\n toField: string | null\n title: string | null\n message: string | null\n severity: Severity | null\n minutes: number | null\n event: HostEventType | null\n stage: ContactLifecycleStage | null\n tag: string | null\n owner: string | null\n taskKind: CrmTaskKind | null\n /** A created task's priority, by meaning (AGL-3538). */\n priority: CrmTaskPriority | null\n dueInDays: number | null\n activityKind: CrmActivityKind | null\n /** Which way a logged call or email went (AGL-3538); `null` for every other kind. */\n direction: CrmActivityDirection | null\n}\n\nexport interface AiAutomationAnswer {\n name: string\n trigger: {\n event: HostEventType\n conditions: AiAutomationCondition[]\n combinator: TriggerCombinator\n }\n steps: AiAutomationAnswerStep[]\n notes: string[]\n unsupported: AiAutomationUnsupported | null\n}\n\n/** The codes that say an answer could not be read as an automation at all. */\nexport const AI_AUTOMATION_UNREADABLE_CODES: readonly string[] = [\n 'automation-shape',\n 'automation-trigger',\n 'automation-step-type',\n]\n\n/** The codes that say an answer is larger than an automation may be. */\nexport const AI_AUTOMATION_OVERSIZE_CODES: readonly string[] = [\n 'automation-too-many-steps',\n 'automation-too-many-conditions',\n 'automation-too-long',\n]\n\n/** The address field a step reads when it names none. */\nconst DEFAULT_TO_FIELD = 'email'\nconst FIELD_NAME = /^[A-Za-z_][A-Za-z0-9_.-]{0,63}$/\nconst EMAIL_ADDRESS = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\nconst ROUND_ROBIN = /^\\s*round[\\s-]*robin\\s*$/i\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** A string trimmed to one space between words, or `null` for none. */\nfunction textOf(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const clean = value.replace(/[ \\t]+/g, ' ').trim()\n return clean ? clean : null\n}\n\n/** The fields a trigger's conditions and its steps' guards may read. */\nfunction fieldsFor(event: HostEventType): readonly string[] | null {\n // A form submission carries every field the form collects, which no fixed\n // list names, so any field name is readable on it.\n if (event === 'formSubmission') return null\n const keys = HOST_EVENT_PAYLOAD_KEYS[event]\n return keys?.length ? keys : null\n}\n\ninterface Reader {\n violations: AiDoctrineViolation[]\n offending: Array<[string, unknown]>\n}\n\nfunction refuse(\n reader: Reader,\n code: string,\n message: string,\n path: string,\n value: unknown,\n): void {\n reader.violations.push({ rule: null, code, message, paths: [path] })\n if (reader.offending.length < 6) reader.offending.push([path, value])\n}\n\nfunction readCondition(\n reader: Reader,\n raw: unknown,\n path: string,\n event: HostEventType,\n guard: boolean,\n): AiAutomationCondition | null {\n if (!isRecord(raw)) {\n refuse(reader, 'automation-shape', 'A condition is not an object with a field, an op and a value.', path, raw)\n return null\n }\n const field = textOf(raw['field'])\n const op = raw['op']\n if (!field || !FIELD_NAME.test(field)) {\n refuse(reader, 'automation-condition', 'Name the event field the condition reads.', path, raw)\n return null\n }\n if (!(TRIGGER_CONDITION_OPS as readonly unknown[]).includes(op)) {\n refuse(reader, 'automation-condition', `The condition on \"${field}\" needs equals, contains or notEmpty.`, path, raw)\n return null\n }\n if (event === 'formSubmission' && field === 'formId') {\n refuse(\n reader,\n 'automation-condition',\n 'A form submission names its form by formName, the form’s name as the site lists it.',\n path,\n raw,\n )\n return null\n }\n // A step's guard may also read whether the wait before it ran out of time.\n const readable = fieldsFor(event)\n if (readable && !readable.includes(field) && !(guard && field === AI_AUTOMATION_TIMED_OUT_FIELD)) {\n refuse(\n reader,\n 'automation-condition',\n `The trigger \"${aiAutomationTriggerLabel(event)}\" does not carry \"${field}\". It carries: ${readable.join(', ')}.`,\n path,\n raw,\n )\n return null\n }\n const value = textOf(raw['value'])\n if (op !== 'notEmpty' && !value) {\n refuse(reader, 'automation-condition', `The condition on \"${field}\" needs the value it compares against.`, path, raw)\n return null\n }\n if (value && value.length > AI_AUTOMATION_VALUE_MAX_CHARS) {\n refuse(reader, 'automation-too-long', `The value the condition on \"${field}\" compares against is too long.`, path, raw)\n return null\n }\n if (field === 'lifecycleStage' || field === 'previousStage') {\n const stage = value ? lifecycleStageOf(value) : null\n if (op !== 'notEmpty' && !stage) {\n refuse(\n reader,\n 'automation-condition',\n `A lifecycle stage is one of: ${CONTACT_LIFECYCLE_STAGES.join(', ')}.`,\n path,\n raw,\n )\n return null\n }\n return { field, op: op as TriggerConditionOp, value: op === 'notEmpty' ? null : stage }\n }\n return { field, op: op as TriggerConditionOp, value: op === 'notEmpty' ? null : value }\n}\n\n/** A lifecycle stage by its id or its label, case aside. */\nexport function lifecycleStageOf(value: string): ContactLifecycleStage | null {\n const wanted = value.trim().toLowerCase()\n return (\n CONTACT_LIFECYCLE_STAGES.find(\n (stage) => stage === wanted || CONTACT_LIFECYCLE_STAGE_LABELS[stage].toLowerCase() === wanted,\n ) ?? null\n )\n}\n\nfunction requireText(\n reader: Reader,\n step: Record<string, unknown>,\n key: string,\n max: number,\n path: string,\n what: string,\n): string | null {\n const value = textOf(step[key])\n if (!value) {\n refuse(reader, 'automation-step-field', `${what} needs ${key}.`, path, step)\n return null\n }\n if (value.length > max) {\n refuse(reader, 'automation-too-long', `${what}: ${key} is longer than ${max} characters.`, path, step)\n return null\n }\n if (HOSTILE_TEXT.test(value)) {\n refuse(reader, 'automation-markup', `${what}: write ${key} as plain text, with no HTML or script.`, path, step)\n return null\n }\n return value\n}\n\nfunction wholeNumber(value: unknown): number | null {\n return typeof value === 'number' && Number.isInteger(value) ? value : null\n}\n\nfunction readStep(\n reader: Reader,\n raw: unknown,\n index: number,\n event: HostEventType,\n capabilities: AiAutomationCapabilities,\n): AiAutomationAnswerStep | null {\n const path = `steps[${index}]`\n if (!isRecord(raw)) {\n refuse(reader, 'automation-shape', `Step ${index + 1} is not an object.`, path, raw)\n return null\n }\n // A step is its guard and its action: `{ when, action }`. An answer that\n // writes the action's own fields beside `when` reads the same, so a model\n // that flattens the step does not spend the one re-ask.\n const action = raw['action']\n const fields: Record<string, unknown> = isRecord(action) ? { ...action, when: raw['when'] } : raw\n const type = fields['type']\n if (!(AI_AUTOMATION_STEP_TYPES as readonly unknown[]).includes(type)) {\n refuse(\n reader,\n 'automation-step-type',\n `Step ${index + 1} is not one of the steps offered: ${AI_AUTOMATION_STEP_TYPES.join(', ')}.`,\n path,\n fields,\n )\n return null\n }\n const stepType = type as AiAutomationStepType\n const what = `Step ${index + 1} (${aiStepLabel(stepType)})`\n const need = AI_AUTOMATION_STEP_NEEDS[stepType]\n if (need && !capabilities[need]) {\n refuse(\n reader,\n 'automation-plan',\n `${what} needs ${AI_AUTOMATION_NEED_LABELS[need]}, which this workspace does not have. Leave it out, or answer with unsupported \"needs-${need}\".`,\n path,\n fields,\n )\n return null\n }\n const before = reader.violations.length\n const step: AiAutomationAnswerStep = {\n type: stepType,\n when: null,\n list: null,\n campaign: null,\n workflow: null,\n webhook: null,\n dataset: null,\n subject: null,\n body: null,\n toField: null,\n title: null,\n message: null,\n severity: null,\n minutes: null,\n event: null,\n stage: null,\n tag: null,\n owner: null,\n taskKind: null,\n priority: null,\n dueInDays: null,\n activityKind: null,\n direction: null,\n }\n // A step's `when` is a list of at most one condition; a lone condition, or\n // none, reads as the list it stands for.\n const when = fields['when']\n const guards = Array.isArray(when) ? when : when === null || when === undefined ? [] : [when]\n if (guards.length > 1) {\n refuse(reader, 'automation-condition', `${what}: when holds one condition at most.`, `${path}.when`, when)\n } else if (guards.length === 1) {\n step.when = readCondition(reader, guards[0], `${path}.when`, event, true)\n }\n // The six steps that name a record share `reference`, and the three that\n // carry one line share `text`; an answer that writes the step's own key\n // instead — `list`, `tag`, `owner` — reads the same.\n const shared = (key: 'reference' | 'text', own: keyof AiAutomationAnswerStep): unknown =>\n fields[key] ?? fields[own]\n const own = TEXT_KEY[stepType as keyof typeof TEXT_KEY]\n const text: Record<string, unknown> = own ? { text: shared('text', own) } : fields\n switch (stepType) {\n case 'sendEmail': {\n step.subject = requireText(reader, fields, 'subject', AI_AUTOMATION_SUBJECT_MAX_CHARS, path, what)\n step.body = requireText(reader, fields, 'body', AI_AUTOMATION_BODY_MAX_CHARS, path, what)\n const toField = textOf(fields['toField'])\n if (toField && !FIELD_NAME.test(toField)) {\n refuse(reader, 'automation-step-field', `${what}: toField names the event field holding the address.`, path, fields)\n } else {\n step.toField = toField && toField !== DEFAULT_TO_FIELD ? toField : null\n }\n break\n }\n case 'notifyAdmins':\n step.title = requireText(reader, text, 'text', AI_AUTOMATION_SUBJECT_MAX_CHARS, path, what)\n break\n case 'enrollList':\n case 'assignCampaign':\n case 'runWorkflow':\n case 'webhookPost':\n case 'datasetAppend':\n case 'updateDataset':\n step[REFERENCE_KEY[stepType]] = requireText(\n reader,\n { reference: shared('reference', REFERENCE_KEY[stepType]) },\n 'reference',\n AI_AUTOMATION_REFERENCE_MAX_CHARS,\n path,\n what,\n )\n break\n case 'siteAlert': {\n step.message = requireText(reader, text, 'text', AI_AUTOMATION_TEXT_MAX_CHARS, path, what)\n const severity = fields['severity']\n step.severity = (SEVERITIES as readonly unknown[]).includes(severity) ? (severity as Severity) : 'info'\n break\n }\n case 'wait':\n case 'waitForEvent': {\n const minutes = wholeNumber(fields['minutes'])\n if (minutes === null || minutes < AI_AUTOMATION_WAIT_MINUTES.min || minutes > AI_AUTOMATION_WAIT_MINUTES.max) {\n refuse(\n reader,\n 'automation-step-field',\n `${what} needs minutes, a whole number from ${AI_AUTOMATION_WAIT_MINUTES.min} to ${AI_AUTOMATION_WAIT_MINUTES.max}.`,\n path,\n fields,\n )\n } else {\n step.minutes = minutes\n }\n if (stepType === 'waitForEvent') {\n const waited = fields['event']\n if (!(HOST_EVENT_TYPES as readonly unknown[]).includes(waited)) {\n refuse(\n reader,\n 'automation-step-field',\n `${what} needs the event it waits for, one of: ${WAITABLE_EVENTS}.`,\n path,\n fields,\n )\n } else {\n step.event = waited as HostEventType\n }\n }\n break\n }\n case 'exitFlow':\n break\n case 'setContactStage': {\n const stage = fields['stage']\n if (!(CONTACT_LIFECYCLE_STAGES as readonly unknown[]).includes(stage)) {\n refuse(reader, 'automation-step-field', `${what} needs the lifecycle stage to set.`, path, fields)\n } else {\n step.stage = stage as ContactLifecycleStage\n }\n break\n }\n case 'addContactTag':\n step.tag = requireText(reader, text, 'text', CONTACT_TAG_MAX_LENGTH, path, what)\n break\n case 'assignContactOwner': {\n const owner = textOf(text['text'])\n if (!owner || !(ROUND_ROBIN.test(owner) || EMAIL_ADDRESS.test(owner))) {\n refuse(\n reader,\n 'automation-owner',\n `${what}: give the owner as \"round robin\", or as the teammate’s email address the description gives. A teammate named without one goes in notes, not in a step.`,\n path,\n fields,\n )\n } else {\n step.owner = ROUND_ROBIN.test(owner) ? 'round robin' : owner\n }\n break\n }\n case 'createCrmTask': {\n step.title = requireText(reader, text, 'text', AI_AUTOMATION_SUBJECT_MAX_CHARS, path, what)\n const kind = fields['taskKind']\n const due = wholeNumber(fields['dueInDays'])\n if (!(CRM_TASK_KINDS as readonly unknown[]).includes(kind)) {\n refuse(reader, 'automation-step-field', `${what} needs taskKind.`, path, fields)\n } else {\n step.taskKind = kind as CrmTaskKind\n }\n // Absent reads as normal, which the runner writes when none is named.\n const priority = fields['priority']\n if (priority !== undefined && !(TASK_PRIORITIES as readonly unknown[]).includes(priority)) {\n refuse(reader, 'automation-step-field', `${what} needs priority: low, normal or high.`, path, fields)\n } else if (priority !== undefined) {\n step.priority = priority as CrmTaskPriority\n }\n if (due === null || due < 0 || due > CRM_TASK_MAX_DUE_DAYS) {\n refuse(reader, 'automation-step-field', `${what} needs dueInDays, from 0 to ${CRM_TASK_MAX_DUE_DAYS}.`, path, fields)\n } else {\n step.dueInDays = due\n }\n break\n }\n case 'logCrmActivity': {\n const kind = fields['activityKind']\n if (!(CRM_ACTIVITY_KINDS as readonly unknown[]).includes(kind)) {\n refuse(reader, 'automation-step-field', `${what} needs activityKind.`, path, fields)\n } else {\n step.activityKind = kind as CrmActivityKind\n /*\n * The direction is the kind's own: a call goes outbound, inbound or\n * internal, an email outbound or inbound, and every other kind\n * carries none (AGL-3538).\n */\n const allowed: readonly string[] = CRM_ACTIVITY_DIRECTIONS[kind as CrmActivityKind] ?? []\n // Absent reads as empty: no direction.\n const raw = fields['direction'] ?? ''\n const direction = typeof raw === 'string' ? raw : null\n if (direction === null || (direction ? !allowed.includes(direction) : false)) {\n refuse(\n reader,\n 'automation-step-field',\n allowed.length\n ? `${what} needs direction: ${allowed.join(', ')}, or empty.`\n : `${what} takes no direction: leave it empty for a ${String(kind)}.`,\n path,\n fields,\n )\n } else if (direction) {\n step.direction = direction as CrmActivityDirection\n }\n }\n step.body = requireText(reader, text, 'text', AI_AUTOMATION_TEXT_MAX_CHARS, path, what)\n break\n }\n }\n return reader.violations.length === before ? step : null\n}\n\n/**\n * The automation tool's answer read and held to the vocabulary and to what\n * this workspace can run — the check the doctrine's loop runs on each answer,\n * beside rule 13. `value` is the automation when nothing is refused.\n */\nexport function readAiAutomationAnswer(\n answer: Record<string, unknown>,\n capabilities: AiAutomationCapabilities,\n): AiGenerationCheckResult<AiAutomationAnswer> {\n const reader: Reader = { violations: [], offending: [] }\n const done = (value: AiAutomationAnswer | null): AiGenerationCheckResult<AiAutomationAnswer> => ({\n value: reader.violations.length ? null : value,\n violations: reader.violations,\n ...(reader.offending.length ? { offending: Object.fromEntries(reader.offending) } : {}),\n })\n const notes = (Array.isArray(answer['notes']) ? answer['notes'] : [])\n .map(textOf)\n .filter((note): note is string => note !== null && !HOSTILE_TEXT.test(note))\n .slice(0, AI_AUTOMATION_NOTES_MAX)\n .map((note) => note.slice(0, AI_AUTOMATION_NOTE_MAX_CHARS))\n const name = (textOf(answer['name']) ?? '').slice(0, AI_AUTOMATION_NAME_MAX_CHARS)\n const written = JSON.stringify(answer).length\n if (written > AI_AUTOMATION_ANSWER_MAX_CHARS) {\n refuse(\n reader,\n 'automation-too-long',\n `Written out, the automation is ${written} characters, over the ${AI_AUTOMATION_ANSWER_MAX_CHARS} it may be. Shorten its emails and messages, or use fewer steps.`,\n 'steps',\n written,\n )\n return done(null)\n }\n\n // An answer that builds an automation leaves `unsupported` empty, which the\n // schema states as an empty string rather than a `null`.\n const unsupported = answer['unsupported']\n if (unsupported !== null && unsupported !== undefined && unsupported !== '') {\n if (!(AI_AUTOMATION_UNSUPPORTED as readonly unknown[]).includes(unsupported)) {\n refuse(reader, 'automation-shape', 'unsupported is empty, or one of the reasons offered.', 'unsupported', unsupported)\n return done(null)\n }\n const need = AI_AUTOMATION_UNSUPPORTED_NEED[unsupported as AiAutomationUnsupported]\n if (need && capabilities[need]) {\n refuse(\n reader,\n 'automation-unsupported',\n `This workspace has ${AI_AUTOMATION_NEED_LABELS[need]}: build the automation with it.`,\n 'unsupported',\n unsupported,\n )\n return done(null)\n }\n return done({\n name,\n trigger: { event: HOST_EVENT_TYPES[0], conditions: [], combinator: 'and' },\n steps: [],\n notes,\n unsupported: unsupported as AiAutomationUnsupported,\n })\n }\n\n const trigger = answer['trigger']\n const steps = answer['steps']\n if (!isRecord(trigger) || !Array.isArray(steps)) {\n refuse(reader, 'automation-shape', 'The answer has no trigger and steps.', 'trigger', trigger)\n return done(null)\n }\n const event = trigger['event']\n if (!(HOST_EVENT_TYPES as readonly unknown[]).includes(event)) {\n refuse(reader, 'automation-trigger', 'The trigger is not one of the events offered.', 'trigger.event', event)\n return done(null)\n }\n const hostEvent = event as HostEventType\n const need = AI_AUTOMATION_TRIGGER_NEEDS[hostEvent]\n if (need && !capabilities[need]) {\n refuse(\n reader,\n 'automation-plan',\n `The trigger \"${aiAutomationTriggerLabel(hostEvent)}\" needs ${AI_AUTOMATION_NEED_LABELS[need]}, which this workspace does not have. Pick another trigger, or answer with unsupported \"needs-${need}\".`,\n 'trigger.event',\n event,\n )\n return done(null)\n }\n const rawConditions = Array.isArray(trigger['conditions']) ? trigger['conditions'] : []\n if (rawConditions.length > ACTION_MAX_CONDITIONS) {\n refuse(\n reader,\n 'automation-too-many-conditions',\n `A trigger holds at most ${ACTION_MAX_CONDITIONS} conditions.`,\n 'trigger.conditions',\n rawConditions.length,\n )\n }\n const conditions = rawConditions\n .slice(0, ACTION_MAX_CONDITIONS)\n .map((raw, index) => readCondition(reader, raw, `trigger.conditions[${index}]`, hostEvent, false))\n .filter((condition): condition is AiAutomationCondition => condition !== null)\n const combinator = (TRIGGER_COMBINATORS as readonly unknown[]).includes(trigger['combinator'])\n ? (trigger['combinator'] as TriggerCombinator)\n : 'and'\n\n if (!steps.length) {\n refuse(reader, 'automation-shape', 'An automation has at least one step; answer unsupported when none fits.', 'steps', steps)\n }\n if (steps.length > ACTION_MAX_STEPS) {\n refuse(reader, 'automation-too-many-steps', `An automation holds at most ${ACTION_MAX_STEPS} steps.`, 'steps', steps.length)\n }\n const read = steps\n .slice(0, ACTION_MAX_STEPS)\n .map((raw, index) => readStep(reader, raw, index, hostEvent, capabilities))\n .filter((step): step is AiAutomationAnswerStep => step !== null)\n\n return done({\n name,\n trigger: { event: hostEvent, conditions, combinator },\n steps: read,\n notes,\n unsupported: null,\n })\n}\n\n// ── Reading an explanation ────────────────────────────────────────────────\n\nexport interface AiWorkflowExplanation {\n summary: string\n points: string[]\n suggestions: string[]\n}\n\n/** Whether a line carries an email address, which nothing an explanation was shown holds. */\nconst CARRIES_ADDRESS = /[^\\s@]+@[^\\s@]+\\.[^\\s@]+/\n\nfunction lines(value: unknown, max: number): string[] {\n return (Array.isArray(value) ? value : [])\n .map(textOf)\n .filter((line): line is string => line !== null)\n .slice(0, max)\n .map((line) => line.slice(0, AI_WORKFLOW_LINE_MAX_CHARS))\n}\n\n/**\n * The explanation tool's answer: a summary, and at most so many points and\n * suggestions, each cut to a line. Refused when it has no summary, or carries\n * markup or an address — plain words for a person, about a record whose\n * personal details were removed before the model saw it.\n */\nexport function readAiWorkflowExplanation(\n answer: Record<string, unknown>,\n): AiGenerationCheckResult<AiWorkflowExplanation> {\n const summary = (textOf(answer['summary']) ?? '').slice(0, AI_WORKFLOW_SUMMARY_MAX_CHARS)\n const value: AiWorkflowExplanation = {\n summary,\n points: lines(answer['points'], AI_WORKFLOW_POINTS_MAX),\n suggestions: lines(answer['suggestions'], AI_WORKFLOW_SUGGESTIONS_MAX),\n }\n const violations: AiDoctrineViolation[] = []\n if (!summary) {\n violations.push({ rule: null, code: 'explanation-shape', message: 'The explanation has no summary.' })\n }\n const all = [value.summary, ...value.points, ...value.suggestions]\n if (all.some((line) => HOSTILE_TEXT.test(line))) {\n violations.push({\n rule: null,\n code: 'explanation-markup',\n message: 'Write the explanation as plain text, with no HTML or script.',\n })\n }\n if (all.some((line) => CARRIES_ADDRESS.test(line))) {\n violations.push({\n rule: null,\n code: 'explanation-address',\n message: 'The explanation names an email address. Describe the person or the teammate instead.',\n })\n }\n return { value: violations.length ? null : value, violations }\n}\n"],"names":["ACTION_MAX_CONDITIONS","ACTION_MAX_STEPS","TRIGGER_COMBINATORS","TRIGGER_CONDITION_OPS","CONTACT_LIFECYCLE_STAGE_LABELS","CONTACT_LIFECYCLE_STAGES","CONTACT_TAG_MAX_LENGTH","CRM_ACTIVITY_DIRECTIONS","CRM_ACTIVITY_KINDS","CRM_TASK_KINDS","CRM_TASK_MAX_DUE_DAYS","HOST_EVENT_PAYLOAD_KEYS","HOST_EVENT_TYPES","AI_AUTOMATION_NEED_LABELS","AI_AUTOMATION_STEP_NEEDS","AI_AUTOMATION_STEP_TYPES","AI_AUTOMATION_TRIGGER_NEEDS","AI_AUTOMATION_UNSUPPORTED","AI_AUTOMATION_UNSUPPORTED_NEED","aiAutomationTriggerLabel","AI_AUTOMATION_TIMED_OUT_FIELD","AI_AUTOMATION_WAIT_MINUTES","aiStepLabel","HOSTILE_TEXT","AI_AUTOMATION_TOOL_NAME","AI_WORKFLOW_EXPLANATION_TOOL_NAME","AI_AUTOMATION_NAME_MAX_CHARS","AI_AUTOMATION_SUBJECT_MAX_CHARS","AI_AUTOMATION_BODY_MAX_CHARS","AI_AUTOMATION_TEXT_MAX_CHARS","AI_AUTOMATION_REFERENCE_MAX_CHARS","AI_AUTOMATION_VALUE_MAX_CHARS","AI_AUTOMATION_NOTES_MAX","AI_AUTOMATION_NOTE_MAX_CHARS","AI_AUTOMATION_ANSWER_MAX_CHARS","AI_WORKFLOW_SUMMARY_MAX_CHARS","AI_WORKFLOW_POINTS_MAX","AI_WORKFLOW_SUGGESTIONS_MAX","AI_WORKFLOW_LINE_MAX_CHARS","SEVERITIES","TASK_PRIORITIES","ACTIVITY_DIRECTIONS","Set","Object","values","flatMap","directions","WAITABLE_EVENTS","join","DEFINITIONS","condition","type","additionalProperties","required","properties","field","description","op","enum","value","event","when","items","$ref","REFERENCE_STEPS","TEXT_STEPS","REFERENCE_KEY","enrollList","assignCampaign","runWorkflow","datasetAppend","updateDataset","webhookPost","TEXT_KEY","notifyAdmins","addContactTag","assignContactOwner","siteAlert","createCrmTask","logCrmActivity","minutes","what","min","max","stepVariant","types","fields","keys","AI_AUTOMATION_STEP_VARIANTS","reference","text","subject","body","toField","severity","stage","taskKind","priority","dueInDays","activityKind","direction","aiAutomationTool","name","strict","inputSchema","trigger","conditions","combinator","steps","action","anyOf","notes","unsupported","$defs","aiWorkflowExplanationTool","summary","points","suggestions","AI_AUTOMATION_UNREADABLE_CODES","AI_AUTOMATION_OVERSIZE_CODES","DEFAULT_TO_FIELD","FIELD_NAME","EMAIL_ADDRESS","ROUND_ROBIN","isRecord","Array","isArray","textOf","clean","replace","trim","fieldsFor","length","refuse","reader","code","message","path","violations","push","rule","paths","offending","readCondition","raw","guard","test","includes","readable","lifecycleStageOf","wanted","toLowerCase","find","requireText","step","key","wholeNumber","Number","isInteger","readStep","index","capabilities","stepType","need","before","list","campaign","workflow","webhook","dataset","title","tag","owner","guards","undefined","shared","own","waited","kind","due","allowed","String","readAiAutomationAnswer","answer","done","fromEntries","map","filter","note","slice","written","JSON","stringify","hostEvent","rawConditions","read","CARRIES_ADDRESS","lines","line","readAiWorkflowExplanation","all","some"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/tools/ai-workflow-tool.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n ACTION_MAX_CONDITIONS,\n ACTION_MAX_STEPS,\n TRIGGER_COMBINATORS,\n TRIGGER_CONDITION_OPS,\n type TriggerCombinator,\n type TriggerConditionOp,\n} from '@aglyn/aglyn/app-utils/site-interactions'\nimport {\n CONTACT_LIFECYCLE_STAGE_LABELS,\n CONTACT_LIFECYCLE_STAGES,\n CONTACT_TAG_MAX_LENGTH,\n CRM_ACTIVITY_DIRECTIONS,\n CRM_ACTIVITY_KINDS,\n CRM_TASK_KINDS,\n CRM_TASK_MAX_DUE_DAYS,\n type ContactLifecycleStage,\n type CrmActivityDirection,\n type CrmActivityKind,\n type CrmTaskKind,\n type CrmTaskPriority,\n} from '@aglyn/aglyn/app-utils/crm'\nimport {\n HOST_EVENT_PAYLOAD_KEYS,\n HOST_EVENT_TYPES,\n type HostEventType,\n} from '@aglyn/aglyn/app-utils/host-events'\nimport {\n AI_AUTOMATION_NEED_LABELS,\n AI_AUTOMATION_STEP_NEEDS,\n AI_AUTOMATION_STEP_TYPES,\n AI_AUTOMATION_TRIGGER_NEEDS,\n AI_AUTOMATION_UNSUPPORTED,\n AI_AUTOMATION_UNSUPPORTED_NEED,\n aiAutomationTriggerLabel,\n type AiAutomationCapabilities,\n type AiAutomationStepType,\n type AiAutomationUnsupported,\n} from '../model/ai-workflow-job'\nimport {\n AI_AUTOMATION_TIMED_OUT_FIELD,\n AI_AUTOMATION_WAIT_MINUTES,\n aiStepLabel,\n} from '../model/ai-automation-format'\nimport type { AiTool } from '../providers/contract'\nimport type { AiGenerationCheckResult } from '../runtime/ai-doctrine'\nimport type { AiDoctrineViolation } from '../runtime/ai-doctrine-validators'\nimport { HOSTILE_TEXT } from '../runtime/ai-node-tree'\n\n/**\n * The two tools a `workflow` job answers through (AGL-2919).\n *\n * `submit_automation` carries one automation in the Actions editor's own\n * vocabulary — a trigger from the host events, conditions over the fields the\n * trigger carries, and steps from the automation step types — with every\n * record it names written as WORDS. The step resolves those words against the\n * site after the answer, so the model is never shown the site's lists,\n * campaigns, workflows, webhooks or pipeline stages, and never writes an id it\n * could only have invented.\n *\n * `submit_explanation` carries a plain-words account of one automation, or of\n * why one of its runs failed.\n *\n * Both schemas are strict: every object forbids extra keys and requires every\n * key it has, and nothing in either is a `null`. Where a step may go without\n * something, the absence is an empty list or an empty string: a step that\n * always runs has an empty `when`, a notEmpty condition compares against\n * nothing, and an answer that builds an automation leaves `unsupported`\n * empty. Bounds a strict schema cannot state are stated in the descriptions\n * and held by the readers below, whose refusals name the field so the one\n * re-ask can fix it.\n *\n * A provider that constrains decoding compiles every strict schema of a\n * request into a grammar, and refuses one whose grammar grows past its size\n * (`AiToolSchemaLimits`). So the steps' union carries as little repetition as\n * a grammar can be made to hold:\n *\n * - a step is its guard and its action, `{ when, action }`, so the condition\n * is compiled once for the whole union instead of once per variant;\n * - a variant serves every step type that carries the same fields, named for\n * what they hold: the six steps that name a record share `reference`, and\n * the three that carry one line share `text`;\n * - every field stays required and every vocabulary but one stays an enum,\n * so the model is still decoded into the Actions editor's words. The one\n * is the event a waitForEvent step waits for: the trigger's `event` already\n * compiles the host events' enum, and a second copy of it is what a task's\n * priority and a logged call's direction cost (AGL-3538). It is a string\n * described as one of the trigger events, and the reader refuses any\n * other naming every event, so the one re-ask can fix it;\n * - a description is free: a compiler drops it (`aiToolSchemaCompiledBytes`),\n * so the words a field needs go there and never into the grammar.\n *\n * Ten variants carry all seventeen step types, and the reader below maps the\n * shared fields back onto the step's own key.\n */\n\nexport const AI_AUTOMATION_TOOL_NAME = 'submit_automation'\nexport const AI_WORKFLOW_EXPLANATION_TOOL_NAME = 'submit_explanation'\n\n/** The longest automation name the Actions editor stores. */\nexport const AI_AUTOMATION_NAME_MAX_CHARS = 60\n/** The longest subject the executor sends. */\nexport const AI_AUTOMATION_SUBJECT_MAX_CHARS = 200\n/** The longest email text the executor sends. */\nexport const AI_AUTOMATION_BODY_MAX_CHARS = 5_000\n/** The longest title, message or record name a step carries. */\nexport const AI_AUTOMATION_TEXT_MAX_CHARS = 300\n/** The longest words naming a record. */\nexport const AI_AUTOMATION_REFERENCE_MAX_CHARS = 100\n/** The longest condition value. */\nexport const AI_AUTOMATION_VALUE_MAX_CHARS = 200\n/** The most notes an answer keeps, and the longest. */\nexport const AI_AUTOMATION_NOTES_MAX = 3\nexport const AI_AUTOMATION_NOTE_MAX_CHARS = 300\n/**\n * The longest a whole answer may be, written out as JSON: what the routing\n * ceiling for `job.workflow` holds at three characters a token with as much\n * again to think in (`ai-job-workflow-step.spec.ts` measures it). Ten steps\n * with two emails of a paragraph or two each come to about 2,400, each step\n * carrying only its own fields.\n */\nexport const AI_AUTOMATION_ANSWER_MAX_CHARS = 6_000\n\n/** The explanation's bounds. */\nexport const AI_WORKFLOW_SUMMARY_MAX_CHARS = 400\nexport const AI_WORKFLOW_POINTS_MAX = 8\nexport const AI_WORKFLOW_SUGGESTIONS_MAX = 4\nexport const AI_WORKFLOW_LINE_MAX_CHARS = 300\n\nconst SEVERITIES = ['info', 'success', 'warning', 'error'] as const\ntype Severity = (typeof SEVERITIES)[number]\n\n/** A task's priorities, by meaning — the runner stores the org's label for each (AGL-3517). */\nconst TASK_PRIORITIES = ['low', 'normal', 'high'] as const satisfies readonly CrmTaskPriority[]\n\n/** Every direction any activity takes, and `''` for a kind that takes none (AGL-3538). */\nconst ACTIVITY_DIRECTIONS = [\n '',\n ...new Set(Object.values(CRM_ACTIVITY_DIRECTIONS).flatMap((directions) => [...(directions ?? [])])),\n] as const\n\n/** The events a waitForEvent step may wait for, as its refusal names them. */\nconst WAITABLE_EVENTS = HOST_EVENT_TYPES.join(', ')\n\ntype Schema = Record<string, unknown>\n\n/**\n * What the schema defines once and refers to where it is used: a condition,\n * which the trigger and every step's guard share, and the guard itself.\n */\nconst DEFINITIONS: Schema = {\n condition: {\n type: 'object',\n additionalProperties: false,\n required: ['field', 'op', 'value'],\n properties: {\n field: {\n type: 'string',\n description: 'The field of the event the condition reads, as the trigger lists its fields.',\n },\n op: { type: 'string', enum: [...TRIGGER_CONDITION_OPS] },\n value: {\n type: 'string',\n description: 'What equals or contains compares against; empty for notEmpty.',\n },\n },\n },\n event: {\n type: 'string',\n enum: [...HOST_EVENT_TYPES],\n description: 'One of the things that happen on a site.',\n },\n // A list rather than a condition or `null`: a step goes without one by an\n // empty list, which costs no union.\n when: {\n type: 'array',\n description: 'At most one condition: the step runs only when it holds. Empty when the step always runs.',\n items: { $ref: '#/$defs/condition' },\n },\n}\n\n/** The steps that name one of the site's records, which they all name the same way. */\nconst REFERENCE_STEPS = [\n 'enrollList',\n 'assignCampaign',\n 'runWorkflow',\n 'datasetAppend',\n 'updateDataset',\n 'webhookPost',\n] as const satisfies readonly AiAutomationStepType[]\n\n/** The steps whose only field is the one line they carry. */\nconst TEXT_STEPS = [\n 'notifyAdmins',\n 'addContactTag',\n 'assignContactOwner',\n] as const satisfies readonly AiAutomationStepType[]\n\n/** The key each `reference` step fills in the answer the reader gives back. */\nconst REFERENCE_KEY = {\n enrollList: 'list',\n assignCampaign: 'campaign',\n runWorkflow: 'workflow',\n datasetAppend: 'dataset',\n updateDataset: 'dataset',\n webhookPost: 'webhook',\n} as const satisfies Readonly<Record<(typeof REFERENCE_STEPS)[number], keyof AiAutomationAnswerStep>>\n\n/**\n * The key each step's one line fills in the answer the reader gives back. Six\n * step types carry one line between them, so the schema asks for all six\n * under `text` and the reader puts each back where the automation keeps it.\n */\nconst TEXT_KEY = {\n notifyAdmins: 'title',\n addContactTag: 'tag',\n assignContactOwner: 'owner',\n siteAlert: 'message',\n createCrmTask: 'title',\n logCrmActivity: 'body',\n} as const satisfies Readonly<Partial<Record<AiAutomationStepType, keyof AiAutomationAnswerStep>>>\n\nconst minutes = (what: string): Schema => ({\n type: 'integer',\n description: `${what}, in whole minutes from ${AI_AUTOMATION_WAIT_MINUTES.min} to ${AI_AUTOMATION_WAIT_MINUTES.max}; an hour is 60 and a day 1440.`,\n})\n\n/**\n * One variant of the steps' union: the step types it serves, and the fields\n * every one of them carries, each required and never nullable. A type belongs\n * to exactly one variant, so the type a step names settles which fields it\n * writes.\n */\nfunction stepVariant(\n types: readonly AiAutomationStepType[],\n fields: Readonly<Record<string, Schema>>,\n): Schema {\n return {\n type: 'object',\n additionalProperties: false,\n required: ['type', ...Object.keys(fields)],\n properties: { type: { type: 'string', enum: [...types] }, ...fields },\n }\n}\n\n/** Every step type an automation may carry, grouped by the fields it needs. */\nexport const AI_AUTOMATION_STEP_VARIANTS: readonly Schema[] = [\n stepVariant([...REFERENCE_STEPS], {\n reference: {\n type: 'string',\n description:\n 'The record the step acts on — the email list, campaign, workflow, dataset or outbound webhook — in the words of the description, such as \"newsletter\". Never an id.',\n },\n }),\n stepVariant([...TEXT_STEPS], {\n text: {\n type: 'string',\n description: `The one line the step carries: for notifyAdmins what the admins read, for addContactTag the tag (at most ${CONTACT_TAG_MAX_LENGTH} characters), and for assignContactOwner \"round robin\" or the teammate’s email address the description gives.`,\n },\n }),\n stepVariant(['sendEmail'], {\n subject: { type: 'string', description: 'The subject line.' },\n body: { type: 'string', description: 'The email, as plain text.' },\n toField: {\n type: 'string',\n description: 'The event field holding the address: \"email\" unless the description names another.',\n },\n }),\n stepVariant(['siteAlert'], {\n text: { type: 'string', description: 'What the visitor reads.' },\n severity: { type: 'string', enum: [...SEVERITIES] },\n }),\n stepVariant(['wait'], { minutes: minutes('How long to wait') }),\n stepVariant(['waitForEvent'], {\n // A string, not the events' enum again — see the header.\n event: { type: 'string', description: 'One of the trigger events.' },\n minutes: minutes('When to give up'),\n }),\n stepVariant(['exitFlow'], {}),\n stepVariant(['setContactStage'], { stage: { type: 'string', enum: [...CONTACT_LIFECYCLE_STAGES] } }),\n stepVariant(['createCrmTask'], {\n text: { type: 'string', description: 'The task.' },\n taskKind: { type: 'string', enum: [...CRM_TASK_KINDS] },\n priority: { type: 'string', enum: [...TASK_PRIORITIES] },\n dueInDays: {\n type: 'integer',\n description: `Days from the run, 0 for today, at most ${CRM_TASK_MAX_DUE_DAYS}.`,\n },\n }),\n stepVariant(['logCrmActivity'], {\n activityKind: { type: 'string', enum: [...CRM_ACTIVITY_KINDS] },\n direction: {\n type: 'string',\n enum: [...ACTIVITY_DIRECTIONS],\n description: 'A call: outbound, inbound or internal. An email: outbound or inbound. Else empty.',\n },\n text: { type: 'string', description: 'What happened.' },\n }),\n]\n\n/** The automation tool. The same bytes on every request, so it sits inside the cached prefix. */\nexport function aiAutomationTool(): AiTool {\n return {\n name: AI_AUTOMATION_TOOL_NAME,\n description: `Submit one automation: its trigger, the conditions on it, and its steps in order. Written out, the whole automation is at most ${AI_AUTOMATION_ANSWER_MAX_CHARS} characters, so keep its emails short.`,\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['name', 'trigger', 'steps', 'notes', 'unsupported'],\n properties: {\n name: {\n type: 'string',\n description: `A short name for the automation, at most ${AI_AUTOMATION_NAME_MAX_CHARS} characters.`,\n },\n trigger: {\n type: 'object',\n additionalProperties: false,\n required: ['event', 'conditions', 'combinator'],\n properties: {\n event: { $ref: '#/$defs/event' },\n conditions: {\n type: 'array',\n description: `What the event must meet: at most ${ACTION_MAX_CONDITIONS} conditions, empty when it runs every time.`,\n items: { $ref: '#/$defs/condition' },\n },\n combinator: { type: 'string', enum: [...TRIGGER_COMBINATORS] },\n },\n },\n steps: {\n type: 'array',\n description: `The steps in order, at most ${ACTION_MAX_STEPS}. Each is when it runs and what it does, and the action carries only the fields its type takes. Empty only with unsupported.`,\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['when', 'action'],\n properties: {\n when: { $ref: '#/$defs/when' },\n action: { anyOf: AI_AUTOMATION_STEP_VARIANTS },\n },\n },\n },\n notes: {\n type: 'array',\n description: `What the person must decide that the automation cannot hold; at most ${AI_AUTOMATION_NOTES_MAX} short sentences.`,\n items: { type: 'string' },\n },\n unsupported: {\n type: 'string',\n enum: ['', ...AI_AUTOMATION_UNSUPPORTED],\n description: 'Why no automation can be built from this description; empty when one is built.',\n },\n },\n $defs: DEFINITIONS,\n },\n }\n}\n\n/** The explanation tool. */\nexport function aiWorkflowExplanationTool(): AiTool {\n return {\n name: AI_WORKFLOW_EXPLANATION_TOOL_NAME,\n description: 'Submit the explanation, in plain words for someone who does not write code.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['summary', 'points', 'suggestions'],\n properties: {\n summary: {\n type: 'string',\n description: `One or two sentences, at most ${AI_WORKFLOW_SUMMARY_MAX_CHARS} characters.`,\n },\n points: {\n type: 'array',\n description: `At most ${AI_WORKFLOW_POINTS_MAX}, one sentence each.`,\n items: { type: 'string' },\n },\n suggestions: {\n type: 'array',\n description: `At most ${AI_WORKFLOW_SUGGESTIONS_MAX}, one sentence each; empty when there is nothing to suggest.`,\n items: { type: 'string' },\n },\n },\n },\n }\n}\n\n// ── Reading an automation ─────────────────────────────────────────────────\n\nexport interface AiAutomationCondition {\n field: string\n op: TriggerConditionOp\n /** `null` for `notEmpty`. */\n value: string | null\n}\n\n/** One step as the reader gives it back, every field the step does not use `null`. */\nexport interface AiAutomationAnswerStep {\n type: AiAutomationStepType\n when: AiAutomationCondition | null\n list: string | null\n campaign: string | null\n workflow: string | null\n webhook: string | null\n dataset: string | null\n subject: string | null\n body: string | null\n toField: string | null\n title: string | null\n message: string | null\n severity: Severity | null\n minutes: number | null\n event: HostEventType | null\n stage: ContactLifecycleStage | null\n tag: string | null\n owner: string | null\n taskKind: CrmTaskKind | null\n /** A created task's priority, by meaning (AGL-3538). */\n priority: CrmTaskPriority | null\n dueInDays: number | null\n activityKind: CrmActivityKind | null\n /** Which way a logged call or email went (AGL-3538); `null` for every other kind. */\n direction: CrmActivityDirection | null\n}\n\nexport interface AiAutomationAnswer {\n name: string\n trigger: {\n event: HostEventType\n conditions: AiAutomationCondition[]\n combinator: TriggerCombinator\n }\n steps: AiAutomationAnswerStep[]\n notes: string[]\n unsupported: AiAutomationUnsupported | null\n}\n\n/** The codes that say an answer could not be read as an automation at all. */\nexport const AI_AUTOMATION_UNREADABLE_CODES: readonly string[] = [\n 'automation-shape',\n 'automation-trigger',\n 'automation-step-type',\n]\n\n/** The codes that say an answer is larger than an automation may be. */\nexport const AI_AUTOMATION_OVERSIZE_CODES: readonly string[] = [\n 'automation-too-many-steps',\n 'automation-too-many-conditions',\n 'automation-too-long',\n]\n\n/** The address field a step reads when it names none. */\nconst DEFAULT_TO_FIELD = 'email'\nconst FIELD_NAME = /^[A-Za-z_][A-Za-z0-9_.-]{0,63}$/\nconst EMAIL_ADDRESS = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\nconst ROUND_ROBIN = /^\\s*round[\\s-]*robin\\s*$/i\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** A string trimmed to one space between words, or `null` for none. */\nfunction textOf(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const clean = value.replace(/[ \\t]+/g, ' ').trim()\n return clean ? clean : null\n}\n\n/** The fields a trigger's conditions and its steps' guards may read. */\nfunction fieldsFor(event: HostEventType): readonly string[] | null {\n // A form submission carries every field the form collects, which no fixed\n // list names, so any field name is readable on it.\n if (event === 'formSubmission') return null\n const keys = HOST_EVENT_PAYLOAD_KEYS[event]\n return keys?.length ? keys : null\n}\n\ninterface Reader {\n violations: AiDoctrineViolation[]\n offending: Array<[string, unknown]>\n}\n\nfunction refuse(\n reader: Reader,\n code: string,\n message: string,\n path: string,\n value: unknown,\n): void {\n reader.violations.push({ rule: null, code, message, paths: [path] })\n if (reader.offending.length < 6) reader.offending.push([path, value])\n}\n\nfunction readCondition(\n reader: Reader,\n raw: unknown,\n path: string,\n event: HostEventType,\n guard: boolean,\n): AiAutomationCondition | null {\n if (!isRecord(raw)) {\n refuse(reader, 'automation-shape', 'A condition is not an object with a field, an op and a value.', path, raw)\n return null\n }\n const field = textOf(raw['field'])\n const op = raw['op']\n if (!field || !FIELD_NAME.test(field)) {\n refuse(reader, 'automation-condition', 'Name the event field the condition reads.', path, raw)\n return null\n }\n if (!(TRIGGER_CONDITION_OPS as readonly unknown[]).includes(op)) {\n refuse(reader, 'automation-condition', `The condition on \"${field}\" needs equals, contains or notEmpty.`, path, raw)\n return null\n }\n if (event === 'formSubmission' && field === 'formId') {\n refuse(\n reader,\n 'automation-condition',\n 'A form submission names its form by formName, the form’s name as the site lists it.',\n path,\n raw,\n )\n return null\n }\n // A step's guard may also read whether the wait before it ran out of time.\n const readable = fieldsFor(event)\n if (readable && !readable.includes(field) && !(guard && field === AI_AUTOMATION_TIMED_OUT_FIELD)) {\n refuse(\n reader,\n 'automation-condition',\n `The trigger \"${aiAutomationTriggerLabel(event)}\" does not carry \"${field}\". It carries: ${readable.join(', ')}.`,\n path,\n raw,\n )\n return null\n }\n const value = textOf(raw['value'])\n if (op !== 'notEmpty' && !value) {\n refuse(reader, 'automation-condition', `The condition on \"${field}\" needs the value it compares against.`, path, raw)\n return null\n }\n if (value && value.length > AI_AUTOMATION_VALUE_MAX_CHARS) {\n refuse(reader, 'automation-too-long', `The value the condition on \"${field}\" compares against is too long.`, path, raw)\n return null\n }\n if (field === 'lifecycleStage' || field === 'previousStage') {\n const stage = value ? lifecycleStageOf(value) : null\n if (op !== 'notEmpty' && !stage) {\n refuse(\n reader,\n 'automation-condition',\n `A lifecycle stage is one of: ${CONTACT_LIFECYCLE_STAGES.join(', ')}.`,\n path,\n raw,\n )\n return null\n }\n return { field, op: op as TriggerConditionOp, value: op === 'notEmpty' ? null : stage }\n }\n return { field, op: op as TriggerConditionOp, value: op === 'notEmpty' ? null : value }\n}\n\n/** A lifecycle stage by its id or its label, case aside. */\nexport function lifecycleStageOf(value: string): ContactLifecycleStage | null {\n const wanted = value.trim().toLowerCase()\n return (\n CONTACT_LIFECYCLE_STAGES.find(\n (stage) => stage === wanted || CONTACT_LIFECYCLE_STAGE_LABELS[stage].toLowerCase() === wanted,\n ) ?? null\n )\n}\n\nfunction requireText(\n reader: Reader,\n step: Record<string, unknown>,\n key: string,\n max: number,\n path: string,\n what: string,\n): string | null {\n const value = textOf(step[key])\n if (!value) {\n refuse(reader, 'automation-step-field', `${what} needs ${key}.`, path, step)\n return null\n }\n if (value.length > max) {\n refuse(reader, 'automation-too-long', `${what}: ${key} is longer than ${max} characters.`, path, step)\n return null\n }\n if (HOSTILE_TEXT.test(value)) {\n refuse(reader, 'automation-markup', `${what}: write ${key} as plain text, with no HTML or script.`, path, step)\n return null\n }\n return value\n}\n\nfunction wholeNumber(value: unknown): number | null {\n return typeof value === 'number' && Number.isInteger(value) ? value : null\n}\n\nfunction readStep(\n reader: Reader,\n raw: unknown,\n index: number,\n event: HostEventType,\n capabilities: AiAutomationCapabilities,\n): AiAutomationAnswerStep | null {\n const path = `steps[${index}]`\n if (!isRecord(raw)) {\n refuse(reader, 'automation-shape', `Step ${index + 1} is not an object.`, path, raw)\n return null\n }\n // A step is its guard and its action: `{ when, action }`. An answer that\n // writes the action's own fields beside `when` reads the same, so a model\n // that flattens the step does not spend the one re-ask.\n const action = raw['action']\n const fields: Record<string, unknown> = isRecord(action) ? { ...action, when: raw['when'] } : raw\n const type = fields['type']\n if (!(AI_AUTOMATION_STEP_TYPES as readonly unknown[]).includes(type)) {\n refuse(\n reader,\n 'automation-step-type',\n `Step ${index + 1} is not one of the steps offered: ${AI_AUTOMATION_STEP_TYPES.join(', ')}.`,\n path,\n fields,\n )\n return null\n }\n const stepType = type as AiAutomationStepType\n const what = `Step ${index + 1} (${aiStepLabel(stepType)})`\n const need = AI_AUTOMATION_STEP_NEEDS[stepType]\n if (need && !capabilities[need]) {\n refuse(\n reader,\n 'automation-plan',\n `${what} needs ${AI_AUTOMATION_NEED_LABELS[need]}, which this workspace does not have. Leave it out, or answer with unsupported \"needs-${need}\".`,\n path,\n fields,\n )\n return null\n }\n const before = reader.violations.length\n const step: AiAutomationAnswerStep = {\n type: stepType,\n when: null,\n list: null,\n campaign: null,\n workflow: null,\n webhook: null,\n dataset: null,\n subject: null,\n body: null,\n toField: null,\n title: null,\n message: null,\n severity: null,\n minutes: null,\n event: null,\n stage: null,\n tag: null,\n owner: null,\n taskKind: null,\n priority: null,\n dueInDays: null,\n activityKind: null,\n direction: null,\n }\n // A step's `when` is a list of at most one condition; a lone condition, or\n // none, reads as the list it stands for.\n const when = fields['when']\n const guards = Array.isArray(when) ? when : when === null || when === undefined ? [] : [when]\n if (guards.length > 1) {\n refuse(reader, 'automation-condition', `${what}: when holds one condition at most.`, `${path}.when`, when)\n } else if (guards.length === 1) {\n step.when = readCondition(reader, guards[0], `${path}.when`, event, true)\n }\n // The six steps that name a record share `reference`, and the three that\n // carry one line share `text`; an answer that writes the step's own key\n // instead — `list`, `tag`, `owner` — reads the same.\n const shared = (key: 'reference' | 'text', own: keyof AiAutomationAnswerStep): unknown =>\n fields[key] ?? fields[own]\n const own = TEXT_KEY[stepType as keyof typeof TEXT_KEY]\n const text: Record<string, unknown> = own ? { text: shared('text', own) } : fields\n switch (stepType) {\n case 'sendEmail': {\n step.subject = requireText(reader, fields, 'subject', AI_AUTOMATION_SUBJECT_MAX_CHARS, path, what)\n step.body = requireText(reader, fields, 'body', AI_AUTOMATION_BODY_MAX_CHARS, path, what)\n const toField = textOf(fields['toField'])\n if (toField && !FIELD_NAME.test(toField)) {\n refuse(reader, 'automation-step-field', `${what}: toField names the event field holding the address.`, path, fields)\n } else {\n step.toField = toField && toField !== DEFAULT_TO_FIELD ? toField : null\n }\n break\n }\n case 'notifyAdmins':\n step.title = requireText(reader, text, 'text', AI_AUTOMATION_SUBJECT_MAX_CHARS, path, what)\n break\n case 'enrollList':\n case 'assignCampaign':\n case 'runWorkflow':\n case 'webhookPost':\n case 'datasetAppend':\n case 'updateDataset':\n step[REFERENCE_KEY[stepType]] = requireText(\n reader,\n { reference: shared('reference', REFERENCE_KEY[stepType]) },\n 'reference',\n AI_AUTOMATION_REFERENCE_MAX_CHARS,\n path,\n what,\n )\n break\n case 'siteAlert': {\n step.message = requireText(reader, text, 'text', AI_AUTOMATION_TEXT_MAX_CHARS, path, what)\n const severity = fields['severity']\n step.severity = (SEVERITIES as readonly unknown[]).includes(severity) ? (severity as Severity) : 'info'\n break\n }\n case 'wait':\n case 'waitForEvent': {\n const minutes = wholeNumber(fields['minutes'])\n if (minutes === null || minutes < AI_AUTOMATION_WAIT_MINUTES.min || minutes > AI_AUTOMATION_WAIT_MINUTES.max) {\n refuse(\n reader,\n 'automation-step-field',\n `${what} needs minutes, a whole number from ${AI_AUTOMATION_WAIT_MINUTES.min} to ${AI_AUTOMATION_WAIT_MINUTES.max}.`,\n path,\n fields,\n )\n } else {\n step.minutes = minutes\n }\n if (stepType === 'waitForEvent') {\n const waited = fields['event']\n if (!(HOST_EVENT_TYPES as readonly unknown[]).includes(waited)) {\n refuse(\n reader,\n 'automation-step-field',\n `${what} needs the event it waits for, one of: ${WAITABLE_EVENTS}.`,\n path,\n fields,\n )\n } else {\n step.event = waited as HostEventType\n }\n }\n break\n }\n case 'exitFlow':\n break\n case 'setContactStage': {\n const stage = fields['stage']\n if (!(CONTACT_LIFECYCLE_STAGES as readonly unknown[]).includes(stage)) {\n refuse(reader, 'automation-step-field', `${what} needs the lifecycle stage to set.`, path, fields)\n } else {\n step.stage = stage as ContactLifecycleStage\n }\n break\n }\n case 'addContactTag':\n step.tag = requireText(reader, text, 'text', CONTACT_TAG_MAX_LENGTH, path, what)\n break\n case 'assignContactOwner': {\n const owner = textOf(text['text'])\n if (!owner || !(ROUND_ROBIN.test(owner) || EMAIL_ADDRESS.test(owner))) {\n refuse(\n reader,\n 'automation-owner',\n `${what}: give the owner as \"round robin\", or as the teammate’s email address the description gives. A teammate named without one goes in notes, not in a step.`,\n path,\n fields,\n )\n } else {\n step.owner = ROUND_ROBIN.test(owner) ? 'round robin' : owner\n }\n break\n }\n case 'createCrmTask': {\n step.title = requireText(reader, text, 'text', AI_AUTOMATION_SUBJECT_MAX_CHARS, path, what)\n const kind = fields['taskKind']\n const due = wholeNumber(fields['dueInDays'])\n if (!(CRM_TASK_KINDS as readonly unknown[]).includes(kind)) {\n refuse(reader, 'automation-step-field', `${what} needs taskKind.`, path, fields)\n } else {\n step.taskKind = kind as CrmTaskKind\n }\n // Absent reads as normal, which the runner writes when none is named.\n const priority = fields['priority']\n if (priority !== undefined && !(TASK_PRIORITIES as readonly unknown[]).includes(priority)) {\n refuse(reader, 'automation-step-field', `${what} needs priority: low, normal or high.`, path, fields)\n } else if (priority !== undefined) {\n step.priority = priority as CrmTaskPriority\n }\n if (due === null || due < 0 || due > CRM_TASK_MAX_DUE_DAYS) {\n refuse(reader, 'automation-step-field', `${what} needs dueInDays, from 0 to ${CRM_TASK_MAX_DUE_DAYS}.`, path, fields)\n } else {\n step.dueInDays = due\n }\n break\n }\n case 'logCrmActivity': {\n const kind = fields['activityKind']\n if (!(CRM_ACTIVITY_KINDS as readonly unknown[]).includes(kind)) {\n refuse(reader, 'automation-step-field', `${what} needs activityKind.`, path, fields)\n } else {\n step.activityKind = kind as CrmActivityKind\n /*\n * The direction is the kind's own: a call goes outbound, inbound or\n * internal, an email outbound or inbound, and every other kind\n * carries none (AGL-3538).\n */\n const allowed: readonly string[] = CRM_ACTIVITY_DIRECTIONS[kind as CrmActivityKind] ?? []\n // Absent reads as empty: no direction.\n const raw = fields['direction'] ?? ''\n const direction = typeof raw === 'string' ? raw : null\n if (direction === null || (direction ? !allowed.includes(direction) : false)) {\n refuse(\n reader,\n 'automation-step-field',\n allowed.length\n ? `${what} needs direction: ${allowed.join(', ')}, or empty.`\n : `${what} takes no direction: leave it empty for a ${String(kind)}.`,\n path,\n fields,\n )\n } else if (direction) {\n step.direction = direction as CrmActivityDirection\n }\n }\n step.body = requireText(reader, text, 'text', AI_AUTOMATION_TEXT_MAX_CHARS, path, what)\n break\n }\n }\n return reader.violations.length === before ? step : null\n}\n\n/**\n * The automation tool's answer read and held to the vocabulary and to what\n * this workspace can run — the check the doctrine's loop runs on each answer,\n * beside rule 13. `value` is the automation when nothing is refused.\n */\nexport function readAiAutomationAnswer(\n answer: Record<string, unknown>,\n capabilities: AiAutomationCapabilities,\n): AiGenerationCheckResult<AiAutomationAnswer> {\n const reader: Reader = { violations: [], offending: [] }\n const done = (value: AiAutomationAnswer | null): AiGenerationCheckResult<AiAutomationAnswer> => ({\n value: reader.violations.length ? null : value,\n violations: reader.violations,\n ...(reader.offending.length ? { offending: Object.fromEntries(reader.offending) } : {}),\n })\n const notes = (Array.isArray(answer['notes']) ? answer['notes'] : [])\n .map(textOf)\n .filter((note): note is string => note !== null && !HOSTILE_TEXT.test(note))\n .slice(0, AI_AUTOMATION_NOTES_MAX)\n .map((note) => note.slice(0, AI_AUTOMATION_NOTE_MAX_CHARS))\n const name = (textOf(answer['name']) ?? '').slice(0, AI_AUTOMATION_NAME_MAX_CHARS)\n const written = JSON.stringify(answer).length\n if (written > AI_AUTOMATION_ANSWER_MAX_CHARS) {\n refuse(\n reader,\n 'automation-too-long',\n `Written out, the automation is ${written} characters, over the ${AI_AUTOMATION_ANSWER_MAX_CHARS} it may be. Shorten its emails and messages, or use fewer steps.`,\n 'steps',\n written,\n )\n return done(null)\n }\n\n // An answer that builds an automation leaves `unsupported` empty, which the\n // schema states as an empty string rather than a `null`.\n const unsupported = answer['unsupported']\n if (unsupported !== null && unsupported !== undefined && unsupported !== '') {\n if (!(AI_AUTOMATION_UNSUPPORTED as readonly unknown[]).includes(unsupported)) {\n refuse(reader, 'automation-shape', 'unsupported is empty, or one of the reasons offered.', 'unsupported', unsupported)\n return done(null)\n }\n const need = AI_AUTOMATION_UNSUPPORTED_NEED[unsupported as AiAutomationUnsupported]\n if (need && capabilities[need]) {\n refuse(\n reader,\n 'automation-unsupported',\n `This workspace has ${AI_AUTOMATION_NEED_LABELS[need]}: build the automation with it.`,\n 'unsupported',\n unsupported,\n )\n return done(null)\n }\n return done({\n name,\n trigger: { event: HOST_EVENT_TYPES[0], conditions: [], combinator: 'and' },\n steps: [],\n notes,\n unsupported: unsupported as AiAutomationUnsupported,\n })\n }\n\n const trigger = answer['trigger']\n const steps = answer['steps']\n if (!isRecord(trigger) || !Array.isArray(steps)) {\n refuse(reader, 'automation-shape', 'The answer has no trigger and steps.', 'trigger', trigger)\n return done(null)\n }\n const event = trigger['event']\n if (!(HOST_EVENT_TYPES as readonly unknown[]).includes(event)) {\n refuse(reader, 'automation-trigger', 'The trigger is not one of the events offered.', 'trigger.event', event)\n return done(null)\n }\n const hostEvent = event as HostEventType\n const need = AI_AUTOMATION_TRIGGER_NEEDS[hostEvent]\n if (need && !capabilities[need]) {\n refuse(\n reader,\n 'automation-plan',\n `The trigger \"${aiAutomationTriggerLabel(hostEvent)}\" needs ${AI_AUTOMATION_NEED_LABELS[need]}, which this workspace does not have. Pick another trigger, or answer with unsupported \"needs-${need}\".`,\n 'trigger.event',\n event,\n )\n return done(null)\n }\n const rawConditions = Array.isArray(trigger['conditions']) ? trigger['conditions'] : []\n if (rawConditions.length > ACTION_MAX_CONDITIONS) {\n refuse(\n reader,\n 'automation-too-many-conditions',\n `A trigger holds at most ${ACTION_MAX_CONDITIONS} conditions.`,\n 'trigger.conditions',\n rawConditions.length,\n )\n }\n const conditions = rawConditions\n .slice(0, ACTION_MAX_CONDITIONS)\n .map((raw, index) => readCondition(reader, raw, `trigger.conditions[${index}]`, hostEvent, false))\n .filter((condition): condition is AiAutomationCondition => condition !== null)\n const combinator = (TRIGGER_COMBINATORS as readonly unknown[]).includes(trigger['combinator'])\n ? (trigger['combinator'] as TriggerCombinator)\n : 'and'\n\n if (!steps.length) {\n refuse(reader, 'automation-shape', 'An automation has at least one step; answer unsupported when none fits.', 'steps', steps)\n }\n if (steps.length > ACTION_MAX_STEPS) {\n refuse(reader, 'automation-too-many-steps', `An automation holds at most ${ACTION_MAX_STEPS} steps.`, 'steps', steps.length)\n }\n const read = steps\n .slice(0, ACTION_MAX_STEPS)\n .map((raw, index) => readStep(reader, raw, index, hostEvent, capabilities))\n .filter((step): step is AiAutomationAnswerStep => step !== null)\n\n return done({\n name,\n trigger: { event: hostEvent, conditions, combinator },\n steps: read,\n notes,\n unsupported: null,\n })\n}\n\n/**\n * An answer already read, held as well to an org automation's narrower\n * vocabulary (AGL-3603): its trigger one the vocabulary names, and every step\n * a type it names. A refusal names what the vocabulary holds, so the re-ask\n * can pick from it. An answer that says the automation cannot be built is\n * left as it is.\n */\nexport function holdAiAutomationAnswerToVocabulary(\n result: AiGenerationCheckResult<AiAutomationAnswer>,\n vocabulary: { triggers: readonly HostEventType[]; steps: readonly AiAutomationStepType[] },\n): AiGenerationCheckResult<AiAutomationAnswer> {\n const answer = result.value\n if (!answer || answer.unsupported) return result\n const reader: Reader = { violations: [...result.violations], offending: Object.entries(result.offending ?? {}) }\n if (!vocabulary.triggers.includes(answer.trigger.event)) {\n refuse(\n reader,\n 'automation-trigger',\n `An org automation starts only on one of: ${vocabulary.triggers.join(', ')}. Pick one of them, or answer with unsupported \"no-trigger\".`,\n 'trigger.event',\n answer.trigger.event,\n )\n }\n answer.steps.forEach((step, index) => {\n if (vocabulary.steps.includes(step.type)) return\n refuse(\n reader,\n 'automation-step-type',\n `An org automation uses only these steps: ${vocabulary.steps.join(', ')}. Leave \"${step.type}\" out, or answer with unsupported \"no-step\".`,\n `steps[${index}]`,\n step.type,\n )\n })\n if (reader.violations.length === result.violations.length) return result\n return {\n value: null,\n violations: reader.violations,\n ...(reader.offending.length ? { offending: Object.fromEntries(reader.offending) } : {}),\n }\n}\n\n// ── Reading an explanation ────────────────────────────────────────────────\n\nexport interface AiWorkflowExplanation {\n summary: string\n points: string[]\n suggestions: string[]\n}\n\n/** Whether a line carries an email address, which nothing an explanation was shown holds. */\nconst CARRIES_ADDRESS = /[^\\s@]+@[^\\s@]+\\.[^\\s@]+/\n\nfunction lines(value: unknown, max: number): string[] {\n return (Array.isArray(value) ? value : [])\n .map(textOf)\n .filter((line): line is string => line !== null)\n .slice(0, max)\n .map((line) => line.slice(0, AI_WORKFLOW_LINE_MAX_CHARS))\n}\n\n/**\n * The explanation tool's answer: a summary, and at most so many points and\n * suggestions, each cut to a line. Refused when it has no summary, or carries\n * markup or an address — plain words for a person, about a record whose\n * personal details were removed before the model saw it.\n */\nexport function readAiWorkflowExplanation(\n answer: Record<string, unknown>,\n): AiGenerationCheckResult<AiWorkflowExplanation> {\n const summary = (textOf(answer['summary']) ?? '').slice(0, AI_WORKFLOW_SUMMARY_MAX_CHARS)\n const value: AiWorkflowExplanation = {\n summary,\n points: lines(answer['points'], AI_WORKFLOW_POINTS_MAX),\n suggestions: lines(answer['suggestions'], AI_WORKFLOW_SUGGESTIONS_MAX),\n }\n const violations: AiDoctrineViolation[] = []\n if (!summary) {\n violations.push({ rule: null, code: 'explanation-shape', message: 'The explanation has no summary.' })\n }\n const all = [value.summary, ...value.points, ...value.suggestions]\n if (all.some((line) => HOSTILE_TEXT.test(line))) {\n violations.push({\n rule: null,\n code: 'explanation-markup',\n message: 'Write the explanation as plain text, with no HTML or script.',\n })\n }\n if (all.some((line) => CARRIES_ADDRESS.test(line))) {\n violations.push({\n rule: null,\n code: 'explanation-address',\n message: 'The explanation names an email address. Describe the person or the teammate instead.',\n })\n }\n return { value: violations.length ? null : value, violations }\n}\n"],"names":["ACTION_MAX_CONDITIONS","ACTION_MAX_STEPS","TRIGGER_COMBINATORS","TRIGGER_CONDITION_OPS","CONTACT_LIFECYCLE_STAGE_LABELS","CONTACT_LIFECYCLE_STAGES","CONTACT_TAG_MAX_LENGTH","CRM_ACTIVITY_DIRECTIONS","CRM_ACTIVITY_KINDS","CRM_TASK_KINDS","CRM_TASK_MAX_DUE_DAYS","HOST_EVENT_PAYLOAD_KEYS","HOST_EVENT_TYPES","AI_AUTOMATION_NEED_LABELS","AI_AUTOMATION_STEP_NEEDS","AI_AUTOMATION_STEP_TYPES","AI_AUTOMATION_TRIGGER_NEEDS","AI_AUTOMATION_UNSUPPORTED","AI_AUTOMATION_UNSUPPORTED_NEED","aiAutomationTriggerLabel","AI_AUTOMATION_TIMED_OUT_FIELD","AI_AUTOMATION_WAIT_MINUTES","aiStepLabel","HOSTILE_TEXT","AI_AUTOMATION_TOOL_NAME","AI_WORKFLOW_EXPLANATION_TOOL_NAME","AI_AUTOMATION_NAME_MAX_CHARS","AI_AUTOMATION_SUBJECT_MAX_CHARS","AI_AUTOMATION_BODY_MAX_CHARS","AI_AUTOMATION_TEXT_MAX_CHARS","AI_AUTOMATION_REFERENCE_MAX_CHARS","AI_AUTOMATION_VALUE_MAX_CHARS","AI_AUTOMATION_NOTES_MAX","AI_AUTOMATION_NOTE_MAX_CHARS","AI_AUTOMATION_ANSWER_MAX_CHARS","AI_WORKFLOW_SUMMARY_MAX_CHARS","AI_WORKFLOW_POINTS_MAX","AI_WORKFLOW_SUGGESTIONS_MAX","AI_WORKFLOW_LINE_MAX_CHARS","SEVERITIES","TASK_PRIORITIES","ACTIVITY_DIRECTIONS","Set","Object","values","flatMap","directions","WAITABLE_EVENTS","join","DEFINITIONS","condition","type","additionalProperties","required","properties","field","description","op","enum","value","event","when","items","$ref","REFERENCE_STEPS","TEXT_STEPS","REFERENCE_KEY","enrollList","assignCampaign","runWorkflow","datasetAppend","updateDataset","webhookPost","TEXT_KEY","notifyAdmins","addContactTag","assignContactOwner","siteAlert","createCrmTask","logCrmActivity","minutes","what","min","max","stepVariant","types","fields","keys","AI_AUTOMATION_STEP_VARIANTS","reference","text","subject","body","toField","severity","stage","taskKind","priority","dueInDays","activityKind","direction","aiAutomationTool","name","strict","inputSchema","trigger","conditions","combinator","steps","action","anyOf","notes","unsupported","$defs","aiWorkflowExplanationTool","summary","points","suggestions","AI_AUTOMATION_UNREADABLE_CODES","AI_AUTOMATION_OVERSIZE_CODES","DEFAULT_TO_FIELD","FIELD_NAME","EMAIL_ADDRESS","ROUND_ROBIN","isRecord","Array","isArray","textOf","clean","replace","trim","fieldsFor","length","refuse","reader","code","message","path","violations","push","rule","paths","offending","readCondition","raw","guard","test","includes","readable","lifecycleStageOf","wanted","toLowerCase","find","requireText","step","key","wholeNumber","Number","isInteger","readStep","index","capabilities","stepType","need","before","list","campaign","workflow","webhook","dataset","title","tag","owner","guards","undefined","shared","own","waited","kind","due","allowed","String","readAiAutomationAnswer","answer","done","fromEntries","map","filter","note","slice","written","JSON","stringify","hostEvent","rawConditions","read","holdAiAutomationAnswerToVocabulary","result","vocabulary","entries","triggers","forEach","CARRIES_ADDRESS","lines","line","readAiWorkflowExplanation","all","some"],"mappings":"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"}
@@ -20,7 +20,8 @@ import type { FreeAssistAccount } from './assist-free-taste';
20
20
  * A job that failed on our side gives back what it spent (AGL-3594).
21
21
  *
22
22
  * "On our side" is the machine's to say, and it says it for: a plan the plan
23
- * rules still refused after their re-ask, a provider or model error, a step
23
+ * rules still refused after their re-ask, a building step whose answer still
24
+ * broke a building rule after its re-ask (AGL-3596), a provider or model error, a step
24
25
  * the machine gave up on after its attempts (a timeout or a lease that kept
25
26
  * expiring), a step that failed of its own accord, and a step no runner could
26
27
  * take. Never for a person canceling — they pay for what ran until they did —
@@ -53,7 +54,7 @@ export declare const AI_JOB_REFUND_SOURCE = "job-refund";
53
54
  /** Who gives it back: the platform, not a member of staff. */
54
55
  export declare const AI_JOB_REFUND_ACTOR = "system:job-refund";
55
56
  /** Why a job's credits went back, as the job records it. */
56
- export type AiJobRefundReason = 'plan-refused' | 'provider' | 'timeout' | 'step-failure' | 'unavailable';
57
+ export type AiJobRefundReason = 'plan-refused' | 'doctrine-refused' | 'provider' | 'timeout' | 'step-failure' | 'unavailable';
57
58
  /**
58
59
  * The give-back's key. Per job and per give-back on it (`ordinal`, the job's
59
60
  * count of give-backs so far), so one failure gives back once however often
@@ -67,8 +68,25 @@ export declare function aiJobRefundKey(input: {
67
68
  jobId: string;
68
69
  ordinal: number;
69
70
  }): string;
70
- /** How many give-backs a meter's month already records for `day`. */
71
+ /**
72
+ * A build item's give-back key (AGL-3616): per job, per item and per
73
+ * attempt, so one item's failure gives back once however often its pass is
74
+ * replayed, and Try again's next attempt is a give-back of its own. The day
75
+ * is the JOB's (the day it was created), not the day of the give-back, so a
76
+ * pass replayed across midnight finds the same key. `item` marks it, so the
77
+ * daily bound counts a build's give-backs once per job. `slot` is the unit's
78
+ * slot (`c0`, `p1`, `i2`) or `plan` for the build's own planning.
79
+ */
80
+ export declare function aiJobItemRefundKey(input: {
81
+ day: string;
82
+ jobId: string;
83
+ slot: string;
84
+ attempt: number;
85
+ }): string;
86
+ /** How many give-backs a meter's month already records for `day`: build items once per job. */
71
87
  export declare function aiJobRefundsOn(returns: unknown, day: string): number;
88
+ /** The give-backs a meter's month records for `day`, each by what it counts as. */
89
+ export declare function aiJobRefundIdentitiesOn(returns: unknown, day: string): Set<string>;
72
90
  /**
73
91
  * What a failure gives back: everything the job spent and has not already
74
92
  * given back when it delivered nothing, else the failing step's own spend.
@@ -97,4 +115,14 @@ export declare function refundJobCredits(firestore: FirebaseFirestore.Firestore,
97
115
  month: string;
98
116
  day: string;
99
117
  reason: AiJobRefundReason;
118
+ /**
119
+ * A build item's give-back (AGL-3616), keyed by `aiJobItemRefundKey`
120
+ * rather than by `ordinal`: its slot and attempt, and the day the job was
121
+ * created. Counted toward the daily bound once per job.
122
+ */
123
+ item?: {
124
+ slot: string;
125
+ attempt: number;
126
+ jobDay: string;
127
+ };
100
128
  }): Promise<AiJobRefundOutcome>;
@@ -19,7 +19,8 @@ import { accountCreditMeter, returnAssistCredits, workspaceCreditMeter } from ".
19
19
  * A job that failed on our side gives back what it spent (AGL-3594).
20
20
  *
21
21
  * "On our side" is the machine's to say, and it says it for: a plan the plan
22
- * rules still refused after their re-ask, a provider or model error, a step
22
+ * rules still refused after their re-ask, a building step whose answer still
23
+ * broke a building rule after its re-ask (AGL-3596), a provider or model error, a step
23
24
  * the machine gave up on after its attempts (a timeout or a lease that kept
24
25
  * expiring), a step that failed of its own accord, and a step no runner could
25
26
  * take. Never for a person canceling — they pay for what ran until they did —
@@ -58,10 +59,35 @@ import { accountCreditMeter, returnAssistCredits, workspaceCreditMeter } from ".
58
59
  const job = input.jobId.replace(/[^A-Za-z0-9_-]/g, '').slice(0, 48);
59
60
  return `${AI_JOB_REFUND_SOURCE}-${input.day.replace(/-/g, '')}-${job}-${Math.max(0, Math.floor(input.ordinal))}`;
60
61
  }
61
- /** How many give-backs a meter's month already records for `day`. */ export function aiJobRefundsOn(returns, day) {
62
- if (!returns || typeof returns !== 'object') return 0;
62
+ /**
63
+ * A build item's give-back key (AGL-3616): per job, per item and per
64
+ * attempt, so one item's failure gives back once however often its pass is
65
+ * replayed, and Try again's next attempt is a give-back of its own. The day
66
+ * is the JOB's (the day it was created), not the day of the give-back, so a
67
+ * pass replayed across midnight finds the same key. `item` marks it, so the
68
+ * daily bound counts a build's give-backs once per job. `slot` is the unit's
69
+ * slot (`c0`, `p1`, `i2`) or `plan` for the build's own planning.
70
+ */ export function aiJobItemRefundKey(input) {
71
+ const job = input.jobId.replace(/[^A-Za-z0-9_-]/g, '').slice(0, 40);
72
+ const slot = input.slot.replace(/[^A-Za-z0-9]/g, '').slice(0, 8) || 'x';
73
+ return `${AI_JOB_REFUND_SOURCE}-${input.day.replace(/-/g, '')}-item-${job}-${slot}-${Math.max(0, Math.floor(input.attempt))}`;
74
+ }
75
+ /** The tail every item key ends in: its slot and attempt. */ const ITEM_KEY_TAIL = /-[A-Za-z0-9]{1,8}-\d+$/;
76
+ /**
77
+ * What one give-back counts as toward the daily bound: the key itself, or
78
+ * for a build item's key its job (AGL-3616) — a ten-item build whose items
79
+ * fail is one give-back, not ten, or the bound would starve it.
80
+ */ function aiJobRefundIdentity(key, prefix) {
81
+ const rest = key.slice(prefix.length);
82
+ return rest.startsWith('item-') ? rest.replace(ITEM_KEY_TAIL, '') : rest;
83
+ }
84
+ /** How many give-backs a meter's month already records for `day`: build items once per job. */ export function aiJobRefundsOn(returns, day) {
85
+ return aiJobRefundIdentitiesOn(returns, day).size;
86
+ }
87
+ /** The give-backs a meter's month records for `day`, each by what it counts as. */ export function aiJobRefundIdentitiesOn(returns, day) {
88
+ if (!returns || typeof returns !== 'object') return new Set();
63
89
  const prefix = `${AI_JOB_REFUND_SOURCE}-${day.replace(/-/g, '')}-`;
64
- return Object.keys(returns).filter((key)=>key.startsWith(prefix)).length;
90
+ return new Set(Object.keys(returns).filter((key)=>key.startsWith(prefix)).map((key)=>aiJobRefundIdentity(key, prefix)));
65
91
  }
66
92
  /**
67
93
  * What a failure gives back: everything the job spent and has not already
@@ -78,8 +104,22 @@ import { accountCreditMeter, returnAssistCredits, workspaceCreditMeter } from ".
78
104
  var _input_free;
79
105
  const workspace = workspaceCreditMeter(firestore, input.orgId, input.month);
80
106
  const account = ((_input_free = input.free) == null ? void 0 : _input_free.accountUid) ? accountCreditMeter(firestore, input.free.accountUid, input.month) : null;
107
+ const key = input.item ? aiJobItemRefundKey({
108
+ day: input.item.jobDay,
109
+ jobId: input.jobId,
110
+ slot: input.item.slot,
111
+ attempt: input.item.attempt
112
+ }) : aiJobRefundKey({
113
+ day: input.day,
114
+ jobId: input.jobId,
115
+ ordinal: input.ordinal
116
+ });
81
117
  const counter = await (account != null ? account : workspace).ref.get();
82
- if (aiJobRefundsOn(counter.get(ASSIST_CREDIT_RETURNS_FIELD), input.day) >= AI_JOB_REFUNDS_PER_DAY) {
118
+ const returns = counter.get(ASSIST_CREDIT_RETURNS_FIELD);
119
+ const counted = aiJobRefundIdentitiesOn(returns, input.day);
120
+ // A build already given back today counts once: its later items ride the same give-back.
121
+ const jobCounted = input.item ? counted.has(aiJobRefundIdentity(key, `${AI_JOB_REFUND_SOURCE}-${input.item.jobDay.replace(/-/g, '')}-`)) : false;
122
+ if (!jobCounted && counted.size >= AI_JOB_REFUNDS_PER_DAY) {
83
123
  return {
84
124
  status: 'bounded',
85
125
  lines: []
@@ -94,11 +134,7 @@ import { accountCreditMeter, returnAssistCredits, workspaceCreditMeter } from ".
94
134
  workspace
95
135
  ],
96
136
  credits: input.credits,
97
- key: aiJobRefundKey({
98
- day: input.day,
99
- jobId: input.jobId,
100
- ordinal: input.ordinal
101
- }),
137
+ key,
102
138
  reason: `An AI job failed on our side (${input.reason})`,
103
139
  actorUid: AI_JOB_REFUND_ACTOR,
104
140
  source: AI_JOB_REFUND_SOURCE,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/usage/assist-job-refund.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 { ASSIST_CREDIT_RETURNS_FIELD } from './assist-credit-returns'\nimport {\n accountCreditMeter,\n returnAssistCredits,\n workspaceCreditMeter,\n type AssistCreditReturnOutcome,\n} from './assist-credit-returns-write'\nimport type { FreeAssistAccount } from './assist-free-taste'\n\n/**\n * A job that failed on our side gives back what it spent (AGL-3594).\n *\n * \"On our side\" is the machine's to say, and it says it for: a plan the plan\n * rules still refused after their re-ask, a provider or model error, a step\n * the machine gave up on after its attempts (a timeout or a lease that kept\n * expiring), a step that failed of its own accord, and a step no runner could\n * take. Never for a person canceling — they pay for what ran until they did —\n * nor for a model declining the brief, which the Free taste already meters as\n * a refusal and the refusal pause bounds.\n *\n * WHAT IS GIVEN BACK. A job that delivered nothing gives back everything it\n * spent and has not already given back. A job that delivered drafts and then\n * failed keeps them and pays for them: only the failing step's own spend goes\n * back. The site's listing proposal is not a delivery — it is derived from the\n * answers, costs nothing, and rides out with the first unit.\n *\n * HOW. Through the one give-back writer (`returnAssistCredits`, AGL-3595), to\n * the workspace's band and, on the Free taste, the owner's allowance — the\n * meters the spend was charged to — recorded as `system:job-refund`. The\n * tokens stay on the bill and the signal; the give-back nets them out of what\n * every reader calls \"used\".\n *\n * BOUNDED. The writer never returns more than a meter's month used. On top\n * of it, at most `AI_JOB_REFUNDS_PER_DAY` automatic give-backs an account a\n * UTC day (the workspace's own month where it names no owner), counted off\n * the give-backs the month already records, so a brief that keeps failing\n * cannot keep spending for free. Past it, the failure is metered as it always\n * was.\n */\n\n/** Automatic give-backs an account may have a UTC day. */\nexport const AI_JOB_REFUNDS_PER_DAY = 3\n\n/** The `source` a job's give-back is recorded under. */\nexport const AI_JOB_REFUND_SOURCE = 'job-refund'\n\n/** Who gives it back: the platform, not a member of staff. */\nexport const AI_JOB_REFUND_ACTOR = `system:${AI_JOB_REFUND_SOURCE}`\n\n/** Why a job's credits went back, as the job records it. */\nexport type AiJobRefundReason =\n | 'plan-refused'\n | 'provider'\n | 'timeout'\n | 'step-failure'\n | 'unavailable'\n\n/**\n * The give-back's key. Per job and per give-back on it (`ordinal`, the job's\n * count of give-backs so far), so one failure gives back once however often\n * it is handled, and a later failure of the same job — a plan tried again and\n * refused again — is a give-back of its own. The day leads, so a day's\n * give-backs are counted off the month's keys. The writer holds a key to\n * `[A-Za-z0-9_-]{8,100}`; the job id is cut to fit.\n */\nexport function aiJobRefundKey(input: { day: string; jobId: string; ordinal: number }): string {\n const job = input.jobId.replace(/[^A-Za-z0-9_-]/g, '').slice(0, 48)\n return `${AI_JOB_REFUND_SOURCE}-${input.day.replace(/-/g, '')}-${job}-${Math.max(0, Math.floor(input.ordinal))}`\n}\n\n/** How many give-backs a meter's month already records for `day`. */\nexport function aiJobRefundsOn(returns: unknown, day: string): number {\n if (!returns || typeof returns !== 'object') return 0\n const prefix = `${AI_JOB_REFUND_SOURCE}-${day.replace(/-/g, '')}-`\n return Object.keys(returns).filter((key) => key.startsWith(prefix)).length\n}\n\n/**\n * What a failure gives back: everything the job spent and has not already\n * given back when it delivered nothing, else the failing step's own spend.\n */\nexport function aiJobRefundCredits(input: {\n delivered: boolean\n jobCredits: number\n stepCredits: number\n alreadyRefunded: number\n}): number {\n const step = Math.max(0, Math.floor(input.stepCredits))\n if (input.delivered) return step\n return Math.max(0, Math.floor(input.jobCredits) - Math.max(0, Math.floor(input.alreadyRefunded)))\n}\n\nexport type AiJobRefundOutcome = AssistCreditReturnOutcome | { status: 'bounded'; lines: [] }\n\n/**\n * Give a failed job's credits back to the meters it was charged to, or say\n * why not.\n */\nexport async function refundJobCredits(\n firestore: FirebaseFirestore.Firestore,\n input: {\n orgId: string\n /** The Free taste's attribution, or `null` on a paid workspace. */\n free: FreeAssistAccount | null\n jobId: string\n ordinal: number\n credits: number\n month: string\n day: string\n reason: AiJobRefundReason\n },\n): Promise<AiJobRefundOutcome> {\n const workspace = workspaceCreditMeter(firestore, input.orgId, input.month)\n const account = input.free?.accountUid\n ? accountCreditMeter(firestore, input.free.accountUid, input.month)\n : null\n const counter = await (account ?? workspace).ref.get()\n if (aiJobRefundsOn(counter.get(ASSIST_CREDIT_RETURNS_FIELD), input.day) >= AI_JOB_REFUNDS_PER_DAY) {\n return { status: 'bounded', lines: [] }\n }\n return returnAssistCredits(firestore, {\n month: input.month,\n meters: account ? [workspace, account] : [workspace],\n credits: input.credits,\n key: aiJobRefundKey({ day: input.day, jobId: input.jobId, ordinal: input.ordinal }),\n reason: `An AI job failed on our side (${input.reason})`,\n actorUid: AI_JOB_REFUND_ACTOR,\n source: AI_JOB_REFUND_SOURCE,\n jobId: input.jobId,\n })\n}\n"],"names":["ASSIST_CREDIT_RETURNS_FIELD","accountCreditMeter","returnAssistCredits","workspaceCreditMeter","AI_JOB_REFUNDS_PER_DAY","AI_JOB_REFUND_SOURCE","AI_JOB_REFUND_ACTOR","aiJobRefundKey","input","job","jobId","replace","slice","day","Math","max","floor","ordinal","aiJobRefundsOn","returns","prefix","Object","keys","filter","key","startsWith","length","aiJobRefundCredits","step","stepCredits","delivered","jobCredits","alreadyRefunded","refundJobCredits","firestore","workspace","orgId","month","account","free","accountUid","counter","ref","get","status","lines","meters","credits","reason","actorUid","source"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,2BAA2B,QAAQ,6BAAyB;AACrE,SACEC,kBAAkB,EAClBC,mBAAmB,EACnBC,oBAAoB,QAEf,mCAA+B;AAGtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BC,GAED,wDAAwD,GACxD,OAAO,MAAMC,yBAAyB,EAAC;AAEvC,sDAAsD,GACtD,OAAO,MAAMC,uBAAuB,aAAY;AAEhD,4DAA4D,GAC5D,OAAO,MAAMC,sBAAsB,CAAC,OAAO,EAAED,sBAAsB,CAAA;AAUnE;;;;;;;CAOC,GACD,OAAO,SAASE,eAAeC,KAAsD;IACnF,MAAMC,MAAMD,MAAME,KAAK,CAACC,OAAO,CAAC,mBAAmB,IAAIC,KAAK,CAAC,GAAG;IAChE,OAAO,GAAGP,qBAAqB,CAAC,EAAEG,MAAMK,GAAG,CAACF,OAAO,CAAC,MAAM,IAAI,CAAC,EAAEF,IAAI,CAAC,EAAEK,KAAKC,GAAG,CAAC,GAAGD,KAAKE,KAAK,CAACR,MAAMS,OAAO,IAAI;AAClH;AAEA,mEAAmE,GACnE,OAAO,SAASC,eAAeC,OAAgB,EAAEN,GAAW;IAC1D,IAAI,CAACM,WAAW,OAAOA,YAAY,UAAU,OAAO;IACpD,MAAMC,SAAS,GAAGf,qBAAqB,CAAC,EAAEQ,IAAIF,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC;IAClE,OAAOU,OAAOC,IAAI,CAACH,SAASI,MAAM,CAAC,CAACC,MAAQA,IAAIC,UAAU,CAACL,SAASM,MAAM;AAC5E;AAEA;;;CAGC,GACD,OAAO,SAASC,mBAAmBnB,KAKlC;IACC,MAAMoB,OAAOd,KAAKC,GAAG,CAAC,GAAGD,KAAKE,KAAK,CAACR,MAAMqB,WAAW;IACrD,IAAIrB,MAAMsB,SAAS,EAAE,OAAOF;IAC5B,OAAOd,KAAKC,GAAG,CAAC,GAAGD,KAAKE,KAAK,CAACR,MAAMuB,UAAU,IAAIjB,KAAKC,GAAG,CAAC,GAAGD,KAAKE,KAAK,CAACR,MAAMwB,eAAe;AAChG;AAIA;;;CAGC,GACD,OAAO,eAAeC,iBACpBC,SAAsC,EACtC1B,KAUC;QAGeA;IADhB,MAAM2B,YAAYhC,qBAAqB+B,WAAW1B,MAAM4B,KAAK,EAAE5B,MAAM6B,KAAK;IAC1E,MAAMC,UAAU9B,EAAAA,cAAAA,MAAM+B,IAAI,qBAAV/B,YAAYgC,UAAU,IAClCvC,mBAAmBiC,WAAW1B,MAAM+B,IAAI,CAACC,UAAU,EAAEhC,MAAM6B,KAAK,IAChE;IACJ,MAAMI,UAAU,MAAM,CAACH,kBAAAA,UAAWH,WAAWO,GAAG,CAACC,GAAG;IACpD,IAAIzB,eAAeuB,QAAQE,GAAG,CAAC3C,8BAA8BQ,MAAMK,GAAG,KAAKT,wBAAwB;QACjG,OAAO;YAAEwC,QAAQ;YAAWC,OAAO,EAAE;QAAC;IACxC;IACA,OAAO3C,oBAAoBgC,WAAW;QACpCG,OAAO7B,MAAM6B,KAAK;QAClBS,QAAQR,UAAU;YAACH;YAAWG;SAAQ,GAAG;YAACH;SAAU;QACpDY,SAASvC,MAAMuC,OAAO;QACtBvB,KAAKjB,eAAe;YAAEM,KAAKL,MAAMK,GAAG;YAAEH,OAAOF,MAAME,KAAK;YAAEO,SAAST,MAAMS,OAAO;QAAC;QACjF+B,QAAQ,CAAC,8BAA8B,EAAExC,MAAMwC,MAAM,CAAC,CAAC,CAAC;QACxDC,UAAU3C;QACV4C,QAAQ7C;QACRK,OAAOF,MAAME,KAAK;IACpB;AACF"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/usage/assist-job-refund.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 { ASSIST_CREDIT_RETURNS_FIELD } from './assist-credit-returns'\nimport {\n accountCreditMeter,\n returnAssistCredits,\n workspaceCreditMeter,\n type AssistCreditReturnOutcome,\n} from './assist-credit-returns-write'\nimport type { FreeAssistAccount } from './assist-free-taste'\n\n/**\n * A job that failed on our side gives back what it spent (AGL-3594).\n *\n * \"On our side\" is the machine's to say, and it says it for: a plan the plan\n * rules still refused after their re-ask, a building step whose answer still\n * broke a building rule after its re-ask (AGL-3596), a provider or model error, a step\n * the machine gave up on after its attempts (a timeout or a lease that kept\n * expiring), a step that failed of its own accord, and a step no runner could\n * take. Never for a person canceling — they pay for what ran until they did —\n * nor for a model declining the brief, which the Free taste already meters as\n * a refusal and the refusal pause bounds.\n *\n * WHAT IS GIVEN BACK. A job that delivered nothing gives back everything it\n * spent and has not already given back. A job that delivered drafts and then\n * failed keeps them and pays for them: only the failing step's own spend goes\n * back. The site's listing proposal is not a delivery — it is derived from the\n * answers, costs nothing, and rides out with the first unit.\n *\n * HOW. Through the one give-back writer (`returnAssistCredits`, AGL-3595), to\n * the workspace's band and, on the Free taste, the owner's allowance — the\n * meters the spend was charged to — recorded as `system:job-refund`. The\n * tokens stay on the bill and the signal; the give-back nets them out of what\n * every reader calls \"used\".\n *\n * BOUNDED. The writer never returns more than a meter's month used. On top\n * of it, at most `AI_JOB_REFUNDS_PER_DAY` automatic give-backs an account a\n * UTC day (the workspace's own month where it names no owner), counted off\n * the give-backs the month already records, so a brief that keeps failing\n * cannot keep spending for free. Past it, the failure is metered as it always\n * was.\n */\n\n/** Automatic give-backs an account may have a UTC day. */\nexport const AI_JOB_REFUNDS_PER_DAY = 3\n\n/** The `source` a job's give-back is recorded under. */\nexport const AI_JOB_REFUND_SOURCE = 'job-refund'\n\n/** Who gives it back: the platform, not a member of staff. */\nexport const AI_JOB_REFUND_ACTOR = `system:${AI_JOB_REFUND_SOURCE}`\n\n/** Why a job's credits went back, as the job records it. */\nexport type AiJobRefundReason =\n | 'plan-refused'\n | 'doctrine-refused'\n | 'provider'\n | 'timeout'\n | 'step-failure'\n | 'unavailable'\n\n/**\n * The give-back's key. Per job and per give-back on it (`ordinal`, the job's\n * count of give-backs so far), so one failure gives back once however often\n * it is handled, and a later failure of the same job — a plan tried again and\n * refused again — is a give-back of its own. The day leads, so a day's\n * give-backs are counted off the month's keys. The writer holds a key to\n * `[A-Za-z0-9_-]{8,100}`; the job id is cut to fit.\n */\nexport function aiJobRefundKey(input: { day: string; jobId: string; ordinal: number }): string {\n const job = input.jobId.replace(/[^A-Za-z0-9_-]/g, '').slice(0, 48)\n return `${AI_JOB_REFUND_SOURCE}-${input.day.replace(/-/g, '')}-${job}-${Math.max(0, Math.floor(input.ordinal))}`\n}\n\n/**\n * A build item's give-back key (AGL-3616): per job, per item and per\n * attempt, so one item's failure gives back once however often its pass is\n * replayed, and Try again's next attempt is a give-back of its own. The day\n * is the JOB's (the day it was created), not the day of the give-back, so a\n * pass replayed across midnight finds the same key. `item` marks it, so the\n * daily bound counts a build's give-backs once per job. `slot` is the unit's\n * slot (`c0`, `p1`, `i2`) or `plan` for the build's own planning.\n */\nexport function aiJobItemRefundKey(input: { day: string; jobId: string; slot: string; attempt: number }): string {\n const job = input.jobId.replace(/[^A-Za-z0-9_-]/g, '').slice(0, 40)\n const slot = input.slot.replace(/[^A-Za-z0-9]/g, '').slice(0, 8) || 'x'\n return `${AI_JOB_REFUND_SOURCE}-${input.day.replace(/-/g, '')}-item-${job}-${slot}-${Math.max(0, Math.floor(input.attempt))}`\n}\n\n/** The tail every item key ends in: its slot and attempt. */\nconst ITEM_KEY_TAIL = /-[A-Za-z0-9]{1,8}-\\d+$/\n\n/**\n * What one give-back counts as toward the daily bound: the key itself, or\n * for a build item's key its job (AGL-3616) — a ten-item build whose items\n * fail is one give-back, not ten, or the bound would starve it.\n */\nfunction aiJobRefundIdentity(key: string, prefix: string): string {\n const rest = key.slice(prefix.length)\n return rest.startsWith('item-') ? rest.replace(ITEM_KEY_TAIL, '') : rest\n}\n\n/** How many give-backs a meter's month already records for `day`: build items once per job. */\nexport function aiJobRefundsOn(returns: unknown, day: string): number {\n return aiJobRefundIdentitiesOn(returns, day).size\n}\n\n/** The give-backs a meter's month records for `day`, each by what it counts as. */\nexport function aiJobRefundIdentitiesOn(returns: unknown, day: string): Set<string> {\n if (!returns || typeof returns !== 'object') return new Set()\n const prefix = `${AI_JOB_REFUND_SOURCE}-${day.replace(/-/g, '')}-`\n return new Set(\n Object.keys(returns)\n .filter((key) => key.startsWith(prefix))\n .map((key) => aiJobRefundIdentity(key, prefix)),\n )\n}\n\n/**\n * What a failure gives back: everything the job spent and has not already\n * given back when it delivered nothing, else the failing step's own spend.\n */\nexport function aiJobRefundCredits(input: {\n delivered: boolean\n jobCredits: number\n stepCredits: number\n alreadyRefunded: number\n}): number {\n const step = Math.max(0, Math.floor(input.stepCredits))\n if (input.delivered) return step\n return Math.max(0, Math.floor(input.jobCredits) - Math.max(0, Math.floor(input.alreadyRefunded)))\n}\n\nexport type AiJobRefundOutcome = AssistCreditReturnOutcome | { status: 'bounded'; lines: [] }\n\n/**\n * Give a failed job's credits back to the meters it was charged to, or say\n * why not.\n */\nexport async function refundJobCredits(\n firestore: FirebaseFirestore.Firestore,\n input: {\n orgId: string\n /** The Free taste's attribution, or `null` on a paid workspace. */\n free: FreeAssistAccount | null\n jobId: string\n ordinal: number\n credits: number\n month: string\n day: string\n reason: AiJobRefundReason\n /**\n * A build item's give-back (AGL-3616), keyed by `aiJobItemRefundKey`\n * rather than by `ordinal`: its slot and attempt, and the day the job was\n * created. Counted toward the daily bound once per job.\n */\n item?: { slot: string; attempt: number; jobDay: string }\n },\n): Promise<AiJobRefundOutcome> {\n const workspace = workspaceCreditMeter(firestore, input.orgId, input.month)\n const account = input.free?.accountUid\n ? accountCreditMeter(firestore, input.free.accountUid, input.month)\n : null\n const key = input.item\n ? aiJobItemRefundKey({ day: input.item.jobDay, jobId: input.jobId, slot: input.item.slot, attempt: input.item.attempt })\n : aiJobRefundKey({ day: input.day, jobId: input.jobId, ordinal: input.ordinal })\n const counter = await (account ?? workspace).ref.get()\n const returns = counter.get(ASSIST_CREDIT_RETURNS_FIELD)\n const counted = aiJobRefundIdentitiesOn(returns, input.day)\n // A build already given back today counts once: its later items ride the same give-back.\n const jobCounted = input.item\n ? counted.has(aiJobRefundIdentity(key, `${AI_JOB_REFUND_SOURCE}-${input.item.jobDay.replace(/-/g, '')}-`))\n : false\n if (!jobCounted && counted.size >= AI_JOB_REFUNDS_PER_DAY) {\n return { status: 'bounded', lines: [] }\n }\n return returnAssistCredits(firestore, {\n month: input.month,\n meters: account ? [workspace, account] : [workspace],\n credits: input.credits,\n key,\n reason: `An AI job failed on our side (${input.reason})`,\n actorUid: AI_JOB_REFUND_ACTOR,\n source: AI_JOB_REFUND_SOURCE,\n jobId: input.jobId,\n })\n}\n"],"names":["ASSIST_CREDIT_RETURNS_FIELD","accountCreditMeter","returnAssistCredits","workspaceCreditMeter","AI_JOB_REFUNDS_PER_DAY","AI_JOB_REFUND_SOURCE","AI_JOB_REFUND_ACTOR","aiJobRefundKey","input","job","jobId","replace","slice","day","Math","max","floor","ordinal","aiJobItemRefundKey","slot","attempt","ITEM_KEY_TAIL","aiJobRefundIdentity","key","prefix","rest","length","startsWith","aiJobRefundsOn","returns","aiJobRefundIdentitiesOn","size","Set","Object","keys","filter","map","aiJobRefundCredits","step","stepCredits","delivered","jobCredits","alreadyRefunded","refundJobCredits","firestore","workspace","orgId","month","account","free","accountUid","item","jobDay","counter","ref","get","counted","jobCounted","has","status","lines","meters","credits","reason","actorUid","source"],"mappings":"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"}
@@ -148,6 +148,11 @@ export interface AssistTokenUsage {
148
148
  outputTokens: number;
149
149
  cacheReadTokens: number;
150
150
  cacheWriteTokens: number;
151
+ /**
152
+ * Pictures a door delivered (AGL-3602), priced per picture by the image
153
+ * catalog. Absent on every text exchange.
154
+ */
155
+ images?: number;
151
156
  }
152
157
  /**
153
158
  * What one exchange DRAWS FROM THE CUSTOMER, at the serving model's billed
@@ -793,6 +793,8 @@ async function reserveInTransaction(firestore, orgId, entitled, now, org, subjec
793
793
  feedback: null
794
794
  }, record.editOps && record.editOps > 0 ? {
795
795
  editOps: Math.floor(record.editOps)
796
+ } : {}, record.usage.images && record.usage.images > 0 ? {
797
+ images: Math.floor(record.usage.images)
796
798
  } : {}, {
797
799
  createdAt: serverTimestamp()
798
800
  }));