@aglyn/plugins-ai 1.0.0-beta.229 → 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 (248) 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-logic-zones.d.ts +64 -0
  47. package/src/lib/components/ai-logic-zones.js +18 -0
  48. package/src/lib/components/ai-logic-zones.js.map +1 -0
  49. package/src/lib/components/ai-logic.component.d.ts +77 -0
  50. package/src/lib/components/ai-logic.component.js +553 -0
  51. package/src/lib/components/ai-logic.component.js.map +1 -0
  52. package/src/lib/components/ai-marketing-insight.component.d.ts +30 -0
  53. package/src/lib/components/ai-marketing-insight.component.js +80 -0
  54. package/src/lib/components/ai-marketing-insight.component.js.map +1 -0
  55. package/src/lib/components/ai-media-create.component.d.ts +55 -0
  56. package/src/lib/components/ai-media-create.component.js +433 -0
  57. package/src/lib/components/ai-media-create.component.js.map +1 -0
  58. package/src/lib/components/ai-overlay-cards.component.d.ts +83 -0
  59. package/src/lib/components/ai-overlay-cards.component.js +505 -0
  60. package/src/lib/components/ai-overlay-cards.component.js.map +1 -0
  61. package/src/lib/components/ai-product-zones.d.ts +5 -0
  62. package/src/lib/components/ai-product-zones.js +1 -1
  63. package/src/lib/components/ai-product-zones.js.map +1 -1
  64. package/src/lib/components/ai-products-create-button.component.d.ts +42 -0
  65. package/src/lib/components/ai-products-create-button.component.js +67 -0
  66. package/src/lib/components/ai-products-create-button.component.js.map +1 -0
  67. package/src/lib/components/ai-products-hub-card.component.js +100 -81
  68. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  69. package/src/lib/components/ai-revise-automation.component.d.ts +16 -0
  70. package/src/lib/components/ai-revise-automation.component.js +207 -0
  71. package/src/lib/components/ai-revise-automation.component.js.map +1 -0
  72. package/src/lib/components/ai-site-build-page.component.js +140 -16
  73. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  74. package/src/lib/components/ai-upsell-dialog.component.d.ts +53 -0
  75. package/src/lib/components/ai-upsell-dialog.component.js +177 -0
  76. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -0
  77. package/src/lib/components/assist-build-card.component.d.ts +42 -0
  78. package/src/lib/components/assist-build-card.component.js +183 -0
  79. package/src/lib/components/assist-build-card.component.js.map +1 -0
  80. package/src/lib/components/assist-edit-canvas.d.ts +2 -0
  81. package/src/lib/components/assist-edit-canvas.js +56 -1
  82. package/src/lib/components/assist-edit-canvas.js.map +1 -1
  83. package/src/lib/components/assist-jobs-drawer.component.js +48 -3
  84. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  85. package/src/lib/components/assist-panel.component.js +51 -2
  86. package/src/lib/components/assist-panel.component.js.map +1 -1
  87. package/src/lib/components/use-ai-job-run.d.ts +3 -14
  88. package/src/lib/components/use-ai-job-run.js +0 -50
  89. package/src/lib/components/use-ai-job-run.js.map +1 -1
  90. package/src/lib/jobs/ai-build-capabilities.d.ts +58 -0
  91. package/src/lib/jobs/ai-build-capabilities.js +256 -0
  92. package/src/lib/jobs/ai-build-capabilities.js.map +1 -0
  93. package/src/lib/jobs/ai-build-unit-outcome.d.ts +52 -0
  94. package/src/lib/jobs/ai-build-unit-outcome.js +138 -0
  95. package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -0
  96. package/src/lib/jobs/ai-job-build-step.d.ts +144 -0
  97. package/src/lib/jobs/ai-job-build-step.js +657 -0
  98. package/src/lib/jobs/ai-job-build-step.js.map +1 -0
  99. package/src/lib/jobs/ai-job-draft-ids.js +9 -1
  100. package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
  101. package/src/lib/jobs/ai-job-drafts.d.ts +14 -4
  102. package/src/lib/jobs/ai-job-drafts.js +25 -4
  103. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  104. package/src/lib/jobs/ai-job-logic-step.d.ts +107 -0
  105. package/src/lib/jobs/ai-job-logic-step.js +395 -0
  106. package/src/lib/jobs/ai-job-logic-step.js.map +1 -0
  107. package/src/lib/jobs/ai-job-overlay-copy.d.ts +70 -0
  108. package/src/lib/jobs/ai-job-overlay-copy.js +178 -0
  109. package/src/lib/jobs/ai-job-overlay-copy.js.map +1 -0
  110. package/src/lib/jobs/ai-job-page-step.js +6 -6
  111. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  112. package/src/lib/jobs/ai-job-plan-step.d.ts +26 -1
  113. package/src/lib/jobs/ai-job-plan-step.js +79 -11
  114. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  115. package/src/lib/jobs/ai-job-site-step.d.ts +15 -1
  116. package/src/lib/jobs/ai-job-site-step.js +200 -58
  117. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  118. package/src/lib/jobs/ai-job-text-step.d.ts +22 -1
  119. package/src/lib/jobs/ai-job-text-step.js +12 -0
  120. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  121. package/src/lib/jobs/ai-job-workflow-step.d.ts +54 -4
  122. package/src/lib/jobs/ai-job-workflow-step.js +318 -13
  123. package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
  124. package/src/lib/jobs/ai-jobs.d.ts +80 -2
  125. package/src/lib/jobs/ai-jobs.js +377 -13
  126. package/src/lib/jobs/ai-jobs.js.map +1 -1
  127. package/src/lib/jobs/ai-logic-records.d.ts +40 -0
  128. package/src/lib/jobs/ai-logic-records.js +55 -0
  129. package/src/lib/jobs/ai-logic-records.js.map +1 -0
  130. package/src/lib/jobs/ai-site-publish.d.ts +7 -0
  131. package/src/lib/jobs/ai-site-publish.js +9 -0
  132. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  133. package/src/lib/jobs/ai-workflow-records.d.ts +4 -0
  134. package/src/lib/jobs/ai-workflow-records.js +40 -1
  135. package/src/lib/jobs/ai-workflow-records.js.map +1 -1
  136. package/src/lib/model/ai-automation-draft.d.ts +18 -0
  137. package/src/lib/model/ai-automation-draft.js +86 -0
  138. package/src/lib/model/ai-automation-draft.js.map +1 -1
  139. package/src/lib/model/ai-build-job.d.ts +201 -0
  140. package/src/lib/model/ai-build-job.js +471 -0
  141. package/src/lib/model/ai-build-job.js.map +1 -0
  142. package/src/lib/model/ai-build-plan.d.ts +61 -0
  143. package/src/lib/model/ai-build-plan.js +154 -9
  144. package/src/lib/model/ai-build-plan.js.map +1 -1
  145. package/src/lib/model/ai-build-progress.d.ts +56 -0
  146. package/src/lib/model/ai-build-progress.js +128 -0
  147. package/src/lib/model/ai-build-progress.js.map +1 -0
  148. package/src/lib/model/ai-insight.d.ts +20 -10
  149. package/src/lib/model/ai-insight.js +72 -9
  150. package/src/lib/model/ai-insight.js.map +1 -1
  151. package/src/lib/model/ai-job-activity.js +3 -1
  152. package/src/lib/model/ai-job-activity.js.map +1 -1
  153. package/src/lib/model/ai-jobs.types.d.ts +80 -2
  154. package/src/lib/model/ai-jobs.types.js +3 -1
  155. package/src/lib/model/ai-jobs.types.js.map +1 -1
  156. package/src/lib/model/ai-logic-job.d.ts +108 -0
  157. package/src/lib/model/ai-logic-job.js +327 -0
  158. package/src/lib/model/ai-logic-job.js.map +1 -0
  159. package/src/lib/model/ai-media-credits.d.ts +52 -0
  160. package/src/lib/model/ai-media-credits.js +61 -0
  161. package/src/lib/model/ai-media-credits.js.map +1 -0
  162. package/src/lib/model/ai-overlay-copy.d.ts +114 -0
  163. package/src/lib/model/ai-overlay-copy.js +175 -0
  164. package/src/lib/model/ai-overlay-copy.js.map +1 -0
  165. package/src/lib/model/ai-site-build-progress.d.ts +20 -3
  166. package/src/lib/model/ai-site-build-progress.js +170 -10
  167. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  168. package/src/lib/model/ai-svg.d.ts +69 -0
  169. package/src/lib/model/ai-svg.js +265 -0
  170. package/src/lib/model/ai-svg.js.map +1 -0
  171. package/src/lib/model/ai-usage-by-user.d.ts +3 -2
  172. package/src/lib/model/ai-usage-by-user.js.map +1 -1
  173. package/src/lib/model/ai-workflow-job.d.ts +92 -4
  174. package/src/lib/model/ai-workflow-job.js +124 -7
  175. package/src/lib/model/ai-workflow-job.js.map +1 -1
  176. package/src/lib/model/assist-build.d.ts +58 -0
  177. package/src/lib/model/assist-build.js +52 -0
  178. package/src/lib/model/assist-build.js.map +1 -0
  179. package/src/lib/model/assist-edit.d.ts +48 -3
  180. package/src/lib/model/assist-edit.js +48 -4
  181. package/src/lib/model/assist-edit.js.map +1 -1
  182. package/src/lib/plugin-config.js +2 -0
  183. package/src/lib/plugin-config.js.map +1 -1
  184. package/src/lib/plugin.js +240 -11
  185. package/src/lib/plugin.js.map +1 -1
  186. package/src/lib/providers/catalog.d.ts +81 -4
  187. package/src/lib/providers/catalog.js +80 -9
  188. package/src/lib/providers/catalog.js.map +1 -1
  189. package/src/lib/providers/image-contract.d.ts +116 -0
  190. package/src/lib/providers/image-contract.js +74 -0
  191. package/src/lib/providers/image-contract.js.map +1 -0
  192. package/src/lib/providers/model-choice.js +11 -0
  193. package/src/lib/providers/model-choice.js.map +1 -1
  194. package/src/lib/providers/routing.js +18 -0
  195. package/src/lib/providers/routing.js.map +1 -1
  196. package/src/lib/providers/vertex-image.d.ts +121 -0
  197. package/src/lib/providers/vertex-image.js +312 -0
  198. package/src/lib/providers/vertex-image.js.map +1 -0
  199. package/src/lib/runtime/ai-doctrine-validators.d.ts +19 -0
  200. package/src/lib/runtime/ai-doctrine-validators.js +89 -0
  201. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  202. package/src/lib/runtime/ai-doctrine.js +11 -5
  203. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  204. package/src/lib/runtime/ai-eval-live.js +4 -0
  205. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  206. package/src/lib/runtime/ai-eval.d.ts +15 -1
  207. package/src/lib/runtime/ai-eval.js +52 -1
  208. package/src/lib/runtime/ai-eval.js.map +1 -1
  209. package/src/lib/runtime/experiment-variant-copy.d.ts +34 -0
  210. package/src/lib/runtime/experiment-variant-copy.js +102 -0
  211. package/src/lib/runtime/experiment-variant-copy.js.map +1 -0
  212. package/src/lib/server/ai-experiment-versions.d.ts +51 -0
  213. package/src/lib/server/ai-experiment-versions.js +270 -0
  214. package/src/lib/server/ai-experiment-versions.js.map +1 -0
  215. package/src/lib/server/ai-jobs-resume.js +74 -4
  216. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  217. package/src/lib/server/ai-media-image.d.ts +66 -0
  218. package/src/lib/server/ai-media-image.js +521 -0
  219. package/src/lib/server/ai-media-image.js.map +1 -0
  220. package/src/lib/server/ai-media-svg.d.ts +91 -0
  221. package/src/lib/server/ai-media-svg.js +186 -0
  222. package/src/lib/server/ai-media-svg.js.map +1 -0
  223. package/src/lib/server/assist-build.d.ts +54 -0
  224. package/src/lib/server/assist-build.js +121 -0
  225. package/src/lib/server/assist-build.js.map +1 -0
  226. package/src/lib/server/assist-chat.js +108 -12
  227. package/src/lib/server/assist-chat.js.map +1 -1
  228. package/src/lib/server/assist-edit.js +213 -3
  229. package/src/lib/server/assist-edit.js.map +1 -1
  230. package/src/lib/server/assist-view-context.js +242 -4
  231. package/src/lib/server/assist-view-context.js.map +1 -1
  232. package/src/lib/server.js +26 -0
  233. package/src/lib/server.js.map +1 -1
  234. package/src/lib/tools/ai-logic-tool.d.ts +27 -0
  235. package/src/lib/tools/ai-logic-tool.js +207 -0
  236. package/src/lib/tools/ai-logic-tool.js.map +1 -0
  237. package/src/lib/tools/ai-overlay-tool.d.ts +35 -0
  238. package/src/lib/tools/ai-overlay-tool.js +107 -0
  239. package/src/lib/tools/ai-overlay-tool.js.map +1 -0
  240. package/src/lib/tools/ai-workflow-tool.d.ts +11 -0
  241. package/src/lib/tools/ai-workflow-tool.js +31 -0
  242. package/src/lib/tools/ai-workflow-tool.js.map +1 -1
  243. package/src/lib/usage/assist-job-refund.d.ts +28 -1
  244. package/src/lib/usage/assist-job-refund.js +44 -9
  245. package/src/lib/usage/assist-job-refund.js.map +1 -1
  246. package/src/lib/usage/assist-usage.d.ts +5 -0
  247. package/src/lib/usage/assist-usage.js +2 -0
  248. package/src/lib/usage/assist-usage.js.map +1 -1
