@vercel/factory 0.0.14 → 0.0.16

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 (510) hide show
  1. package/CHANGELOG.md +356 -0
  2. package/README.md +96 -2
  3. package/dist/agent-routes.d.mts +113 -0
  4. package/dist/agent-routes.mjs +91 -0
  5. package/dist/agent-routes.mjs.map +1 -0
  6. package/dist/api-contracts.d.mts +1768 -0
  7. package/dist/api-contracts.mjs +119 -0
  8. package/dist/api-contracts.mjs.map +1 -0
  9. package/dist/api-images.d.mts +30 -0
  10. package/dist/api-images.mjs +28 -0
  11. package/dist/api-images.mjs.map +1 -0
  12. package/dist/api-task-graph.mjs +57 -0
  13. package/dist/api-task-graph.mjs.map +1 -0
  14. package/dist/api.d.mts +111 -216
  15. package/dist/api.mjs +1063 -99
  16. package/dist/api.mjs.map +1 -1
  17. package/dist/approval-access.d.mts +39 -0
  18. package/dist/approval-access.mjs +62 -0
  19. package/dist/approval-access.mjs.map +1 -0
  20. package/dist/approval-contracts.d.mts +698 -0
  21. package/dist/approval-contracts.mjs +27 -0
  22. package/dist/approval-contracts.mjs.map +1 -0
  23. package/dist/approval.d.mts +49 -0
  24. package/dist/approval.mjs +39 -0
  25. package/dist/approval.mjs.map +1 -0
  26. package/dist/blob/artifacts.d.mts +37 -0
  27. package/dist/blob/artifacts.mjs +45 -0
  28. package/dist/blob/artifacts.mjs.map +1 -0
  29. package/dist/blob/index.d.mts +84 -12
  30. package/dist/blob/index.mjs +179 -45
  31. package/dist/blob/index.mjs.map +1 -1
  32. package/dist/budget.d.mts +115 -0
  33. package/dist/budget.mjs +147 -0
  34. package/dist/budget.mjs.map +1 -0
  35. package/dist/budgets.d.mts +4 -0
  36. package/dist/budgets.mjs +4 -0
  37. package/dist/build-factory.d.mts +7 -4
  38. package/dist/build-factory.mjs +2 -1
  39. package/dist/build-factory.mjs.map +1 -1
  40. package/dist/change-verification/brief.d.mts +9 -0
  41. package/dist/change-verification/brief.mjs +13 -0
  42. package/dist/change-verification/brief.mjs.map +1 -0
  43. package/dist/change-verification/dispatch.d.mts +37 -0
  44. package/dist/change-verification/dispatch.mjs +83 -0
  45. package/dist/change-verification/dispatch.mjs.map +1 -0
  46. package/dist/change-verification/eve-tool.d.mts +61 -0
  47. package/dist/change-verification/eve-tool.mjs +89 -0
  48. package/dist/change-verification/eve-tool.mjs.map +1 -0
  49. package/dist/change-verification/result.mjs +130 -0
  50. package/dist/change-verification/result.mjs.map +1 -0
  51. package/dist/changes/eve-record-change.d.mts +56 -0
  52. package/dist/changes/eve-record-change.mjs +131 -0
  53. package/dist/changes/eve-record-change.mjs.map +1 -0
  54. package/dist/changes/presentation.d.mts +14 -0
  55. package/dist/changes.d.mts +24 -0
  56. package/dist/changes.mjs +29 -0
  57. package/dist/changes.mjs.map +1 -0
  58. package/dist/client-events.d.mts +25 -0
  59. package/dist/client-events.mjs +45 -0
  60. package/dist/client-events.mjs.map +1 -0
  61. package/dist/client-stream.d.mts +28 -0
  62. package/dist/client-stream.mjs +210 -0
  63. package/dist/client-stream.mjs.map +1 -0
  64. package/dist/client-transcript.d.mts +41 -0
  65. package/dist/client-transcript.mjs +233 -0
  66. package/dist/client-transcript.mjs.map +1 -0
  67. package/dist/client.d.mts +343 -0
  68. package/dist/client.mjs +366 -0
  69. package/dist/client.mjs.map +1 -0
  70. package/dist/code-review/contracts.d.mts +27 -0
  71. package/dist/code-review/eve-post-review.d.mts +70 -0
  72. package/dist/code-review/eve-post-review.mjs +195 -0
  73. package/dist/code-review/eve-post-review.mjs.map +1 -0
  74. package/dist/code-review/eve-review-comments.d.mts +75 -0
  75. package/dist/code-review/eve-review-comments.mjs +193 -0
  76. package/dist/code-review/eve-review-comments.mjs.map +1 -0
  77. package/dist/code-review/github-contracts.d.mts +10 -0
  78. package/dist/code-review/github-reporter.d.mts +46 -0
  79. package/dist/code-review/github-reporter.mjs +262 -0
  80. package/dist/code-review/github-reporter.mjs.map +1 -0
  81. package/dist/code-review/preparation.d.mts +34 -0
  82. package/dist/code-review/preparation.mjs +23 -0
  83. package/dist/code-review/preparation.mjs.map +1 -0
  84. package/dist/code-review/stall.d.mts +17 -0
  85. package/dist/code-review/stall.mjs +30 -0
  86. package/dist/code-review/stall.mjs.map +1 -0
  87. package/dist/code-review.d.mts +7 -0
  88. package/dist/code-review.mjs +5 -0
  89. package/dist/communication.d.mts +9 -0
  90. package/dist/communication.mjs +9 -0
  91. package/dist/connectors/github.d.mts +26 -0
  92. package/dist/connectors/github.mjs +55 -0
  93. package/dist/connectors/github.mjs.map +1 -0
  94. package/dist/connectors/index.d.mts +35 -3
  95. package/dist/connectors/index.mjs +3 -52
  96. package/dist/connectors/index.mjs.map +1 -1
  97. package/dist/connectors/outbox.d.mts +34 -0
  98. package/dist/connectors/outbox.mjs +50 -0
  99. package/dist/connectors/outbox.mjs.map +1 -0
  100. package/dist/connectors/slack-approval-blocks.mjs +136 -0
  101. package/dist/connectors/slack-approval-blocks.mjs.map +1 -0
  102. package/dist/connectors/slack-approvals.d.mts +29 -0
  103. package/dist/connectors/slack-approvals.mjs +346 -0
  104. package/dist/connectors/slack-approvals.mjs.map +1 -0
  105. package/dist/connectors/slack.d.mts +35 -52
  106. package/dist/connectors/slack.mjs +13 -85
  107. package/dist/connectors/slack.mjs.map +1 -1
  108. package/dist/deepsec/eve-tool.d.mts +110 -0
  109. package/dist/deepsec/eve-tool.mjs +166 -0
  110. package/dist/deepsec/eve-tool.mjs.map +1 -0
  111. package/dist/deepsec/finding.d.mts +84 -0
  112. package/dist/deepsec/finding.mjs +56 -0
  113. package/dist/deepsec/finding.mjs.map +1 -0
  114. package/dist/deepsec.d.mts +3 -0
  115. package/dist/delivery-metrics/metrics.d.mts +93 -0
  116. package/dist/delivery-metrics/metrics.mjs +200 -0
  117. package/dist/delivery-metrics/metrics.mjs.map +1 -0
  118. package/dist/delivery-metrics.d.mts +2 -0
  119. package/dist/delivery-metrics.mjs +2 -0
  120. package/dist/dispatch.d.mts +152 -0
  121. package/dist/dispatch.mjs +199 -0
  122. package/dist/dispatch.mjs.map +1 -0
  123. package/dist/errors.d.mts +15 -0
  124. package/dist/errors.mjs +38 -0
  125. package/dist/errors.mjs.map +1 -0
  126. package/dist/eve/index.d.mts +209 -1277
  127. package/dist/eve/index.mjs +312 -1073
  128. package/dist/eve/index.mjs.map +1 -1
  129. package/dist/eve/invoke.d.mts +144 -0
  130. package/dist/eve/invoke.mjs +254 -0
  131. package/dist/eve/invoke.mjs.map +1 -0
  132. package/dist/eve/session-client.d.mts +186 -0
  133. package/dist/eve/session-client.mjs +245 -0
  134. package/dist/eve/session-client.mjs.map +1 -0
  135. package/dist/eve/task-execution.d.mts +380 -0
  136. package/dist/eve/task-execution.mjs +73 -0
  137. package/dist/eve/task-execution.mjs.map +1 -0
  138. package/dist/eve/task-session.d.mts +129 -0
  139. package/dist/eve/task-session.mjs +245 -0
  140. package/dist/eve/task-session.mjs.map +1 -0
  141. package/dist/eve/transcript.d.mts +37 -0
  142. package/dist/eve/transcript.mjs +69 -0
  143. package/dist/eve/transcript.mjs.map +1 -0
  144. package/dist/execution-public.d.mts +8 -0
  145. package/dist/execution-public.mjs +7 -0
  146. package/dist/execution.d.mts +183 -0
  147. package/dist/execution.mjs +223 -0
  148. package/dist/execution.mjs.map +1 -0
  149. package/dist/finding-remediation/admission.d.mts +53 -0
  150. package/dist/finding-remediation/admission.mjs +90 -0
  151. package/dist/finding-remediation/admission.mjs.map +1 -0
  152. package/dist/finding-remediation/brief.d.mts +14 -0
  153. package/dist/finding-remediation/brief.mjs +25 -0
  154. package/dist/finding-remediation/brief.mjs.map +1 -0
  155. package/dist/finding-remediation/eve-tool.d.mts +29 -0
  156. package/dist/finding-remediation/eve-tool.mjs +34 -0
  157. package/dist/finding-remediation/eve-tool.mjs.map +1 -0
  158. package/dist/findings/fingerprint.d.mts +14 -0
  159. package/dist/findings/fingerprint.mjs +28 -0
  160. package/dist/findings/fingerprint.mjs.map +1 -0
  161. package/dist/findings.d.mts +7 -0
  162. package/dist/findings.mjs +6 -0
  163. package/dist/github-app.d.mts +54 -0
  164. package/dist/github-app.mjs +121 -0
  165. package/dist/github-app.mjs.map +1 -0
  166. package/dist/github-publication.d.mts +199 -0
  167. package/dist/github-publication.mjs +442 -0
  168. package/dist/github-publication.mjs.map +1 -0
  169. package/dist/github-transfer.d.mts +81 -0
  170. package/dist/github-transfer.mjs +273 -0
  171. package/dist/github-transfer.mjs.map +1 -0
  172. package/dist/github.d.mts +446 -2
  173. package/dist/github.mjs +357 -56
  174. package/dist/github.mjs.map +1 -1
  175. package/dist/image-input.d.mts +51 -0
  176. package/dist/image-input.mjs +63 -0
  177. package/dist/image-input.mjs.map +1 -0
  178. package/dist/inbox-activity.d.mts +53 -0
  179. package/dist/inbox-activity.mjs +41 -0
  180. package/dist/inbox-activity.mjs.map +1 -0
  181. package/dist/index.d.mts +16 -484
  182. package/dist/index.mjs +15 -522
  183. package/dist/intake-contracts.d.mts +55 -0
  184. package/dist/intake-contracts.mjs +33 -0
  185. package/dist/intake-contracts.mjs.map +1 -0
  186. package/dist/integrations/deepsec.d.mts +5 -0
  187. package/dist/integrations/deepsec.mjs +3 -0
  188. package/dist/integrations/github.d.mts +11 -0
  189. package/dist/integrations/github.mjs +9 -0
  190. package/dist/integrations/slack.d.mts +5 -0
  191. package/dist/integrations/slack.mjs +5 -0
  192. package/dist/integrations/vercel.d.mts +5 -0
  193. package/dist/integrations/vercel.mjs +4 -0
  194. package/dist/merge-resolution/contracts.d.mts +17 -0
  195. package/dist/merge-resolution/contracts.mjs +19 -0
  196. package/dist/merge-resolution/contracts.mjs.map +1 -0
  197. package/dist/merge-resolution/eve-tools.d.mts +47 -0
  198. package/dist/merge-resolution/eve-tools.mjs +122 -0
  199. package/dist/merge-resolution/eve-tools.mjs.map +1 -0
  200. package/dist/model-settings.d.mts +41 -0
  201. package/dist/model-settings.mjs +35 -0
  202. package/dist/model-settings.mjs.map +1 -0
  203. package/dist/multi-repository-changes/coordination.d.mts +49 -0
  204. package/dist/multi-repository-changes/coordination.mjs +115 -0
  205. package/dist/multi-repository-changes/coordination.mjs.map +1 -0
  206. package/dist/planning/cancellation.mjs +82 -0
  207. package/dist/planning/cancellation.mjs.map +1 -0
  208. package/dist/planning/configuration.d.mts +23 -0
  209. package/dist/planning/configuration.mjs +56 -0
  210. package/dist/planning/configuration.mjs.map +1 -0
  211. package/dist/planning/contracts.d.mts +136 -0
  212. package/dist/planning/contracts.mjs +42 -0
  213. package/dist/planning/contracts.mjs.map +1 -0
  214. package/dist/planning/dependencies.mjs +44 -0
  215. package/dist/planning/dependencies.mjs.map +1 -0
  216. package/dist/planning/effects.mjs +122 -0
  217. package/dist/planning/effects.mjs.map +1 -0
  218. package/dist/planning/reconcile.mjs +279 -0
  219. package/dist/planning/reconcile.mjs.map +1 -0
  220. package/dist/planning/runtime.d.mts +27 -0
  221. package/dist/planning/runtime.mjs +25 -0
  222. package/dist/planning/runtime.mjs.map +1 -0
  223. package/dist/planning/state.mjs +92 -0
  224. package/dist/planning/state.mjs.map +1 -0
  225. package/dist/planning/turns.mjs +262 -0
  226. package/dist/planning/turns.mjs.map +1 -0
  227. package/dist/planning/validation.mjs +106 -0
  228. package/dist/planning/validation.mjs.map +1 -0
  229. package/dist/postgres/index.d.mts +48 -1
  230. package/dist/postgres/index.mjs +45 -3
  231. package/dist/postgres/index.mjs.map +1 -1
  232. package/dist/presets/software-development/approval.d.mts +14 -0
  233. package/dist/presets/software-development/approval.mjs +21 -0
  234. package/dist/presets/software-development/approval.mjs.map +1 -0
  235. package/dist/presets/software-development/deepsec-brief.d.mts +8 -0
  236. package/dist/presets/software-development/deepsec-brief.mjs +14 -0
  237. package/dist/presets/software-development/deepsec-brief.mjs.map +1 -0
  238. package/dist/presets/software-development/dispatch.d.mts +56 -0
  239. package/dist/presets/software-development/dispatch.mjs +67 -0
  240. package/dist/presets/software-development/dispatch.mjs.map +1 -0
  241. package/dist/presets/software-development/eve-delegation-policy.d.mts +7 -0
  242. package/dist/presets/software-development/eve-delegation-policy.mjs +34 -0
  243. package/dist/presets/software-development/eve-delegation-policy.mjs.map +1 -0
  244. package/dist/presets/software-development/eve.d.mts +18 -0
  245. package/dist/presets/software-development/eve.mjs +19 -0
  246. package/dist/presets/software-development/eve.mjs.map +1 -0
  247. package/dist/presets/software-development/finish-task-policy.d.mts +7 -0
  248. package/dist/presets/software-development/finish-task-policy.mjs +9 -0
  249. package/dist/presets/software-development/finish-task-policy.mjs.map +1 -0
  250. package/dist/presets/software-development/recovery.d.mts +32 -0
  251. package/dist/presets/software-development/recovery.mjs +120 -0
  252. package/dist/presets/software-development/recovery.mjs.map +1 -0
  253. package/dist/presets/software-development/task-communication.d.mts +24 -0
  254. package/dist/presets/software-development/task-communication.mjs +157 -0
  255. package/dist/presets/software-development/task-communication.mjs.map +1 -0
  256. package/dist/presets/software-development.d.mts +10 -0
  257. package/dist/presets/software-development.mjs +9 -0
  258. package/dist/pull-requests/contracts.d.mts +72 -0
  259. package/dist/pull-requests/eve-fetch.d.mts +57 -0
  260. package/dist/pull-requests/eve-fetch.mjs +151 -0
  261. package/dist/pull-requests/eve-fetch.mjs.map +1 -0
  262. package/dist/pull-requests/github-label-manager.d.mts +27 -0
  263. package/dist/pull-requests/github-label-manager.mjs +40 -0
  264. package/dist/pull-requests/github-label-manager.mjs.map +1 -0
  265. package/dist/pull-requests/github-publisher.d.mts +67 -0
  266. package/dist/pull-requests/github-publisher.mjs +405 -0
  267. package/dist/pull-requests/github-publisher.mjs.map +1 -0
  268. package/dist/pull-requests.d.mts +6 -0
  269. package/dist/pull-requests.mjs +4 -0
  270. package/dist/receipts.d.mts +4 -0
  271. package/dist/receipts.mjs +3 -0
  272. package/dist/repositories.d.mts +18 -0
  273. package/dist/repositories.mjs +33 -0
  274. package/dist/repositories.mjs.map +1 -0
  275. package/dist/sandbox/command.d.mts +18 -0
  276. package/dist/sandbox/execution-abort.d.mts +16 -0
  277. package/dist/sandbox/execution-abort.mjs +49 -0
  278. package/dist/sandbox/execution-abort.mjs.map +1 -0
  279. package/dist/sandbox/execution-environment.d.mts +25 -0
  280. package/dist/sandbox/execution-run-ledger.d.mts +115 -0
  281. package/dist/sandbox/execution-run-ledger.mjs +209 -0
  282. package/dist/sandbox/execution-run-ledger.mjs.map +1 -0
  283. package/dist/sandbox/execution.d.mts +54 -0
  284. package/dist/sandbox/execution.mjs +228 -0
  285. package/dist/sandbox/execution.mjs.map +1 -0
  286. package/dist/sandbox/harness-runner.d.mts +79 -0
  287. package/dist/sandbox/harness-runner.mjs +162 -0
  288. package/dist/sandbox/harness-runner.mjs.map +1 -0
  289. package/dist/sandbox/index.d.mts +10 -0
  290. package/dist/sandbox/index.mjs +7 -0
  291. package/dist/sandbox/task-environment.d.mts +50 -0
  292. package/dist/sandbox/task-environment.mjs +140 -0
  293. package/dist/sandbox/task-environment.mjs.map +1 -0
  294. package/dist/sandbox/workspace.d.mts +29 -0
  295. package/dist/sandbox/workspace.mjs +28 -0
  296. package/dist/sandbox/workspace.mjs.map +1 -0
  297. package/dist/schema/agent-route.d.mts +40 -0
  298. package/dist/schema/agent-route.mjs +33 -0
  299. package/dist/schema/agent-route.mjs.map +1 -0
  300. package/dist/schema/answer.d.mts +23 -0
  301. package/dist/schema/answer.mjs +21 -0
  302. package/dist/schema/answer.mjs.map +1 -0
  303. package/dist/schema/budget.d.mts +38 -0
  304. package/dist/schema/budget.mjs +30 -0
  305. package/dist/schema/budget.mjs.map +1 -0
  306. package/dist/schema/catalog.mjs +72 -0
  307. package/dist/schema/catalog.mjs.map +1 -0
  308. package/dist/schema/change.d.mts +117 -0
  309. package/dist/schema/change.mjs +68 -0
  310. package/dist/schema/change.mjs.map +1 -0
  311. package/dist/schema/common.d.mts +33 -0
  312. package/dist/{id-BLIkCVue.mjs → schema/common.mjs} +7 -16
  313. package/dist/schema/common.mjs.map +1 -0
  314. package/dist/schema/communication.d.mts +52 -0
  315. package/dist/{communication-Kd9nU5RP.mjs → schema/communication.mjs} +8 -3
  316. package/dist/schema/communication.mjs.map +1 -0
  317. package/dist/schema/execution.d.mts +101 -0
  318. package/dist/schema/execution.mjs +86 -0
  319. package/dist/schema/execution.mjs.map +1 -0
  320. package/dist/schema/factory-config.d.mts +122 -0
  321. package/dist/schema/factory-config.mjs +90 -0
  322. package/dist/schema/factory-config.mjs.map +1 -0
  323. package/dist/schema/finding.d.mts +125 -0
  324. package/dist/schema/finding.mjs +67 -0
  325. package/dist/schema/finding.mjs.map +1 -0
  326. package/dist/schema/id.d.mts +43 -0
  327. package/dist/schema/id.mjs +32 -0
  328. package/dist/schema/id.mjs.map +1 -0
  329. package/dist/schema/outbox.d.mts +240 -0
  330. package/dist/schema/outbox.mjs +53 -0
  331. package/dist/schema/outbox.mjs.map +1 -0
  332. package/dist/schema/question-route.mjs +16 -0
  333. package/dist/schema/question-route.mjs.map +1 -0
  334. package/dist/schema/receipt.d.mts +81 -0
  335. package/dist/schema/receipt.mjs +94 -0
  336. package/dist/schema/receipt.mjs.map +1 -0
  337. package/dist/schema/repository.d.mts +30 -0
  338. package/dist/schema/repository.mjs +29 -0
  339. package/dist/schema/repository.mjs.map +1 -0
  340. package/dist/schema/session.d.mts +136 -0
  341. package/dist/schema/session.mjs +54 -0
  342. package/dist/schema/session.mjs.map +1 -0
  343. package/dist/schema/signal.d.mts +105 -0
  344. package/dist/schema/signal.mjs +81 -0
  345. package/dist/schema/signal.mjs.map +1 -0
  346. package/dist/schema/slack-pr-notifications.d.mts +12 -0
  347. package/dist/schema/slack-pr-notifications.mjs +11 -0
  348. package/dist/schema/slack-pr-notifications.mjs.map +1 -0
  349. package/dist/schema/stored-session.mjs +10 -0
  350. package/dist/schema/stored-session.mjs.map +1 -0
  351. package/dist/schema/stored-task.mjs +20 -0
  352. package/dist/schema/stored-task.mjs.map +1 -0
  353. package/dist/schema/task-effect.d.mts +39 -0
  354. package/dist/schema/task-effect.mjs +29 -0
  355. package/dist/schema/task-effect.mjs.map +1 -0
  356. package/dist/schema/task-graph.d.mts +3173 -0
  357. package/dist/schema/task-graph.mjs +97 -0
  358. package/dist/schema/task-graph.mjs.map +1 -0
  359. package/dist/schema/task-message.d.mts +254 -0
  360. package/dist/schema/task-message.mjs +189 -0
  361. package/dist/schema/task-message.mjs.map +1 -0
  362. package/dist/schema/task.d.mts +273 -0
  363. package/dist/schema/task.mjs +200 -0
  364. package/dist/schema/task.mjs.map +1 -0
  365. package/dist/schema/transcript.d.mts +176 -0
  366. package/dist/schema/transcript.mjs +87 -0
  367. package/dist/schema/transcript.mjs.map +1 -0
  368. package/dist/schema/transitions.d.mts +50 -0
  369. package/dist/schema/transitions.mjs +303 -0
  370. package/dist/schema/transitions.mjs.map +1 -0
  371. package/dist/schema/work.d.mts +150 -0
  372. package/dist/schema/work.mjs +102 -0
  373. package/dist/schema/work.mjs.map +1 -0
  374. package/dist/session-previews.d.mts +76 -0
  375. package/dist/session-previews.mjs +55 -0
  376. package/dist/session-previews.mjs.map +1 -0
  377. package/dist/session-review.d.mts +120 -0
  378. package/dist/session-review.mjs +79 -0
  379. package/dist/session-review.mjs.map +1 -0
  380. package/dist/signal-triage.d.mts +20 -0
  381. package/dist/signal-triage.mjs +85 -0
  382. package/dist/signal-triage.mjs.map +1 -0
  383. package/dist/signals.d.mts +20 -0
  384. package/dist/signals.mjs +19 -0
  385. package/dist/signals.mjs.map +1 -0
  386. package/dist/stall.d.mts +41 -0
  387. package/dist/stall.mjs +83 -0
  388. package/dist/stall.mjs.map +1 -0
  389. package/dist/storage.d.mts +4 -0
  390. package/dist/storage.mjs +4 -0
  391. package/dist/store/driver.d.mts +65 -0
  392. package/dist/store/driver.mjs +42 -0
  393. package/dist/store/driver.mjs.map +1 -0
  394. package/dist/store/engine.d.mts +683 -0
  395. package/dist/store/engine.mjs +2309 -0
  396. package/dist/store/engine.mjs.map +1 -0
  397. package/dist/store/memory.d.mts +24 -0
  398. package/dist/store/memory.mjs +69 -0
  399. package/dist/store/memory.mjs.map +1 -0
  400. package/dist/store/slack-pr-notifications.d.mts +44 -0
  401. package/dist/store/slack-pr-notifications.mjs +121 -0
  402. package/dist/store/slack-pr-notifications.mjs.map +1 -0
  403. package/dist/store/task-effects.d.mts +38 -0
  404. package/dist/store/task-effects.mjs +118 -0
  405. package/dist/store/task-effects.mjs.map +1 -0
  406. package/dist/store/task-graphs.d.mts +60 -0
  407. package/dist/store/task-graphs.mjs +212 -0
  408. package/dist/store/task-graphs.mjs.map +1 -0
  409. package/dist/store/task-messages.d.mts +136 -0
  410. package/dist/store/task-messages.mjs +475 -0
  411. package/dist/store/task-messages.mjs.map +1 -0
  412. package/dist/store/task-work.d.mts +180 -0
  413. package/dist/store/task-work.mjs +137 -0
  414. package/dist/store/task-work.mjs.map +1 -0
  415. package/dist/store/work-notifications.d.mts +19 -0
  416. package/dist/store/work-notifications.mjs +48 -0
  417. package/dist/store/work-notifications.mjs.map +1 -0
  418. package/dist/sweep.d.mts +79 -0
  419. package/dist/sweep.mjs +171 -0
  420. package/dist/sweep.mjs.map +1 -0
  421. package/dist/task-communication.d.mts +35 -0
  422. package/dist/task-communication.mjs +47 -0
  423. package/dist/task-communication.mjs.map +1 -0
  424. package/dist/task-graph-view.d.mts +267 -0
  425. package/dist/task-graph-view.mjs +17 -0
  426. package/dist/task-graph-view.mjs.map +1 -0
  427. package/dist/task-message-processing.d.mts +60 -0
  428. package/dist/task-message-processing.mjs +200 -0
  429. package/dist/task-message-processing.mjs.map +1 -0
  430. package/dist/task-protocols.d.mts +80 -0
  431. package/dist/task-protocols.mjs +47 -0
  432. package/dist/task-protocols.mjs.map +1 -0
  433. package/dist/tasks.d.mts +17 -0
  434. package/dist/tasks.mjs +17 -0
  435. package/dist/vercel-git.d.mts +53 -0
  436. package/dist/vercel-git.mjs +344 -0
  437. package/dist/vercel-git.mjs.map +1 -0
  438. package/dist/vercel-github-api.d.mts +103 -0
  439. package/dist/vercel-github-api.mjs +363 -0
  440. package/dist/vercel-github-api.mjs.map +1 -0
  441. package/dist/vercel.d.mts +34 -2
  442. package/dist/vercel.mjs +49 -3
  443. package/dist/vercel.mjs.map +1 -1
  444. package/dist/work-triage/eve-tool.d.mts +29 -0
  445. package/dist/work-triage/eve-tool.mjs +32 -0
  446. package/dist/work-triage/eve-tool.mjs.map +1 -0
  447. package/dist/work-triage/find-existing-work.d.mts +32 -0
  448. package/dist/work-triage/find-existing-work.mjs +31 -0
  449. package/dist/work-triage/find-existing-work.mjs.map +1 -0
  450. package/dist/work-triage/github.d.mts +15 -0
  451. package/dist/work-triage/github.mjs +14 -0
  452. package/dist/work-triage/github.mjs.map +1 -0
  453. package/dist/work-triage.d.mts +4 -0
  454. package/dist/work-triage.mjs +3 -0
  455. package/dist/workflows.d.mts +146 -0
  456. package/dist/workflows.mjs +139 -0
  457. package/dist/workflows.mjs.map +1 -0
  458. package/dist/workspace-diff-assembly.d.mts +33 -0
  459. package/dist/workspace-diff-assembly.mjs +95 -0
  460. package/dist/workspace-diff-assembly.mjs.map +1 -0
  461. package/dist/workspace-diff.d.mts +90 -0
  462. package/dist/workspace-diff.mjs +61 -0
  463. package/dist/workspace-diff.mjs.map +1 -0
  464. package/dist/workspace-files-git.d.mts +15 -0
  465. package/dist/workspace-files-git.mjs +61 -0
  466. package/dist/workspace-files-git.mjs.map +1 -0
  467. package/dist/workspace-files.d.mts +107 -0
  468. package/dist/workspace-files.mjs +74 -0
  469. package/dist/workspace-files.mjs.map +1 -0
  470. package/dist/workspace-snapshot.d.mts +26 -0
  471. package/dist/workspace-snapshot.mjs +80 -0
  472. package/dist/workspace-snapshot.mjs.map +1 -0
  473. package/docs/getting-started.md +104 -0
  474. package/docs/index.md +100 -0
  475. package/docs/recipes/cancellation.md +215 -0
  476. package/docs/recipes/custom-workflow.md +153 -0
  477. package/docs/recipes/dependent-tasks.md +207 -0
  478. package/docs/recipes/eve-agent.md +277 -0
  479. package/docs/recipes/human-input.md +204 -0
  480. package/docs/recipes/persistence-recovery.md +268 -0
  481. package/docs/recipes/retry-recovery.md +241 -0
  482. package/docs/recipes/task-messaging.md +215 -0
  483. package/docs/recipes/typed-eve-result.md +161 -0
  484. package/docs/runtime-integration.md +137 -0
  485. package/package.json +142 -46
  486. package/dist/approval-JX2Px9IC.mjs +0 -132
  487. package/dist/approval-JX2Px9IC.mjs.map +0 -1
  488. package/dist/budget-DzSiIA4t.mjs +0 -42
  489. package/dist/budget-DzSiIA4t.mjs.map +0 -1
  490. package/dist/change-D2U6v7Ek.mjs +0 -554
  491. package/dist/change-D2U6v7Ek.mjs.map +0 -1
  492. package/dist/communication-Kd9nU5RP.mjs.map +0 -1
  493. package/dist/coordination-Cq08hb1l.mjs +0 -76
  494. package/dist/coordination-Cq08hb1l.mjs.map +0 -1
  495. package/dist/dispatch-BaZS3PT8.d.mts +0 -76
  496. package/dist/driver-BC0Mt5KG.d.mts +0 -34
  497. package/dist/driver-Bbd25rNM.mjs +0 -23
  498. package/dist/driver-Bbd25rNM.mjs.map +0 -1
  499. package/dist/engine-DFHU2Pe-.d.mts +0 -781
  500. package/dist/engine-WaUM2cGA.mjs +0 -1068
  501. package/dist/engine-WaUM2cGA.mjs.map +0 -1
  502. package/dist/factory-config-BoRXOw43.mjs +0 -50
  503. package/dist/factory-config-BoRXOw43.mjs.map +0 -1
  504. package/dist/factory-config-D2yU3G0c.d.mts +0 -58
  505. package/dist/github-C5HNBN6p.d.mts +0 -279
  506. package/dist/id-BLIkCVue.mjs.map +0 -1
  507. package/dist/index-Bw6hggoH.d.mts +0 -40
  508. package/dist/index.mjs.map +0 -1
  509. package/dist/task-CcOyOJ3k.mjs +0 -154
  510. package/dist/task-CcOyOJ3k.mjs.map +0 -1
