@codespar/cli 0.17.0 → 0.18.1

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 (482) hide show
  1. package/README.md +1 -0
  2. package/dist/surface.js +53 -1
  3. package/dist/surface.js.map +1 -1
  4. package/package.json +1 -1
  5. package/templates/bills-agent/README.md +1 -1
  6. package/templates/bills-agent/agents/bills-agent/AGENTS.md +1 -1
  7. package/templates/bills-agent/agents/bills-agent/CLAUDE.md +1 -1
  8. package/templates/bills-agent/agents/bills-agent/README.md +6 -5
  9. package/templates/bills-agent/agents/bills-agent/src/modules/embedded-consent.ts +5 -4
  10. package/templates/bills-agent/packages/agent-core/README.md +14 -1
  11. package/templates/bills-agent/packages/agent-core/package.json +1 -1
  12. package/templates/bills-agent/packages/agent-core/src/agent.ts +39 -1
  13. package/templates/bills-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
  14. package/templates/bills-agent/packages/agent-core/src/api/client.ts +28 -5
  15. package/templates/bills-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
  16. package/templates/bills-agent/packages/agent-core/src/api/rail.ts +62 -26
  17. package/templates/bills-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
  18. package/templates/bills-agent/packages/agent-core/src/approval.ts +14 -2
  19. package/templates/bills-agent/packages/agent-core/src/bundle.ts +35 -3
  20. package/templates/bills-agent/packages/agent-core/src/channels.ts +49 -3
  21. package/templates/bills-agent/packages/agent-core/src/check.ts +25 -2
  22. package/templates/bills-agent/packages/agent-core/src/engine.ts +235 -46
  23. package/templates/bills-agent/packages/agent-core/src/hash.ts +57 -1
  24. package/templates/bills-agent/packages/agent-core/src/index.ts +2 -0
  25. package/templates/bills-agent/packages/agent-core/src/quote.ts +50 -0
  26. package/templates/bills-agent/packages/agent-core/src/rail.ts +89 -4
  27. package/templates/bills-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  28. package/templates/bills-agent/packages/agent-core/src/receipt-verification.ts +194 -18
  29. package/templates/bills-agent/packages/agent-core/src/revocation.ts +1 -1
  30. package/templates/bills-agent/packages/agent-core/src/state-machine.ts +10 -1
  31. package/templates/bills-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
  32. package/templates/bills-agent/packages/agent-core/src/stubs/rail.ts +100 -32
  33. package/templates/bills-agent/packages/agent-core/src/tools.ts +17 -6
  34. package/templates/bills-agent/packages/agent-core/src/types.ts +54 -1
  35. package/templates/bills-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  36. package/templates/bills-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  37. package/templates/bills-agent/packages/agent-core/test/composition.test.ts +204 -0
  38. package/templates/bills-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  39. package/templates/bills-agent/packages/agent-core/test/helpers.ts +4 -1
  40. package/templates/bills-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
  41. package/templates/bills-agent/packages/agent-core/test/quote.test.ts +198 -0
  42. package/templates/bills-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  43. package/templates/bills-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  44. package/templates/bills-agent/packages/agent-core/test/receivables.test.ts +103 -3
  45. package/templates/bills-agent/packages/agent-runtime/README.md +12 -0
  46. package/templates/bills-agent/packages/agent-runtime/src/adversarial.ts +32 -2
  47. package/templates/bills-agent/packages/agent-runtime/src/channels/types.ts +53 -4
  48. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
  49. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
  50. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
  51. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
  52. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
  53. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
  54. package/templates/bills-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
  55. package/templates/bills-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
  56. package/templates/bills-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
  57. package/templates/bills-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
  58. package/templates/bills-agent/packages/agent-runtime/src/commands/start.ts +6 -3
  59. package/templates/bills-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
  60. package/templates/bills-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
  61. package/templates/bills-agent/packages/agent-runtime/src/index.ts +1 -0
  62. package/templates/bills-agent/packages/agent-runtime/src/inspect.ts +5 -1
  63. package/templates/bills-agent/packages/agent-runtime/src/kit.ts +25 -2
  64. package/templates/bills-agent/packages/agent-runtime/src/scenarios.ts +8 -2
  65. package/templates/bills-agent/packages/agent-runtime/src/setup.ts +13 -3
  66. package/templates/bills-agent/packages/agent-runtime/src/terminal.ts +127 -18
  67. package/templates/bills-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
  68. package/templates/bills-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
  69. package/templates/bills-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
  70. package/templates/bills-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  71. package/templates/checkout-agent/LICENSE +21 -0
  72. package/templates/checkout-agent/README.md +28 -0
  73. package/templates/checkout-agent/_gitignore +10 -0
  74. package/templates/checkout-agent/agents/checkout-agent/.env.example +5 -0
  75. package/templates/checkout-agent/agents/checkout-agent/AGENTS.md +20 -0
  76. package/templates/checkout-agent/agents/checkout-agent/CLAUDE.md +20 -0
  77. package/templates/checkout-agent/agents/checkout-agent/README.md +94 -0
  78. package/templates/checkout-agent/agents/checkout-agent/SYSTEM_PROMPT.md +36 -0
  79. package/templates/checkout-agent/agents/checkout-agent/agent.yaml +30 -0
  80. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/README.md +28 -0
  81. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/pedido-beatriz.json +11 -0
  82. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/pedido-marina-retomada.json +10 -0
  83. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/pedido-marina.json +11 -0
  84. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/templates.json +49 -0
  85. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/beneficiary-swap-human.json +12 -0
  86. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/beneficiary-swap.json +11 -0
  87. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/beneficiary-swap.transcript.jsonl +5 -0
  88. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/coupon-unknown.json +13 -0
  89. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/coupon-unknown.transcript.jsonl +4 -0
  90. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/exfiltration.json +26 -0
  91. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/exfiltration.transcript.jsonl +3 -0
  92. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/false-authority.json +11 -0
  93. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/false-authority.transcript.jsonl +5 -0
  94. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/fractioning.json +12 -0
  95. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/fractioning.transcript.jsonl +5 -0
  96. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/model-total.json +13 -0
  97. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/model-total.transcript.jsonl +5 -0
  98. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/payment-claimed.json +15 -0
  99. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/payment-claimed.transcript.jsonl +7 -0
  100. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/price-injected.json +12 -0
  101. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/price-injected.transcript.jsonl +5 -0
  102. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/prompt-injection.json +13 -0
  103. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/prompt-injection.transcript.jsonl +4 -0
  104. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped-human.json +16 -0
  105. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped-human.transcript.jsonl +9 -0
  106. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped.json +15 -0
  107. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped.transcript.jsonl +8 -0
  108. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/webhook-replay.json +11 -0
  109. package/templates/checkout-agent/agents/checkout-agent/evals/eval.yaml +11 -0
  110. package/templates/checkout-agent/agents/checkout-agent/guardrails.json +25 -0
  111. package/templates/checkout-agent/agents/checkout-agent/mandate.example.json +28 -0
  112. package/templates/checkout-agent/agents/checkout-agent/package.json +33 -0
  113. package/templates/checkout-agent/agents/checkout-agent/runbook.md +19 -0
  114. package/templates/checkout-agent/agents/checkout-agent/scenarios/beneficiary-not-allowed.json +42 -0
  115. package/templates/checkout-agent/agents/checkout-agent/scenarios/beneficiary-not-allowed.transcript.jsonl +4 -0
  116. package/templates/checkout-agent/agents/checkout-agent/scenarios/cap-exceeded.json +32 -0
  117. package/templates/checkout-agent/agents/checkout-agent/scenarios/cap-exceeded.transcript.jsonl +4 -0
  118. package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-recomposed.json +61 -0
  119. package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-recomposed.transcript.jsonl +9 -0
  120. package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-replaced.json +61 -0
  121. package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-replaced.transcript.jsonl +9 -0
  122. package/templates/checkout-agent/agents/checkout-agent/scenarios/charge-expired.json +36 -0
  123. package/templates/checkout-agent/agents/checkout-agent/scenarios/charge-expired.transcript.jsonl +6 -0
  124. package/templates/checkout-agent/agents/checkout-agent/scenarios/coupon-unknown.json +23 -0
  125. package/templates/checkout-agent/agents/checkout-agent/scenarios/coupon-unknown.transcript.jsonl +4 -0
  126. package/templates/checkout-agent/agents/checkout-agent/scenarios/escalated-above-threshold.json +72 -0
  127. package/templates/checkout-agent/agents/checkout-agent/scenarios/escalated-above-threshold.transcript.jsonl +9 -0
  128. package/templates/checkout-agent/agents/checkout-agent/scenarios/happy-path.json +15 -0
  129. package/templates/checkout-agent/agents/checkout-agent/scenarios/happy-path.transcript.jsonl +6 -0
  130. package/templates/checkout-agent/agents/checkout-agent/scenarios/item-unavailable.json +23 -0
  131. package/templates/checkout-agent/agents/checkout-agent/scenarios/item-unavailable.transcript.jsonl +3 -0
  132. package/templates/checkout-agent/agents/checkout-agent/scenarios/mandate-revoked.json +42 -0
  133. package/templates/checkout-agent/agents/checkout-agent/scenarios/mandate-revoked.transcript.jsonl +12 -0
  134. package/templates/checkout-agent/agents/checkout-agent/scenarios/nfse-failed.json +14 -0
  135. package/templates/checkout-agent/agents/checkout-agent/scenarios/nfse-failed.transcript.jsonl +9 -0
  136. package/templates/checkout-agent/agents/checkout-agent/scenarios/payment-claimed.json +34 -0
  137. package/templates/checkout-agent/agents/checkout-agent/scenarios/payment-claimed.transcript.jsonl +7 -0
  138. package/templates/checkout-agent/agents/checkout-agent/scenarios/price-injected.json +33 -0
  139. package/templates/checkout-agent/agents/checkout-agent/scenarios/price-injected.transcript.jsonl +4 -0
  140. package/templates/checkout-agent/agents/checkout-agent/scenarios/prompt-injection.json +32 -0
  141. package/templates/checkout-agent/agents/checkout-agent/scenarios/prompt-injection.transcript.jsonl +4 -0
  142. package/templates/checkout-agent/agents/checkout-agent/src/catalog.ts +92 -0
  143. package/templates/checkout-agent/agents/checkout-agent/src/kit.ts +338 -0
  144. package/templates/checkout-agent/agents/checkout-agent/src/modules/bolepix-receivables.ts +179 -0
  145. package/templates/checkout-agent/agents/checkout-agent/src/modules/nfse-invoice.ts +374 -0
  146. package/templates/checkout-agent/agents/checkout-agent/src/modules/storefront-cart.ts +220 -0
  147. package/templates/checkout-agent/agents/checkout-agent/src/pricing.ts +297 -0
  148. package/templates/checkout-agent/agents/checkout-agent/test/adversarial.test.ts +117 -0
  149. package/templates/checkout-agent/agents/checkout-agent/test/check.test.ts +67 -0
  150. package/templates/checkout-agent/agents/checkout-agent/test/cli.test.ts +169 -0
  151. package/templates/checkout-agent/agents/checkout-agent/test/fixtures/order-marina.transcript.jsonl +5 -0
  152. package/templates/checkout-agent/agents/checkout-agent/test/fixtures/ordered-marina-awaiting-payer.transcript.jsonl +6 -0
  153. package/templates/checkout-agent/agents/checkout-agent/test/fixtures/tap-falar-agora-marina.transcript.jsonl +2 -0
  154. package/templates/checkout-agent/agents/checkout-agent/test/nfse.test.ts +170 -0
  155. package/templates/checkout-agent/agents/checkout-agent/test/pricing.test.ts +138 -0
  156. package/templates/checkout-agent/agents/checkout-agent/test/scenarios.test.ts +113 -0
  157. package/templates/checkout-agent/agents/checkout-agent/test/whatsapp.test.ts +171 -0
  158. package/templates/checkout-agent/agents/checkout-agent/tools.json +68 -0
  159. package/templates/checkout-agent/agents/checkout-agent/tsconfig.json +10 -0
  160. package/templates/checkout-agent/package.json +38 -0
  161. package/templates/checkout-agent/packages/agent-core/README.md +34 -0
  162. package/templates/checkout-agent/packages/agent-core/package.json +24 -0
  163. package/templates/checkout-agent/packages/agent-core/src/agent.ts +163 -0
  164. package/templates/checkout-agent/packages/agent-core/src/api/charge-rail.ts +195 -0
  165. package/templates/checkout-agent/packages/agent-core/src/api/client.ts +76 -0
  166. package/templates/checkout-agent/packages/agent-core/src/api/mandate-status.ts +74 -0
  167. package/templates/checkout-agent/packages/agent-core/src/api/rail.ts +153 -0
  168. package/templates/checkout-agent/packages/agent-core/src/api/sandbox-payer.ts +43 -0
  169. package/templates/checkout-agent/packages/agent-core/src/approval.ts +135 -0
  170. package/templates/checkout-agent/packages/agent-core/src/bundle.ts +191 -0
  171. package/templates/checkout-agent/packages/agent-core/src/channels.ts +186 -0
  172. package/templates/checkout-agent/packages/agent-core/src/check.ts +276 -0
  173. package/templates/checkout-agent/packages/agent-core/src/clock.ts +42 -0
  174. package/templates/checkout-agent/packages/agent-core/src/engine.ts +1074 -0
  175. package/templates/checkout-agent/packages/agent-core/src/escalate.ts +85 -0
  176. package/templates/checkout-agent/packages/agent-core/src/events.ts +40 -0
  177. package/templates/checkout-agent/packages/agent-core/src/guardrails.ts +45 -0
  178. package/templates/checkout-agent/packages/agent-core/src/hash.ts +118 -0
  179. package/templates/checkout-agent/packages/agent-core/src/ids.ts +10 -0
  180. package/templates/checkout-agent/packages/agent-core/src/index.ts +34 -0
  181. package/templates/checkout-agent/packages/agent-core/src/mandate.ts +100 -0
  182. package/templates/checkout-agent/packages/agent-core/src/manifest.ts +76 -0
  183. package/templates/checkout-agent/packages/agent-core/src/providers/anthropic.ts +76 -0
  184. package/templates/checkout-agent/packages/agent-core/src/providers/replay.ts +63 -0
  185. package/templates/checkout-agent/packages/agent-core/src/providers/types.ts +39 -0
  186. package/templates/checkout-agent/packages/agent-core/src/quote.ts +50 -0
  187. package/templates/checkout-agent/packages/agent-core/src/rail.ts +186 -0
  188. package/templates/checkout-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  189. package/templates/checkout-agent/packages/agent-core/src/receipt-verification.ts +577 -0
  190. package/templates/checkout-agent/packages/agent-core/src/revocation.ts +30 -0
  191. package/templates/checkout-agent/packages/agent-core/src/secrets.ts +30 -0
  192. package/templates/checkout-agent/packages/agent-core/src/state/store.ts +307 -0
  193. package/templates/checkout-agent/packages/agent-core/src/state-machine.ts +157 -0
  194. package/templates/checkout-agent/packages/agent-core/src/stubs/charge-rail.ts +195 -0
  195. package/templates/checkout-agent/packages/agent-core/src/stubs/mandate-status.ts +58 -0
  196. package/templates/checkout-agent/packages/agent-core/src/stubs/rail.ts +209 -0
  197. package/templates/checkout-agent/packages/agent-core/src/tools.ts +66 -0
  198. package/templates/checkout-agent/packages/agent-core/src/types.ts +187 -0
  199. package/templates/checkout-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  200. package/templates/checkout-agent/packages/agent-core/test/approval.test.ts +117 -0
  201. package/templates/checkout-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  202. package/templates/checkout-agent/packages/agent-core/test/bundle.test.ts +85 -0
  203. package/templates/checkout-agent/packages/agent-core/test/clock.test.ts +25 -0
  204. package/templates/checkout-agent/packages/agent-core/test/composition.test.ts +204 -0
  205. package/templates/checkout-agent/packages/agent-core/test/engine.test.ts +596 -0
  206. package/templates/checkout-agent/packages/agent-core/test/escalate.test.ts +42 -0
  207. package/templates/checkout-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  208. package/templates/checkout-agent/packages/agent-core/test/fixtures/staging-receipt-keys.json +18 -0
  209. package/templates/checkout-agent/packages/agent-core/test/fixtures/staging-receipt.json +24 -0
  210. package/templates/checkout-agent/packages/agent-core/test/helpers.ts +142 -0
  211. package/templates/checkout-agent/packages/agent-core/test/mandate-status.test.ts +229 -0
  212. package/templates/checkout-agent/packages/agent-core/test/manifest.test.ts +45 -0
  213. package/templates/checkout-agent/packages/agent-core/test/quote.test.ts +198 -0
  214. package/templates/checkout-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  215. package/templates/checkout-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  216. package/templates/checkout-agent/packages/agent-core/test/receipt-verification.test.ts +316 -0
  217. package/templates/checkout-agent/packages/agent-core/test/receivables.test.ts +416 -0
  218. package/templates/checkout-agent/packages/agent-core/test/secrets.test.ts +28 -0
  219. package/templates/checkout-agent/packages/agent-core/test/state-machine.test.ts +67 -0
  220. package/templates/checkout-agent/packages/agent-core/test/transitions.type-test.ts +59 -0
  221. package/templates/checkout-agent/packages/agent-core/tsconfig.json +10 -0
  222. package/templates/checkout-agent/packages/agent-runtime/README.md +80 -0
  223. package/templates/checkout-agent/packages/agent-runtime/bin.mjs +32 -0
  224. package/templates/checkout-agent/packages/agent-runtime/package.json +23 -0
  225. package/templates/checkout-agent/packages/agent-runtime/src/adversarial.ts +199 -0
  226. package/templates/checkout-agent/packages/agent-runtime/src/agent.ts +68 -0
  227. package/templates/checkout-agent/packages/agent-runtime/src/channels/contact.ts +13 -0
  228. package/templates/checkout-agent/packages/agent-runtime/src/channels/index.ts +72 -0
  229. package/templates/checkout-agent/packages/agent-runtime/src/channels/rules.ts +120 -0
  230. package/templates/checkout-agent/packages/agent-runtime/src/channels/types.ts +173 -0
  231. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +459 -0
  232. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +216 -0
  233. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/evidence.ts +109 -0
  234. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +350 -0
  235. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +180 -0
  236. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/present.ts +47 -0
  237. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +120 -0
  238. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +127 -0
  239. package/templates/checkout-agent/packages/agent-runtime/src/cli.ts +98 -0
  240. package/templates/checkout-agent/packages/agent-runtime/src/commands/check.ts +15 -0
  241. package/templates/checkout-agent/packages/agent-runtime/src/commands/decide.ts +63 -0
  242. package/templates/checkout-agent/packages/agent-runtime/src/commands/eval.ts +58 -0
  243. package/templates/checkout-agent/packages/agent-runtime/src/commands/inspect.ts +100 -0
  244. package/templates/checkout-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +367 -0
  245. package/templates/checkout-agent/packages/agent-runtime/src/commands/poll.ts +145 -0
  246. package/templates/checkout-agent/packages/agent-runtime/src/commands/reconcile.ts +42 -0
  247. package/templates/checkout-agent/packages/agent-runtime/src/commands/rerun.ts +86 -0
  248. package/templates/checkout-agent/packages/agent-runtime/src/commands/resume.ts +49 -0
  249. package/templates/checkout-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +134 -0
  250. package/templates/checkout-agent/packages/agent-runtime/src/commands/start.ts +248 -0
  251. package/templates/checkout-agent/packages/agent-runtime/src/commands/verify.ts +330 -0
  252. package/templates/checkout-agent/packages/agent-runtime/src/commands/webhook.ts +40 -0
  253. package/templates/checkout-agent/packages/agent-runtime/src/default-kit.ts +83 -0
  254. package/templates/checkout-agent/packages/agent-runtime/src/index.ts +13 -0
  255. package/templates/checkout-agent/packages/agent-runtime/src/inspect.ts +801 -0
  256. package/templates/checkout-agent/packages/agent-runtime/src/kit.ts +229 -0
  257. package/templates/checkout-agent/packages/agent-runtime/src/poll.ts +105 -0
  258. package/templates/checkout-agent/packages/agent-runtime/src/scenarios.ts +274 -0
  259. package/templates/checkout-agent/packages/agent-runtime/src/setup.ts +239 -0
  260. package/templates/checkout-agent/packages/agent-runtime/src/terminal.ts +263 -0
  261. package/templates/checkout-agent/packages/agent-runtime/src/webhook.ts +131 -0
  262. package/templates/checkout-agent/packages/agent-runtime/test/inspect.test.ts +336 -0
  263. package/templates/checkout-agent/packages/agent-runtime/test/verify-cli.test.ts +329 -0
  264. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +277 -0
  265. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-evidence.test.ts +94 -0
  266. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-rules.test.ts +174 -0
  267. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-session.test.ts +167 -0
  268. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  269. package/templates/checkout-agent/packages/agent-runtime/tsconfig.json +9 -0
  270. package/templates/checkout-agent/scripts/whatsapp-emulator.mjs +84 -0
  271. package/templates/checkout-agent/tsconfig.base.json +16 -0
  272. package/templates/checkout-agent/vitest.config.ts +12 -0
  273. package/templates/collections-agent/README.md +6 -1
  274. package/templates/collections-agent/agents/collections-agent/README.md +12 -8
  275. package/templates/collections-agent/agents/collections-agent/channels/whatsapp/README.md +8 -0
  276. package/templates/collections-agent/agents/collections-agent/channels/whatsapp/acordo-1042-retomada.json +10 -0
  277. package/templates/collections-agent/agents/collections-agent/channels/whatsapp/templates.json +27 -1
  278. package/templates/collections-agent/agents/collections-agent/src/kit.ts +2 -0
  279. package/templates/collections-agent/agents/collections-agent/src/modules/bolepix-receivables.ts +12 -2
  280. package/templates/collections-agent/agents/collections-agent/test/ambiguous-charge.test.ts +55 -0
  281. package/templates/collections-agent/agents/collections-agent/test/fixtures/tap-emitir-nova-1042.transcript.jsonl +2 -0
  282. package/templates/collections-agent/agents/collections-agent/test/whatsapp-poll.test.ts +12 -6
  283. package/templates/collections-agent/agents/collections-agent/test/whatsapp-status-replies.test.ts +148 -0
  284. package/templates/collections-agent/agents/collections-agent/test/whatsapp.test.ts +5 -0
  285. package/templates/collections-agent/package.json +1 -0
  286. package/templates/collections-agent/packages/agent-core/README.md +14 -1
  287. package/templates/collections-agent/packages/agent-core/package.json +1 -1
  288. package/templates/collections-agent/packages/agent-core/src/agent.ts +39 -1
  289. package/templates/collections-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
  290. package/templates/collections-agent/packages/agent-core/src/api/client.ts +28 -5
  291. package/templates/collections-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
  292. package/templates/collections-agent/packages/agent-core/src/api/rail.ts +62 -26
  293. package/templates/collections-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
  294. package/templates/collections-agent/packages/agent-core/src/approval.ts +14 -2
  295. package/templates/collections-agent/packages/agent-core/src/bundle.ts +35 -3
  296. package/templates/collections-agent/packages/agent-core/src/channels.ts +49 -3
  297. package/templates/collections-agent/packages/agent-core/src/check.ts +25 -2
  298. package/templates/collections-agent/packages/agent-core/src/engine.ts +235 -46
  299. package/templates/collections-agent/packages/agent-core/src/hash.ts +57 -1
  300. package/templates/collections-agent/packages/agent-core/src/index.ts +2 -0
  301. package/templates/collections-agent/packages/agent-core/src/quote.ts +50 -0
  302. package/templates/collections-agent/packages/agent-core/src/rail.ts +89 -4
  303. package/templates/collections-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  304. package/templates/collections-agent/packages/agent-core/src/receipt-verification.ts +194 -18
  305. package/templates/collections-agent/packages/agent-core/src/revocation.ts +1 -1
  306. package/templates/collections-agent/packages/agent-core/src/state-machine.ts +10 -1
  307. package/templates/collections-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
  308. package/templates/collections-agent/packages/agent-core/src/stubs/rail.ts +100 -32
  309. package/templates/collections-agent/packages/agent-core/src/tools.ts +17 -6
  310. package/templates/collections-agent/packages/agent-core/src/types.ts +54 -1
  311. package/templates/collections-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  312. package/templates/collections-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  313. package/templates/collections-agent/packages/agent-core/test/composition.test.ts +204 -0
  314. package/templates/collections-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  315. package/templates/collections-agent/packages/agent-core/test/helpers.ts +4 -1
  316. package/templates/collections-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
  317. package/templates/collections-agent/packages/agent-core/test/quote.test.ts +198 -0
  318. package/templates/collections-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  319. package/templates/collections-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  320. package/templates/collections-agent/packages/agent-core/test/receivables.test.ts +103 -3
  321. package/templates/collections-agent/packages/agent-runtime/README.md +12 -0
  322. package/templates/collections-agent/packages/agent-runtime/src/adversarial.ts +32 -2
  323. package/templates/collections-agent/packages/agent-runtime/src/channels/types.ts +53 -4
  324. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
  325. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
  326. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
  327. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
  328. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
  329. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
  330. package/templates/collections-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
  331. package/templates/collections-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
  332. package/templates/collections-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
  333. package/templates/collections-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
  334. package/templates/collections-agent/packages/agent-runtime/src/commands/start.ts +6 -3
  335. package/templates/collections-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
  336. package/templates/collections-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
  337. package/templates/collections-agent/packages/agent-runtime/src/index.ts +1 -0
  338. package/templates/collections-agent/packages/agent-runtime/src/inspect.ts +5 -1
  339. package/templates/collections-agent/packages/agent-runtime/src/kit.ts +25 -2
  340. package/templates/collections-agent/packages/agent-runtime/src/scenarios.ts +8 -2
  341. package/templates/collections-agent/packages/agent-runtime/src/setup.ts +13 -3
  342. package/templates/collections-agent/packages/agent-runtime/src/terminal.ts +127 -18
  343. package/templates/collections-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
  344. package/templates/collections-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
  345. package/templates/collections-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
  346. package/templates/collections-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  347. package/templates/collections-agent/scripts/whatsapp-emulator.mjs +84 -0
  348. package/templates/hello-agent/README.md +1 -1
  349. package/templates/hello-agent/packages/agent-core/README.md +14 -1
  350. package/templates/hello-agent/packages/agent-core/package.json +1 -1
  351. package/templates/hello-agent/packages/agent-core/src/agent.ts +39 -1
  352. package/templates/hello-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
  353. package/templates/hello-agent/packages/agent-core/src/api/client.ts +28 -5
  354. package/templates/hello-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
  355. package/templates/hello-agent/packages/agent-core/src/api/rail.ts +62 -26
  356. package/templates/hello-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
  357. package/templates/hello-agent/packages/agent-core/src/approval.ts +14 -2
  358. package/templates/hello-agent/packages/agent-core/src/bundle.ts +35 -3
  359. package/templates/hello-agent/packages/agent-core/src/channels.ts +49 -3
  360. package/templates/hello-agent/packages/agent-core/src/check.ts +25 -2
  361. package/templates/hello-agent/packages/agent-core/src/engine.ts +235 -46
  362. package/templates/hello-agent/packages/agent-core/src/hash.ts +57 -1
  363. package/templates/hello-agent/packages/agent-core/src/index.ts +2 -0
  364. package/templates/hello-agent/packages/agent-core/src/quote.ts +50 -0
  365. package/templates/hello-agent/packages/agent-core/src/rail.ts +89 -4
  366. package/templates/hello-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  367. package/templates/hello-agent/packages/agent-core/src/receipt-verification.ts +194 -18
  368. package/templates/hello-agent/packages/agent-core/src/revocation.ts +1 -1
  369. package/templates/hello-agent/packages/agent-core/src/state-machine.ts +10 -1
  370. package/templates/hello-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
  371. package/templates/hello-agent/packages/agent-core/src/stubs/rail.ts +100 -32
  372. package/templates/hello-agent/packages/agent-core/src/tools.ts +17 -6
  373. package/templates/hello-agent/packages/agent-core/src/types.ts +54 -1
  374. package/templates/hello-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  375. package/templates/hello-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  376. package/templates/hello-agent/packages/agent-core/test/composition.test.ts +204 -0
  377. package/templates/hello-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  378. package/templates/hello-agent/packages/agent-core/test/helpers.ts +4 -1
  379. package/templates/hello-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
  380. package/templates/hello-agent/packages/agent-core/test/quote.test.ts +198 -0
  381. package/templates/hello-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  382. package/templates/hello-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  383. package/templates/hello-agent/packages/agent-core/test/receivables.test.ts +103 -3
  384. package/templates/hello-agent/packages/agent-runtime/README.md +12 -0
  385. package/templates/hello-agent/packages/agent-runtime/src/adversarial.ts +32 -2
  386. package/templates/hello-agent/packages/agent-runtime/src/channels/types.ts +53 -4
  387. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
  388. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
  389. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
  390. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
  391. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
  392. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
  393. package/templates/hello-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
  394. package/templates/hello-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
  395. package/templates/hello-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
  396. package/templates/hello-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
  397. package/templates/hello-agent/packages/agent-runtime/src/commands/start.ts +6 -3
  398. package/templates/hello-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
  399. package/templates/hello-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
  400. package/templates/hello-agent/packages/agent-runtime/src/index.ts +1 -0
  401. package/templates/hello-agent/packages/agent-runtime/src/inspect.ts +5 -1
  402. package/templates/hello-agent/packages/agent-runtime/src/kit.ts +25 -2
  403. package/templates/hello-agent/packages/agent-runtime/src/scenarios.ts +8 -2
  404. package/templates/hello-agent/packages/agent-runtime/src/setup.ts +13 -3
  405. package/templates/hello-agent/packages/agent-runtime/src/terminal.ts +127 -18
  406. package/templates/hello-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
  407. package/templates/hello-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
  408. package/templates/hello-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
  409. package/templates/hello-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  410. package/templates/kits.lock.json +29 -6
  411. package/templates/supplier-payments-agent/README.md +1 -1
  412. package/templates/supplier-payments-agent/agents/supplier-payments-agent/AGENTS.md +1 -1
  413. package/templates/supplier-payments-agent/agents/supplier-payments-agent/CLAUDE.md +1 -1
  414. package/templates/supplier-payments-agent/agents/supplier-payments-agent/README.md +11 -9
  415. package/templates/supplier-payments-agent/agents/supplier-payments-agent/src/kit.ts +1 -1
  416. package/templates/supplier-payments-agent/agents/supplier-payments-agent/src/modules/batch-payout.ts +67 -20
  417. package/templates/supplier-payments-agent/agents/supplier-payments-agent/test/batch.test.ts +504 -5
  418. package/templates/supplier-payments-agent/packages/agent-core/README.md +14 -1
  419. package/templates/supplier-payments-agent/packages/agent-core/package.json +1 -1
  420. package/templates/supplier-payments-agent/packages/agent-core/src/agent.ts +39 -1
  421. package/templates/supplier-payments-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
  422. package/templates/supplier-payments-agent/packages/agent-core/src/api/client.ts +28 -5
  423. package/templates/supplier-payments-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
  424. package/templates/supplier-payments-agent/packages/agent-core/src/api/rail.ts +62 -26
  425. package/templates/supplier-payments-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
  426. package/templates/supplier-payments-agent/packages/agent-core/src/approval.ts +14 -2
  427. package/templates/supplier-payments-agent/packages/agent-core/src/bundle.ts +35 -3
  428. package/templates/supplier-payments-agent/packages/agent-core/src/channels.ts +49 -3
  429. package/templates/supplier-payments-agent/packages/agent-core/src/check.ts +25 -2
  430. package/templates/supplier-payments-agent/packages/agent-core/src/engine.ts +235 -46
  431. package/templates/supplier-payments-agent/packages/agent-core/src/hash.ts +57 -1
  432. package/templates/supplier-payments-agent/packages/agent-core/src/index.ts +2 -0
  433. package/templates/supplier-payments-agent/packages/agent-core/src/quote.ts +50 -0
  434. package/templates/supplier-payments-agent/packages/agent-core/src/rail.ts +89 -4
  435. package/templates/supplier-payments-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  436. package/templates/supplier-payments-agent/packages/agent-core/src/receipt-verification.ts +194 -18
  437. package/templates/supplier-payments-agent/packages/agent-core/src/revocation.ts +1 -1
  438. package/templates/supplier-payments-agent/packages/agent-core/src/state-machine.ts +10 -1
  439. package/templates/supplier-payments-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
  440. package/templates/supplier-payments-agent/packages/agent-core/src/stubs/rail.ts +100 -32
  441. package/templates/supplier-payments-agent/packages/agent-core/src/tools.ts +17 -6
  442. package/templates/supplier-payments-agent/packages/agent-core/src/types.ts +54 -1
  443. package/templates/supplier-payments-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  444. package/templates/supplier-payments-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  445. package/templates/supplier-payments-agent/packages/agent-core/test/composition.test.ts +204 -0
  446. package/templates/supplier-payments-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  447. package/templates/supplier-payments-agent/packages/agent-core/test/helpers.ts +4 -1
  448. package/templates/supplier-payments-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
  449. package/templates/supplier-payments-agent/packages/agent-core/test/quote.test.ts +198 -0
  450. package/templates/supplier-payments-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  451. package/templates/supplier-payments-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  452. package/templates/supplier-payments-agent/packages/agent-core/test/receivables.test.ts +103 -3
  453. package/templates/supplier-payments-agent/packages/agent-runtime/README.md +12 -0
  454. package/templates/supplier-payments-agent/packages/agent-runtime/src/adversarial.ts +32 -2
  455. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/types.ts +53 -4
  456. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
  457. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
  458. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
  459. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
  460. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
  461. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
  462. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
  463. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
  464. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
  465. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
  466. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/start.ts +6 -3
  467. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
  468. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
  469. package/templates/supplier-payments-agent/packages/agent-runtime/src/index.ts +1 -0
  470. package/templates/supplier-payments-agent/packages/agent-runtime/src/inspect.ts +5 -1
  471. package/templates/supplier-payments-agent/packages/agent-runtime/src/kit.ts +25 -2
  472. package/templates/supplier-payments-agent/packages/agent-runtime/src/scenarios.ts +8 -2
  473. package/templates/supplier-payments-agent/packages/agent-runtime/src/setup.ts +13 -3
  474. package/templates/supplier-payments-agent/packages/agent-runtime/src/terminal.ts +127 -18
  475. package/templates/supplier-payments-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
  476. package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
  477. package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
  478. package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  479. package/templates/bills-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
  480. package/templates/collections-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
  481. package/templates/hello-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
  482. package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