@@ -27,7 +27,6 @@ import { AI_JOB_PLAN_STEP, AI_JOB_TERMINAL_STATUSES } from "../model/ai-jobs.typ
27
27
  import { resolveEffectivePlan } from "@aglyn/aglyn/app-utils/plan-entitlements";
28
28
  import { aiOverageReservationRefusal } from "../billing/ai-overage-gate.js";
29
29
  import { aiAllotmentRefusalText } from "../model/ai-allotments.js";
30
- import { aiJobPlanCreditEstimate } from "../model/ai-site-job.js";
31
30
  import { AI_OFF_FOR_SITE_COPY, isAiOffForSite } from "../model/ai-site-switch.js";
32
31
  import { resolveAiModelChoice } from "../providers/model-choice.js";
33
32
  import { aiOutputTargetType } from "../activity/ai-activity-actions.js";
@@ -40,7 +39,8 @@ import { AI_JOB_TEXT_STEP_MINIMUM_MS, runAiJobTextStep } from "./ai-job-text-ste
40
39
  import { AI_JOB_SEO_STEP_MINIMUM_MS } from "./ai-job-seo-budget.js";
41
40
  import { AI_JOB_THEME_STEP_MINIMUM_MS } from "./ai-job-theme-budget.js";
42
41
  import { assistExchangeExpiry, assistUsageDay, assistUsageMonth, recordAssistCost, releaseAssistMessage, reserveAssistMessage } from "../usage/assist-usage.js";