@@ -0,0 +1,2309 @@
1
+ import { answerIdSchema, changeIdSchema, outboxIdSchema, repositoryIdsSchema, taskIdSchema } from "../schema/id.mjs";
2
+ import { replyToSchema, usdSchema } from "../schema/common.mjs";
3
+ import { repositorySchema } from "../schema/repository.mjs";
4
+ import { budgetSchema } from "../schema/budget.mjs";
5
+ import { parseAgentRouteBinding } from "../schema/agent-route.mjs";
6
+ import { jsonValueSchema, taskWorkSchema } from "../schema/work.mjs";
7
+ import { taskKindSchema, taskOriginSchema, taskQuestionSchema, taskSchema } from "../schema/task.mjs";
8
+ import { InvalidTransitionError, assertTransition, changeInitialState, changeTransitions, legalNextStates, signalOutcomeTransitions, signalTriageTransitions, taskInitialState, taskTransitions } from "../schema/transitions.mjs";
9
+ import { costSchema, receiptSchema } from "../schema/receipt.mjs";
10
+ import { imageAttachmentSchema, imageAttachmentsSchema, imageIdSchema, imageUploadSchema } from "../image-input.mjs";
11
+ import { transcriptSnapshotSchema } from "../schema/transcript.mjs";
12
+ import { routeTaskWork } from "../agent-routes.mjs";
13
+ import { RecordExistsError, RecordNotFoundError, TransientStoreError, VersionConflictError } from "./driver.mjs";
14
+ import { answerEventSchema } from "../schema/answer.mjs";
15
+ import { taskGraphDependenciesSchema } from "../schema/task-graph.mjs";
16
+ import { defineTaskMessageProtocols } from "../schema/task-message.mjs";
17
+ import { createTaskGraphStore } from "./task-graphs.mjs";
18
+ import { createTaskMessageStore } from "./task-messages.mjs";
19
+ import { createTaskWorkStore } from "./task-work.mjs";
20
+ import { createTaskEffectStore } from "./task-effects.mjs";
21
+ import { defaultTaskWorkflow, defineWorkflows } from "../workflows.mjs";
22
+ import { createWorkNotifier } from "./work-notifications.mjs";
23
+ import { communicationEventSchema } from "../schema/communication.mjs";
24
+ import { agentSessionIdSchema, agentSessionSchema } from "../schema/session.mjs";
25
+ import { externalSessionSchema, storedSessionSchema } from "../schema/stored-session.mjs";
26
+ import { changeSchema } from "../schema/change.mjs";
27
+ import { outboxMessageSchema, outboxQueueSchema } from "../schema/outbox.mjs";
28
+ import { storedTaskSchema } from "../schema/stored-task.mjs";
29
+ import { changeCatalogSchema, externalSessionCatalogSchema, sessionCatalogSchema, taskCatalogSchema, taskRouteSchema } from "../schema/catalog.mjs";
30
+ import { questionRouteSchema } from "../schema/question-route.mjs";
31
+ import { signalSchema } from "../schema/signal.mjs";
32
+ import { createHash, randomUUID } from "node:crypto";
33
+ import { z } from "zod";
34
+ import { isDeepStrictEqual } from "node:util";
35
+ //#region src/store/engine.ts
36
+ const storedImageSchema = imageAttachmentSchema.extend({
37
+ schemaVersion: z.literal(1).describe("Stored image schema version."),
38
+ owner: z.string().min(1).describe("Authenticated image owner."),
39
+ base64: imageUploadSchema.shape.base64,
40
+ createdAt: z.iso.datetime().describe("Image creation time.")
41
+ }).refine((image) => {
42
+ const bytes = Buffer.from(image.base64, "base64");
43
+ if (bytes.length !== image.size || bytes.toString("base64") !== image.base64) return false;
44
+ if (image.mediaType === "image/png") return bytes.length >= 24 && bytes.subarray(0, 8).equals(Buffer.from([
45
+ 137,
46
+ 80,
47
+ 78,
48
+ 71,
49
+ 13,
50
+ 10,
51
+ 26,
52
+ 10
53
+ ]));
54
+ if (image.mediaType === "image/jpeg") return bytes.length >= 4 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255;
55
+ return bytes.length >= 16 && bytes.toString("ascii", 0, 4) === "RIFF" && bytes.toString("ascii", 8, 12) === "WEBP";
56
+ }, "Image bytes do not match the declared PNG, JPEG, or WebP format");
57
+ const storedTranscriptSchema = z.object({
58
+ schemaVersion: z.literal(1),
59
+ execution: z.object({
60
+ provider: z.string().min(1),
61
+ sessionId: z.string().min(1)
62
+ }),
63
+ snapshot: transcriptSnapshotSchema,
64
+ updatedAt: z.iso.datetime()
65
+ });
66
+ /** Error raised when uploaded image bytes, ownership, or attachment metadata is invalid. */
67
+ var ImageInputError = class extends Error {};
68
+ /** An approval decision that does not apply to the task's current approval or state. */
69
+ var ApprovalError = class extends Error {
70
+ taskId;
71
+ reason;
72
+ constructor(taskId, reason) {
73
+ super(`approval decision on "${taskId}" refused: ${reason}`);
74
+ this.name = "ApprovalError";
75
+ this.taskId = taskId;
76
+ this.reason = reason;
77
+ }
78
+ };
79
+ /** A settlement that does not apply to the task's current state. */
80
+ var SettlementError = class extends Error {
81
+ taskId;
82
+ reason;
83
+ constructor(taskId, reason) {
84
+ super(`settlement on "${taskId}" refused: ${reason}`);
85
+ this.name = "SettlementError";
86
+ this.taskId = taskId;
87
+ this.reason = reason;
88
+ }
89
+ };
90
+ /** Error raised when a Task is not authorized to create or update a requested Change. */
91
+ var ChangeAssociationError = class extends Error {
92
+ taskId;
93
+ repositoryId;
94
+ reason;
95
+ constructor(taskId, repositoryId, reason) {
96
+ super(`change for task "${taskId}" refused: ${reason}`);
97
+ this.name = "ChangeAssociationError";
98
+ this.taskId = taskId;
99
+ this.repositoryId = repositoryId;
100
+ this.reason = reason;
101
+ }
102
+ };
103
+ /** Error raised when a stable outbox event ID is reused with different content. */
104
+ var OutboxEventConflictError = class extends Error {
105
+ constructor(eventId) {
106
+ super(`outbox event "${eventId}" was reused with different content`);
107
+ this.name = "OutboxEventConflictError";
108
+ }
109
+ };
110
+ /** Error raised when a QuestionTask already has a different immutable answer event. */
111
+ var AnswerEventConflictError = class extends Error {
112
+ constructor(questionTaskId) {
113
+ super(`question "${questionTaskId}" already has a different answer event`);
114
+ this.name = "AnswerEventConflictError";
115
+ }
116
+ };
117
+ const createTaskInputSchema = z.strictObject({
118
+ repositoryIds: repositoryIdsSchema,
119
+ kind: taskKindSchema,
120
+ origin: taskOriginSchema,
121
+ replyTo: replyToSchema,
122
+ dedupeKey: z.string().min(1).refine((key) => key.trim().length > 0).optional().describe("Stable key for retryable task admission; omitted for one-shot creation."),
123
+ area: z.string().min(1).optional(),
124
+ changeId: changeIdSchema.optional(),
125
+ question: taskQuestionSchema.optional(),
126
+ approval: z.literal("required").optional().describe("Require approval before execution."),
127
+ work: taskWorkSchema,
128
+ parentTaskId: taskIdSchema.optional(),
129
+ parentTaskAttempt: z.number().int().positive().optional(),
130
+ dependencies: taskGraphDependenciesSchema.optional()
131
+ });
132
+ /** One completed model step's usage, as eve's step.completed event carries it. */
133
+ const stepUsageSchema = z.strictObject({
134
+ turnId: z.string().min(1).describe("Provider turn containing the completed step."),
135
+ stepIndex: z.number().int().nonnegative().describe("Nonnegative index of the completed step."),
136
+ costUsd: usdSchema.optional().describe("Measured cost for this step."),
137
+ inputTokens: z.number().int().nonnegative().optional().describe("Input tokens charged for this step."),
138
+ outputTokens: z.number().int().nonnegative().optional().describe("Output tokens charged for this step."),
139
+ cacheReadTokens: z.number().int().nonnegative().optional().describe("Cached input tokens read for this step."),
140
+ cacheWriteTokens: z.number().int().nonnegative().optional().describe("Input tokens written to cache for this step.")
141
+ });
142
+ /** Error raised when a Task operation loses its exact-attempt concurrency fence. */
143
+ var TaskAttemptConflictError = class extends Error {
144
+ taskId;
145
+ actualAttempt;
146
+ expectedAttempt;
147
+ constructor(taskId, actualAttempt, expectedAttempt) {
148
+ super(`task ${taskId} is on attempt ${actualAttempt}, not ${expectedAttempt}`);
149
+ this.taskId = taskId;
150
+ this.actualAttempt = actualAttempt;
151
+ this.expectedAttempt = expectedAttempt;
152
+ this.name = "TaskAttemptConflictError";
153
+ }
154
+ };
155
+ /** Error raised when an idempotent session operation conflicts with its original intent. */
156
+ var SessionConflictError = class extends Error {};
157
+ function sameStoredValue(left, right) {
158
+ return isDeepStrictEqual(JSON.parse(JSON.stringify(left)), JSON.parse(JSON.stringify(right)));
159
+ }
160
+ /**
161
+ * Whether an operator's answer matches one of the question's declared
162
+ * closing options (trimmed, case-insensitive). The asker judged that these
163
+ * exact replies mean no further attempt can help, so answering with one
164
+ * resolves the parent task instead of requeuing it for identical retry.
165
+ */
166
+ function isClosingAnswer(closingOptions, answer) {
167
+ if (closingOptions === void 0 || closingOptions.length === 0) return false;
168
+ const normalized = answer.trim().toLowerCase();
169
+ return closingOptions.some((option) => option.trim().toLowerCase() === normalized);
170
+ }
171
+ const taskReceiptStates = {
172
+ queued: "queued",
173
+ running: "dispatched",
174
+ stalled: "stalled",
175
+ succeeded: "succeeded",
176
+ failed: "failed",
177
+ needs_human: "needs_human",
178
+ cancelled: "cancelled"
179
+ };
180
+ function needsLifecycleCommunication(task, state, phase) {
181
+ if (state === "needs_human") return task.question !== void 0;
182
+ if (task.parentTaskId !== void 0) return false;
183
+ if (state === "dispatched") return phase === "retry";
184
+ if (state === "failed" || state === "cancelled") return true;
185
+ if (state !== "succeeded") return false;
186
+ return task.question === void 0;
187
+ }
188
+ const changeReceiptStates = {
189
+ verifying: "verifying",
190
+ ready: "ready",
191
+ merged: "merged",
192
+ deployed: "deployed",
193
+ rolled_out: "rolled_out",
194
+ rolled_back: "rolled_back"
195
+ };
196
+ function randomId(prefix) {
197
+ return `${prefix}_${randomUUID().replaceAll("-", "")}`;
198
+ }
199
+ function derivedId(prefix, key) {
200
+ return `${prefix}_${createHash("sha256").update(key).digest("hex").slice(0, 32)}`;
201
+ }
202
+ function bumpsAttempt(from, to) {
203
+ return to === "queued" || from === "verifying" && to === "running";
204
+ }
205
+ const emptyUsage = {
206
+ costUsd: 0,
207
+ inputTokens: 0,
208
+ outputTokens: 0,
209
+ cacheReadTokens: 0,
210
+ cacheWriteTokens: 0,
211
+ steps: {}
212
+ };
213
+ const unverifiedEvidence = {
214
+ reproVerified: false,
215
+ testsAdded: 0,
216
+ checksGreen: false,
217
+ attackPassed: false,
218
+ scopeMatches: false
219
+ };
220
+ function addUsd(a, b) {
221
+ return (Math.round(a * 1e6) + Math.round(b * 1e6)) / 1e6;
222
+ }
223
+ const TRANSIENT_RETRY_BASE_MS = 200;
224
+ const TRANSIENT_RETRY_MAX_TOTAL_MS = 1e4;
225
+ /**
226
+ * Backs off and returns the next attempt count, or rethrows `error` once
227
+ * the bound (attempts or total elapsed time) is reached.
228
+ */
229
+ async function nextTransientRetry(error, attempt, startedAt) {
230
+ const nextDelayMs = TRANSIENT_RETRY_BASE_MS * 2 ** attempt;
231
+ if (attempt >= 4 || Date.now() - startedAt + nextDelayMs > TRANSIENT_RETRY_MAX_TOTAL_MS) throw error;
232
+ await new Promise((resolve) => setTimeout(resolve, nextDelayMs));
233
+ return attempt + 1;
234
+ }
235
+ /**
236
+ * Creates the validating Factory store engine over a deliberately policy-free driver.
237
+ *
238
+ * @remarks
239
+ * The returned stores validate entities on read and write, enforce legal transitions and
240
+ * compare-and-swap fences, and emit immutable receipts. Construct one configured instance per
241
+ * runtime and share it with launchers, hooks, and tools. A custom workflow must be registered on
242
+ * every process that may read its Tasks; omission is a configuration error rather than permission
243
+ * to select a newer workflow version. Supplying `workflows` replaces the default registry, so
244
+ * include `defaultTaskWorkflow` when generic `task@1` work, Factory tools, or human questions may
245
+ * be created.
246
+ *
247
+ * Wakeups run after their source write commits. Notification failure therefore cannot roll the
248
+ * mutation back; reconciliation must be able to rediscover canonical state.
249
+ *
250
+ * @param options - Persistence and exact workflow/message configuration for this runtime.
251
+ * @returns The complete set of validating Factory stores backed by `options.driver`.
252
+ * @see The version-matched `docs/getting-started.md` shipped with the package.
253
+ *
254
+ * @example
255
+ * ```ts
256
+ * import { createInMemoryDriver, createStores } from "@vercel/factory/storage";
257
+ * import {
258
+ * defaultTaskWorkflow,
259
+ * defineWorkflow,
260
+ * defineWorkflows,
261
+ * } from "@vercel/factory/workflows";
262
+ * import { z } from "zod";
263
+ *
264
+ * const greeting = defineWorkflow({
265
+ * id: "greeting",
266
+ * version: 1,
267
+ * input: z.object({ name: z.string() }),
268
+ * output: z.object({ message: z.string() }),
269
+ * });
270
+ * const stores = createStores({
271
+ * driver: createInMemoryDriver(),
272
+ * workflows: defineWorkflows([defaultTaskWorkflow, greeting]),
273
+ * });
274
+ * ```
275
+ */
276
+ function createStores(options) {
277
+ const { driver } = options;
278
+ const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
279
+ const workflows = options.workflows ?? defineWorkflows([defaultTaskWorkflow]);
280
+ const notifications = createWorkNotifier(options.workNotifications);
281
+ function validateTaskWork(task) {
282
+ workflows.validateStoredInput(task.work.workflow, task.work.input);
283
+ if (task.workResult !== void 0) workflows.validateStoredOutput(task.work.workflow, task.workResult.output);
284
+ }
285
+ async function read(collection, id, schema) {
286
+ const record = await driver.get(collection, id);
287
+ if (record === null) return null;
288
+ const value = schema.parse(record.value);
289
+ if (collection === "tasks") {
290
+ const task = taskSchema.parse(value);
291
+ if (task.id !== id) throw new Error(`Task ${id} does not match its storage key`);
292
+ validateTaskWork(task);
293
+ }
294
+ return {
295
+ value,
296
+ version: record.version
297
+ };
298
+ }
299
+ async function mustRead(collection, id, schema) {
300
+ const record = await read(collection, id, schema);
301
+ if (record === null) throw new RecordNotFoundError(collection, id);
302
+ return record;
303
+ }
304
+ async function listAll(collection, schema) {
305
+ return (await driver.list(collection)).map((entry) => schema.parse(entry.record.value));
306
+ }
307
+ async function forEachConcurrent(values, limit, operation) {
308
+ let next = 0;
309
+ await Promise.all(Array.from({ length: Math.min(limit, values.length) }, async () => {
310
+ while (next < values.length) await operation(values[next++]);
311
+ }));
312
+ }
313
+ const catalogShards = "0123456789abcdef".split("");
314
+ const shardFor = (id) => createHash("sha256").update(id).digest("hex")[0];
315
+ function sessionSummary(session) {
316
+ return agentSessionSchema.parse({
317
+ ...session,
318
+ continuations: session.continuations?.map(({ context: _context, ...continuation }) => continuation)
319
+ });
320
+ }
321
+ async function projectCatalogEntry(collection, prefix, schema, value, sourceVersion) {
322
+ const id = `${prefix}-${shardFor(value.id)}`;
323
+ for (;;) {
324
+ const existing = await driver.get(collection, id);
325
+ const parsed = existing === null ? null : schema.parse(existing.value);
326
+ const previous = parsed?.entries.find((entry) => entry.value.id === value.id);
327
+ const repairs = parsed?.repairs.filter((repair) => repair.id !== value.id || repair.sourceVersion > sourceVersion) ?? [];
328
+ if (previous !== void 0 && previous.sourceVersion >= sourceVersion && repairs.length === parsed?.repairs.length) return;
329
+ const entries = previous !== void 0 && previous.sourceVersion >= sourceVersion ? parsed?.entries ?? [] : [...parsed?.entries.filter((entry) => entry.value.id !== value.id) ?? [], {
330
+ sourceVersion,
331
+ value
332
+ }];
333
+ const next = schema.parse({
334
+ schemaVersion: 1,
335
+ id,
336
+ entries,
337
+ repairs,
338
+ updatedAt: now()
339
+ });
340
+ try {
341
+ if (existing === null) await driver.insert({
342
+ collection,
343
+ id,
344
+ value: next
345
+ });
346
+ else await driver.update({
347
+ collection,
348
+ id,
349
+ value: next,
350
+ expectedVersion: existing.version
351
+ });
352
+ return;
353
+ } catch (error) {
354
+ if (!(error instanceof RecordExistsError) && !(error instanceof VersionConflictError)) throw error;
355
+ }
356
+ }
357
+ }
358
+ const projectTaskCatalog = (value, sourceVersion) => projectCatalogEntry("taskCatalogs", "tasks", taskCatalogSchema, value, sourceVersion);
359
+ const projectChangeCatalog = (value, sourceVersion) => projectCatalogEntry("changeCatalogs", "changes", changeCatalogSchema, value, sourceVersion);
360
+ const projectSessionCatalog = (value, sourceVersion) => projectCatalogEntry("sessionCatalogs", "sessions", sessionCatalogSchema, sessionSummary(value), sourceVersion);
361
+ const projectExternalSessionCatalog = (value, sourceVersion) => projectCatalogEntry("externalSessionCatalogs", "external-sessions", externalSessionCatalogSchema, sessionSummary(value), sourceVersion);
362
+ async function markCatalogRepair(collection, prefix, schema, sourceId, sourceVersion) {
363
+ const id = `${prefix}-${shardFor(sourceId)}`;
364
+ for (;;) {
365
+ const existing = await driver.get(collection, id);
366
+ const parsed = existing === null ? null : schema.parse(existing.value);
367
+ const previous = parsed?.repairs.find((repair) => repair.id === sourceId);
368
+ if (previous !== void 0 && previous.sourceVersion >= sourceVersion) return;
369
+ const repairs = parsed?.repairs.filter((repair) => repair.id !== sourceId) ?? [];
370
+ const next = schema.parse({
371
+ schemaVersion: 1,
372
+ id,
373
+ entries: parsed?.entries ?? [],
374
+ repairs: [...repairs, {
375
+ id: sourceId,
376
+ sourceVersion
377
+ }],
378
+ updatedAt: now()
379
+ });
380
+ try {
381
+ if (existing === null) await driver.insert({
382
+ collection,
383
+ id,
384
+ value: next
385
+ });
386
+ else await driver.update({
387
+ collection,
388
+ id,
389
+ value: next,
390
+ expectedVersion: existing.version
391
+ });
392
+ return;
393
+ } catch (error) {
394
+ if (!(error instanceof RecordExistsError) && !(error instanceof VersionConflictError)) throw error;
395
+ }
396
+ }
397
+ }
398
+ const markTaskCatalogRepair = (id, sourceVersion) => markCatalogRepair("taskCatalogs", "tasks", taskCatalogSchema, id, sourceVersion);
399
+ const markChangeCatalogRepair = (id, sourceVersion) => markCatalogRepair("changeCatalogs", "changes", changeCatalogSchema, id, sourceVersion);
400
+ const markSessionCatalogRepair = (id, sourceVersion) => markCatalogRepair("sessionCatalogs", "sessions", sessionCatalogSchema, id, sourceVersion);
401
+ const markExternalSessionCatalogRepair = (id, sourceVersion) => markCatalogRepair("externalSessionCatalogs", "external-sessions", externalSessionCatalogSchema, id, sourceVersion);
402
+ async function projectTaskRoute(value, sourceVersion) {
403
+ if (value.replyTo.channel !== "api") return;
404
+ const id = derivedId("task-route", JSON.stringify([value.replyTo.channel, value.replyTo.address]));
405
+ for (;;) {
406
+ const existing = await read("taskRoutes", id, taskRouteSchema);
407
+ const previous = existing?.value.entries.find((entry) => entry.value.id === value.id);
408
+ const repairs = existing?.value.repairs.filter((repair) => repair.id !== value.id || repair.sourceVersion > sourceVersion) ?? [];
409
+ if (previous !== void 0 && previous.sourceVersion >= sourceVersion && repairs.length === existing?.value.repairs.length) return;
410
+ const entries = previous !== void 0 && previous.sourceVersion >= sourceVersion ? existing?.value.entries ?? [] : [...existing?.value.entries.filter((entry) => entry.value.id !== value.id) ?? [], {
411
+ sourceVersion,
412
+ value
413
+ }];
414
+ const next = taskRouteSchema.parse({
415
+ schemaVersion: 1,
416
+ id,
417
+ replyTo: value.replyTo,
418
+ entries,
419
+ repairs,
420
+ updatedAt: now()
421
+ });
422
+ try {
423
+ if (existing === null) await driver.insert({
424
+ collection: "taskRoutes",
425
+ id,
426
+ value: next
427
+ });
428
+ else await driver.update({
429
+ collection: "taskRoutes",
430
+ id,
431
+ value: next,
432
+ expectedVersion: existing.version
433
+ });
434
+ return;
435
+ } catch (error) {
436
+ if (!(error instanceof RecordExistsError) && !(error instanceof VersionConflictError)) throw error;
437
+ }
438
+ }
439
+ }
440
+ async function markTaskRouteRepair(value, sourceVersion) {
441
+ if (value.replyTo.channel !== "api") return;
442
+ const id = derivedId("task-route", JSON.stringify([value.replyTo.channel, value.replyTo.address]));
443
+ for (;;) {
444
+ const existing = await read("taskRoutes", id, taskRouteSchema);
445
+ const previous = existing?.value.repairs.find((repair) => repair.id === value.id);
446
+ if (previous !== void 0 && previous.sourceVersion >= sourceVersion) return;
447
+ const repairs = existing?.value.repairs.filter((repair) => repair.id !== value.id) ?? [];
448
+ const next = taskRouteSchema.parse({
449
+ schemaVersion: 1,
450
+ id,
451
+ replyTo: value.replyTo,
452
+ entries: existing?.value.entries ?? [],
453
+ repairs: [...repairs, {
454
+ id: value.id,
455
+ sourceVersion
456
+ }],
457
+ updatedAt: now()
458
+ });
459
+ try {
460
+ if (existing === null) await driver.insert({
461
+ collection: "taskRoutes",
462
+ id,
463
+ value: next
464
+ });
465
+ else await driver.update({
466
+ collection: "taskRoutes",
467
+ id,
468
+ value: next,
469
+ expectedVersion: existing.version
470
+ });
471
+ return;
472
+ } catch (error) {
473
+ if (!(error instanceof RecordExistsError) && !(error instanceof VersionConflictError)) throw error;
474
+ }
475
+ }
476
+ }
477
+ async function prepareTaskProjection(value, sourceVersion) {
478
+ await Promise.all([markTaskCatalogRepair(value.id, sourceVersion), markTaskRouteRepair(value, sourceVersion)]);
479
+ }
480
+ async function projectTask(value, sourceVersion) {
481
+ await Promise.all([projectTaskCatalog(value, sourceVersion), projectTaskRoute(value, sourceVersion)]);
482
+ }
483
+ async function projectSafely(project) {
484
+ try {
485
+ await project();
486
+ } catch {
487
+ return;
488
+ }
489
+ }
490
+ async function catalogValues(source, collection, prefix, sourceSchema, catalogSchema, project, fallbackValue = (value) => value) {
491
+ let catalogs = await Promise.all(catalogShards.map((shard) => driver.get(collection, `${prefix}-${shard}`)));
492
+ if (catalogs.some((catalog) => catalog === null)) {
493
+ const entries = await driver.list(source);
494
+ const values = entries.map((entry) => fallbackValue(sourceSchema.parse(entry.record.value)));
495
+ let projectionFailed = false;
496
+ await forEachConcurrent(entries, 16, async (entry) => {
497
+ try {
498
+ await project(sourceSchema.parse(entry.record.value), entry.record.version);
499
+ } catch {
500
+ projectionFailed = true;
501
+ }
502
+ });
503
+ if (!projectionFailed) for (const shard of catalogShards) {
504
+ const id = `${prefix}-${shard}`;
505
+ if (await driver.get(collection, id) !== null) continue;
506
+ const empty = catalogSchema.parse({
507
+ schemaVersion: 1,
508
+ id,
509
+ entries: [],
510
+ updatedAt: now()
511
+ });
512
+ try {
513
+ await driver.insert({
514
+ collection,
515
+ id,
516
+ value: empty
517
+ });
518
+ } catch (error) {
519
+ if (!(error instanceof RecordExistsError)) projectionFailed = true;
520
+ }
521
+ }
522
+ if (projectionFailed) return values;
523
+ catalogs = await Promise.all(catalogShards.map((shard) => driver.get(collection, `${prefix}-${shard}`)));
524
+ if (catalogs.some((catalog) => catalog === null)) return values;
525
+ }
526
+ const parsedCatalogs = catalogs.flatMap((catalog) => catalog === null ? [] : [catalogSchema.parse(catalog.value)]);
527
+ const values = /* @__PURE__ */ new Map();
528
+ for (const catalog of parsedCatalogs) for (const entry of catalog.entries) {
529
+ const value = sourceSchema.parse(entry.value);
530
+ values.set(value.id, value);
531
+ }
532
+ const repairs = /* @__PURE__ */ new Map();
533
+ for (const catalog of parsedCatalogs) for (const repair of catalog.repairs) repairs.set(repair.id, Math.max(repairs.get(repair.id) ?? 0, repair.sourceVersion));
534
+ await forEachConcurrent([...repairs.keys()], 16, async (id) => {
535
+ const record = await driver.get(source, id);
536
+ if (record === null) {
537
+ values.delete(id);
538
+ return;
539
+ }
540
+ const canonical = sourceSchema.parse(record.value);
541
+ values.set(id, fallbackValue(canonical));
542
+ await projectSafely(() => project(canonical, record.version));
543
+ });
544
+ return [...values.values()];
545
+ }
546
+ async function updateTask(input, expectedVersion) {
547
+ const value = taskSchema.parse(input);
548
+ validateTaskWork(value);
549
+ const previous = options.workNotifications ? await mustRead("tasks", value.id, storedTaskSchema) : null;
550
+ await prepareTaskProjection(value, expectedVersion + 1);
551
+ await driver.update({
552
+ collection: "tasks",
553
+ id: value.id,
554
+ value,
555
+ expectedVersion
556
+ });
557
+ await projectSafely(() => projectTask(value, expectedVersion + 1));
558
+ await notifications.task(value, previous?.value ?? null, expectedVersion + 1);
559
+ }
560
+ async function updateChange(value, expectedVersion) {
561
+ await markChangeCatalogRepair(value.id, expectedVersion + 1);
562
+ await driver.update({
563
+ collection: "changes",
564
+ id: value.id,
565
+ value,
566
+ expectedVersion
567
+ });
568
+ await projectSafely(() => projectChangeCatalog(value, expectedVersion + 1));
569
+ }
570
+ async function updateSession(value, expectedVersion) {
571
+ await markSessionCatalogRepair(value.id, expectedVersion + 1);
572
+ await driver.update({
573
+ collection: "sessions",
574
+ id: value.id,
575
+ value,
576
+ expectedVersion
577
+ });
578
+ await projectSafely(() => projectSessionCatalog(value, expectedVersion + 1));
579
+ }
580
+ async function insertReceipt(receiptId, receipt) {
581
+ const reason = receipt.reason?.trim().slice(0, 2e3) || void 0;
582
+ const full = receiptSchema.parse({
583
+ ...receipt,
584
+ reason,
585
+ schemaVersion: 1,
586
+ receiptId,
587
+ recordedAt: now()
588
+ });
589
+ try {
590
+ await driver.insert({
591
+ collection: "receipts",
592
+ id: receiptId,
593
+ value: full
594
+ });
595
+ return full;
596
+ } catch (error) {
597
+ if (error instanceof RecordExistsError) return (await mustRead("receipts", receiptId, receiptSchema)).value;
598
+ throw error;
599
+ }
600
+ }
601
+ async function readApprovalReceipt(task) {
602
+ const current = await read("receipts", derivedId("rcpt", `task:${task.id}:approval`), receiptSchema);
603
+ if (current) return current.value;
604
+ return null;
605
+ }
606
+ async function decideApproval(id, state, decision) {
607
+ const by = z.string().trim().min(1).parse(decision.by);
608
+ const reason = state === "rejected" ? z.string().trim().min(1).max(2e3).parse(decision.reason) : decision.reason?.trim().slice(0, 2e3) || void 0;
609
+ const receiptId = derivedId("rcpt", `task:${id}:approval`);
610
+ for (;;) {
611
+ const existing = await mustRead("tasks", id, storedTaskSchema);
612
+ const task = existing.value;
613
+ const saved = await readApprovalReceipt(task);
614
+ if (saved && (saved.state !== state || saved.decidedBy !== by || saved.reason !== reason)) throw new ApprovalError(id, "a different decision has already been recorded");
615
+ const approval = state === "approved" ? "granted" : "denied";
616
+ if (saved && task.approval === approval) {
617
+ await projectSafely(() => projectTask(task, existing.version));
618
+ return task;
619
+ }
620
+ if (task.approval !== "required") throw new ApprovalError(id, `approval is ${task.approval ?? "not required"}`);
621
+ if (task.state !== "queued") throw new ApprovalError(id, `task is ${task.state}`);
622
+ const receipt = saved ?? await insertReceipt(receiptId, {
623
+ repositoryIds: task.repositoryIds,
624
+ taskId: id,
625
+ kind: task.kind,
626
+ state,
627
+ phase: "approval",
628
+ reason,
629
+ attempt: task.attempt,
630
+ decidedBy: by
631
+ });
632
+ if (receipt.state !== state || receipt.decidedBy !== by || receipt.reason !== reason) throw new ApprovalError(id, "a different decision has already been recorded");
633
+ if (state === "rejected") assertTransition(taskTransitions, {
634
+ from: task.state,
635
+ to: "cancelled"
636
+ });
637
+ let next = taskSchema.parse({
638
+ ...task,
639
+ approval,
640
+ state: state === "rejected" ? "cancelled" : task.state,
641
+ updatedAt: now()
642
+ });
643
+ if (state === "rejected") {
644
+ const cancelled = await appendReceipt(`task:${id}:cancelled:${task.attempt}`, {
645
+ repositoryIds: task.repositoryIds,
646
+ taskId: id,
647
+ kind: task.kind,
648
+ state: "cancelled",
649
+ phase: "approval",
650
+ reason: reason ?? `rejected by ${by}`,
651
+ attempt: task.attempt
652
+ });
653
+ if (needsLifecycleCommunication(next, "cancelled", "approval")) next = taskSchema.parse({
654
+ ...next,
655
+ pendingCommunicationReceiptIds: [.../* @__PURE__ */ new Set([...next.pendingCommunicationReceiptIds ?? [], cancelled.receiptId])]
656
+ });
657
+ }
658
+ try {
659
+ await updateTask(next, existing.version);
660
+ return next;
661
+ } catch (error) {
662
+ if (!(error instanceof VersionConflictError)) throw error;
663
+ }
664
+ }
665
+ }
666
+ async function appendReceipt(key, receipt) {
667
+ return insertReceipt(derivedId("rcpt", key), receipt);
668
+ }
669
+ async function setQuestionRoute(replyTo, questionTaskId, present) {
670
+ const id = derivedId("qroute", JSON.stringify([replyTo.channel, replyTo.address]));
671
+ for (;;) {
672
+ const existing = await read("questionRoutes", id, questionRouteSchema);
673
+ const currentIds = existing?.value.questionTaskIds ?? [];
674
+ const questionTaskIds = present ? [.../* @__PURE__ */ new Set([...currentIds, questionTaskId])] : currentIds.filter((candidate) => candidate !== questionTaskId);
675
+ if (existing !== null && questionTaskIds.length === existing.value.questionTaskIds.length) return;
676
+ const next = questionRouteSchema.parse({
677
+ schemaVersion: 1,
678
+ id,
679
+ replyTo,
680
+ questionTaskIds,
681
+ updatedAt: now()
682
+ });
683
+ try {
684
+ if (existing === null) await driver.insert({
685
+ collection: "questionRoutes",
686
+ id,
687
+ value: next
688
+ });
689
+ else await driver.update({
690
+ collection: "questionRoutes",
691
+ id,
692
+ value: next,
693
+ expectedVersion: existing.version
694
+ });
695
+ return;
696
+ } catch (error) {
697
+ if (!(error instanceof RecordExistsError) && !(error instanceof VersionConflictError)) throw error;
698
+ }
699
+ }
700
+ }
701
+ const stores = {
702
+ driverName: driver.name,
703
+ workflowRecords: {
704
+ get: driver.get.bind(driver),
705
+ insert: driver.insert.bind(driver),
706
+ update: driver.update.bind(driver)
707
+ },
708
+ taskEffects: createTaskEffectStore({
709
+ driver,
710
+ now,
711
+ getTask: (id) => stores.tasks.get(id)
712
+ }),
713
+ graphs: createTaskGraphStore({
714
+ driver,
715
+ now,
716
+ getTask: (id) => stores.tasks.get(id),
717
+ validateTask: validateTaskWork,
718
+ notify: notifications.publish
719
+ }),
720
+ messages: createTaskMessageStore({
721
+ driver,
722
+ now: () => new Date(now()),
723
+ getTask: (id) => stores.tasks.get(id),
724
+ protocols: options.messages?.protocols ?? defineTaskMessageProtocols([]),
725
+ authorize: options.messages?.authorize ?? (() => false),
726
+ notify: notifications.publish
727
+ }),
728
+ work: createTaskWorkStore({
729
+ driver,
730
+ now,
731
+ stores: () => stores,
732
+ workflows,
733
+ update: async (task, version) => {
734
+ await updateTask(task, version);
735
+ },
736
+ restore: async (task) => {
737
+ validateTaskWork(task);
738
+ await prepareTaskProjection(task, 1);
739
+ try {
740
+ await driver.insert({
741
+ collection: "tasks",
742
+ id: task.id,
743
+ value: task
744
+ });
745
+ } catch (error) {
746
+ if (!(error instanceof RecordExistsError)) throw error;
747
+ }
748
+ const existing = await mustRead("tasks", task.id, taskSchema);
749
+ await projectSafely(() => projectTask(existing.value, existing.version));
750
+ await notifications.task(existing.value, null, existing.version);
751
+ return existing.value;
752
+ }
753
+ }),
754
+ transcripts: {
755
+ async get(execution) {
756
+ const record = await read("transcripts", derivedId("transcript", JSON.stringify([execution.provider, execution.sessionId])), storedTranscriptSchema);
757
+ if (record === null) return null;
758
+ if (record.value.execution.provider !== execution.provider || record.value.execution.sessionId !== execution.sessionId) throw new Error("Transcript execution key does not match its stored value");
759
+ return record.value.snapshot;
760
+ },
761
+ async put(execution, value) {
762
+ const snapshot = transcriptSnapshotSchema.parse(value);
763
+ const id = derivedId("transcript", JSON.stringify([execution.provider, execution.sessionId]));
764
+ for (;;) {
765
+ const existing = await read("transcripts", id, storedTranscriptSchema);
766
+ if (existing !== null && existing.value.snapshot.cursor >= snapshot.cursor) return existing.value.snapshot;
767
+ const next = storedTranscriptSchema.parse({
768
+ schemaVersion: 1,
769
+ execution,
770
+ snapshot,
771
+ updatedAt: now()
772
+ });
773
+ try {
774
+ if (existing === null) await driver.insert({
775
+ collection: "transcripts",
776
+ id,
777
+ value: next
778
+ });
779
+ else await driver.update({
780
+ collection: "transcripts",
781
+ id,
782
+ value: next,
783
+ expectedVersion: existing.version
784
+ });
785
+ return snapshot;
786
+ } catch (error) {
787
+ if (!(error instanceof RecordExistsError) && !(error instanceof VersionConflictError)) throw error;
788
+ }
789
+ }
790
+ }
791
+ },
792
+ images: {
793
+ async put(owner, input) {
794
+ const parsed = imageUploadSchema.parse(input);
795
+ const id = imageIdSchema.parse(`img_${createHash("sha256").update(JSON.stringify([
796
+ owner,
797
+ parsed.filename,
798
+ parsed.mediaType,
799
+ parsed.base64
800
+ ])).digest("hex")}`);
801
+ const image = storedImageSchema.parse({
802
+ ...parsed,
803
+ id,
804
+ owner,
805
+ schemaVersion: 1,
806
+ size: Buffer.from(parsed.base64, "base64").length,
807
+ createdAt: now()
808
+ });
809
+ try {
810
+ await driver.insert({
811
+ collection: "images",
812
+ id,
813
+ value: image
814
+ });
815
+ } catch (error) {
816
+ if (!(error instanceof RecordExistsError)) throw error;
817
+ await stores.images.get(owner, id);
818
+ }
819
+ return imageAttachmentSchema.parse({
820
+ id,
821
+ filename: image.filename,
822
+ mediaType: image.mediaType,
823
+ size: image.size
824
+ });
825
+ },
826
+ async get(owner, id) {
827
+ imageIdSchema.parse(id);
828
+ const record = await driver.get("images", id);
829
+ if (!record) return null;
830
+ const image = storedImageSchema.parse(record.value);
831
+ return image.owner === owner ? image : null;
832
+ },
833
+ async validate(owner, attachments) {
834
+ imageAttachmentsSchema.parse(attachments);
835
+ return Promise.all(attachments.map(async (attachment) => {
836
+ const image = await stores.images.get(owner, attachment.id);
837
+ if (!image) throw new ImageInputError("Image is unavailable; upload it again");
838
+ const ref = {
839
+ id: image.id,
840
+ filename: image.filename,
841
+ mediaType: image.mediaType,
842
+ size: image.size
843
+ };
844
+ if (!sameStoredValue(ref, attachment)) throw new ImageInputError("Image metadata does not match the uploaded image");
845
+ return ref;
846
+ }));
847
+ }
848
+ },
849
+ sessions: {
850
+ async getByOperation(owner, operationId) {
851
+ return stores.sessions.get(derivedId("ses", JSON.stringify([owner, operationId])));
852
+ },
853
+ async create({ operationId, ...input }) {
854
+ if (operationId.trim().length === 0 || operationId.length > 200) throw new Error("operationId must contain 1–200 characters");
855
+ const id = derivedId("ses", JSON.stringify([input.owner, operationId]));
856
+ const candidate = storedSessionSchema.parse({
857
+ ...input,
858
+ id,
859
+ schemaVersion: 1,
860
+ createdAt: now()
861
+ });
862
+ await stores.images.validate(input.owner, input.attachments ?? []);
863
+ await markSessionCatalogRepair(candidate.id, 1);
864
+ try {
865
+ await driver.insert({
866
+ collection: "sessions",
867
+ id,
868
+ value: candidate
869
+ });
870
+ await projectSafely(() => projectSessionCatalog(candidate, 1));
871
+ return candidate;
872
+ } catch (error) {
873
+ if (!(error instanceof RecordExistsError)) throw error;
874
+ const record = await mustRead("sessions", id, storedSessionSchema);
875
+ await projectSafely(() => projectSessionCatalog(record.value, record.version));
876
+ const existing = record.value;
877
+ const { createdAt: _at, execution: _execution, taskId: _task, notifications: _notifications, continuations: _continuations, ...intent } = existing;
878
+ const { createdAt: _newAt, notifications: _newNotifications, ...nextIntent } = candidate;
879
+ if (!sameStoredValue(intent, nextIntent)) throw new SessionConflictError("Session operationId was reused with different input");
880
+ return existing;
881
+ }
882
+ },
883
+ async attachConversation(input) {
884
+ const id = derivedId("ses", JSON.stringify([
885
+ "conversation",
886
+ input.owner,
887
+ input.execution.provider,
888
+ input.execution.sessionId
889
+ ]));
890
+ const candidate = externalSessionSchema.parse({
891
+ ...input,
892
+ id,
893
+ schemaVersion: 1,
894
+ createdAt: input.createdAt ?? now()
895
+ });
896
+ await markExternalSessionCatalogRepair(id, 1);
897
+ try {
898
+ await driver.insert({
899
+ collection: "externalSessions",
900
+ id,
901
+ value: candidate
902
+ });
903
+ await projectSafely(() => projectExternalSessionCatalog(candidate, 1));
904
+ return candidate;
905
+ } catch (error) {
906
+ if (!(error instanceof RecordExistsError)) throw error;
907
+ const record = await mustRead("externalSessions", id, externalSessionSchema);
908
+ const existing = record.value;
909
+ if (existing.owner !== candidate.owner || existing.agent !== candidate.agent || !sameStoredValue(existing.external, candidate.external) || !sameStoredValue(existing.execution, candidate.execution)) throw new SessionConflictError("External conversation binding cannot be replaced");
910
+ await projectSafely(() => projectExternalSessionCatalog(existing, record.version));
911
+ return existing;
912
+ }
913
+ },
914
+ async attachTask(input) {
915
+ const task = (await mustRead("tasks", input.taskId, storedTaskSchema)).value;
916
+ if (task.repositoryIds.length !== 1) throw new SessionConflictError("Only single-repository tasks can be opened");
917
+ if (task.replyTo.channel === "api") {
918
+ const original = await stores.sessions.get(agentSessionIdSchema.parse(task.replyTo.address));
919
+ if (original?.owner !== input.owner) throw new SessionConflictError("Task belongs to another operator interaction");
920
+ const record = await mustRead("sessions", original.id, storedSessionSchema);
921
+ await projectSafely(() => projectSessionCatalog(record.value, record.version));
922
+ return original;
923
+ }
924
+ if (task.work.route === void 0) throw new SessionConflictError("Only tasks with an exact agent route can be opened");
925
+ const id = derivedId("ses", JSON.stringify([
926
+ "task",
927
+ input.owner,
928
+ task.id
929
+ ]));
930
+ const candidate = storedSessionSchema.parse({
931
+ schemaVersion: 1,
932
+ id,
933
+ owner: input.owner,
934
+ agent: input.agent,
935
+ route: task.work.route,
936
+ repositoryId: task.repositoryIds[0],
937
+ message: input.message,
938
+ taskId: task.id,
939
+ attached: true,
940
+ createdAt: task.createdAt
941
+ });
942
+ await markSessionCatalogRepair(candidate.id, 1);
943
+ try {
944
+ await driver.insert({
945
+ collection: "sessions",
946
+ id,
947
+ value: candidate
948
+ });
949
+ await projectSafely(() => projectSessionCatalog(candidate, 1));
950
+ return candidate;
951
+ } catch (error) {
952
+ if (!(error instanceof RecordExistsError)) throw error;
953
+ const existing = await mustRead("sessions", id, storedSessionSchema);
954
+ await projectSafely(() => projectSessionCatalog(existing.value, existing.version));
955
+ return existing.value;
956
+ }
957
+ },
958
+ async cacheContinuationContext(id, input) {
959
+ const { taskId, context, attachments } = input;
960
+ for (;;) {
961
+ const record = await mustRead("sessions", id, storedSessionSchema);
962
+ const continuation = record.value.continuations?.find((item) => item.taskId === taskId);
963
+ if (!continuation) throw new SessionConflictError("Task is not a conversation continuation");
964
+ if (continuation.context !== void 0) return continuation.context;
965
+ await stores.images.validate(record.value.owner, attachments ?? []);
966
+ const next = storedSessionSchema.parse({
967
+ ...record.value,
968
+ continuations: record.value.continuations?.map((item) => item.taskId === taskId ? {
969
+ ...item,
970
+ context,
971
+ ...attachments?.length ? { contextAttachments: attachments } : {}
972
+ } : item)
973
+ });
974
+ try {
975
+ await updateSession(next, record.version);
976
+ return context;
977
+ } catch (error) {
978
+ if (!(error instanceof VersionConflictError)) throw error;
979
+ }
980
+ }
981
+ },
982
+ async continue(id, input) {
983
+ if (input.operationId.trim().length === 0 || input.operationId.length > 200) throw new SessionConflictError("operationId must contain 1–200 characters");
984
+ const dedupeKey = `session:${id}:follow-up:${input.operationId}`;
985
+ for (;;) {
986
+ const record = await mustRead("sessions", id, storedSessionSchema);
987
+ const session = record.value;
988
+ await stores.images.validate(session.owner, input.attachments ?? []);
989
+ if (session.repositoryId === void 0 || session.taskId === void 0) throw new SessionConflictError("Follow-up work requires a repository-bound task conversation");
990
+ const parent = (await mustRead("tasks", input.parentTaskId, storedTaskSchema)).value;
991
+ if (parent.id !== session.taskId && !(parent.replyTo.channel === "api" && parent.replyTo.address === id)) throw new SessionConflictError("Follow-up parent is outside this conversation");
992
+ if (parent.repositoryIds.length !== 1 || parent.repositoryIds[0] !== session.repositoryId) throw new SessionConflictError("Follow-up repository differs from its conversation");
993
+ const previous = session.continuations?.find((item) => item.operationId === input.operationId);
994
+ if (previous !== void 0 && !sameStoredValue({
995
+ operationId: input.operationId,
996
+ parentTaskId: input.parentTaskId,
997
+ agent: input.agent,
998
+ message: input.message,
999
+ attachments: input.attachments ?? []
1000
+ }, {
1001
+ operationId: previous.operationId,
1002
+ parentTaskId: previous.parentTaskId,
1003
+ agent: previous.agent,
1004
+ message: previous.message,
1005
+ attachments: previous.attachments ?? []
1006
+ })) throw new SessionConflictError("Follow-up operationId was reused with different input");
1007
+ const continuation = previous ?? {
1008
+ ...input,
1009
+ taskId: derivedId("task", JSON.stringify([
1010
+ "task",
1011
+ parent.id,
1012
+ dedupeKey
1013
+ ])),
1014
+ createdAt: now()
1015
+ };
1016
+ let next = session;
1017
+ if (previous === void 0) {
1018
+ if (![
1019
+ "succeeded",
1020
+ "failed",
1021
+ "cancelled",
1022
+ "verifying"
1023
+ ].includes(parent.state)) throw new SessionConflictError("Wait for this task or answer its pending question before starting a follow-up");
1024
+ const latest = session.continuations?.at(-1);
1025
+ if (latest !== void 0 && latest.taskId !== parent.id) {
1026
+ let ancestor = parent;
1027
+ const visited = /* @__PURE__ */ new Set();
1028
+ while (ancestor && ancestor.id !== latest.taskId && !visited.has(ancestor.id)) {
1029
+ visited.add(ancestor.id);
1030
+ ancestor = ancestor.parentTaskId ? await stores.tasks.get(ancestor.parentTaskId) : null;
1031
+ }
1032
+ if (ancestor?.id !== latest.taskId) throw new SessionConflictError("Conversation changed; refresh before sending another follow-up");
1033
+ const latestTask = await stores.tasks.get(latest.taskId);
1034
+ if (latestTask === null || ![
1035
+ "succeeded",
1036
+ "failed",
1037
+ "cancelled"
1038
+ ].includes(latestTask.state)) throw new SessionConflictError("A follow-up is already being started; refresh the conversation");
1039
+ }
1040
+ next = storedSessionSchema.parse({
1041
+ ...session,
1042
+ continuations: [...session.continuations ?? [], continuation]
1043
+ });
1044
+ try {
1045
+ await updateSession(next, record.version);
1046
+ } catch (error) {
1047
+ if (error instanceof VersionConflictError) continue;
1048
+ throw error;
1049
+ }
1050
+ }
1051
+ const replyTo = {
1052
+ channel: "api",
1053
+ address: id
1054
+ };
1055
+ const signal = await stores.signals.create({
1056
+ repositoryId: session.repositoryId,
1057
+ source: "api",
1058
+ deliveryId: dedupeKey,
1059
+ replyTo,
1060
+ payload: {
1061
+ message: continuation.message,
1062
+ parentTaskId: parent.id
1063
+ }
1064
+ });
1065
+ if (signal.outcome === "pending") try {
1066
+ await stores.signals.decide(signal.id, {
1067
+ outcome: "accepted",
1068
+ reason: `Follow-up requested by ${session.owner}`
1069
+ });
1070
+ } catch (error) {
1071
+ if (!(error instanceof VersionConflictError || error instanceof InvalidTransitionError)) throw error;
1072
+ }
1073
+ await stores.tasks.create({
1074
+ repositoryIds: [session.repositoryId],
1075
+ kind: "investigation",
1076
+ area: "conversation",
1077
+ origin: {
1078
+ operator: session.owner,
1079
+ signalId: signal.id
1080
+ },
1081
+ parentTaskId: parent.id,
1082
+ work: routeTaskWork({
1083
+ route: parseAgentRouteBinding({
1084
+ id: "conversation",
1085
+ version: 1
1086
+ }),
1087
+ title: "Conversation follow-up",
1088
+ input: { message: continuation.message }
1089
+ }),
1090
+ replyTo,
1091
+ dedupeKey,
1092
+ changeId: parent.changeId
1093
+ });
1094
+ return next;
1095
+ }
1096
+ },
1097
+ async get(id) {
1098
+ const records = (await Promise.all([read("sessions", id, storedSessionSchema), read("externalSessions", id, externalSessionSchema)])).filter((record) => record !== null);
1099
+ if (records.length > 1) throw new SessionConflictError("Session ID exists in multiple collections");
1100
+ return records[0]?.value ?? null;
1101
+ },
1102
+ async list() {
1103
+ return (await Promise.all([listAll("sessions", storedSessionSchema), listAll("externalSessions", externalSessionSchema)])).flat();
1104
+ },
1105
+ async summaries() {
1106
+ return (await Promise.all([catalogValues("sessions", "sessionCatalogs", "sessions", storedSessionSchema, sessionCatalogSchema, projectSessionCatalog, sessionSummary), catalogValues("externalSessions", "externalSessionCatalogs", "external-sessions", externalSessionSchema, externalSessionCatalogSchema, projectExternalSessionCatalog, sessionSummary)])).flat();
1107
+ },
1108
+ async bind(id, reference) {
1109
+ const external = await read("externalSessions", id, externalSessionSchema);
1110
+ if (external) {
1111
+ if (!("execution" in reference) || !sameStoredValue(external.value.execution, reference.execution)) throw new SessionConflictError("Session execution binding cannot be replaced");
1112
+ await projectSafely(() => projectExternalSessionCatalog(external.value, external.version));
1113
+ return external.value;
1114
+ }
1115
+ for (;;) {
1116
+ const existing = await mustRead("sessions", id, storedSessionSchema);
1117
+ if (reference.taskId !== void 0) {
1118
+ const task = (await mustRead("tasks", reference.taskId, storedTaskSchema)).value;
1119
+ if (task.origin.operator !== existing.value.owner || task.replyTo.channel !== "api" || task.replyTo.address !== id || task.repositoryIds[0] !== existing.value.repositoryId) throw new SessionConflictError("Task does not belong to this session");
1120
+ }
1121
+ const previous = existing.value.taskId === void 0 ? existing.value.execution === void 0 ? void 0 : { execution: existing.value.execution } : { taskId: existing.value.taskId };
1122
+ if (previous !== void 0) {
1123
+ if (!sameStoredValue(previous, reference)) throw new SessionConflictError("Session execution binding cannot be replaced");
1124
+ await projectSafely(() => projectSessionCatalog(existing.value, existing.version));
1125
+ return existing.value;
1126
+ }
1127
+ const next = storedSessionSchema.parse({
1128
+ ...existing.value,
1129
+ ...reference
1130
+ });
1131
+ try {
1132
+ await updateSession(next, existing.version);
1133
+ return next;
1134
+ } catch (error) {
1135
+ if (!(error instanceof VersionConflictError)) throw error;
1136
+ }
1137
+ }
1138
+ },
1139
+ async notify(input) {
1140
+ const event = communicationEventSchema.parse(input);
1141
+ if (event.replyTo.channel !== "api") throw new SessionConflictError("Expected an API notification");
1142
+ for (;;) {
1143
+ const record = await mustRead("sessions", event.replyTo.address, storedSessionSchema);
1144
+ const existing = record.value.notifications.find((item) => item.eventId === event.eventId);
1145
+ if (existing !== void 0) {
1146
+ if (!sameStoredValue(existing, event)) throw new SessionConflictError("Notification eventId was reused");
1147
+ return;
1148
+ }
1149
+ const next = storedSessionSchema.parse({
1150
+ ...record.value,
1151
+ notifications: [...record.value.notifications, event]
1152
+ });
1153
+ try {
1154
+ await updateSession(next, record.version);
1155
+ return;
1156
+ } catch (error) {
1157
+ if (!(error instanceof VersionConflictError)) throw error;
1158
+ }
1159
+ }
1160
+ }
1161
+ },
1162
+ repositories: {
1163
+ async setModelDefaults(id, input) {
1164
+ const existing = await mustRead("repositories", id, repositorySchema);
1165
+ if ((existing.value.modelDefaults?.revision ?? 0) !== input.expectedRevision) throw new VersionConflictError("repositories", id, existing.version);
1166
+ const next = repositorySchema.parse({
1167
+ ...existing.value,
1168
+ modelDefaults: {
1169
+ revision: input.expectedRevision + 1,
1170
+ agents: input.agents
1171
+ }
1172
+ });
1173
+ await driver.update({
1174
+ collection: "repositories",
1175
+ id,
1176
+ value: next,
1177
+ expectedVersion: existing.version
1178
+ });
1179
+ return next;
1180
+ },
1181
+ async put(repository) {
1182
+ let parsed = repositorySchema.parse(repository);
1183
+ const existing = await read("repositories", parsed.id, repositorySchema);
1184
+ if (existing === null) await driver.insert({
1185
+ collection: "repositories",
1186
+ id: parsed.id,
1187
+ value: parsed
1188
+ });
1189
+ else {
1190
+ parsed = repositorySchema.parse({
1191
+ ...parsed,
1192
+ modelDefaults: existing.value.modelDefaults
1193
+ });
1194
+ await driver.update({
1195
+ collection: "repositories",
1196
+ id: parsed.id,
1197
+ value: parsed,
1198
+ expectedVersion: existing.version
1199
+ });
1200
+ }
1201
+ return parsed;
1202
+ },
1203
+ async get(id) {
1204
+ const record = await read("repositories", id, repositorySchema);
1205
+ return record === null ? null : record.value;
1206
+ },
1207
+ list() {
1208
+ return listAll("repositories", repositorySchema);
1209
+ }
1210
+ },
1211
+ signals: {
1212
+ async create(input) {
1213
+ const id = derivedId("sig", `${input.source}:${input.deliveryId}`);
1214
+ const signal = signalSchema.parse({
1215
+ schemaVersion: 1,
1216
+ id,
1217
+ repositoryId: input.repositoryId,
1218
+ source: input.source,
1219
+ deliveryId: input.deliveryId,
1220
+ replyTo: input.replyTo,
1221
+ payload: input.payload,
1222
+ receivedAt: now(),
1223
+ outcome: "pending"
1224
+ });
1225
+ try {
1226
+ await driver.insert({
1227
+ collection: "signals",
1228
+ id,
1229
+ value: signal
1230
+ });
1231
+ return signal;
1232
+ } catch (error) {
1233
+ if (error instanceof RecordExistsError) return (await mustRead("signals", id, signalSchema)).value;
1234
+ throw error;
1235
+ }
1236
+ },
1237
+ async get(id) {
1238
+ const record = await read("signals", id, signalSchema);
1239
+ return record === null ? null : record.value;
1240
+ },
1241
+ async getByDelivery({ source, deliveryId }) {
1242
+ return stores.signals.get(derivedId("sig", `${source}:${deliveryId}`));
1243
+ },
1244
+ async beginTriage(id, options) {
1245
+ const existing = await mustRead("signals", id, signalSchema);
1246
+ const current = existing.value;
1247
+ if (current.outcome !== "pending" || (current.triage?.attempt ?? 0) !== options.expectedAttempt) throw new VersionConflictError("signals", id, existing.version);
1248
+ if (current.triage) assertTransition(signalTriageTransitions, {
1249
+ from: current.triage.state,
1250
+ to: "running"
1251
+ });
1252
+ const timestamp = now();
1253
+ const next = signalSchema.parse({
1254
+ ...current,
1255
+ triage: {
1256
+ attempt: options.expectedAttempt + 1,
1257
+ state: "running",
1258
+ by: options.by,
1259
+ startedAt: timestamp,
1260
+ updatedAt: timestamp
1261
+ }
1262
+ });
1263
+ await driver.update({
1264
+ collection: "signals",
1265
+ id,
1266
+ value: next,
1267
+ expectedVersion: existing.version
1268
+ });
1269
+ return next;
1270
+ },
1271
+ async updateTriage(id, attempt, observation) {
1272
+ for (let retry = 0; retry < 5; retry++) {
1273
+ const existing = await mustRead("signals", id, signalSchema);
1274
+ const current = existing.value;
1275
+ const triage = current.triage;
1276
+ if (current.outcome !== "pending" || triage?.attempt !== attempt || triage.state === "blocked") return current;
1277
+ if (triage.sessionId && observation.sessionId && triage.sessionId !== observation.sessionId) return current;
1278
+ if (triage.state !== observation.state) assertTransition(signalTriageTransitions, {
1279
+ from: triage.state,
1280
+ to: observation.state
1281
+ });
1282
+ const next = signalSchema.parse({
1283
+ ...current,
1284
+ triage: {
1285
+ ...triage,
1286
+ ...observation,
1287
+ sessionId: observation.sessionId ?? triage.sessionId,
1288
+ updatedAt: now()
1289
+ }
1290
+ });
1291
+ try {
1292
+ await driver.update({
1293
+ collection: "signals",
1294
+ id,
1295
+ value: next,
1296
+ expectedVersion: existing.version
1297
+ });
1298
+ return next;
1299
+ } catch (error) {
1300
+ if (!(error instanceof VersionConflictError) || retry === 4) throw error;
1301
+ }
1302
+ }
1303
+ throw new Error("Triage update retries exhausted");
1304
+ },
1305
+ async decide(id, input) {
1306
+ const { outcome, detail, reason } = input;
1307
+ const existing = await mustRead("signals", id, signalSchema);
1308
+ assertTransition(signalOutcomeTransitions, {
1309
+ from: existing.value.outcome,
1310
+ to: outcome
1311
+ });
1312
+ const next = signalSchema.parse({
1313
+ ...existing.value,
1314
+ outcome,
1315
+ detail,
1316
+ reason,
1317
+ decidedAt: now()
1318
+ });
1319
+ await driver.update({
1320
+ collection: "signals",
1321
+ id,
1322
+ value: next,
1323
+ expectedVersion: existing.version
1324
+ });
1325
+ return next;
1326
+ },
1327
+ list() {
1328
+ return listAll("signals", signalSchema);
1329
+ }
1330
+ },
1331
+ tasks: {
1332
+ async pinModel(id, input) {
1333
+ for (let retry = 0; retry < 5; retry++) {
1334
+ const existing = await mustRead("tasks", id, storedTaskSchema);
1335
+ if (existing.value.attempt !== input.attempt) throw new Error("Cannot select a model for a replaced Task attempt");
1336
+ if (existing.value.model !== void 0) {
1337
+ if (existing.value.model !== input.model) throw new Error("Task model cannot be replaced");
1338
+ return existing.value;
1339
+ }
1340
+ if (!["queued", "running"].includes(existing.value.state)) throw new Error("Select a model before work completes or waits");
1341
+ const next = taskSchema.parse({
1342
+ ...existing.value,
1343
+ model: input.model
1344
+ });
1345
+ try {
1346
+ await updateTask(next, existing.version);
1347
+ return next;
1348
+ } catch (error) {
1349
+ if (!(error instanceof VersionConflictError) || retry === 4) throw error;
1350
+ }
1351
+ }
1352
+ throw new Error("Task model selection conflicted repeatedly");
1353
+ },
1354
+ async create(value) {
1355
+ const input = createTaskInputSchema.parse(value);
1356
+ const timestamp = now();
1357
+ const id = input.dedupeKey === void 0 ? randomId("task") : derivedId("task", JSON.stringify([
1358
+ "task",
1359
+ input.parentTaskId ?? null,
1360
+ input.dedupeKey
1361
+ ]));
1362
+ const parsedWork = structuredClone(taskWorkSchema.parse(input.work));
1363
+ const work = {
1364
+ ...parsedWork,
1365
+ input: workflows.validateInput(parsedWork.workflow, parsedWork.input)
1366
+ };
1367
+ const parent = input.parentTaskId === void 0 ? null : await stores.tasks.get(input.parentTaskId);
1368
+ if (input.parentTaskId !== void 0 && parent === null) throw new Error("Child Tasks require an existing parent");
1369
+ if (input.parentTaskAttempt !== void 0 && (parent === null || input.question === void 0 || input.parentTaskAttempt !== parent.attempt)) throw new Error("Child questions must bind the current parent execution attempt");
1370
+ const origin = taskOriginSchema.parse(input.origin);
1371
+ let task = taskSchema.parse({
1372
+ schemaVersion: 1,
1373
+ id,
1374
+ repositoryIds: input.repositoryIds,
1375
+ kind: input.kind,
1376
+ area: input.area,
1377
+ state: taskInitialState,
1378
+ approval: input.approval,
1379
+ origin: origin.signalId === void 0 && origin.operator === void 0 && parent ? parent.origin : origin,
1380
+ work,
1381
+ rootTaskId: parent?.rootTaskId ?? id,
1382
+ parentTaskId: input.parentTaskId,
1383
+ parentTaskAttempt: input.question === void 0 ? void 0 : parent?.attempt,
1384
+ replyTo: input.replyTo,
1385
+ attempt: 1,
1386
+ question: input.question,
1387
+ changeId: input.changeId,
1388
+ createdAt: timestamp,
1389
+ updatedAt: timestamp
1390
+ });
1391
+ validateTaskWork(task);
1392
+ await stores.graphs.admit({
1393
+ task,
1394
+ dependencies: input.dependencies
1395
+ });
1396
+ const admission = (await stores.graphs.get(task.rootTaskId))?.nodes.find((node) => node.taskId === id)?.admission;
1397
+ if (admission === void 0) throw new Error(`Task ${id} has no durable graph admission`);
1398
+ task = admission;
1399
+ await prepareTaskProjection(task, 1);
1400
+ try {
1401
+ await driver.insert({
1402
+ collection: "tasks",
1403
+ id: task.id,
1404
+ value: task
1405
+ });
1406
+ await projectSafely(() => projectTask(task, 1));
1407
+ await notifications.task(task, null, 1);
1408
+ return task;
1409
+ } catch (error) {
1410
+ if (input.dedupeKey !== void 0 && error instanceof RecordExistsError) {
1411
+ const existing = await mustRead("tasks", task.id, storedTaskSchema);
1412
+ await projectSafely(() => projectTask(existing.value, existing.version));
1413
+ await notifications.task(existing.value, null, existing.version);
1414
+ return existing.value;
1415
+ }
1416
+ throw error;
1417
+ }
1418
+ },
1419
+ approve: (id, decision) => decideApproval(id, "approved", decision),
1420
+ reject: (id, decision) => decideApproval(id, "rejected", decision),
1421
+ async approvalReceipt(id) {
1422
+ return readApprovalReceipt((await mustRead("tasks", id, storedTaskSchema)).value);
1423
+ },
1424
+ async recordExecution(id, input) {
1425
+ const { execution, expectAttempt } = input;
1426
+ const startedAt = Date.now();
1427
+ for (let retries = 0;;) try {
1428
+ const existing = await mustRead("tasks", id, storedTaskSchema);
1429
+ const task = existing.value;
1430
+ if (task.state !== "running") throw new InvalidTransitionError(task.state, "running", legalNextStates(taskTransitions, task.state));
1431
+ if (expectAttempt !== void 0 && task.attempt !== expectAttempt) throw new TaskAttemptConflictError(id, task.attempt, expectAttempt);
1432
+ if (expectAttempt !== void 0 && task.execution) {
1433
+ if (!sameStoredValue(task.execution, execution)) throw new Error(`Task ${id} already has a different execution for attempt ${expectAttempt}`);
1434
+ return task;
1435
+ }
1436
+ const timestamp = now();
1437
+ const next = taskSchema.parse({
1438
+ ...task,
1439
+ execution,
1440
+ lastProgressAt: timestamp,
1441
+ updatedAt: timestamp
1442
+ });
1443
+ await updateTask(next, existing.version);
1444
+ return next;
1445
+ } catch (error) {
1446
+ if (!(error instanceof TransientStoreError)) throw error;
1447
+ retries = await nextTransientRetry(error, retries, startedAt);
1448
+ }
1449
+ },
1450
+ async recordUsage(id, input) {
1451
+ const step = stepUsageSchema.parse(input.step);
1452
+ const { fence } = input;
1453
+ const startedAt = Date.now();
1454
+ let transientAttempt = 0;
1455
+ for (;;) {
1456
+ let existing;
1457
+ try {
1458
+ existing = await mustRead("tasks", id, storedTaskSchema);
1459
+ } catch (error) {
1460
+ if (!(error instanceof TransientStoreError)) throw error;
1461
+ transientAttempt = await nextTransientRetry(error, transientAttempt, startedAt);
1462
+ continue;
1463
+ }
1464
+ const task = existing.value;
1465
+ if (fence && (task.attempt !== fence.attempt || !sameStoredValue(task.execution, fence.execution))) throw new Error(`Usage for task ${id} belongs to a replaced execution`);
1466
+ const usage = task.usage ?? emptyUsage;
1467
+ const recordedIndex = Object.hasOwn(usage.steps, step.turnId) ? usage.steps[step.turnId] : void 0;
1468
+ if (task.settledUsd !== void 0 && (recordedIndex === void 0 || step.stepIndex > recordedIndex)) throw new SettlementError(id, "task is already settled; new usage cannot be silently added");
1469
+ if (recordedIndex !== void 0 && step.stepIndex <= recordedIndex) {
1470
+ await projectSafely(() => projectTask(task, existing.version));
1471
+ return task;
1472
+ }
1473
+ const timestamp = now();
1474
+ const next = taskSchema.parse({
1475
+ ...task,
1476
+ lastProgressAt: timestamp,
1477
+ usage: {
1478
+ costUsd: addUsd(usage.costUsd, step.costUsd ?? 0),
1479
+ inputTokens: usage.inputTokens + (step.inputTokens ?? 0),
1480
+ outputTokens: usage.outputTokens + (step.outputTokens ?? 0),
1481
+ cacheReadTokens: usage.cacheReadTokens + (step.cacheReadTokens ?? 0),
1482
+ cacheWriteTokens: usage.cacheWriteTokens + (step.cacheWriteTokens ?? 0),
1483
+ steps: {
1484
+ ...usage.steps,
1485
+ [step.turnId]: step.stepIndex
1486
+ }
1487
+ },
1488
+ updatedAt: timestamp
1489
+ });
1490
+ try {
1491
+ await updateTask(next, existing.version);
1492
+ return next;
1493
+ } catch (error) {
1494
+ if (error instanceof VersionConflictError) continue;
1495
+ if (error instanceof TransientStoreError) {
1496
+ transientAttempt = await nextTransientRetry(error, transientAttempt, startedAt);
1497
+ continue;
1498
+ }
1499
+ throw error;
1500
+ }
1501
+ }
1502
+ },
1503
+ async settle(id, input) {
1504
+ const existing = await mustRead("tasks", id, storedTaskSchema);
1505
+ const task = existing.value;
1506
+ if (task.settledUsd !== void 0) {
1507
+ await projectSafely(() => projectTask(task, existing.version));
1508
+ return task;
1509
+ }
1510
+ if (task.state !== "succeeded" && task.state !== "failed" && task.state !== "cancelled") throw new SettlementError(id, `task is ${task.state}, not terminal`);
1511
+ const validated = costSchema.parse(input.cost);
1512
+ const normalized = { totalUsd: Math.round(validated.totalUsd * 1e6) / 1e6 };
1513
+ let recorded = normalized;
1514
+ if (task.repositoryIds !== void 0) recorded = (await appendReceipt(`task:${id}:settled:${task.attempt}`, {
1515
+ repositoryIds: task.repositoryIds,
1516
+ taskId: id,
1517
+ changeId: task.changeId,
1518
+ kind: task.kind,
1519
+ state: "settled",
1520
+ phase: "budget",
1521
+ reason: input.reason,
1522
+ attempt: task.attempt,
1523
+ cost: normalized
1524
+ })).cost ?? normalized;
1525
+ const next = taskSchema.parse({
1526
+ ...task,
1527
+ settledUsd: recorded.totalUsd,
1528
+ updatedAt: now()
1529
+ });
1530
+ await updateTask(next, existing.version);
1531
+ return next;
1532
+ },
1533
+ async get(id) {
1534
+ const record = await read("tasks", id, storedTaskSchema);
1535
+ return record === null ? null : record.value;
1536
+ },
1537
+ async findOpenQuestions(replyTo) {
1538
+ const route = await read("questionRoutes", derivedId("qroute", JSON.stringify([replyTo.channel, replyTo.address])), questionRouteSchema);
1539
+ if (route === null) return [];
1540
+ const openQuestions = (await Promise.all(route.value.questionTaskIds.map((taskId) => stores.tasks.get(taskId)))).filter((task) => task !== null && task.question !== void 0 && task.state === "needs_human" && task.replyTo.channel === replyTo.channel && task.replyTo.address === replyTo.address);
1541
+ const openIds = new Set(openQuestions.map((task) => task.id));
1542
+ for (const taskId of route.value.questionTaskIds) if (!openIds.has(taskId)) await setQuestionRoute(replyTo, taskId, false);
1543
+ return openQuestions;
1544
+ },
1545
+ async answerQuestion(input) {
1546
+ const question = await mustRead("tasks", input.questionTaskId, storedTaskSchema);
1547
+ if (question.value.question === void 0) throw new Error(`task ${input.questionTaskId} is not a question`);
1548
+ if (question.value.state !== "needs_human" && question.value.state !== "succeeded") throw new Error(`question task ${input.questionTaskId} is ${question.value.state}, not waiting`);
1549
+ const questionDetails = question.value.question;
1550
+ const id = answerIdSchema.parse(derivedId("ans", input.questionTaskId));
1551
+ const candidate = answerEventSchema.parse({
1552
+ schemaVersion: 1,
1553
+ id,
1554
+ questionTaskId: input.questionTaskId,
1555
+ parentTaskId: question.value.parentTaskId,
1556
+ repositoryIds: question.value.repositoryIds,
1557
+ replyTo: question.value.replyTo,
1558
+ question: questionDetails.text,
1559
+ answer: input.answer,
1560
+ answeredBy: input.answeredBy,
1561
+ recordedAt: now()
1562
+ });
1563
+ const answerOutput = jsonValueSchema.parse({
1564
+ answer: candidate.answer,
1565
+ answeredBy: candidate.answeredBy,
1566
+ questionTaskId: candidate.questionTaskId
1567
+ });
1568
+ let answerEvent = candidate;
1569
+ try {
1570
+ await driver.insert({
1571
+ collection: "answers",
1572
+ id,
1573
+ value: candidate
1574
+ });
1575
+ } catch (error) {
1576
+ if (!(error instanceof RecordExistsError)) throw error;
1577
+ const existing = await mustRead("answers", id, answerEventSchema);
1578
+ const { recordedAt: _existingAt, ...existingIntent } = existing.value;
1579
+ const { recordedAt: _candidateAt, ...candidateIntent } = candidate;
1580
+ if (!isDeepStrictEqual(existingIntent, candidateIntent)) throw new AnswerEventConflictError(input.questionTaskId);
1581
+ answerEvent = existing.value;
1582
+ }
1583
+ let parentTask = null;
1584
+ if (answerEvent.parentTaskId !== void 0) {
1585
+ parentTask = await stores.tasks.get(answerEvent.parentTaskId);
1586
+ if (parentTask === null) throw new Error(`parent task ${answerEvent.parentTaskId} does not exist`);
1587
+ for (;;) {
1588
+ const parent = await mustRead("tasks", answerEvent.parentTaskId, storedTaskSchema);
1589
+ if (parent.value.answerEventIds?.includes(answerEvent.id) === true) {
1590
+ parentTask = parent.value;
1591
+ break;
1592
+ }
1593
+ const projected = taskSchema.parse({
1594
+ ...parent.value,
1595
+ answerEventIds: [...parent.value.answerEventIds ?? [], answerEvent.id],
1596
+ updatedAt: now()
1597
+ });
1598
+ try {
1599
+ await updateTask(projected, parent.version);
1600
+ parentTask = projected;
1601
+ break;
1602
+ } catch (error) {
1603
+ if (!(error instanceof VersionConflictError)) throw error;
1604
+ }
1605
+ }
1606
+ if (parentTask.state === "needs_human" && parentTask.questionTaskId === input.questionTaskId) {
1607
+ const closes = isClosingAnswer(questionDetails.closingOptions, answerEvent.answer);
1608
+ parentTask = await stores.tasks.transition(parentTask.id, closes ? "cancelled" : "queued", {
1609
+ expectFrom: "needs_human",
1610
+ expectAttempt: parentTask.attempt,
1611
+ expectQuestionTaskId: input.questionTaskId,
1612
+ reason: closes ? `question ${answerEvent.questionTaskId} answered by ${answerEvent.answeredBy}: ${answerEvent.answer}` : `question ${answerEvent.questionTaskId} answered by ${answerEvent.answeredBy}`
1613
+ });
1614
+ }
1615
+ }
1616
+ let questionTask = await mustRead("tasks", input.questionTaskId, storedTaskSchema).then((record) => record.value);
1617
+ if (questionTask.state === "needs_human") questionTask = await stores.tasks.transition(questionTask.id, "succeeded", {
1618
+ patch: { question: {
1619
+ ...questionDetails,
1620
+ answer: answerEvent.answer,
1621
+ answeredBy: answerEvent.answeredBy
1622
+ } },
1623
+ output: answerOutput,
1624
+ reason: "question answered by authenticated operator"
1625
+ });
1626
+ else if (questionTask.state !== "succeeded" || questionTask.question?.answer !== answerEvent.answer || questionTask.question.answeredBy !== answerEvent.answeredBy) throw new Error(`question task ${questionTask.id} is ${questionTask.state}, not waiting`);
1627
+ return {
1628
+ answerEvent,
1629
+ questionTask,
1630
+ parentTask
1631
+ };
1632
+ },
1633
+ async transition(id, to, ...args) {
1634
+ const transitionOptions = args[0] ?? {};
1635
+ if (transitionOptions.patch !== void 0 && "settledUsd" in transitionOptions.patch) throw new SettlementError(id, "settledUsd is set only by settle, not a transition");
1636
+ if (transitionOptions.patch !== void 0 && "usage" in transitionOptions.patch) throw new SettlementError(id, "usage is set only by recordUsage, not a transition");
1637
+ let existing = await mustRead("tasks", id, storedTaskSchema);
1638
+ if (transitionOptions.expectFrom !== void 0 && existing.value.state !== transitionOptions.expectFrom) throw new InvalidTransitionError(existing.value.state, to, legalNextStates(taskTransitions, existing.value.state));
1639
+ if (transitionOptions.expectAttempt !== void 0 && existing.value.attempt !== transitionOptions.expectAttempt) throw new TaskAttemptConflictError(id, existing.value.attempt, transitionOptions.expectAttempt);
1640
+ if (transitionOptions.expectExecution !== void 0 && !sameStoredValue(existing.value.execution, transitionOptions.expectExecution)) throw new Error(`Task ${id} execution has been replaced`);
1641
+ if (transitionOptions.expectQuestionTaskId !== void 0 && existing.value.questionTaskId !== transitionOptions.expectQuestionTaskId) throw new Error(`task ${id} is no longer waiting for question ${transitionOptions.expectQuestionTaskId}`);
1642
+ assertTransition(taskTransitions, {
1643
+ from: existing.value.state,
1644
+ to
1645
+ });
1646
+ if (transitionOptions.patch !== void 0 && Object.keys(transitionOptions.patch).some((key) => ![
1647
+ "changeId",
1648
+ "reportRef",
1649
+ "reservedUsd",
1650
+ "reservedAt",
1651
+ "question"
1652
+ ].includes(key))) throw new Error("Task transitions cannot replace workflow-owned fields");
1653
+ if (to !== "succeeded" && (transitionOptions.output !== void 0 || transitionOptions.reuseRecordedOutput === true)) throw new Error("Workflow output can only be recorded when succeeding");
1654
+ if (to === "succeeded" && transitionOptions.output === void 0 && transitionOptions.reuseRecordedOutput !== true) throw new Error("Succeeded transitions require output or reuseRecordedOutput after work completion");
1655
+ if (to === "succeeded" && transitionOptions.output !== void 0 && transitionOptions.reuseRecordedOutput === true) throw new Error("Choose output or reuseRecordedOutput, not both");
1656
+ if (to !== "needs_human" && (transitionOptions.needsHumanAction !== void 0 || transitionOptions.questionTaskId !== void 0)) throw new Error("Question context is only valid when entering needs_human");
1657
+ if (to === "running") {
1658
+ if (existing.value.approval !== void 0 && existing.value.approval !== "granted") throw new Error(`Task ${id} requires approval before execution`);
1659
+ }
1660
+ const timestamp = now();
1661
+ let workResult = existing.value.workResult;
1662
+ if (to === "succeeded") {
1663
+ const output = transitionOptions.output === void 0 ? existing.value.workResult?.output : workflows.validateOutput(existing.value.work.workflow, transitionOptions.output);
1664
+ if (output === void 0) throw new Error(`Task ${id} requires validated output to succeed`);
1665
+ if (existing.value.workResult && !isDeepStrictEqual(existing.value.workResult.output, output)) throw new Error(`Task ${id} already recorded a different workflow output`);
1666
+ workResult ??= {
1667
+ output,
1668
+ completedAt: timestamp
1669
+ };
1670
+ }
1671
+ if (to === "stalled" && existing.value.waitingForMessages) throw new Error(`Task ${id} is waiting for messages, not stalled execution`);
1672
+ if (bumpsAttempt(existing.value.state, to) && existing.value.settledUsd !== void 0) throw new Error(`Task ${id} is settled; create new work rather than silently losing retry costs`);
1673
+ if (to === "needs_human" && existing.value.question === void 0) {
1674
+ if (transitionOptions.questionTaskId === void 0) throw new Error(`task ${id} cannot enter needs_human without a QuestionTask id`);
1675
+ const question = await mustRead("tasks", transitionOptions.questionTaskId, storedTaskSchema);
1676
+ if (question.value.question === void 0 || question.value.state !== "needs_human" || question.value.parentTaskId !== id || question.value.rootTaskId !== existing.value.rootTaskId || question.value.parentTaskAttempt !== existing.value.attempt || !sameStoredValue(question.value.replyTo, existing.value.replyTo)) throw new Error(`task ${id} cannot enter needs_human for question ${question.value.id}`);
1677
+ }
1678
+ const needsHumanAction = transitionOptions.needsHumanAction?.trim();
1679
+ if (to === "needs_human" && (needsHumanAction === void 0 || needsHumanAction === "")) throw new Error(`task ${id} cannot enter needs_human without an actionable needsHumanAction`);
1680
+ const bumps = bumpsAttempt(existing.value.state, to);
1681
+ const { stalledAt: _stalledAt, stalledReason: _stalledReason, questionTaskId: _questionTaskId, needsHumanAction: _needsHumanAction, needsHumanNotifiedAt: _needsHumanNotifiedAt, failureReceiptId: _failureReceiptId, ...cleared } = existing.value;
1682
+ let next = taskSchema.parse({
1683
+ ...cleared,
1684
+ workResult,
1685
+ ...bumps ? {
1686
+ execution: void 0,
1687
+ workResult: void 0
1688
+ } : {},
1689
+ waitingForMessages: void 0,
1690
+ ...transitionOptions.patch,
1691
+ ...to === "needs_human" && existing.value.question === void 0 ? { questionTaskId: transitionOptions.questionTaskId } : {},
1692
+ ...to === "needs_human" ? { needsHumanAction } : {},
1693
+ ...to === "stalled" ? {
1694
+ stalledAt: timestamp,
1695
+ stalledReason: transitionOptions.reason
1696
+ } : {},
1697
+ ...to === "running" ? { lastProgressAt: timestamp } : {},
1698
+ state: to,
1699
+ attempt: bumps ? existing.value.attempt + 1 : existing.value.attempt,
1700
+ updatedAt: timestamp
1701
+ });
1702
+ validateTaskWork(next);
1703
+ if (to === "running") await stores.graphs.seal({
1704
+ rootTaskId: existing.value.rootTaskId,
1705
+ taskId: id
1706
+ });
1707
+ if (existing.value.question !== void 0 && to === "needs_human") await setQuestionRoute(existing.value.replyTo, existing.value.id, true);
1708
+ await projectSafely(() => projectTask(existing.value, existing.version));
1709
+ if (to === "succeeded" && existing.value.workResult === void 0) {
1710
+ const prepared = taskSchema.parse({
1711
+ ...existing.value,
1712
+ workResult: next.workResult,
1713
+ updatedAt: timestamp
1714
+ });
1715
+ await updateTask(prepared, existing.version);
1716
+ existing = {
1717
+ value: prepared,
1718
+ version: existing.version + 1
1719
+ };
1720
+ }
1721
+ const receiptState = taskReceiptStates[to];
1722
+ if (receiptState !== void 0 && next.repositoryIds !== void 0) {
1723
+ const receipt = await appendReceipt(JSON.stringify([
1724
+ "task",
1725
+ id,
1726
+ receiptState,
1727
+ next.attempt,
1728
+ next.changeId ?? null,
1729
+ transitionOptions.phase ?? null,
1730
+ transitionOptions.reason ?? null
1731
+ ]), {
1732
+ repositoryIds: next.repositoryIds,
1733
+ taskId: id,
1734
+ changeId: next.changeId,
1735
+ kind: next.kind,
1736
+ state: receiptState,
1737
+ phase: transitionOptions.phase,
1738
+ reason: transitionOptions.reason,
1739
+ attempt: next.attempt,
1740
+ execution: next.execution
1741
+ });
1742
+ if (to === "failed") next = taskSchema.parse({
1743
+ ...next,
1744
+ failureReceiptId: receipt.receiptId
1745
+ });
1746
+ if (needsLifecycleCommunication(next, receiptState, transitionOptions.phase)) next = taskSchema.parse({
1747
+ ...next,
1748
+ pendingCommunicationReceiptIds: [...next.pendingCommunicationReceiptIds ?? [], receipt.receiptId]
1749
+ });
1750
+ }
1751
+ if (existing.value.question !== void 0 && existing.value.state === "needs_human" && to !== "needs_human" && existing.value.parentTaskId !== void 0) {
1752
+ const parent = await stores.tasks.get(existing.value.parentTaskId);
1753
+ if (parent?.state === "needs_human" && parent.questionTaskId === id && parent.attempt === existing.value.parentTaskAttempt) await stores.tasks.transition(parent.id, "queued", {
1754
+ expectFrom: "needs_human",
1755
+ expectAttempt: parent.attempt,
1756
+ expectQuestionTaskId: id,
1757
+ reason: `question ${id} is no longer waiting`
1758
+ });
1759
+ }
1760
+ await updateTask(next, existing.version);
1761
+ if (existing.value.question !== void 0 && existing.value.state === "needs_human" && to !== "needs_human") await setQuestionRoute(existing.value.replyTo, existing.value.id, false);
1762
+ return next;
1763
+ },
1764
+ async acknowledgeCommunication(id, receiptId) {
1765
+ for (;;) {
1766
+ const existing = await mustRead("tasks", id, storedTaskSchema);
1767
+ const pending = existing.value.pendingCommunicationReceiptIds ?? [];
1768
+ if (!pending.includes(receiptId)) {
1769
+ await projectSafely(() => projectTask(existing.value, existing.version));
1770
+ return existing.value;
1771
+ }
1772
+ const remaining = pending.filter((candidate) => candidate !== receiptId);
1773
+ const { pendingCommunicationReceiptIds: _pending, ...task } = existing.value;
1774
+ const next = taskSchema.parse({
1775
+ ...task,
1776
+ ...remaining.length === 0 ? {} : { pendingCommunicationReceiptIds: remaining },
1777
+ updatedAt: now()
1778
+ });
1779
+ try {
1780
+ await updateTask(next, existing.version);
1781
+ return next;
1782
+ } catch (error) {
1783
+ if (!(error instanceof VersionConflictError)) throw error;
1784
+ }
1785
+ }
1786
+ },
1787
+ async recordNeedsHumanDelivery(id, deliveryOptions = {}) {
1788
+ const at = deliveryOptions.at ?? now();
1789
+ let question;
1790
+ for (;;) {
1791
+ const existing = await mustRead("tasks", id, storedTaskSchema);
1792
+ if (existing.value.question === void 0) throw new Error(`task ${id} is not a QuestionTask`);
1793
+ if (existing.value.state !== "needs_human" || deliveryOptions.attempt !== void 0 && existing.value.attempt !== deliveryOptions.attempt) return existing.value;
1794
+ if (existing.value.needsHumanNotifiedAt !== void 0) {
1795
+ question = existing.value;
1796
+ break;
1797
+ }
1798
+ const next = taskSchema.parse({
1799
+ ...existing.value,
1800
+ needsHumanNotifiedAt: existing.value.needsHumanNotifiedAt ?? at,
1801
+ updatedAt: now()
1802
+ });
1803
+ try {
1804
+ await updateTask(next, existing.version);
1805
+ question = next;
1806
+ break;
1807
+ } catch (error) {
1808
+ if (!(error instanceof VersionConflictError)) throw error;
1809
+ }
1810
+ }
1811
+ const parentId = question.parentTaskId;
1812
+ if (parentId !== void 0) for (;;) {
1813
+ const parent = await mustRead("tasks", parentId, storedTaskSchema);
1814
+ if (parent.value.state !== "needs_human" || parent.value.questionTaskId !== id || parent.value.attempt !== question.parentTaskAttempt || parent.value.needsHumanNotifiedAt !== void 0) break;
1815
+ const current = await mustRead("tasks", id, storedTaskSchema);
1816
+ if (current.value.state !== "needs_human" || current.value.attempt !== question.attempt) break;
1817
+ const next = taskSchema.parse({
1818
+ ...parent.value,
1819
+ needsHumanNotifiedAt: question.needsHumanNotifiedAt,
1820
+ updatedAt: now()
1821
+ });
1822
+ try {
1823
+ await updateTask(next, parent.version);
1824
+ break;
1825
+ } catch (error) {
1826
+ if (!(error instanceof VersionConflictError)) throw error;
1827
+ }
1828
+ }
1829
+ return question;
1830
+ },
1831
+ async forReplyTo(replyTo) {
1832
+ const id = derivedId("task-route", JSON.stringify([replyTo.channel, replyTo.address]));
1833
+ let route = await read("taskRoutes", id, taskRouteSchema);
1834
+ let discovered = [];
1835
+ if (route === null) {
1836
+ discovered = (await stores.tasks.list()).filter((task) => task.replyTo.channel === replyTo.channel && task.replyTo.address === replyTo.address);
1837
+ for (const task of discovered) {
1838
+ const record = await mustRead("tasks", task.id, storedTaskSchema);
1839
+ await projectSafely(() => projectTaskRoute(record.value, record.version));
1840
+ }
1841
+ if (discovered.length === 0) {
1842
+ const empty = taskRouteSchema.parse({
1843
+ schemaVersion: 1,
1844
+ id,
1845
+ replyTo,
1846
+ entries: [],
1847
+ updatedAt: now()
1848
+ });
1849
+ await projectSafely(async () => {
1850
+ try {
1851
+ await driver.insert({
1852
+ collection: "taskRoutes",
1853
+ id,
1854
+ value: empty
1855
+ });
1856
+ } catch (error) {
1857
+ if (!(error instanceof RecordExistsError)) throw error;
1858
+ }
1859
+ });
1860
+ }
1861
+ route = await read("taskRoutes", id, taskRouteSchema);
1862
+ }
1863
+ if (route === null) return discovered;
1864
+ const values = new Map(route.value.entries.map((entry) => [entry.value.id, entry.value]));
1865
+ await forEachConcurrent(route.value.repairs, 16, async (repair) => {
1866
+ const repairTaskId = taskIdSchema.parse(repair.id);
1867
+ const record = await driver.get("tasks", repairTaskId);
1868
+ if (record === null) {
1869
+ values.delete(repairTaskId);
1870
+ return;
1871
+ }
1872
+ const canonical = storedTaskSchema.parse(record.value);
1873
+ if (canonical.replyTo.channel === replyTo.channel && canonical.replyTo.address === replyTo.address) values.set(canonical.id, canonical);
1874
+ else values.delete(canonical.id);
1875
+ await projectSafely(() => projectTaskRoute(canonical, record.version));
1876
+ });
1877
+ const tasks = [...values.values()];
1878
+ for (const task of tasks) validateTaskWork(task);
1879
+ return tasks;
1880
+ },
1881
+ async list() {
1882
+ const tasks = await catalogValues("tasks", "taskCatalogs", "tasks", storedTaskSchema, taskCatalogSchema, projectTaskCatalog);
1883
+ for (const task of tasks) validateTaskWork(task);
1884
+ return tasks;
1885
+ }
1886
+ },
1887
+ changes: {
1888
+ async create(input) {
1889
+ const timestamp = now();
1890
+ const id = derivedId("chg", `${input.repositoryId}:github-pr:${input.prNumber}`);
1891
+ const change = changeSchema.parse({
1892
+ schemaVersion: 1,
1893
+ id,
1894
+ repositoryId: input.repositoryId,
1895
+ taskId: input.taskId,
1896
+ groupId: input.groupId,
1897
+ prNumber: input.prNumber,
1898
+ prUrl: input.prUrl,
1899
+ author: input.author,
1900
+ risk: input.risk,
1901
+ confidence: input.confidence,
1902
+ evidence: input.evidence,
1903
+ state: changeInitialState,
1904
+ createdAt: timestamp,
1905
+ updatedAt: timestamp
1906
+ });
1907
+ await mustRead("repositories", change.repositoryId, repositorySchema);
1908
+ if (change.taskId !== void 0) {
1909
+ const task = (await mustRead("tasks", change.taskId, storedTaskSchema)).value;
1910
+ const taskRepositoryId = task.repositoryIds[0];
1911
+ if (!task.repositoryIds.includes(change.repositoryId)) throw new ChangeAssociationError(change.taskId, change.repositoryId, `repository "${change.repositoryId}" does not match task repository "${taskRepositoryId}"`);
1912
+ }
1913
+ await markChangeCatalogRepair(change.id, 1);
1914
+ try {
1915
+ await driver.insert({
1916
+ collection: "changes",
1917
+ id: change.id,
1918
+ value: change
1919
+ });
1920
+ await projectSafely(() => projectChangeCatalog(change, 1));
1921
+ return change;
1922
+ } catch (error) {
1923
+ if (error instanceof RecordExistsError) for (;;) {
1924
+ const existing = await mustRead("changes", change.id, changeSchema);
1925
+ if (change.taskId !== void 0) {
1926
+ if (existing.value.prUrl !== change.prUrl || existing.value.taskId !== void 0 && existing.value.taskId !== change.taskId) throw new ChangeAssociationError(change.taskId, change.repositoryId, "pull request is already recorded with a different URL or producing Task");
1927
+ if (existing.value.taskId === void 0) {
1928
+ const associated = changeSchema.parse({
1929
+ ...existing.value,
1930
+ taskId: change.taskId,
1931
+ updatedAt: now()
1932
+ });
1933
+ try {
1934
+ await updateChange(associated, existing.version);
1935
+ return associated;
1936
+ } catch (conflict) {
1937
+ if (conflict instanceof VersionConflictError) continue;
1938
+ throw conflict;
1939
+ }
1940
+ }
1941
+ }
1942
+ await projectSafely(() => projectChangeCatalog(existing.value, existing.version));
1943
+ return existing.value;
1944
+ }
1945
+ throw error;
1946
+ }
1947
+ },
1948
+ async get(id) {
1949
+ const record = await read("changes", id, changeSchema);
1950
+ return record === null ? null : record.value;
1951
+ },
1952
+ async updateEvidence(id, input) {
1953
+ const existing = await mustRead("changes", id, changeSchema);
1954
+ if (!sameStoredValue(existing.value, input.expected)) throw new VersionConflictError("changes", id, existing.version);
1955
+ const next = changeSchema.parse({
1956
+ ...existing.value,
1957
+ evidence: input.evidence,
1958
+ confidence: input.confidence,
1959
+ updatedAt: now()
1960
+ });
1961
+ await updateChange(next, existing.version);
1962
+ return next;
1963
+ },
1964
+ async transition(id, to, ...args) {
1965
+ const transitionOptions = args[0] ?? {};
1966
+ const existing = await mustRead("changes", id, changeSchema);
1967
+ await projectSafely(() => projectChangeCatalog(existing.value, existing.version));
1968
+ assertTransition(changeTransitions, {
1969
+ from: existing.value.state,
1970
+ to
1971
+ });
1972
+ const invalidated = existing.value.state === "ready" && to === "verifying";
1973
+ const next = changeSchema.parse({
1974
+ ...existing.value,
1975
+ ...transitionOptions.patch,
1976
+ ...invalidated ? {
1977
+ confidence: "low",
1978
+ evidence: unverifiedEvidence
1979
+ } : {},
1980
+ state: to,
1981
+ updatedAt: now()
1982
+ });
1983
+ const receiptState = changeReceiptStates[to];
1984
+ if (receiptState !== void 0) await appendReceipt(`change:${id}:${receiptState}`, {
1985
+ repositoryIds: [next.repositoryId],
1986
+ taskId: next.taskId,
1987
+ changeId: id,
1988
+ kind: "change",
1989
+ state: receiptState,
1990
+ reason: transitionOptions.reason,
1991
+ attempt: 1,
1992
+ decidedBy: next.decidedBy
1993
+ });
1994
+ await updateChange(next, existing.version);
1995
+ return next;
1996
+ },
1997
+ list() {
1998
+ return catalogValues("changes", "changeCatalogs", "changes", changeSchema, changeCatalogSchema, projectChangeCatalog);
1999
+ }
2000
+ },
2001
+ receipts: {
2002
+ append(input) {
2003
+ const { dedupeKey, attempt, ...fields } = input;
2004
+ return insertReceipt(dedupeKey === void 0 ? randomId("rcpt") : derivedId("rcpt", `custom:${dedupeKey}`), {
2005
+ ...fields,
2006
+ attempt: attempt ?? 1
2007
+ });
2008
+ },
2009
+ async get(id) {
2010
+ const record = await read("receipts", id, receiptSchema);
2011
+ return record === null ? null : record.value;
2012
+ },
2013
+ list() {
2014
+ return listAll("receipts", receiptSchema);
2015
+ }
2016
+ },
2017
+ budgets: {
2018
+ async ensure(input) {
2019
+ const budget = budgetSchema.parse({
2020
+ schemaVersion: 1,
2021
+ factoryId: input.factoryId,
2022
+ month: input.month,
2023
+ limits: input.limits,
2024
+ notices: {}
2025
+ });
2026
+ try {
2027
+ await driver.insert({
2028
+ collection: "budgets",
2029
+ id: input.month,
2030
+ value: budget
2031
+ });
2032
+ return budget;
2033
+ } catch (error) {
2034
+ if (!(error instanceof RecordExistsError)) throw error;
2035
+ }
2036
+ const existing = await mustRead("budgets", input.month, budgetSchema);
2037
+ const refreshed = budgetSchema.parse({
2038
+ ...existing.value,
2039
+ limits: input.limits
2040
+ });
2041
+ if (JSON.stringify(refreshed) === JSON.stringify(existing.value)) return existing.value;
2042
+ try {
2043
+ await driver.update({
2044
+ collection: "budgets",
2045
+ id: input.month,
2046
+ value: refreshed,
2047
+ expectedVersion: existing.version
2048
+ });
2049
+ return refreshed;
2050
+ } catch (error) {
2051
+ if (error instanceof VersionConflictError) return (await mustRead("budgets", input.month, budgetSchema)).value;
2052
+ throw error;
2053
+ }
2054
+ },
2055
+ async get(month) {
2056
+ const record = await read("budgets", month, budgetSchema);
2057
+ return record === null ? null : record.value;
2058
+ },
2059
+ async stampNotice(month, notice) {
2060
+ const field = notice === "warning" ? "warningAt" : "exhaustedAt";
2061
+ for (;;) {
2062
+ const existing = await mustRead("budgets", month, budgetSchema);
2063
+ if (existing.value.notices[field] !== void 0) return {
2064
+ budget: existing.value,
2065
+ stamped: false
2066
+ };
2067
+ const next = budgetSchema.parse({
2068
+ ...existing.value,
2069
+ notices: {
2070
+ ...existing.value.notices,
2071
+ [field]: now()
2072
+ }
2073
+ });
2074
+ try {
2075
+ await driver.update({
2076
+ collection: "budgets",
2077
+ id: month,
2078
+ value: next,
2079
+ expectedVersion: existing.version
2080
+ });
2081
+ return {
2082
+ budget: next,
2083
+ stamped: true
2084
+ };
2085
+ } catch (error) {
2086
+ if (!(error instanceof VersionConflictError)) throw error;
2087
+ }
2088
+ }
2089
+ }
2090
+ },
2091
+ answers: { async get(id) {
2092
+ return (await read("answers", id, answerEventSchema))?.value ?? null;
2093
+ } },
2094
+ outbox: {
2095
+ async enqueue(input) {
2096
+ const event = communicationEventSchema.parse(input);
2097
+ const id = outboxIdSchema.parse(derivedId("out", event.eventId));
2098
+ const delivered = await read("outboxDeliveries", id, outboxMessageSchema);
2099
+ if (delivered !== null) {
2100
+ if (!isDeepStrictEqual(delivered.value.event, event)) throw new OutboxEventConflictError(event.eventId);
2101
+ return delivered.value;
2102
+ }
2103
+ const queueId = `communication-${id.slice(4, 5)}`;
2104
+ for (;;) {
2105
+ const existing = await read("outboxQueues", queueId, outboxQueueSchema);
2106
+ const duplicate = existing?.value.messages.find((message) => message.id === id);
2107
+ if (duplicate !== void 0) {
2108
+ if (!isDeepStrictEqual(duplicate.event, event)) throw new OutboxEventConflictError(event.eventId);
2109
+ return duplicate;
2110
+ }
2111
+ const timestamp = now();
2112
+ const message = outboxMessageSchema.parse({
2113
+ schemaVersion: 1,
2114
+ id,
2115
+ event,
2116
+ state: "pending",
2117
+ attempt: 0,
2118
+ availableAt: timestamp,
2119
+ createdAt: timestamp,
2120
+ updatedAt: timestamp
2121
+ });
2122
+ const queue = outboxQueueSchema.parse({
2123
+ schemaVersion: 1,
2124
+ id: queueId,
2125
+ messages: [...existing?.value.messages ?? [], message],
2126
+ updatedAt: timestamp
2127
+ });
2128
+ try {
2129
+ if (existing === null) await driver.insert({
2130
+ collection: "outboxQueues",
2131
+ id: queueId,
2132
+ value: queue
2133
+ });
2134
+ else await driver.update({
2135
+ collection: "outboxQueues",
2136
+ id: queueId,
2137
+ value: queue,
2138
+ expectedVersion: existing.version
2139
+ });
2140
+ return message;
2141
+ } catch (error) {
2142
+ if (!(error instanceof RecordExistsError) && !(error instanceof VersionConflictError)) throw error;
2143
+ }
2144
+ }
2145
+ },
2146
+ async get(id) {
2147
+ const delivered = await read("outboxDeliveries", id, outboxMessageSchema);
2148
+ if (delivered !== null) return delivered.value;
2149
+ return (await read("outboxQueues", `communication-${id.slice(4, 5)}`, outboxQueueSchema))?.value.messages.find((message) => message.id === id) ?? null;
2150
+ },
2151
+ async claim(id, leaseMs = 6e4) {
2152
+ if (!Number.isSafeInteger(leaseMs) || leaseMs <= 0) throw new Error("outbox leaseMs must be a positive safe integer");
2153
+ const queueId = `communication-${id.slice(4, 5)}`;
2154
+ for (;;) {
2155
+ const queue = await mustRead("outboxQueues", queueId, outboxQueueSchema);
2156
+ const index = queue.value.messages.findIndex((message) => message.id === id);
2157
+ if (index < 0) return null;
2158
+ if (await read("outboxDeliveries", id, outboxMessageSchema) !== null) {
2159
+ const messages = queue.value.messages.filter((message) => message.id !== id);
2160
+ try {
2161
+ await driver.update({
2162
+ collection: "outboxQueues",
2163
+ id: queueId,
2164
+ value: outboxQueueSchema.parse({
2165
+ ...queue.value,
2166
+ messages,
2167
+ updatedAt: now()
2168
+ }),
2169
+ expectedVersion: queue.version
2170
+ });
2171
+ return null;
2172
+ } catch (error) {
2173
+ if (!(error instanceof VersionConflictError)) throw error;
2174
+ continue;
2175
+ }
2176
+ }
2177
+ const message = queue.value.messages[index];
2178
+ const timestamp = now();
2179
+ if (!(message.state === "pending" && message.availableAt <= timestamp || message.state === "delivering" && message.leaseUntil !== void 0 && message.leaseUntil <= timestamp)) return null;
2180
+ const next = outboxMessageSchema.parse({
2181
+ ...message,
2182
+ state: "delivering",
2183
+ attempt: message.attempt + 1,
2184
+ leaseUntil: new Date(Date.parse(timestamp) + leaseMs).toISOString(),
2185
+ updatedAt: timestamp
2186
+ });
2187
+ const messages = [...queue.value.messages];
2188
+ messages[index] = next;
2189
+ try {
2190
+ await driver.update({
2191
+ collection: "outboxQueues",
2192
+ id: queueId,
2193
+ value: outboxQueueSchema.parse({
2194
+ ...queue.value,
2195
+ messages,
2196
+ updatedAt: timestamp
2197
+ }),
2198
+ expectedVersion: queue.version
2199
+ });
2200
+ return next;
2201
+ } catch (error) {
2202
+ if (!(error instanceof VersionConflictError)) throw error;
2203
+ }
2204
+ }
2205
+ },
2206
+ async retry(id, input) {
2207
+ const { attempt, reason, delayMs } = input;
2208
+ if (!Number.isSafeInteger(delayMs) || delayMs < 0) throw new Error("outbox delayMs must be a nonnegative safe integer");
2209
+ const queueId = `communication-${id.slice(4, 5)}`;
2210
+ for (;;) {
2211
+ const queue = await mustRead("outboxQueues", queueId, outboxQueueSchema);
2212
+ const index = queue.value.messages.findIndex((message) => message.id === id);
2213
+ if (index < 0) return null;
2214
+ const message = queue.value.messages[index];
2215
+ if (message.state !== "delivering" || message.attempt !== attempt) return null;
2216
+ const timestamp = now();
2217
+ const { leaseUntil: _leaseUntil, ...leased } = message;
2218
+ const next = outboxMessageSchema.parse({
2219
+ ...leased,
2220
+ state: "pending",
2221
+ availableAt: new Date(Date.parse(timestamp) + delayMs).toISOString(),
2222
+ lastError: reason.trim().slice(0, 2e3) || "delivery failed",
2223
+ updatedAt: timestamp
2224
+ });
2225
+ const messages = [...queue.value.messages];
2226
+ messages[index] = next;
2227
+ try {
2228
+ await driver.update({
2229
+ collection: "outboxQueues",
2230
+ id: queueId,
2231
+ value: outboxQueueSchema.parse({
2232
+ ...queue.value,
2233
+ messages,
2234
+ updatedAt: timestamp
2235
+ }),
2236
+ expectedVersion: queue.version
2237
+ });
2238
+ return next;
2239
+ } catch (error) {
2240
+ if (!(error instanceof VersionConflictError)) throw error;
2241
+ }
2242
+ }
2243
+ },
2244
+ async complete(id, attempt) {
2245
+ const queueId = `communication-${id.slice(4, 5)}`;
2246
+ let delivered = await read("outboxDeliveries", id, outboxMessageSchema);
2247
+ for (;;) {
2248
+ const queue = await mustRead("outboxQueues", queueId, outboxQueueSchema);
2249
+ const index = queue.value.messages.findIndex((message) => message.id === id);
2250
+ if (index < 0) return delivered?.value ?? null;
2251
+ const message = queue.value.messages[index];
2252
+ const timestamp = now();
2253
+ if (delivered === null) {
2254
+ if (message.state !== "delivering" || message.attempt !== attempt) return null;
2255
+ const { leaseUntil: _leaseUntil, lastError: _lastError, ...leased } = message;
2256
+ const next = outboxMessageSchema.parse({
2257
+ ...leased,
2258
+ state: "delivered",
2259
+ deliveredAt: timestamp,
2260
+ updatedAt: timestamp
2261
+ });
2262
+ try {
2263
+ await driver.insert({
2264
+ collection: "outboxDeliveries",
2265
+ id,
2266
+ value: next
2267
+ });
2268
+ delivered = {
2269
+ value: next,
2270
+ version: 1
2271
+ };
2272
+ } catch (error) {
2273
+ if (!(error instanceof RecordExistsError)) throw error;
2274
+ delivered = await mustRead("outboxDeliveries", id, outboxMessageSchema);
2275
+ if (!isDeepStrictEqual(delivered.value.event, message.event)) throw new OutboxEventConflictError(message.event.eventId);
2276
+ }
2277
+ }
2278
+ const messages = queue.value.messages.filter((candidate) => candidate.id !== id);
2279
+ try {
2280
+ await driver.update({
2281
+ collection: "outboxQueues",
2282
+ id: queueId,
2283
+ value: outboxQueueSchema.parse({
2284
+ ...queue.value,
2285
+ messages,
2286
+ updatedAt: timestamp
2287
+ }),
2288
+ expectedVersion: queue.version
2289
+ });
2290
+ return delivered.value;
2291
+ } catch (error) {
2292
+ if (!(error instanceof VersionConflictError)) throw error;
2293
+ }
2294
+ }
2295
+ },
2296
+ async pending(limit = 100) {
2297
+ if (!Number.isSafeInteger(limit) || limit <= 0) throw new Error("outbox pending limit must be a positive safe integer");
2298
+ const queues = await Promise.all("0123456789abcdef".split("").map((shard) => read("outboxQueues", `communication-${shard}`, outboxQueueSchema)));
2299
+ const timestamp = now();
2300
+ return queues.flatMap((queue) => queue?.value.messages ?? []).filter((message) => message.state === "pending" && message.availableAt <= timestamp || message.state === "delivering" && message.leaseUntil !== void 0 && message.leaseUntil <= timestamp).sort((a, b) => a.createdAt.localeCompare(b.createdAt)).slice(0, limit);
2301
+ }
2302
+ }
2303
+ };
2304
+ return stores;
2305
+ }
2306
+ //#endregion
2307
+ export { AnswerEventConflictError, ApprovalError, ChangeAssociationError, ImageInputError, OutboxEventConflictError, SessionConflictError, SettlementError, TaskAttemptConflictError, createStores };
2308
+
2309
+ //# sourceMappingURL=engine.mjs.map