@@ -0,0 +1,374 @@
1
+ /**
2
+ * Module `nfse-invoice`: the service invoice (NFS-e) of a PAID order, as an
3
+ * execution of its own (checkout §4, decision 4).
4
+ *
5
+ * The invoice is not a state of the sale. The sale ends in `settled`; when it
6
+ * does, this module opens a second execution with its own outbox row and
7
+ * calls `codespar_invoice` BY CODE — the model never reaches it, because a
8
+ * fiscal document is irreversible and must not be one sentence away from the
9
+ * customer. The two executions refer to each other by the cart and the
10
+ * charge, and nothing either one does moves the other: a paid order stays
11
+ * paid whatever the invoice does, and the customer is never told about the
12
+ * invoice.
13
+ *
14
+ * The retry rule is the core's outbox rule, applied to a document instead of
15
+ * money. The outbox row flips to `sent` BEFORE the call. An answer that
16
+ * proves nothing reached the issuer (`unsent`) is retried, a bounded number
17
+ * of times. An issuer refusal is terminal for the attempt and goes to the
18
+ * attendant with the issuer's code. ANYTHING else — a timeout, a 5xx, a
19
+ * provider error the wire does not qualify, a crash after `sent` — may have
20
+ * authorized a note, and ends `failed (invoice_uncertain)` for a person to
21
+ * reconcile. It is never sent again: the issuance carries no idempotency key
22
+ * and the meta-tool cannot read an NFS-e back (checkout §9.14, ent#1675), so
23
+ * a blind retry can mint a second irreversible document for the same sale.
24
+ */
25
+ import { randomBytes } from "node:crypto";
26
+ import { describeApiError, type Execution, type ExecutionEngine, type StateStore } from "@codespar/agent-core";
27
+ import type { ApiClient } from "@codespar/sdk";
28
+ import { catalogItem, customerByDocument, formatBRL, MERCHANT, type Customer } from "../catalog.js";
29
+ import type { Cart } from "./storefront-cart.js";
30
+
31
+ /**
32
+ * `accepted` is the issuer taking the request and naming a document: the
33
+ * municipal AUTHORIZATION that follows is not readable through the
34
+ * meta-tool (checkout §9.14), so this module never claims it.
35
+ */
36
+ export type InvoiceState = "pending" | "issuing" | "accepted" | "failed";
37
+ export type InvoiceReason = "invoice_refused" | "invoice_unsent" | "invoice_uncertain";
38
+
39
+ export interface InvoiceRecord {
40
+ id: string;
41
+ /** The sale this invoice follows, and what ties the two: the cart and the charge. */
42
+ sale_execution_id: string;
43
+ cart_ref: string;
44
+ charge_id: string | null;
45
+ /** The outbox row's key. One per invoice; a retry of an `unsent` attempt reuses it. */
46
+ idempotency_key: string;
47
+ state: InvoiceState;
48
+ reason?: InvoiceReason;
49
+ /** The issuer's code (a refusal) or the transport's (anything else). */
50
+ code?: string;
51
+ detail?: string;
52
+ attempts: number;
53
+ document?: { id: string; status: string; pdf_url: string | null };
54
+ history: Array<{ from: InvoiceState | null; to: InvoiceState; at: string; reason?: InvoiceReason; code?: string }>;
55
+ created_at: string;
56
+ updated_at: string;
57
+ }
58
+
59
+ /** What `codespar_invoice action=issue type=nfse` is asked. Built from the paid order, never from the conversation. */
60
+ export interface NfseRequest {
61
+ action: "issue";
62
+ type: "nfse";
63
+ recipient: {
64
+ name: string;
65
+ federalTaxNumber: string;
66
+ email: string;
67
+ address?: { country: "BRA"; postalCode: string; street: string; number: string; district: string; city: { code: string; name: string }; state: string };
68
+ };
69
+ items: Array<{ description: string; quantity: number; unit_price: number; service_code: string }>;
70
+ metadata: { description: string; servicesAmount: number; additionalInformation: string };
71
+ }
72
+
73
+ export type InvoiceOutcome =
74
+ | { status: "accepted"; document_id: string; document_status: string; pdf_url: string | null }
75
+ /** Proven not to have reached the issuer: the request was refused before any provider call. Retried. */
76
+ | { status: "unsent"; code: string; message: string }
77
+ /** The issuer answered and refused. Nothing was issued; the same data would be refused again. */
78
+ | { status: "refused"; code: string; message: string }
79
+ /** Nobody can say whether a note was authorized. Never retried. */
80
+ | { status: "uncertain"; code: string; message: string };
81
+
82
+ export interface InvoiceRail {
83
+ readonly name: "stub-nfse" | "codespar-nfse";
84
+ issue(request: NfseRequest, key: string): Promise<InvoiceOutcome>;
85
+ }
86
+
87
+ export const MAX_INVOICE_ATTEMPTS = 3;
88
+
89
+ /**
90
+ * The strategy codes raised BEFORE the provider is called on the invoice
91
+ * path (`meta-tools/real-strategy.ts`, `strategy-error.ts` at enterprise
92
+ * main): routing found no provider, the transform or tool is unknown, the
93
+ * credential did not resolve, the arguments did not coerce, the organization
94
+ * is paused. `provider_error` is NOT here: it is raised both for a 4xx the
95
+ * issuer answered and for a timeout or a 5xx, and the wire does not say which
96
+ * (OPEN_QUESTIONS §58).
97
+ */
98
+ const PRE_DISPATCH_CODES = new Set(["no_eligible_providers", "transform_unknown", "tool_unknown", "credential_unavailable", "invalid_args", "org_paused"]);
99
+
100
+ /**
101
+ * `codespar_invoice` through the REST twin of a session: `POST /v1/sessions`,
102
+ * then `POST /v1/sessions/{id}/execute` with the meta-tool's own name and
103
+ * input. The kit runs no MCP session of its own; this is the same call an
104
+ * agent's `codespar_invoice` makes, made by code.
105
+ */
106
+ export class CodeSparInvoiceRail implements InvoiceRail {
107
+ readonly name = "codespar-nfse" as const;
108
+ private sessionId: string | undefined;
109
+
110
+ constructor(private readonly api: ApiClient) {}
111
+
112
+ async issue(request: NfseRequest): Promise<InvoiceOutcome> {
113
+ if (!this.sessionId) {
114
+ try {
115
+ const session = await this.api.post("/v1/sessions", { body: { servers: ["nfe-io"] } });
116
+ this.sessionId = session.id;
117
+ } catch (err) {
118
+ // Opening a session sends no document, whatever the answer was.
119
+ const f = describeApiError(err);
120
+ return { status: "unsent", code: f.code, message: `the session could not be opened: ${f.message}` };
121
+ }
122
+ }
123
+ let answer: { success: boolean; data?: unknown; error: string | null; server?: string };
124
+ try {
125
+ answer = await this.api.post("/v1/sessions/{id}/execute", { path: { id: this.sessionId! }, body: { tool: "codespar_invoice", input: request as unknown as Record<string, unknown> } });
126
+ } catch (err) {
127
+ const f = describeApiError(err);
128
+ // The route answered and refused the request (schema, auth, policy, rate): no tool ran. Anything else may have dispatched.
129
+ if (f.status >= 400 && f.status < 500) return { status: "unsent", code: f.code, message: f.message };
130
+ return { status: "uncertain", code: f.code, message: `${f.message}; the issuance may have reached the issuer` };
131
+ }
132
+ return classifyExecuteAnswer(answer);
133
+ }
134
+ }
135
+
136
+ /** The execute envelope, read by its structure and its codes, never by its prose. Exported for the tests. */
137
+ export function classifyExecuteAnswer(answer: { success: boolean; data?: unknown; error: string | null; server?: string }): InvoiceOutcome {
138
+ const data = (answer.data ?? null) as Record<string, unknown> | null;
139
+ if (answer.success) {
140
+ const id = typeof data?.["id"] === "string" && data["id"] ? data["id"] : undefined;
141
+ if (!id) return { status: "uncertain", code: "invoice_unreadable", message: "the issuer answered success without a document id; a note may exist and nothing here can name it" };
142
+ return { status: "accepted", document_id: id, document_status: typeof data?.["status"] === "string" ? data["status"] : "UNKNOWN", pdf_url: typeof data?.["danfe_url"] === "string" ? data["danfe_url"] : null };
143
+ }
144
+ const code = typeof data?.["code"] === "string" ? data["code"] : "unknown";
145
+ const message = answer.error ?? (typeof data?.["error"] === "string" ? data["error"] : "no message");
146
+ // Forward-compatible: the strategy knows the provenance (`dispatch`) and today does not put it on the wire. The day it does, it decides.
147
+ if (data?.["dispatch"] === "unsent") return { status: "unsent", code, message };
148
+ if (data?.["dispatch"] === "rejected") return { status: "refused", code, message };
149
+ // A policy or scope refusal is ours, not the issuer's: nothing was dispatched.
150
+ if (answer.server === "agentgate" || answer.server === "") return { status: "unsent", code, message };
151
+ if (PRE_DISPATCH_CODES.has(code)) return { status: "unsent", code, message };
152
+ return { status: "uncertain", code, message };
153
+ }
154
+
155
+ export type StubIssuerBehaviour = "issues" | "refuses" | "times_out" | "unsent_once" | "unsent";
156
+
157
+ /**
158
+ * STUB issuer. Offline, deterministic, persisted in state.db so a killed
159
+ * process finds what it issued. It applies one rule a real issuer applies —
160
+ * a borrower without an address is refused — and one script of its own:
161
+ * Beatriz's invoice times out AFTER the request left, which is the case
162
+ * nobody can settle from the answer. `behaviour` overrides both (a test).
163
+ */
164
+ export class StubInvoiceRail implements InvoiceRail {
165
+ readonly name = "stub-nfse" as const;
166
+ private unsentPending = true;
167
+
168
+ constructor(
169
+ private readonly store: StateStore,
170
+ private readonly behaviour?: StubIssuerBehaviour,
171
+ ) {}
172
+
173
+ async issue(request: NfseRequest, key: string): Promise<InvoiceOutcome> {
174
+ const behaviour = this.behaviour ?? (request.recipient.federalTaxNumber === "43091752879" ? "times_out" : undefined);
175
+ if (behaviour === "unsent") return { status: "unsent", code: "no_eligible_providers", message: "stub: this organization has no issuer connected; nothing was sent" };
176
+ if (behaviour === "unsent_once" && this.unsentPending) {
177
+ this.unsentPending = false;
178
+ return { status: "unsent", code: "no_eligible_providers", message: "stub: routing found no issuer this time; nothing was sent" };
179
+ }
180
+ if (behaviour === "refuses" || (behaviour === undefined && !request.recipient.address)) {
181
+ return { status: "refused", code: "E0101", message: "stub issuer: tomador sem endereco; a NFS-e exige o endereco do tomador" };
182
+ }
183
+ // What the issuer did is recorded before the answer is lost, as a real one would have.
184
+ const documentId = `nfse_stub_${key.slice(-16)}`;
185
+ this.store.setCursor(`stub-nfse:${key}`, JSON.stringify({ document_id: documentId, request }));
186
+ if (behaviour === "times_out") return { status: "uncertain", code: "timeout", message: "stub issuer: no answer within the timeout; the request had left" };
187
+ return { status: "accepted", document_id: documentId, document_status: "WAITINGSEND", pdf_url: null };
188
+ }
189
+
190
+ /** Test hook: what the stub issuer holds for a key (a note it authorized, whether or not anybody heard back). */
191
+ issued(key: string): string | undefined {
192
+ const raw = this.store.getCursor(`stub-nfse:${key}`);
193
+ return raw ? (JSON.parse(raw) as { document_id: string }).document_id : undefined;
194
+ }
195
+ }
196
+
197
+ const KEY = (saleId: string) => `checkout:invoice:${saleId}`;
198
+ const INDEX = "checkout:invoices";
199
+
200
+ /** The invoices, in state.db's key-value table; their outbox rows in the core's outbox table (`kind: nfse.issue`). */
201
+ export class InvoiceBook {
202
+ constructor(private readonly store: StateStore) {}
203
+
204
+ of(saleId: string): InvoiceRecord | undefined {
205
+ const raw = this.store.getCursor(KEY(saleId));
206
+ return raw ? (JSON.parse(raw) as InvoiceRecord) : undefined;
207
+ }
208
+
209
+ all(): InvoiceRecord[] {
210
+ const raw = this.store.getCursor(INDEX);
211
+ const ids = raw ? (JSON.parse(raw) as string[]) : [];
212
+ return ids.map((id) => this.of(id)).filter((r): r is InvoiceRecord => r !== undefined);
213
+ }
214
+
215
+ save(record: InvoiceRecord): void {
216
+ this.store.setCursor(KEY(record.sale_execution_id), JSON.stringify(record));
217
+ const raw = this.store.getCursor(INDEX);
218
+ const ids = raw ? (JSON.parse(raw) as string[]) : [];
219
+ if (!ids.includes(record.sale_execution_id)) this.store.setCursor(INDEX, JSON.stringify([...ids, record.sale_execution_id]));
220
+ }
221
+ }
222
+
223
+ export interface InvoiceDeps {
224
+ store: StateStore;
225
+ engine: ExecutionEngine;
226
+ rail: InvoiceRail;
227
+ cart(ref: string): Cart | undefined;
228
+ /** The attendant's console. The customer is never told about the invoice. */
229
+ say(line: string): void;
230
+ }
231
+
232
+ /** The request, from the paid order: the customer book's fiscal data, the cart's lines, the total that was paid. */
233
+ export function nfseRequest(sale: Execution, cart: Cart, customer: Customer): NfseRequest {
234
+ const chargeId = sale.outcomes[0]?.transaction_id ?? "?";
235
+ return {
236
+ action: "issue",
237
+ type: "nfse",
238
+ recipient: {
239
+ name: customer.name,
240
+ federalTaxNumber: customer.document,
241
+ email: customer.email,
242
+ ...(customer.address
243
+ ? { address: { country: "BRA" as const, postalCode: customer.address.postal_code, street: customer.address.street, number: customer.address.number, district: customer.address.district, city: { code: customer.address.city_code, name: customer.address.city }, state: customer.address.state } }
244
+ : {}),
245
+ },
246
+ items: cart.line_items.map((l) => ({ description: l.name, quantity: l.quantity, unit_price: l.unit_amount / 100, service_code: catalogItem(l.sku)?.service_code ?? "" })),
247
+ metadata: {
248
+ description: `${MERCHANT.name} - pedido ${cart.cart_id}: ${cart.line_items.map((l) => `${l.quantity}x ${l.name}`).join("; ")}`,
249
+ // What was PAID, discounts included; the transform would otherwise sum list prices.
250
+ servicesAmount: sale.total / 100,
251
+ additionalInformation: `cobranca ${chargeId}, ${formatBRL(sale.total)}`,
252
+ },
253
+ };
254
+ }
255
+
256
+ function transition(record: InvoiceRecord, to: InvoiceState, deps: InvoiceDeps, extra: { reason?: InvoiceReason; code?: string; detail?: string } = {}): InvoiceRecord {
257
+ const at = deps.engine.clock().toISOString();
258
+ const { reason: _r, code: _c, detail: _d, ...rest } = record;
259
+ const next: InvoiceRecord = { ...rest, state: to, ...extra, updated_at: at, history: [...record.history, { from: record.state, to, at, ...(extra.reason ? { reason: extra.reason } : {}), ...(extra.code ? { code: extra.code } : {}) }] };
260
+ new InvoiceBook(deps.store).save(next);
261
+ deps.engine.note("invoice.transition", record.sale_execution_id, { invoice_id: record.id, from: record.state, to, ...extra });
262
+ return next;
263
+ }
264
+
265
+ /** Opens the invoice of a settled order, once. `undefined` for anything that is not a paid order. */
266
+ export function openInvoice(sale: Execution, deps: InvoiceDeps): InvoiceRecord | undefined {
267
+ if (sale.state !== "settled" || !sale.composition) return undefined;
268
+ const book = new InvoiceBook(deps.store);
269
+ const existing = book.of(sale.id);
270
+ if (existing) return existing;
271
+ const at = deps.engine.clock().toISOString();
272
+ const record: InvoiceRecord = {
273
+ id: `inv_${randomBytes(8).toString("hex")}`,
274
+ sale_execution_id: sale.id,
275
+ cart_ref: sale.composition.ref,
276
+ charge_id: sale.outcomes[0]?.transaction_id ?? null,
277
+ idempotency_key: `nfse_${sale.idempotency_key.slice(4)}`,
278
+ state: "pending",
279
+ attempts: 0,
280
+ history: [{ from: null, to: "pending", at }],
281
+ created_at: at,
282
+ updated_at: at,
283
+ };
284
+ book.save(record);
285
+ deps.store.putOutbox({ idempotency_key: record.idempotency_key, execution_id: record.id, kind: "nfse.issue", payload: { sale_execution_id: sale.id, cart_ref: record.cart_ref, charge_id: record.charge_id }, status: "pending", response: undefined, created_at: at });
286
+ deps.engine.note("invoice.opened", sale.id, { invoice_id: record.id, cart_ref: record.cart_ref, charge_id: record.charge_id, idempotency_key: record.idempotency_key });
287
+ return record;
288
+ }
289
+
290
+ /**
291
+ * Sends a `pending` invoice. The outbox flips to `sent` before each call, so
292
+ * a crash after that point is reconciled as uncertain and never re-sent.
293
+ */
294
+ export async function dispatchInvoice(record: InvoiceRecord, deps: InvoiceDeps): Promise<InvoiceRecord> {
295
+ let current = record;
296
+ const sale = deps.engine.get(record.sale_execution_id);
297
+ const cart = deps.cart(record.cart_ref);
298
+ const customer = sale ? customerByDocument(sale.items[0]!.payee) : undefined;
299
+ if (!sale || !cart || !customer) {
300
+ // Nothing was sent: the order's own records do not resolve. A defect of this state, not the issuer's answer.
301
+ deps.store.updateOutbox(record.idempotency_key, "failed", { code: "invoice_data_missing" }, deps.engine.clock().toISOString());
302
+ return notify(transition(current, "failed", deps, { reason: "invoice_unsent", code: "invoice_data_missing", detail: "the paid order, its cart or its customer could not be read; nothing was sent" }), deps);
303
+ }
304
+ const request = nfseRequest(sale, cart, customer);
305
+ while (current.state === "pending") {
306
+ const attempt = current.attempts + 1;
307
+ current = transition({ ...current, attempts: attempt }, "issuing", deps);
308
+ deps.store.updateOutbox(record.idempotency_key, "sent", undefined, deps.engine.clock().toISOString());
309
+ deps.engine.note("invoice.dispatch", sale.id, { invoice_id: record.id, attempt, rail: deps.rail.name, idempotency_key: record.idempotency_key, services_amount: request.metadata.servicesAmount });
310
+ const outcome = await deps.rail.issue(request, record.idempotency_key);
311
+ deps.engine.note("invoice.outcome", sale.id, { invoice_id: record.id, attempt, status: outcome.status, ...(outcome.status === "accepted" ? { document_id: outcome.document_id, document_status: outcome.document_status } : { code: outcome.code, message: outcome.message }) });
312
+ const at = deps.engine.clock().toISOString();
313
+ switch (outcome.status) {
314
+ case "accepted":
315
+ deps.store.updateOutbox(record.idempotency_key, "done", outcome, at);
316
+ current = transition({ ...current, document: { id: outcome.document_id, status: outcome.document_status, pdf_url: outcome.pdf_url } }, "accepted", deps);
317
+ break;
318
+ case "unsent":
319
+ if (attempt >= MAX_INVOICE_ATTEMPTS) {
320
+ deps.store.updateOutbox(record.idempotency_key, "failed", outcome, at);
321
+ current = notify(transition(current, "failed", deps, { reason: "invoice_unsent", code: outcome.code, detail: `${attempt} attempt(s), none reached the issuer: ${outcome.message}` }), deps);
322
+ } else {
323
+ // Proven not sent: the row goes back to pending, and the next attempt is the same request under the same key.
324
+ deps.store.updateOutbox(record.idempotency_key, "pending", outcome, at);
325
+ current = transition(current, "pending", deps, { code: outcome.code, detail: outcome.message });
326
+ }
327
+ break;
328
+ case "refused":
329
+ deps.store.updateOutbox(record.idempotency_key, "failed", outcome, at);
330
+ current = notify(transition(current, "failed", deps, { reason: "invoice_refused", code: outcome.code, detail: outcome.message }), deps);
331
+ break;
332
+ case "uncertain":
333
+ deps.store.updateOutbox(record.idempotency_key, "failed", outcome, at);
334
+ current = notify(transition(current, "failed", deps, { reason: "invoice_uncertain", code: outcome.code, detail: `${outcome.message}. Not re-issued: the issuance has no idempotency key and the meta-tool cannot read an NFS-e back (ent#1675); reconcile at the issuer before issuing again.` }), deps);
335
+ break;
336
+ }
337
+ }
338
+ return current;
339
+ }
340
+
341
+ /** The attendant hears about a failed invoice; the customer never does. */
342
+ function notify(record: InvoiceRecord, deps: InvoiceDeps): InvoiceRecord {
343
+ const text =
344
+ record.reason === "invoice_uncertain"
345
+ ? `NFS-e do pedido ${record.sale_execution_id} (cobranca ${record.charge_id}) com resultado INCERTO (${record.code}): pode ter sido emitida. Nao reemita; confira no emissor. A venda continua paga.`
346
+ : `NFS-e do pedido ${record.sale_execution_id} (cobranca ${record.charge_id}) falhou (${record.reason}, ${record.code}): ${record.detail}. A venda continua paga.`;
347
+ deps.say(` [atendente] ${text}`);
348
+ deps.engine.note("message.attendant", record.sale_execution_id, { invoice_id: record.id, state: record.state, reason: record.reason ?? null, code: record.code ?? null, text });
349
+ return record;
350
+ }
351
+
352
+ /** After a sale's outcome: open its invoice if it was paid, and send it if it is pending. */
353
+ export async function followSale(sale: Execution, deps: InvoiceDeps): Promise<InvoiceRecord | undefined> {
354
+ const record = openInvoice(sale, deps);
355
+ if (!record || record.state !== "pending") return record;
356
+ return dispatchInvoice(record, deps);
357
+ }
358
+
359
+ /**
360
+ * `resume`: a `pending` invoice provably never left and is sent; one left
361
+ * `issuing` was sent and never answered, which is exactly the uncertain case,
362
+ * so it closes as `invoice_uncertain` and is NOT sent again.
363
+ */
364
+ export async function resumeInvoices(deps: InvoiceDeps): Promise<InvoiceRecord[]> {
365
+ const out: InvoiceRecord[] = [];
366
+ for (const record of new InvoiceBook(deps.store).all()) {
367
+ if (record.state === "pending") out.push(await dispatchInvoice(record, deps));
368
+ else if (record.state === "issuing") {
369
+ deps.store.updateOutbox(record.idempotency_key, "failed", { code: "interrupted" }, deps.engine.clock().toISOString());
370
+ out.push(notify(transition(record, "failed", deps, { reason: "invoice_uncertain", code: "interrupted", detail: "the process stopped after the request was sent and before an answer was recorded" }), deps));
371
+ }
372
+ }
373
+ return out;
374
+ }
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Module `storefront-cart`: the cart, kept by the agent in `state.db` (there
3
+ * is no sales-side cart in the API, checkout §9.1), and the three local tools
4
+ * the model reaches it by. The rules are checkout §2's, applied here by code:
5
+ *
6
+ * - `cart_update` REPLACES the cart with the lines it is given. Nothing is
7
+ * merged; an empty list empties it (§2.1).
8
+ * - Every answer carries `validation_issues: [{ code, field, message }]` from
9
+ * a closed vocabulary (§2.2).
10
+ * - The totals are computed here from the catalog; no input carries a price
11
+ * (§2.3).
12
+ *
13
+ * The shapes follow the ACP `CheckoutSession` the enterprise cart already
14
+ * uses (`line_items`, `totals`, `status`), so a server-side cart one day is a
15
+ * change of storage and not of contract.
16
+ *
17
+ * A cart id the model sees (`cart-1`) counts within the run; the ref the
18
+ * execution carries (`<run_id>/cart-1`) is unique across runs, so an order
19
+ * approved from another process (`npm run approve`) finds its cart.
20
+ */
21
+ import type { ExecutionEngine, StateStore, ToolHandler } from "@codespar/agent-core";
22
+ import { CATALOG, MERCHANT, formatBRL } from "../catalog.js";
23
+ import { localDate, priceCart, type Envelope, type PricedCart, type ValidationIssue } from "../pricing.js";
24
+
25
+ export interface Cart extends PricedCart {
26
+ cart_id: string;
27
+ ref: string;
28
+ run_id: string;
29
+ version: number;
30
+ currency: "BRL";
31
+ order_discount_pct: number;
32
+ updated_at: string;
33
+ }
34
+
35
+ /** ACP `CheckoutStatus`, derived from the order: the cart does not decide it, the execution does. */
36
+ export type CartStatus = "in_progress" | "ready_for_payment" | "completed";
37
+
38
+ const KEY = (ref: string) => `checkout:cart:${ref}`;
39
+ const RUN_KEY = (runId: string) => `checkout:carts:${runId}`;
40
+ const ORDER_KEY = (ref: string) => `order:${ref}`;
41
+
42
+ /** The carts, in state.db's key-value table beside the execution cursors. */
43
+ export class CartBook {
44
+ constructor(private readonly store: StateStore) {}
45
+
46
+ get(ref: string): Cart | undefined {
47
+ const raw = this.store.getCursor(KEY(ref));
48
+ return raw ? (JSON.parse(raw) as Cart) : undefined;
49
+ }
50
+
51
+ save(cart: Cart): void {
52
+ this.store.setCursor(KEY(cart.ref), JSON.stringify(cart));
53
+ const ids = this.idsOf(cart.run_id);
54
+ if (!ids.includes(cart.cart_id)) this.store.setCursor(RUN_KEY(cart.run_id), JSON.stringify([...ids, cart.cart_id]));
55
+ }
56
+
57
+ idsOf(runId: string): string[] {
58
+ const raw = this.store.getCursor(RUN_KEY(runId));
59
+ return raw ? (JSON.parse(raw) as string[]) : [];
60
+ }
61
+
62
+ byId(runId: string, cartId: string): Cart | undefined {
63
+ return this.get(`${runId}/${cartId}`);
64
+ }
65
+
66
+ latest(runId: string): Cart | undefined {
67
+ const ids = this.idsOf(runId);
68
+ const last = ids[ids.length - 1];
69
+ return last ? this.byId(runId, last) : undefined;
70
+ }
71
+
72
+ nextId(runId: string): string {
73
+ return `cart-${this.idsOf(runId).length + 1}`;
74
+ }
75
+ }
76
+
77
+ /** The order a cart belongs to, if one was placed: the execution the claim names. */
78
+ export function orderOf(engine: ExecutionEngine, cart: Pick<Cart, "ref">) {
79
+ const id = engine.claimed(ORDER_KEY(cart.ref));
80
+ return id ? engine.get(id) : undefined;
81
+ }
82
+
83
+ export function claimOrder(engine: ExecutionEngine, cart: Pick<Cart, "ref">, executionId: string): void {
84
+ engine.claim(ORDER_KEY(cart.ref), executionId);
85
+ }
86
+
87
+ export function statusOf(engine: ExecutionEngine, cart: Cart): CartStatus {
88
+ const order = orderOf(engine, cart);
89
+ if (order?.state === "settled") return "completed";
90
+ if (order?.state === "executing") return "ready_for_payment";
91
+ return "in_progress";
92
+ }
93
+
94
+ /** A cart whose charge is out or paid is the record of that sale: it is not edited, a new cart is. */
95
+ export function isLocked(engine: ExecutionEngine, cart: Cart): boolean {
96
+ return statusOf(engine, cart) !== "in_progress";
97
+ }
98
+
99
+ /** What the model reads: the cart, its totals as text, its issues, and the order it belongs to. Never a cost, never the envelope's limits. */
100
+ export function viewOf(engine: ExecutionEngine, cart: Cart) {
101
+ const order = orderOf(engine, cart);
102
+ return {
103
+ cart_id: cart.cart_id,
104
+ version: cart.version,
105
+ status: statusOf(engine, cart),
106
+ currency: cart.currency,
107
+ line_items: cart.line_items.map((l) => ({
108
+ id: l.id,
109
+ sku: l.sku,
110
+ name: l.name,
111
+ quantity: l.quantity,
112
+ unit_price: formatBRL(l.unit_amount),
113
+ ...(l.discount_pct ? { discount_pct: l.discount_pct } : {}),
114
+ total: formatBRL(l.totals.find((t) => t.type === "total")!.amount),
115
+ availability_status: l.availability_status,
116
+ })),
117
+ coupon: cart.coupon,
118
+ totals: cart.totals.map((t) => ({ type: t.type, display_text: t.display_text, amount: formatBRL(t.amount) })),
119
+ total_minor: cart.total,
120
+ total: formatBRL(cart.total),
121
+ validation_issues: cart.validation_issues,
122
+ cart_hash: cart.cart_hash,
123
+ order: order ? { execution_id: order.id, state: order.state, ...(order.reason ? { reason: order.reason } : {}) } : null,
124
+ };
125
+ }
126
+
127
+ interface UpdateInput {
128
+ cart_id?: unknown;
129
+ lines?: unknown;
130
+ coupon?: unknown;
131
+ order_discount_pct?: unknown;
132
+ }
133
+
134
+ export interface CartDeps {
135
+ book: CartBook;
136
+ envelope: Envelope;
137
+ runId: string;
138
+ timezone: string;
139
+ clock: () => Date;
140
+ /** Called after a replacement, with the cart as it is now: an open order follows it (`bolepix-receivables`). */
141
+ onReplaced(cart: Cart, engine: ExecutionEngine): void;
142
+ }
143
+
144
+ /** The cart a call without `cart_id` means: the last one of this conversation that is still being assembled. */
145
+ export function currentCart(engine: ExecutionEngine, deps: Pick<CartDeps, "book" | "runId">): Cart | undefined {
146
+ const cart = deps.book.latest(deps.runId);
147
+ return cart && !isLocked(engine, cart) ? cart : undefined;
148
+ }
149
+
150
+ export function resolveCart(engine: ExecutionEngine, deps: Pick<CartDeps, "book" | "runId">, cartId: unknown): Cart | undefined {
151
+ if (cartId === undefined || cartId === null || cartId === "") return deps.book.latest(deps.runId);
152
+ if (typeof cartId !== "string") throw new Error("cart_id must be a string (cart-1)");
153
+ const cart = deps.book.byId(deps.runId, cartId.trim());
154
+ if (!cart) throw new Error(`no cart ${cartId} in this conversation; carts: ${deps.book.idsOf(deps.runId).join(", ") || "none yet"}`);
155
+ return cart;
156
+ }
157
+
158
+ export function makeCartHandlers(deps: CartDeps): Record<string, ToolHandler> {
159
+ const listCatalog: ToolHandler = async () => ({
160
+ merchant: MERCHANT.name,
161
+ items: CATALOG.map((i) => ({
162
+ sku: i.sku,
163
+ title: i.title,
164
+ category: i.category,
165
+ price: formatBRL(i.price_minor),
166
+ price_minor: i.price_minor,
167
+ available: i.available,
168
+ ...(i.stock !== undefined ? { stock: i.stock } : {}),
169
+ })),
170
+ policy: {
171
+ service_hours: deps.envelope.service_hours,
172
+ payment: "bolepix (cobranca com vencimento): paga por Pix ou por boleto, vence hoje",
173
+ discounts: "so dentro da politica da loja, aplicada pelo codigo; o que estiver fora e recusado. Cupom: so os da tabela da loja, informados pelo cliente.",
174
+ prices: "sempre os do catalogo; nenhuma ferramenta aceita preco",
175
+ },
176
+ });
177
+
178
+ const cartView: ToolHandler = async (raw, ctx) => {
179
+ const input = raw as { cart_id?: unknown };
180
+ const cart = resolveCart(ctx.engine, deps, input.cart_id);
181
+ if (!cart) return { cart: null, message: "nenhum carrinho aberto nesta conversa; cart_update abre um" };
182
+ return viewOf(ctx.engine, cart);
183
+ };
184
+
185
+ const cartUpdate: ToolHandler = async (raw, ctx) => {
186
+ const input = raw as UpdateInput;
187
+ let target: Cart | undefined;
188
+ if (input.cart_id !== undefined && input.cart_id !== null && input.cart_id !== "") {
189
+ target = resolveCart(ctx.engine, deps, input.cart_id);
190
+ if (target && isLocked(ctx.engine, target)) throw new Error(`cart ${target.cart_id} is the record of an order whose charge is ${statusOf(ctx.engine, target) === "completed" ? "paid" : "out"}; it is not edited. Omit cart_id to open a new cart.`);
191
+ } else target = currentCart(ctx.engine, deps);
192
+
193
+ const now = deps.clock();
194
+ const priced = priceCart({ lines: input.lines, coupon: input.coupon, order_discount_pct: input.order_discount_pct }, deps.envelope, localDate(now, deps.timezone));
195
+ const cartId = target?.cart_id ?? deps.book.nextId(deps.runId);
196
+ const cart: Cart = {
197
+ ...priced,
198
+ cart_id: cartId,
199
+ ref: `${deps.runId}/${cartId}`,
200
+ run_id: deps.runId,
201
+ version: (target?.version ?? 0) + 1,
202
+ currency: "BRL",
203
+ order_discount_pct: typeof input.order_discount_pct === "number" ? input.order_discount_pct : 0,
204
+ updated_at: now.toISOString(),
205
+ };
206
+ deps.book.save(cart);
207
+ ctx.engine.note("cart.replaced", orderOf(ctx.engine, cart)?.id ?? null, {
208
+ cart_ref: cart.ref,
209
+ version: cart.version,
210
+ cart_hash: cart.cart_hash,
211
+ lines: cart.composition,
212
+ total_minor: cart.total,
213
+ validation_issues: cart.validation_issues.map((i: ValidationIssue) => ({ code: i.code, field: i.field })),
214
+ });
215
+ deps.onReplaced(cart, ctx.engine);
216
+ return viewOf(ctx.engine, cart);
217
+ };
218
+
219
+ return { list_catalog: listCatalog, cart_view: cartView, cart_update: cartUpdate };
220
+ }