43
- import { aiJobRefundCredits, refundJobCredits } from "../usage/assist-job-refund.js";
42
+ import { aiJobItemRefundKey, aiJobRefundCredits, refundJobCredits } from "../usage/assist-job-refund.js";
43
+ import { aiBuildSettlement, aiBuildUnits, aiBuildRetryLedger, aiBuildCreditEstimate, aiLedgerUnits, aiJobCreditEstimate } from "../model/ai-build-job.js";
44
44
  import { freeAssistAccount } from "../usage/assist-free-taste.js";
45
45
  import { aiJobAutoConfirms } from "./ai-job-auto-confirm.js";
46
46
  import { AI_SITE_GUIDED_BUILD_FAILED_COPY } from "../model/ai-job-failure-copy.js";
@@ -153,7 +153,8 @@ export { AI_JOB_PLAN_STEP };
153
153
  'layout',
154
154
  'template',
155
155
  'form',
156
- 'email'
156
+ 'email',
157
+ 'build'
157
158
  ];
158
159
  const stepRunners = new Map();
159
160
  const stepMinimums = new Map();
@@ -456,7 +457,7 @@ export async function getAiJob(firestore, orgId, jobId) {
456
457
  }
457
458
  /** The wire form. `running` is the lease, read against `now`. */ export function aiJobSummary(job, now = new Date()) {
458
459
  var _job_hostId, _job_steps, _job_outputs, _job_creditsReserved, _job_creditsSpent, _job_refundedCredits, _toIso, _toIso1, _job_error, _job_plan_labels, _job_plan_confirmedBy, _job_review, _job_applied_versions, _job_applied_staged;
459
- var _job_lease, _job_inputs;
460
+ var _job_lease, _job_inputs, _job_items, _job_inputs1;
460
461
  const leaseUntil = toMillis((_job_lease = job.lease) == null ? void 0 : _job_lease.until);
461
462
  return _extends({
462
463
  id: job.$id,
@@ -503,6 +504,12 @@ export async function getAiJob(firestore, orgId, jobId) {
503
504
  sitePublish: job.sitePublish
504
505
  } : {}, job.kind === 'site' ? {
505
506
  siteInputs: aiSiteStartInputsOf(job.inputs)
507
+ } : {}, ((_job_items = job.items) == null ? void 0 : _job_items.length) ? {
508
+ items: job.items
509
+ } : {}, job.orchestration ? {
510
+ orchestration: job.orchestration
511
+ } : {}, job.kind === 'build' && ((_job_inputs1 = job.inputs) == null ? void 0 : _job_inputs1['publish']) === true ? {
512
+ publishAsked: true
506
513
  } : {});
507
514
  }
508
515
  function leaseIsLive(job, owner, nowMs) {
@@ -622,6 +629,38 @@ function nextStepIndex(job) {
622
629
  });
623
630
  });
624
631
  }
632
+ /**
633
+ * A build's ledger with one pass applied (AGL-3616): the pass's credits on
634
+ * the item's row, its status, failure, note and outputs, and what was given
635
+ * back for it. Pure; the machine applies it inside `recordStep`.
636
+ */ export function aiApplyJobItemRecord(ledger, record, credits) {
637
+ return ledger.map((row)=>{
638
+ var _row_creditsSpent, _row_attemptCredits, _row_creditsRefunded, _record_creditsRefunded, _row_outputs, _record_outputs;
639
+ var _record_degradedBy;
640
+ return row.slot !== record.slot ? row : _extends({}, row, {
641
+ status: record.status,
642
+ creditsSpent: ((_row_creditsSpent = row.creditsSpent) != null ? _row_creditsSpent : 0) + credits,
643
+ attemptCredits: ((_row_attemptCredits = row.attemptCredits) != null ? _row_attemptCredits : 0) + credits,
644
+ creditsRefunded: ((_row_creditsRefunded = row.creditsRefunded) != null ? _row_creditsRefunded : 0) + ((_record_creditsRefunded = record.creditsRefunded) != null ? _record_creditsRefunded : 0)
645
+ }, record.refundKey ? {
646
+ refundKey: record.refundKey
647
+ } : {}, record.failure !== undefined ? {
648
+ failure: record.failure
649
+ } : {}, record.note !== undefined ? {
650
+ note: record.note
651
+ } : {}, ((_record_degradedBy = record.degradedBy) == null ? void 0 : _record_degradedBy.length) ? {
652
+ degradedBy: record.degradedBy
653
+ } : {}, {
654
+ outputs: [
655
+ ...(_row_outputs = row.outputs) != null ? _row_outputs : [],
656
+ ...((_record_outputs = record.outputs) != null ? _record_outputs : []).filter((id)=>{
657
+ var _row_outputs;
658
+ return !((_row_outputs = row.outputs) != null ? _row_outputs : []).includes(id);
659
+ })
660
+ ]
661
+ });
662
+ });
663
+ }
625
664
  /**
626
665
  * The runs whose stop reasons a step's record keeps (AGL-3042), newest last.
627
666
  * A page job that builds a layout, a form and a component, then five sections
@@ -679,8 +718,8 @@ function nextStepIndex(job) {
679
718
  */ export async function recordStep(firestore, orgId, jobId, owner, stepIndex, input, now = new Date()) {
680
719
  const ref = jobsCollection(firestore, orgId).doc(jobId);
681
720
  const recorded = await firestore.runTransaction(async (tx)=>{
682
- var _job_outputs, _input_outputs, _job_creditsSpent, _job_creditsReserved, _job_creditsReserved1;
683
- var _job_lease;
721
+ var _ref, _input_items, _job_outputs, _input_outputs, _job_creditsSpent, _job_creditsReserved, _job_creditsReserved1, _job_refundedCredits;
722
+ var _job_lease, _input_item;
684
723
  const job = jobFrom(await tx.get(ref));
685
724
  if (!job) throw new Error(`ai job ${orgId}/${jobId} vanished`);
686
725
  const steps = job.steps.map((step, index)=>{
@@ -698,6 +737,9 @@ function nextStepIndex(job) {
698
737
  } : {}) : step;
699
738
  });
700
739
  const remaining = steps.filter((step)=>step.status === 'pending' || step.status === 'running').length;
740
+ // A build's item ledger moves with its spend, in this one write (AGL-3616).
741
+ const ledger = (_ref = (_input_items = input.items) != null ? _input_items : job.items) != null ? _ref : null;
742
+ const items = ledger && input.item ? aiApplyJobItemRecord(ledger, input.item, input.creditsSpent) : input.items ? ledger : null;
701
743
  const settled = input.status !== 'pending';
702
744
  const ownsLease = ((_job_lease = job.lease) == null ? void 0 : _job_lease.owner) === owner;
703
745
  const parksForReview = Boolean(input.review) && !isAiJobTerminal(job.status);
@@ -720,6 +762,10 @@ function nextStepIndex(job) {
720
762
  plan: input.plan
721
763
  } : {}, input.sitePublish ? {
722
764
  sitePublish: input.sitePublish
765
+ } : {}, items ? {
766
+ items
767
+ } : {}, ((_input_item = input.item) == null ? void 0 : _input_item.creditsRefunded) ? {
768
+ refundedCredits: ((_job_refundedCredits = job.refundedCredits) != null ? _job_refundedCredits : 0) + input.item.creditsRefunded
723
769
  } : {}, parksForReview && input.review ? {
724
770
  review: input.review,
725
771
  error: input.review.reason === 'plan' ? null : input.review.message
@@ -903,7 +949,27 @@ async function transition(firestore, orgId, jobId, apply) {
903
949
  * ended meanwhile — a person's cancel, which pays for what ran — is left as it
904
950
  * ended and given nothing back.
905
951
  */ async function failOurFailure(firestore, orgId, jobId, message, detail, refund, now) {
952
+ var _current_items;
906
953
  const current = await getAiJob(firestore, orgId, jobId);
954
+ // A build that got as far as its items settles item by item (AGL-3616):
955
+ // what it delivered stands, and what it did not is given back per item.
956
+ if (current && aiJobSettlesByItem(current.kind) && ((_current_items = current.items) == null ? void 0 : _current_items.length) && !isAiJobTerminal(current.status)) {
957
+ var _detail_stepIndex;
958
+ console.error('ai build stopped', {
959
+ orgId,
960
+ jobId,
961
+ stepIndex: (_detail_stepIndex = detail.stepIndex) != null ? _detail_stepIndex : null,
962
+ error: detail.error
963
+ });
964
+ return settleAiBuildJob(firestore, orgId, jobId, {
965
+ now,
966
+ free: refund.free,
967
+ stopped: {
968
+ message,
969
+ reason: refund.reason
970
+ }
971
+ });
972
+ }
907
973
  if (current && !isAiJobTerminal(current.status)) {
908
974
  await refundOurFailure(firestore, orgId, current, _extends({}, refund, {
909
975
  now
@@ -911,6 +977,256 @@ async function transition(firestore, orgId, jobId, apply) {
911
977
  }
912
978
  return failAiJob(firestore, orgId, jobId, message, detail, now);
913
979
  }
980
+ // ── A build's settlement (AGL-3616) ──────────────────────────────────────
981
+ /**
982
+ * The kinds settled item by item (AGL-3616): a build, and a site scaffold,
983
+ * which is the build's preset. Their step writes an item ledger; a failed
984
+ * item gives back its own spend and the rest goes on.
985
+ */ export function aiJobSettlesByItem(kind) {
986
+ return kind === 'build' || kind === 'site';
987
+ }
988
+ /**
989
+ * Whether Try again may run a job's failed items: a finished build, done or
990
+ * failed; a site scaffold that delivered part of its site — one that built
991
+ * nothing starts over from its own answers instead.
992
+ */ export function aiBuildRetryable(job) {
993
+ if (job.kind === 'build') return job.status === 'done' || job.status === 'failed';
994
+ return job.kind === 'site' && job.status === 'done';
995
+ }
996
+ /** What a build that delivered nothing says; its planning was given back too. */ export const AI_BUILD_NOTHING_BUILT_COPY = 'Nothing in this request could be built. Try again, or ask for less at once.';
997
+ /** What an item the build never reached says when the build stopped early. */ export const AI_BUILD_NOT_REACHED_COPY = 'Not built: the build stopped before it reached this.';
998
+ /**
999
+ * Gives back one build item's spend, or its planning's (`slot: 'plan'`),
1000
+ * under the item key (job, slot, attempt), so a replayed pass gives back once
1001
+ * and a later attempt is a give-back of its own. The credits given back and
1002
+ * the key, or `null` when nothing was. Never throws.
1003
+ */ async function refundBuildCredits(firestore, orgId, job, input) {
1004
+ const credits = Math.max(0, Math.floor(input.credits));
1005
+ if (credits <= 0) return null;
1006
+ try {
1007
+ var _data;
1008
+ const free = input.free !== undefined ? input.free : freeAssistAccount((_data = (await firestore.collection('orgs').doc(orgId).get()).data()) != null ? _data : {});
1009
+ const created = toMillis(job.createdAt);
1010
+ const jobDay = assistUsageDay(created === null ? input.now : new Date(created));
1011
+ const refund = await refundJobCredits(firestore, {
1012
+ orgId,
1013
+ free,
1014
+ jobId: job.$id,
1015
+ ordinal: 0,
1016
+ credits,
1017
+ month: assistUsageMonth(input.now),
1018
+ day: assistUsageDay(input.now),
1019
+ reason: input.reason,
1020
+ item: {
1021
+ slot: input.slot,
1022
+ attempt: input.attempt,
1023
+ jobDay
1024
+ }
1025
+ });
1026
+ // A key the month already holds was given back by an earlier run of this pass.
1027
+ if (refund.status !== 'returned' && refund.status !== 'duplicate') return null;
1028
+ return {
1029
+ credits,
1030
+ key: aiJobItemRefundKey({
1031
+ day: jobDay,
1032
+ jobId: job.$id,
1033
+ slot: input.slot,
1034
+ attempt: input.attempt
1035
+ })
1036
+ };
1037
+ } catch (error) {
1038
+ console.error('ai build refund failed', {
1039
+ orgId,
1040
+ jobId: job.$id,
1041
+ slot: input.slot,
1042
+ error
1043
+ });
1044
+ return null;
1045
+ }
1046
+ }
1047
+ /** What the build's plan step spent: its orchestration. */ function aiBuildPlanCredits(job) {
1048
+ var _ref;
1049
+ var _job_steps_find;
1050
+ return (_ref = (_job_steps_find = job.steps.find((step)=>step.name === AI_JOB_PLAN_STEP)) == null ? void 0 : _job_steps_find.creditsSpent) != null ? _ref : 0;
1051
+ }
1052
+ /**
1053
+ * Ends a build (AGL-3616), once nothing is left to build or the machine
1054
+ * stopped it. A build that STOPPED — a provider failure, its attempts used up
1055
+ * — fails the item it was on and gives back that attempt's spend, and leaves
1056
+ * the items it never reached `skipped`. Then: anything delivered, and its
1057
+ * planning is charged and the job is `done`, failed items and all; nothing
1058
+ * delivered, and its planning is given back too and the job is `failed`.
1059
+ */ export async function settleAiBuildJob(firestore, orgId, jobId, input) {
1060
+ var _job_items, _job_items1, _job_items2, _ref, _ref1;
1061
+ var _job_orchestration;
1062
+ const { now } = input;
1063
+ let job = await getAiJob(firestore, orgId, jobId);
1064
+ if (!job || isAiJobTerminal(job.status)) return job;
1065
+ const stopped = input.stopped;
1066
+ if (stopped && ((_job_items = job.items) != null ? _job_items : []).some((row)=>row.status === 'pending' || row.status === 'running')) {
1067
+ var _job_items3;
1068
+ const refunds = new Map();
1069
+ for (const row of (_job_items3 = job.items) != null ? _job_items3 : []){
1070
+ var _row_attemptCredits;
1071
+ if (row.status !== 'running') continue;
1072
+ const refund = await refundBuildCredits(firestore, orgId, job, {
1073
+ slot: row.slot,
1074
+ attempt: row.attempt,
1075
+ credits: (_row_attemptCredits = row.attemptCredits) != null ? _row_attemptCredits : 0,
1076
+ reason: stopped.reason,
1077
+ free: input.free,
1078
+ now
1079
+ });
1080
+ if (refund) refunds.set(row.slot, refund);
1081
+ }
1082
+ job = (await transition(firestore, orgId, jobId, (current)=>{
1083
+ var _current_items, _current_refundedCredits;
1084
+ if (isAiJobTerminal(current.status)) return null;
1085
+ let given = 0;
1086
+ const items = ((_current_items = current.items) != null ? _current_items : []).map((row)=>{
1087
+ if (row.status === 'running') {
1088
+ var _ref, _row_creditsRefunded, _ref1;
1089
+ const refund = refunds.get(row.slot);
1090
+ given += (_ref = refund == null ? void 0 : refund.credits) != null ? _ref : 0;
1091
+ return _extends({}, row, {
1092
+ status: 'failed',
1093
+ failure: {
1094
+ ours: true,
1095
+ reason: stopped.reason,
1096
+ message: stopped.message
1097
+ },
1098
+ creditsRefunded: ((_row_creditsRefunded = row.creditsRefunded) != null ? _row_creditsRefunded : 0) + ((_ref1 = refund == null ? void 0 : refund.credits) != null ? _ref1 : 0)
1099
+ }, refund ? {
1100
+ refundKey: refund.key
1101
+ } : {});
1102
+ }
1103
+ return row.status === 'pending' ? _extends({}, row, {
1104
+ status: 'skipped',
1105
+ note: AI_BUILD_NOT_REACHED_COPY
1106
+ }) : row;
1107
+ });
1108
+ return {
1109
+ items,
1110
+ refundedCredits: ((_current_refundedCredits = current.refundedCredits) != null ? _current_refundedCredits : 0) + given,
1111
+ updatedAt: now
1112
+ };
1113
+ })).job;
1114
+ }
1115
+ // A site is delivered when a page is (AGL-3616): a form alone is not a
1116
+ // site. A scaffold that owed no page (a deployment without the page kind)
1117
+ // is delivered by whatever it did build.
1118
+ const sitePages = job.kind === 'site' ? ((_job_items1 = job.items) != null ? _job_items1 : []).filter((row)=>row.op === 'page') : [];
1119
+ const delivered = sitePages.length ? sitePages.some((row)=>row.status === 'succeeded' || row.status === 'degraded') : aiBuildSettlement((_job_items2 = job.items) != null ? _job_items2 : []).delivered;
1120
+ const planCredits = aiBuildPlanCredits(job);
1121
+ if (delivered) {
1122
+ await transition(firestore, orgId, jobId, (current)=>isAiJobTerminal(current.status) ? null : {
1123
+ orchestration: {
1124
+ creditsSpent: planCredits,
1125
+ settled: 'charged'
1126
+ },
1127
+ updatedAt: now
1128
+ });
1129
+ return completeAiJob(firestore, orgId, jobId, now);
1130
+ }
1131
+ // A site that built no page gives back what its other parts cost too: it
1132
+ // was not built, and its Try again starts over from its answers.
1133
+ const parts = new Map();
1134
+ if (job.kind === 'site') {
1135
+ var _job_items4;
1136
+ for (const row of (_job_items4 = job.items) != null ? _job_items4 : []){
1137
+ var _row_creditsSpent, _row_creditsRefunded, _ref2;
1138
+ const owed = Math.max(0, ((_row_creditsSpent = row.creditsSpent) != null ? _row_creditsSpent : 0) - ((_row_creditsRefunded = row.creditsRefunded) != null ? _row_creditsRefunded : 0));
1139
+ if (owed <= 0 || row.status === 'failed') continue;
1140
+ const given = await refundBuildCredits(firestore, orgId, job, {
1141
+ slot: row.slot,
1142
+ attempt: row.attempt,
1143
+ credits: owed,
1144
+ reason: (_ref2 = stopped == null ? void 0 : stopped.reason) != null ? _ref2 : 'step-failure',
1145
+ free: input.free,
1146
+ now
1147
+ });
1148
+ if (given) parts.set(row.slot, given);
1149
+ }
1150
+ }
1151
+ const refund = ((_job_orchestration = job.orchestration) == null ? void 0 : _job_orchestration.settled) === 'refunded' ? null : await refundBuildCredits(firestore, orgId, job, {
1152
+ slot: 'plan',
1153
+ attempt: 1,
1154
+ credits: planCredits,
1155
+ reason: (_ref = stopped == null ? void 0 : stopped.reason) != null ? _ref : 'step-failure',
1156
+ free: input.free,
1157
+ now
1158
+ });
1159
+ await transition(firestore, orgId, jobId, (current)=>{
1160
+ var _current_refundedCredits, _ref, _ref1, _current_items;
1161
+ var _current_orchestration;
1162
+ return isAiJobTerminal(current.status) ? null : _extends({
1163
+ orchestration: {
1164
+ creditsSpent: planCredits,
1165
+ settled: refund || ((_current_orchestration = current.orchestration) == null ? void 0 : _current_orchestration.settled) === 'refunded' ? 'refunded' : 'charged'
1166
+ }
1167
+ }, refund || parts.size ? {
1168
+ refundedCredits: ((_current_refundedCredits = current.refundedCredits) != null ? _current_refundedCredits : 0) + ((_ref = refund == null ? void 0 : refund.credits) != null ? _ref : 0) + [
1169
+ ...parts.values()
1170
+ ].reduce((total, part)=>total + part.credits, 0),
1171
+ refundReason: (_ref1 = stopped == null ? void 0 : stopped.reason) != null ? _ref1 : 'step-failure'
1172
+ } : {}, parts.size ? {
1173
+ items: ((_current_items = current.items) != null ? _current_items : []).map((row)=>{
1174
+ var _row_creditsRefunded;
1175
+ const part = parts.get(row.slot);
1176
+ return part ? _extends({}, row, {
1177
+ creditsRefunded: ((_row_creditsRefunded = row.creditsRefunded) != null ? _row_creditsRefunded : 0) + part.credits,
1178
+ refundKey: part.key
1179
+ }) : row;
1180
+ })
1181
+ } : {}, {
1182
+ updatedAt: now
1183
+ });
1184
+ });
1185
+ const nothing = job.kind === 'site' ? AI_SITE_GUIDED_BUILD_FAILED_COPY : AI_BUILD_NOTHING_BUILT_COPY;
1186
+ return failAiJob(firestore, orgId, jobId, (_ref1 = stopped == null ? void 0 : stopped.message) != null ? _ref1 : nothing, {
1187
+ error: 'build delivered nothing'
1188
+ }, now);
1189
+ }
1190
+ /**
1191
+ * Try again on a build (AGL-3616): its failed items back to pending on their
1192
+ * next attempt, with what they left unbuilt (`aiBuildRetryLedger`), and its
1193
+ * generation step queued again. Never re-runs an item that succeeded, never
1194
+ * re-asks the plan. Held at what the retried items are estimated to cost.
1195
+ * `changed: false` for a job that is not a finished build, or has nothing to
1196
+ * try again.
1197
+ */ export async function retryAiBuildJob(firestore, orgId, jobId, now = new Date()) {
1198
+ let retried = [];
1199
+ const result = await transition(firestore, orgId, jobId, (current)=>{
1200
+ var _current_plan, _current_items;
1201
+ if (!aiBuildRetryable(current)) return null;
1202
+ const plan = ((_current_plan = current.plan) == null ? void 0 : _current_plan.status) === 'confirmed' ? current.plan : null;
1203
+ if (!plan || !((_current_items = current.items) == null ? void 0 : _current_items.length)) return null;
1204
+ const next = aiBuildRetryLedger(current.items, current.kind === 'site' ? aiLedgerUnits(current.items) : aiBuildUnits(plan));
1205
+ if (!next.ledger.some((row)=>row.status === 'pending')) return null;
1206
+ retried = next.retried;
1207
+ return {
1208
+ status: 'queued',
1209
+ items: next.ledger,
1210
+ steps: current.steps.map((step)=>step.name === AI_JOB_PLAN_STEP ? step : _extends({}, step, {
1211
+ status: 'pending',
1212
+ attempts: 0,
1213
+ passes: 0,
1214
+ endedAt: null,
1215
+ error: null
1216
+ })),
1217
+ review: null,
1218
+ error: null,
1219
+ lease: null,
1220
+ creditsReserved: Math.max(AI_JOB_STEP_RESERVE_CREDITS, current.kind === 'site' ? next.retried.length * AI_JOB_STEP_RESERVE_CREDITS : aiBuildCreditEstimate(plan, {
1221
+ slots: new Set(next.retried)
1222
+ })),
1223
+ updatedAt: now
1224
+ };
1225
+ });
1226
+ return _extends({}, result, {
1227
+ retried: result.changed ? retried : []
1228
+ });
1229
+ }
914
1230
  /**
915
1231
  * Resume a job that stopped for a person (AGL-2935): confirm its plan, or
916
1232
  * try again the step whose answer broke a building rule. One transaction,
@@ -921,9 +1237,9 @@ async function transition(firestore, orgId, jobId, apply) {
921
1237
  * it queues are held at the nominal figure again, which the park released —
922
1238
  * and a confirmed plan is held at what the whole job is estimated to cost
923
1239
  * (AGL-3031), creations included, where that is more.
924
- */ export async function resumeAiJob(firestore, orgId, jobId, actor, now = new Date()) {
1240
+ */ export async function resumeAiJob(firestore, orgId, jobId, actor, now = new Date(), options = {}) {
925
1241
  return transition(firestore, orgId, jobId, (current)=>{
926
- var _current_review;
1242
+ var _current_review, _current_inputs;
927
1243
  if (current.status !== 'needs_review') return null;
928
1244
  const steps = current.steps.map((step)=>step.status === 'pending' ? _extends({}, step, {
929
1245
  attempts: 0
@@ -938,13 +1254,18 @@ async function transition(firestore, orgId, jobId, apply) {
938
1254
  confirmedBy: actor.uid
939
1255
  })
940
1256
  } : {};
1257
+ const publish = confirming && current.kind === 'build' && options.publishConfirmed && ((_current_inputs = current.inputs) == null ? void 0 : _current_inputs['publish']) === true ? {
1258
+ inputs: _extends({}, current.inputs, {
1259
+ publishConfirmed: true
1260
+ })
1261
+ } : {};
941
1262
  return _extends({
942
1263
  status: pending ? 'queued' : 'done',
943
1264
  steps,
944
1265
  review: null,
945
1266
  error: null
946
- }, confirmed, {
947
- creditsReserved: pending ? Math.max(outstanding * AI_JOB_STEP_RESERVE_CREDITS, confirming ? aiJobPlanCreditEstimate(current.kind, confirming) : 0) : 0,
1267
+ }, confirmed, publish, {
1268
+ creditsReserved: pending ? Math.max(outstanding * AI_JOB_STEP_RESERVE_CREDITS, confirming ? aiJobCreditEstimate(current.kind, confirming) : 0) : 0,
948
1269
  updatedAt: now
949
1270
  });
950
1271
  });
@@ -1048,7 +1369,7 @@ async function transition(firestore, orgId, jobId, apply) {
1048
1369
  * round.
1049
1370
  */ export async function runAiJobStep(firestore, orgId, jobId, options) {
1050
1371
  var _options_now, _step_attempts, _options_org, _data, _ref, _ref1, _outcome_effort, _step_passes, _options_actor;
1051
- var _reservation_allotment, _reservation_allotment1, _outcome_review;
1372
+ var _reservation_allotment, _reservation_allotment1, _outcome_review, _itemRecord_failure, _recorded_job_items;
1052
1373
  const now = (_options_now = options.now) != null ? _options_now : new Date();
1053
1374
  // A reservation the caller took before the job existed is a message
1054
1375
  // counted against the org; a step that fails before the provider is
@@ -1390,6 +1711,26 @@ async function transition(firestore, orgId, jobId, apply) {
1390
1711
  // for a person never continues: the review is what it waits on. The pass
1391
1712
  // cap is what stops a runner that never finishes: past it the pass is
1392
1713
  // recorded as the step's last, and what it produced stands.
1714
+ // A build item that failed on our side gives back its own attempt's spend
1715
+ // before its row is written (AGL-3616), so the row records what was given.
1716
+ let itemRecord = aiJobSettlesByItem(job.kind) ? outcome.item : undefined;
1717
+ if ((itemRecord == null ? void 0 : itemRecord.status) === 'failed' && ((_itemRecord_failure = itemRecord.failure) == null ? void 0 : _itemRecord_failure.ours)) {
1718
+ var _ref2, _outcome_items, _ref3, _ref4, _reservation_free5;
1719
+ const slot = itemRecord.slot;
1720
+ const row = ((_ref2 = (_outcome_items = outcome.items) != null ? _outcome_items : job.items) != null ? _ref2 : []).find((one)=>one.slot === slot);
1721
+ const refund = await refundBuildCredits(firestore, orgId, job, {
1722
+ slot,
1723
+ attempt: (_ref3 = row == null ? void 0 : row.attempt) != null ? _ref3 : 1,
1724
+ credits: ((_ref4 = row == null ? void 0 : row.attemptCredits) != null ? _ref4 : 0) + credits,
1725
+ reason: itemRecord.failure.reason,
1726
+ free: (_reservation_free5 = reservation.free) != null ? _reservation_free5 : null,
1727
+ now
1728
+ });
1729
+ if (refund) itemRecord = _extends({}, itemRecord, {
1730
+ creditsRefunded: refund.credits,
1731
+ refundKey: refund.key
1732
+ });
1733
+ }
1393
1734
  const review = outcome.review;
1394
1735
  const continuing = !review && Boolean(outcome.continue) && ((_step_passes = step.passes) != null ? _step_passes : 0) + 1 < aiJobStepMaxPasses(job.kind);
1395
1736
  if (!review && outcome.continue && !continuing) {
@@ -1415,6 +1756,10 @@ async function transition(firestore, orgId, jobId, apply) {
1415
1756
  review
1416
1757
  } : {}, outcome.sitePublish ? {
1417
1758
  sitePublish: outcome.sitePublish
1759
+ } : {}, aiJobSettlesByItem(job.kind) && outcome.items ? {
1760
+ items: outcome.items
1761
+ } : {}, itemRecord ? {
1762
+ item: itemRecord
1418
1763
  } : {}, review ? {
1419
1764
  quiet: true
1420
1765
  } : {}), now);
@@ -1473,7 +1818,7 @@ async function transition(firestore, orgId, jobId, apply) {
1473
1818
  };
1474
1819
  }
1475
1820
  if (review && recorded.job.status === 'needs_review') {
1476
- var _reservation_free5;
1821
+ var _reservation_free6;
1477
1822
  // Nobody parked the job but its own step, so the row names no actor, as
1478
1823
  // the meter's park does.
1479
1824
  await logAiJobNeedsInput(orgId, {
@@ -1492,7 +1837,7 @@ async function transition(firestore, orgId, jobId, apply) {
1492
1837
  const parked = ours ? await refundOurFailure(firestore, orgId, recorded.job, {
1493
1838
  reason: ours,
1494
1839
  stepCredits: credits,
1495
- free: (_reservation_free5 = reservation.free) != null ? _reservation_free5 : null,
1840
+ free: (_reservation_free6 = reservation.free) != null ? _reservation_free6 : null,
1496
1841
  now
1497
1842
  }) : recorded.job;
1498
1843
  if (recorded.parked) await announceAiJobTransition(parked, 'needs-review');
@@ -1507,6 +1852,25 @@ async function transition(firestore, orgId, jobId, apply) {
1507
1852
  job: recorded.job
1508
1853
  };
1509
1854
  }
1855
+ // A build ends by its items (AGL-3616): done when anything was delivered.
1856
+ if (aiJobSettlesByItem(job.kind) && step.name !== AI_JOB_PLAN_STEP && ((_recorded_job_items = recorded.job.items) == null ? void 0 : _recorded_job_items.length)) {
1857
+ var _recorded_job_items1, _reservation_free7;
1858
+ // Items still open here are ones the pass cap cut off: the build stopped.
1859
+ const open = aiBuildSettlement((_recorded_job_items1 = recorded.job.items) != null ? _recorded_job_items1 : []).open;
1860
+ const settled = await settleAiBuildJob(firestore, orgId, jobId, _extends({
1861
+ now,
1862
+ free: (_reservation_free7 = reservation.free) != null ? _reservation_free7 : null
1863
+ }, open ? {
1864
+ stopped: {
1865
+ message: AI_UPSTREAM_FAILURE_COPY,
1866
+ reason: 'timeout'
1867
+ }
1868
+ } : {}));
1869
+ return {
1870
+ outcome: settled.status === 'failed' ? 'failed' : 'done',
1871
+ job: settled
1872
+ };
1873
+ }
1510
1874
  return {
1511
1875
  outcome: 'done',
1512
1876
  job: await completeAiJob(firestore, orgId, jobId, now)