@codespar/cli 0.16.0 → 0.18.0

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 (486) hide show
  1. package/README.md +1 -0
  2. package/dist/commands/mandate-verify.js +98 -19
  3. package/dist/commands/mandate-verify.js.map +1 -1
  4. package/dist/index.js +1 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/mandate-codec.js +31 -20
  7. package/dist/mandate-codec.js.map +1 -1
  8. package/package.json +1 -1
  9. package/templates/bills-agent/README.md +1 -1
  10. package/templates/bills-agent/agents/bills-agent/AGENTS.md +1 -1
  11. package/templates/bills-agent/agents/bills-agent/CLAUDE.md +1 -1
  12. package/templates/bills-agent/agents/bills-agent/README.md +6 -5
  13. package/templates/bills-agent/agents/bills-agent/src/modules/embedded-consent.ts +5 -4
  14. package/templates/bills-agent/packages/agent-core/README.md +14 -1
  15. package/templates/bills-agent/packages/agent-core/package.json +1 -1
  16. package/templates/bills-agent/packages/agent-core/src/agent.ts +39 -1
  17. package/templates/bills-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
  18. package/templates/bills-agent/packages/agent-core/src/api/client.ts +28 -5
  19. package/templates/bills-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
  20. package/templates/bills-agent/packages/agent-core/src/api/rail.ts +62 -26
  21. package/templates/bills-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
  22. package/templates/bills-agent/packages/agent-core/src/approval.ts +14 -2
  23. package/templates/bills-agent/packages/agent-core/src/bundle.ts +35 -3
  24. package/templates/bills-agent/packages/agent-core/src/channels.ts +49 -3
  25. package/templates/bills-agent/packages/agent-core/src/check.ts +25 -2
  26. package/templates/bills-agent/packages/agent-core/src/engine.ts +235 -46
  27. package/templates/bills-agent/packages/agent-core/src/hash.ts +57 -1
  28. package/templates/bills-agent/packages/agent-core/src/index.ts +2 -0
  29. package/templates/bills-agent/packages/agent-core/src/quote.ts +50 -0
  30. package/templates/bills-agent/packages/agent-core/src/rail.ts +89 -4
  31. package/templates/bills-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  32. package/templates/bills-agent/packages/agent-core/src/receipt-verification.ts +194 -18
  33. package/templates/bills-agent/packages/agent-core/src/revocation.ts +1 -1
  34. package/templates/bills-agent/packages/agent-core/src/state-machine.ts +10 -1
  35. package/templates/bills-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
  36. package/templates/bills-agent/packages/agent-core/src/stubs/rail.ts +100 -32
  37. package/templates/bills-agent/packages/agent-core/src/tools.ts +17 -6
  38. package/templates/bills-agent/packages/agent-core/src/types.ts +54 -1
  39. package/templates/bills-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  40. package/templates/bills-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  41. package/templates/bills-agent/packages/agent-core/test/composition.test.ts +204 -0
  42. package/templates/bills-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  43. package/templates/bills-agent/packages/agent-core/test/helpers.ts +4 -1
  44. package/templates/bills-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
  45. package/templates/bills-agent/packages/agent-core/test/quote.test.ts +198 -0
  46. package/templates/bills-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  47. package/templates/bills-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  48. package/templates/bills-agent/packages/agent-core/test/receivables.test.ts +103 -3
  49. package/templates/bills-agent/packages/agent-runtime/README.md +12 -0
  50. package/templates/bills-agent/packages/agent-runtime/src/adversarial.ts +32 -2
  51. package/templates/bills-agent/packages/agent-runtime/src/channels/types.ts +53 -4
  52. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
  53. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
  54. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
  55. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
  56. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
  57. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
  58. package/templates/bills-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
  59. package/templates/bills-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
  60. package/templates/bills-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
  61. package/templates/bills-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
  62. package/templates/bills-agent/packages/agent-runtime/src/commands/start.ts +6 -3
  63. package/templates/bills-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
  64. package/templates/bills-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
  65. package/templates/bills-agent/packages/agent-runtime/src/index.ts +1 -0
  66. package/templates/bills-agent/packages/agent-runtime/src/inspect.ts +5 -1
  67. package/templates/bills-agent/packages/agent-runtime/src/kit.ts +25 -2
  68. package/templates/bills-agent/packages/agent-runtime/src/scenarios.ts +8 -2
  69. package/templates/bills-agent/packages/agent-runtime/src/setup.ts +13 -3
  70. package/templates/bills-agent/packages/agent-runtime/src/terminal.ts +127 -18
  71. package/templates/bills-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
  72. package/templates/bills-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
  73. package/templates/bills-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
  74. package/templates/bills-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  75. package/templates/checkout-agent/LICENSE +21 -0
  76. package/templates/checkout-agent/README.md +28 -0
  77. package/templates/checkout-agent/_gitignore +10 -0
  78. package/templates/checkout-agent/agents/checkout-agent/.env.example +5 -0
  79. package/templates/checkout-agent/agents/checkout-agent/AGENTS.md +20 -0
  80. package/templates/checkout-agent/agents/checkout-agent/CLAUDE.md +20 -0
  81. package/templates/checkout-agent/agents/checkout-agent/README.md +94 -0
  82. package/templates/checkout-agent/agents/checkout-agent/SYSTEM_PROMPT.md +36 -0
  83. package/templates/checkout-agent/agents/checkout-agent/agent.yaml +30 -0
  84. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/README.md +28 -0
  85. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/pedido-beatriz.json +11 -0
  86. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/pedido-marina-retomada.json +10 -0
  87. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/pedido-marina.json +11 -0
  88. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/templates.json +49 -0
  89. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/beneficiary-swap-human.json +12 -0
  90. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/beneficiary-swap.json +11 -0
  91. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/beneficiary-swap.transcript.jsonl +5 -0
  92. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/coupon-unknown.json +13 -0
  93. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/coupon-unknown.transcript.jsonl +4 -0
  94. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/exfiltration.json +26 -0
  95. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/exfiltration.transcript.jsonl +3 -0
  96. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/false-authority.json +11 -0
  97. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/false-authority.transcript.jsonl +5 -0
  98. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/fractioning.json +12 -0
  99. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/fractioning.transcript.jsonl +5 -0
  100. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/model-total.json +13 -0
  101. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/model-total.transcript.jsonl +5 -0
  102. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/payment-claimed.json +15 -0
  103. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/payment-claimed.transcript.jsonl +7 -0
  104. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/price-injected.json +12 -0
  105. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/price-injected.transcript.jsonl +5 -0
  106. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/prompt-injection.json +13 -0
  107. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/prompt-injection.transcript.jsonl +4 -0
  108. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped-human.json +16 -0
  109. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped-human.transcript.jsonl +9 -0
  110. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped.json +15 -0
  111. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped.transcript.jsonl +8 -0
  112. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/webhook-replay.json +11 -0
  113. package/templates/checkout-agent/agents/checkout-agent/evals/eval.yaml +11 -0
  114. package/templates/checkout-agent/agents/checkout-agent/guardrails.json +25 -0
  115. package/templates/checkout-agent/agents/checkout-agent/mandate.example.json +28 -0
  116. package/templates/checkout-agent/agents/checkout-agent/package.json +33 -0
  117. package/templates/checkout-agent/agents/checkout-agent/runbook.md +19 -0
  118. package/templates/checkout-agent/agents/checkout-agent/scenarios/beneficiary-not-allowed.json +42 -0
  119. package/templates/checkout-agent/agents/checkout-agent/scenarios/beneficiary-not-allowed.transcript.jsonl +4 -0
  120. package/templates/checkout-agent/agents/checkout-agent/scenarios/cap-exceeded.json +32 -0
  121. package/templates/checkout-agent/agents/checkout-agent/scenarios/cap-exceeded.transcript.jsonl +4 -0
  122. package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-recomposed.json +61 -0
  123. package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-recomposed.transcript.jsonl +9 -0
  124. package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-replaced.json +61 -0
  125. package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-replaced.transcript.jsonl +9 -0
  126. package/templates/checkout-agent/agents/checkout-agent/scenarios/charge-expired.json +36 -0
  127. package/templates/checkout-agent/agents/checkout-agent/scenarios/charge-expired.transcript.jsonl +6 -0
  128. package/templates/checkout-agent/agents/checkout-agent/scenarios/coupon-unknown.json +23 -0
  129. package/templates/checkout-agent/agents/checkout-agent/scenarios/coupon-unknown.transcript.jsonl +4 -0
  130. package/templates/checkout-agent/agents/checkout-agent/scenarios/escalated-above-threshold.json +72 -0
  131. package/templates/checkout-agent/agents/checkout-agent/scenarios/escalated-above-threshold.transcript.jsonl +9 -0
  132. package/templates/checkout-agent/agents/checkout-agent/scenarios/happy-path.json +15 -0
  133. package/templates/checkout-agent/agents/checkout-agent/scenarios/happy-path.transcript.jsonl +6 -0
  134. package/templates/checkout-agent/agents/checkout-agent/scenarios/item-unavailable.json +23 -0
  135. package/templates/checkout-agent/agents/checkout-agent/scenarios/item-unavailable.transcript.jsonl +3 -0
  136. package/templates/checkout-agent/agents/checkout-agent/scenarios/mandate-revoked.json +42 -0
  137. package/templates/checkout-agent/agents/checkout-agent/scenarios/mandate-revoked.transcript.jsonl +12 -0
  138. package/templates/checkout-agent/agents/checkout-agent/scenarios/nfse-failed.json +14 -0
  139. package/templates/checkout-agent/agents/checkout-agent/scenarios/nfse-failed.transcript.jsonl +9 -0
  140. package/templates/checkout-agent/agents/checkout-agent/scenarios/payment-claimed.json +34 -0
  141. package/templates/checkout-agent/agents/checkout-agent/scenarios/payment-claimed.transcript.jsonl +7 -0
  142. package/templates/checkout-agent/agents/checkout-agent/scenarios/price-injected.json +33 -0
  143. package/templates/checkout-agent/agents/checkout-agent/scenarios/price-injected.transcript.jsonl +4 -0
  144. package/templates/checkout-agent/agents/checkout-agent/scenarios/prompt-injection.json +32 -0
  145. package/templates/checkout-agent/agents/checkout-agent/scenarios/prompt-injection.transcript.jsonl +4 -0
  146. package/templates/checkout-agent/agents/checkout-agent/src/catalog.ts +92 -0
  147. package/templates/checkout-agent/agents/checkout-agent/src/kit.ts +338 -0
  148. package/templates/checkout-agent/agents/checkout-agent/src/modules/bolepix-receivables.ts +179 -0
  149. package/templates/checkout-agent/agents/checkout-agent/src/modules/nfse-invoice.ts +374 -0
  150. package/templates/checkout-agent/agents/checkout-agent/src/modules/storefront-cart.ts +220 -0
  151. package/templates/checkout-agent/agents/checkout-agent/src/pricing.ts +297 -0
  152. package/templates/checkout-agent/agents/checkout-agent/test/adversarial.test.ts +117 -0
  153. package/templates/checkout-agent/agents/checkout-agent/test/check.test.ts +67 -0
  154. package/templates/checkout-agent/agents/checkout-agent/test/cli.test.ts +169 -0
  155. package/templates/checkout-agent/agents/checkout-agent/test/fixtures/order-marina.transcript.jsonl +5 -0
  156. package/templates/checkout-agent/agents/checkout-agent/test/fixtures/ordered-marina-awaiting-payer.transcript.jsonl +6 -0
  157. package/templates/checkout-agent/agents/checkout-agent/test/fixtures/tap-falar-agora-marina.transcript.jsonl +2 -0
  158. package/templates/checkout-agent/agents/checkout-agent/test/nfse.test.ts +170 -0
  159. package/templates/checkout-agent/agents/checkout-agent/test/pricing.test.ts +138 -0
  160. package/templates/checkout-agent/agents/checkout-agent/test/scenarios.test.ts +113 -0
  161. package/templates/checkout-agent/agents/checkout-agent/test/whatsapp.test.ts +171 -0
  162. package/templates/checkout-agent/agents/checkout-agent/tools.json +68 -0
  163. package/templates/checkout-agent/agents/checkout-agent/tsconfig.json +10 -0
  164. package/templates/checkout-agent/package.json +38 -0
  165. package/templates/checkout-agent/packages/agent-core/README.md +34 -0
  166. package/templates/checkout-agent/packages/agent-core/package.json +24 -0
  167. package/templates/checkout-agent/packages/agent-core/src/agent.ts +163 -0
  168. package/templates/checkout-agent/packages/agent-core/src/api/charge-rail.ts +195 -0
  169. package/templates/checkout-agent/packages/agent-core/src/api/client.ts +76 -0
  170. package/templates/checkout-agent/packages/agent-core/src/api/mandate-status.ts +74 -0
  171. package/templates/checkout-agent/packages/agent-core/src/api/rail.ts +153 -0
  172. package/templates/checkout-agent/packages/agent-core/src/api/sandbox-payer.ts +43 -0
  173. package/templates/checkout-agent/packages/agent-core/src/approval.ts +135 -0
  174. package/templates/checkout-agent/packages/agent-core/src/bundle.ts +191 -0
  175. package/templates/checkout-agent/packages/agent-core/src/channels.ts +186 -0
  176. package/templates/checkout-agent/packages/agent-core/src/check.ts +276 -0
  177. package/templates/checkout-agent/packages/agent-core/src/clock.ts +42 -0
  178. package/templates/checkout-agent/packages/agent-core/src/engine.ts +1074 -0
  179. package/templates/checkout-agent/packages/agent-core/src/escalate.ts +85 -0
  180. package/templates/checkout-agent/packages/agent-core/src/events.ts +40 -0
  181. package/templates/checkout-agent/packages/agent-core/src/guardrails.ts +45 -0
  182. package/templates/checkout-agent/packages/agent-core/src/hash.ts +118 -0
  183. package/templates/checkout-agent/packages/agent-core/src/ids.ts +10 -0
  184. package/templates/checkout-agent/packages/agent-core/src/index.ts +34 -0
  185. package/templates/checkout-agent/packages/agent-core/src/mandate.ts +100 -0
  186. package/templates/checkout-agent/packages/agent-core/src/manifest.ts +76 -0
  187. package/templates/checkout-agent/packages/agent-core/src/providers/anthropic.ts +76 -0
  188. package/templates/checkout-agent/packages/agent-core/src/providers/replay.ts +63 -0
  189. package/templates/checkout-agent/packages/agent-core/src/providers/types.ts +39 -0
  190. package/templates/checkout-agent/packages/agent-core/src/quote.ts +50 -0
  191. package/templates/checkout-agent/packages/agent-core/src/rail.ts +186 -0
  192. package/templates/checkout-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  193. package/templates/checkout-agent/packages/agent-core/src/receipt-verification.ts +577 -0
  194. package/templates/checkout-agent/packages/agent-core/src/revocation.ts +30 -0
  195. package/templates/checkout-agent/packages/agent-core/src/secrets.ts +30 -0
  196. package/templates/checkout-agent/packages/agent-core/src/state/store.ts +307 -0
  197. package/templates/checkout-agent/packages/agent-core/src/state-machine.ts +157 -0
  198. package/templates/checkout-agent/packages/agent-core/src/stubs/charge-rail.ts +195 -0
  199. package/templates/checkout-agent/packages/agent-core/src/stubs/mandate-status.ts +58 -0
  200. package/templates/checkout-agent/packages/agent-core/src/stubs/rail.ts +209 -0
  201. package/templates/checkout-agent/packages/agent-core/src/tools.ts +66 -0
  202. package/templates/checkout-agent/packages/agent-core/src/types.ts +187 -0
  203. package/templates/checkout-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  204. package/templates/checkout-agent/packages/agent-core/test/approval.test.ts +117 -0
  205. package/templates/checkout-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  206. package/templates/checkout-agent/packages/agent-core/test/bundle.test.ts +85 -0
  207. package/templates/checkout-agent/packages/agent-core/test/clock.test.ts +25 -0
  208. package/templates/checkout-agent/packages/agent-core/test/composition.test.ts +204 -0
  209. package/templates/checkout-agent/packages/agent-core/test/engine.test.ts +596 -0
  210. package/templates/checkout-agent/packages/agent-core/test/escalate.test.ts +42 -0
  211. package/templates/checkout-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  212. package/templates/checkout-agent/packages/agent-core/test/fixtures/staging-receipt-keys.json +18 -0
  213. package/templates/checkout-agent/packages/agent-core/test/fixtures/staging-receipt.json +24 -0
  214. package/templates/checkout-agent/packages/agent-core/test/helpers.ts +142 -0
  215. package/templates/checkout-agent/packages/agent-core/test/mandate-status.test.ts +229 -0
  216. package/templates/checkout-agent/packages/agent-core/test/manifest.test.ts +45 -0
  217. package/templates/checkout-agent/packages/agent-core/test/quote.test.ts +198 -0
  218. package/templates/checkout-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  219. package/templates/checkout-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  220. package/templates/checkout-agent/packages/agent-core/test/receipt-verification.test.ts +316 -0
  221. package/templates/checkout-agent/packages/agent-core/test/receivables.test.ts +416 -0
  222. package/templates/checkout-agent/packages/agent-core/test/secrets.test.ts +28 -0
  223. package/templates/checkout-agent/packages/agent-core/test/state-machine.test.ts +67 -0
  224. package/templates/checkout-agent/packages/agent-core/test/transitions.type-test.ts +59 -0
  225. package/templates/checkout-agent/packages/agent-core/tsconfig.json +10 -0
  226. package/templates/checkout-agent/packages/agent-runtime/README.md +80 -0
  227. package/templates/checkout-agent/packages/agent-runtime/bin.mjs +32 -0
  228. package/templates/checkout-agent/packages/agent-runtime/package.json +23 -0
  229. package/templates/checkout-agent/packages/agent-runtime/src/adversarial.ts +199 -0
  230. package/templates/checkout-agent/packages/agent-runtime/src/agent.ts +68 -0
  231. package/templates/checkout-agent/packages/agent-runtime/src/channels/contact.ts +13 -0
  232. package/templates/checkout-agent/packages/agent-runtime/src/channels/index.ts +72 -0
  233. package/templates/checkout-agent/packages/agent-runtime/src/channels/rules.ts +120 -0
  234. package/templates/checkout-agent/packages/agent-runtime/src/channels/types.ts +173 -0
  235. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +459 -0
  236. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +216 -0
  237. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/evidence.ts +109 -0
  238. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +350 -0
  239. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +180 -0
  240. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/present.ts +47 -0
  241. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +120 -0
  242. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +127 -0
  243. package/templates/checkout-agent/packages/agent-runtime/src/cli.ts +98 -0
  244. package/templates/checkout-agent/packages/agent-runtime/src/commands/check.ts +15 -0
  245. package/templates/checkout-agent/packages/agent-runtime/src/commands/decide.ts +63 -0
  246. package/templates/checkout-agent/packages/agent-runtime/src/commands/eval.ts +58 -0
  247. package/templates/checkout-agent/packages/agent-runtime/src/commands/inspect.ts +100 -0
  248. package/templates/checkout-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +367 -0
  249. package/templates/checkout-agent/packages/agent-runtime/src/commands/poll.ts +145 -0
  250. package/templates/checkout-agent/packages/agent-runtime/src/commands/reconcile.ts +42 -0
  251. package/templates/checkout-agent/packages/agent-runtime/src/commands/rerun.ts +86 -0
  252. package/templates/checkout-agent/packages/agent-runtime/src/commands/resume.ts +49 -0
  253. package/templates/checkout-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +134 -0
  254. package/templates/checkout-agent/packages/agent-runtime/src/commands/start.ts +248 -0
  255. package/templates/checkout-agent/packages/agent-runtime/src/commands/verify.ts +330 -0
  256. package/templates/checkout-agent/packages/agent-runtime/src/commands/webhook.ts +40 -0
  257. package/templates/checkout-agent/packages/agent-runtime/src/default-kit.ts +83 -0
  258. package/templates/checkout-agent/packages/agent-runtime/src/index.ts +13 -0
  259. package/templates/checkout-agent/packages/agent-runtime/src/inspect.ts +801 -0
  260. package/templates/checkout-agent/packages/agent-runtime/src/kit.ts +229 -0
  261. package/templates/checkout-agent/packages/agent-runtime/src/poll.ts +105 -0
  262. package/templates/checkout-agent/packages/agent-runtime/src/scenarios.ts +274 -0
  263. package/templates/checkout-agent/packages/agent-runtime/src/setup.ts +239 -0
  264. package/templates/checkout-agent/packages/agent-runtime/src/terminal.ts +263 -0
  265. package/templates/checkout-agent/packages/agent-runtime/src/webhook.ts +131 -0
  266. package/templates/checkout-agent/packages/agent-runtime/test/inspect.test.ts +336 -0
  267. package/templates/checkout-agent/packages/agent-runtime/test/verify-cli.test.ts +329 -0
  268. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +277 -0
  269. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-evidence.test.ts +94 -0
  270. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-rules.test.ts +174 -0
  271. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-session.test.ts +167 -0
  272. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  273. package/templates/checkout-agent/packages/agent-runtime/tsconfig.json +9 -0
  274. package/templates/checkout-agent/scripts/whatsapp-emulator.mjs +84 -0
  275. package/templates/checkout-agent/tsconfig.base.json +16 -0
  276. package/templates/checkout-agent/vitest.config.ts +12 -0
  277. package/templates/collections-agent/README.md +6 -1
  278. package/templates/collections-agent/agents/collections-agent/README.md +12 -8
  279. package/templates/collections-agent/agents/collections-agent/channels/whatsapp/README.md +8 -0
  280. package/templates/collections-agent/agents/collections-agent/channels/whatsapp/acordo-1042-retomada.json +10 -0
  281. package/templates/collections-agent/agents/collections-agent/channels/whatsapp/templates.json +27 -1
  282. package/templates/collections-agent/agents/collections-agent/src/kit.ts +2 -0
  283. package/templates/collections-agent/agents/collections-agent/src/modules/bolepix-receivables.ts +12 -2
  284. package/templates/collections-agent/agents/collections-agent/test/ambiguous-charge.test.ts +55 -0
  285. package/templates/collections-agent/agents/collections-agent/test/fixtures/tap-emitir-nova-1042.transcript.jsonl +2 -0
  286. package/templates/collections-agent/agents/collections-agent/test/whatsapp-poll.test.ts +12 -6
  287. package/templates/collections-agent/agents/collections-agent/test/whatsapp-status-replies.test.ts +148 -0
  288. package/templates/collections-agent/agents/collections-agent/test/whatsapp.test.ts +5 -0
  289. package/templates/collections-agent/package.json +1 -0
  290. package/templates/collections-agent/packages/agent-core/README.md +14 -1
  291. package/templates/collections-agent/packages/agent-core/package.json +1 -1
  292. package/templates/collections-agent/packages/agent-core/src/agent.ts +39 -1
  293. package/templates/collections-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
  294. package/templates/collections-agent/packages/agent-core/src/api/client.ts +28 -5
  295. package/templates/collections-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
  296. package/templates/collections-agent/packages/agent-core/src/api/rail.ts +62 -26
  297. package/templates/collections-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
  298. package/templates/collections-agent/packages/agent-core/src/approval.ts +14 -2
  299. package/templates/collections-agent/packages/agent-core/src/bundle.ts +35 -3
  300. package/templates/collections-agent/packages/agent-core/src/channels.ts +49 -3
  301. package/templates/collections-agent/packages/agent-core/src/check.ts +25 -2
  302. package/templates/collections-agent/packages/agent-core/src/engine.ts +235 -46
  303. package/templates/collections-agent/packages/agent-core/src/hash.ts +57 -1
  304. package/templates/collections-agent/packages/agent-core/src/index.ts +2 -0
  305. package/templates/collections-agent/packages/agent-core/src/quote.ts +50 -0
  306. package/templates/collections-agent/packages/agent-core/src/rail.ts +89 -4
  307. package/templates/collections-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  308. package/templates/collections-agent/packages/agent-core/src/receipt-verification.ts +194 -18
  309. package/templates/collections-agent/packages/agent-core/src/revocation.ts +1 -1
  310. package/templates/collections-agent/packages/agent-core/src/state-machine.ts +10 -1
  311. package/templates/collections-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
  312. package/templates/collections-agent/packages/agent-core/src/stubs/rail.ts +100 -32
  313. package/templates/collections-agent/packages/agent-core/src/tools.ts +17 -6
  314. package/templates/collections-agent/packages/agent-core/src/types.ts +54 -1
  315. package/templates/collections-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  316. package/templates/collections-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  317. package/templates/collections-agent/packages/agent-core/test/composition.test.ts +204 -0
  318. package/templates/collections-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  319. package/templates/collections-agent/packages/agent-core/test/helpers.ts +4 -1
  320. package/templates/collections-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
  321. package/templates/collections-agent/packages/agent-core/test/quote.test.ts +198 -0
  322. package/templates/collections-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  323. package/templates/collections-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  324. package/templates/collections-agent/packages/agent-core/test/receivables.test.ts +103 -3
  325. package/templates/collections-agent/packages/agent-runtime/README.md +12 -0
  326. package/templates/collections-agent/packages/agent-runtime/src/adversarial.ts +32 -2
  327. package/templates/collections-agent/packages/agent-runtime/src/channels/types.ts +53 -4
  328. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
  329. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
  330. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
  331. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
  332. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
  333. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
  334. package/templates/collections-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
  335. package/templates/collections-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
  336. package/templates/collections-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
  337. package/templates/collections-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
  338. package/templates/collections-agent/packages/agent-runtime/src/commands/start.ts +6 -3
  339. package/templates/collections-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
  340. package/templates/collections-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
  341. package/templates/collections-agent/packages/agent-runtime/src/index.ts +1 -0
  342. package/templates/collections-agent/packages/agent-runtime/src/inspect.ts +5 -1
  343. package/templates/collections-agent/packages/agent-runtime/src/kit.ts +25 -2
  344. package/templates/collections-agent/packages/agent-runtime/src/scenarios.ts +8 -2
  345. package/templates/collections-agent/packages/agent-runtime/src/setup.ts +13 -3
  346. package/templates/collections-agent/packages/agent-runtime/src/terminal.ts +127 -18
  347. package/templates/collections-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
  348. package/templates/collections-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
  349. package/templates/collections-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
  350. package/templates/collections-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  351. package/templates/collections-agent/scripts/whatsapp-emulator.mjs +84 -0
  352. package/templates/hello-agent/README.md +1 -1
  353. package/templates/hello-agent/packages/agent-core/README.md +14 -1
  354. package/templates/hello-agent/packages/agent-core/package.json +1 -1
  355. package/templates/hello-agent/packages/agent-core/src/agent.ts +39 -1
  356. package/templates/hello-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
  357. package/templates/hello-agent/packages/agent-core/src/api/client.ts +28 -5
  358. package/templates/hello-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
  359. package/templates/hello-agent/packages/agent-core/src/api/rail.ts +62 -26
  360. package/templates/hello-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
  361. package/templates/hello-agent/packages/agent-core/src/approval.ts +14 -2
  362. package/templates/hello-agent/packages/agent-core/src/bundle.ts +35 -3
  363. package/templates/hello-agent/packages/agent-core/src/channels.ts +49 -3
  364. package/templates/hello-agent/packages/agent-core/src/check.ts +25 -2
  365. package/templates/hello-agent/packages/agent-core/src/engine.ts +235 -46
  366. package/templates/hello-agent/packages/agent-core/src/hash.ts +57 -1
  367. package/templates/hello-agent/packages/agent-core/src/index.ts +2 -0
  368. package/templates/hello-agent/packages/agent-core/src/quote.ts +50 -0
  369. package/templates/hello-agent/packages/agent-core/src/rail.ts +89 -4
  370. package/templates/hello-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  371. package/templates/hello-agent/packages/agent-core/src/receipt-verification.ts +194 -18
  372. package/templates/hello-agent/packages/agent-core/src/revocation.ts +1 -1
  373. package/templates/hello-agent/packages/agent-core/src/state-machine.ts +10 -1
  374. package/templates/hello-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
  375. package/templates/hello-agent/packages/agent-core/src/stubs/rail.ts +100 -32
  376. package/templates/hello-agent/packages/agent-core/src/tools.ts +17 -6
  377. package/templates/hello-agent/packages/agent-core/src/types.ts +54 -1
  378. package/templates/hello-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  379. package/templates/hello-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  380. package/templates/hello-agent/packages/agent-core/test/composition.test.ts +204 -0
  381. package/templates/hello-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  382. package/templates/hello-agent/packages/agent-core/test/helpers.ts +4 -1
  383. package/templates/hello-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
  384. package/templates/hello-agent/packages/agent-core/test/quote.test.ts +198 -0
  385. package/templates/hello-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  386. package/templates/hello-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  387. package/templates/hello-agent/packages/agent-core/test/receivables.test.ts +103 -3
  388. package/templates/hello-agent/packages/agent-runtime/README.md +12 -0
  389. package/templates/hello-agent/packages/agent-runtime/src/adversarial.ts +32 -2
  390. package/templates/hello-agent/packages/agent-runtime/src/channels/types.ts +53 -4
  391. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
  392. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
  393. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
  394. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
  395. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
  396. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
  397. package/templates/hello-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
  398. package/templates/hello-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
  399. package/templates/hello-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
  400. package/templates/hello-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
  401. package/templates/hello-agent/packages/agent-runtime/src/commands/start.ts +6 -3
  402. package/templates/hello-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
  403. package/templates/hello-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
  404. package/templates/hello-agent/packages/agent-runtime/src/index.ts +1 -0
  405. package/templates/hello-agent/packages/agent-runtime/src/inspect.ts +5 -1
  406. package/templates/hello-agent/packages/agent-runtime/src/kit.ts +25 -2
  407. package/templates/hello-agent/packages/agent-runtime/src/scenarios.ts +8 -2
  408. package/templates/hello-agent/packages/agent-runtime/src/setup.ts +13 -3
  409. package/templates/hello-agent/packages/agent-runtime/src/terminal.ts +127 -18
  410. package/templates/hello-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
  411. package/templates/hello-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
  412. package/templates/hello-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
  413. package/templates/hello-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  414. package/templates/kits.lock.json +29 -6
  415. package/templates/supplier-payments-agent/README.md +1 -1
  416. package/templates/supplier-payments-agent/agents/supplier-payments-agent/AGENTS.md +1 -1
  417. package/templates/supplier-payments-agent/agents/supplier-payments-agent/CLAUDE.md +1 -1
  418. package/templates/supplier-payments-agent/agents/supplier-payments-agent/README.md +11 -9
  419. package/templates/supplier-payments-agent/agents/supplier-payments-agent/src/kit.ts +1 -1
  420. package/templates/supplier-payments-agent/agents/supplier-payments-agent/src/modules/batch-payout.ts +67 -20
  421. package/templates/supplier-payments-agent/agents/supplier-payments-agent/test/batch.test.ts +504 -5
  422. package/templates/supplier-payments-agent/packages/agent-core/README.md +14 -1
  423. package/templates/supplier-payments-agent/packages/agent-core/package.json +1 -1
  424. package/templates/supplier-payments-agent/packages/agent-core/src/agent.ts +39 -1
  425. package/templates/supplier-payments-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
  426. package/templates/supplier-payments-agent/packages/agent-core/src/api/client.ts +28 -5
  427. package/templates/supplier-payments-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
  428. package/templates/supplier-payments-agent/packages/agent-core/src/api/rail.ts +62 -26
  429. package/templates/supplier-payments-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
  430. package/templates/supplier-payments-agent/packages/agent-core/src/approval.ts +14 -2
  431. package/templates/supplier-payments-agent/packages/agent-core/src/bundle.ts +35 -3
  432. package/templates/supplier-payments-agent/packages/agent-core/src/channels.ts +49 -3
  433. package/templates/supplier-payments-agent/packages/agent-core/src/check.ts +25 -2
  434. package/templates/supplier-payments-agent/packages/agent-core/src/engine.ts +235 -46
  435. package/templates/supplier-payments-agent/packages/agent-core/src/hash.ts +57 -1
  436. package/templates/supplier-payments-agent/packages/agent-core/src/index.ts +2 -0
  437. package/templates/supplier-payments-agent/packages/agent-core/src/quote.ts +50 -0
  438. package/templates/supplier-payments-agent/packages/agent-core/src/rail.ts +89 -4
  439. package/templates/supplier-payments-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  440. package/templates/supplier-payments-agent/packages/agent-core/src/receipt-verification.ts +194 -18
  441. package/templates/supplier-payments-agent/packages/agent-core/src/revocation.ts +1 -1
  442. package/templates/supplier-payments-agent/packages/agent-core/src/state-machine.ts +10 -1
  443. package/templates/supplier-payments-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
  444. package/templates/supplier-payments-agent/packages/agent-core/src/stubs/rail.ts +100 -32
  445. package/templates/supplier-payments-agent/packages/agent-core/src/tools.ts +17 -6
  446. package/templates/supplier-payments-agent/packages/agent-core/src/types.ts +54 -1
  447. package/templates/supplier-payments-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  448. package/templates/supplier-payments-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  449. package/templates/supplier-payments-agent/packages/agent-core/test/composition.test.ts +204 -0
  450. package/templates/supplier-payments-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  451. package/templates/supplier-payments-agent/packages/agent-core/test/helpers.ts +4 -1
  452. package/templates/supplier-payments-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
  453. package/templates/supplier-payments-agent/packages/agent-core/test/quote.test.ts +198 -0
  454. package/templates/supplier-payments-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  455. package/templates/supplier-payments-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  456. package/templates/supplier-payments-agent/packages/agent-core/test/receivables.test.ts +103 -3
  457. package/templates/supplier-payments-agent/packages/agent-runtime/README.md +12 -0
  458. package/templates/supplier-payments-agent/packages/agent-runtime/src/adversarial.ts +32 -2
  459. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/types.ts +53 -4
  460. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
  461. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
  462. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
  463. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
  464. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
  465. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
  466. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
  467. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
  468. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
  469. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
  470. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/start.ts +6 -3
  471. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
  472. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
  473. package/templates/supplier-payments-agent/packages/agent-runtime/src/index.ts +1 -0
  474. package/templates/supplier-payments-agent/packages/agent-runtime/src/inspect.ts +5 -1
  475. package/templates/supplier-payments-agent/packages/agent-runtime/src/kit.ts +25 -2
  476. package/templates/supplier-payments-agent/packages/agent-runtime/src/scenarios.ts +8 -2
  477. package/templates/supplier-payments-agent/packages/agent-runtime/src/setup.ts +13 -3
  478. package/templates/supplier-payments-agent/packages/agent-runtime/src/terminal.ts +127 -18
  479. package/templates/supplier-payments-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
  480. package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
  481. package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
  482. package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  483. package/templates/bills-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
  484. package/templates/collections-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
  485. package/templates/hello-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
  486. package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
@@ -0,0 +1,5 @@
1
+ {"kind": "assistant_step", "tool_calls": [{"id": "tc_1", "name": "list_catalog", "input": {}}]}
2
+ {"kind": "assistant_step", "tool_calls": [{"id": "tc_2", "name": "cart_update", "input": {"lines": [{"sku": "aula-avulsa", "quantity": 2}]}}]}
3
+ {"kind": "assistant_step", "tool_calls": [{"id": "tc_3", "name": "codespar_charge", "input": {"action": "create", "customer": "marina", "total_minor": 20000}}]}
4
+ {"kind": "assistant_step", "tool_calls": [{"id": "tc_4", "name": "codespar_charge", "input": {"action": "issue"}}]}
5
+ {"kind": "assistant_step", "reply": "Pedido de duas aulas avulsas, R$ 200,00: o QR e o copia e cola estao acima."}
@@ -0,0 +1,6 @@
1
+ {"kind": "assistant_step", "tool_calls": [{"id": "tc_1", "name": "list_catalog", "input": {}}]}
2
+ {"kind": "assistant_step", "tool_calls": [{"id": "tc_2", "name": "cart_update", "input": {"lines": [{"sku": "pacote-10-aulas", "quantity": 1}, {"sku": "avaliacao-inicial", "quantity": 1}]}}]}
3
+ {"kind": "assistant_step", "reply": "Oi, Marina! Montei o carrinho: pacote de 10 aulas de violao (R$ 390,00) e avaliacao inicial de nivel (R$ 89,90). Total R$ 479,90, cobranca por Pix ou boleto com vencimento hoje. Posso fechar o pedido?"}
4
+ {"kind": "assistant_step", "tool_calls": [{"id": "tc_3", "name": "codespar_charge", "input": {"action": "create", "customer": "marina", "total_minor": 47990}}]}
5
+ {"kind": "assistant_step", "tool_calls": [{"id": "tc_4", "name": "codespar_charge", "input": {"action": "issue"}}]}
6
+ {"kind": "assistant_step", "reply": "Pronto, Marina: a cobranca de R$ 479,90 esta emitida, o codigo esta acima. Assim que o pagamento cair, eu te confirmo por aqui."}
@@ -0,0 +1,2 @@
1
+ {"kind": "assistant_step", "tool_calls": [{"id": "tc_1", "name": "codespar_charge", "input": {"action": "status"}}]}
2
+ {"kind": "assistant_step", "reply": "Oi, Marina! Nao ha pedido em aberto nesta conversa. Quer montar um novo?"}
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Checkout §4: the NFS-e is a second execution after `settled`, called by
3
+ * code, that never moves the sale. A proven-unsent attempt is retried; an
4
+ * issuer refusal is terminal; anything else is `invoice_uncertain` and is
5
+ * never sent again.
6
+ */
7
+ import { mkdtempSync, readFileSync } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { afterEach, describe, expect, it } from "vitest";
11
+ import { StateStore, type ExecutionEngine } from "@codespar/agent-core";
12
+ import { CodesparApiError } from "@codespar/sdk";
13
+ import { checkScenario, loadScenario, runScenario, setup, type ScenarioRun } from "@codespar/agent-runtime";
14
+ import { agent } from "../src/kit.js";
15
+ import { classifyExecuteAnswer, CodeSparInvoiceRail, InvoiceBook, resumeInvoices, type InvoiceRail, type InvoiceRecord, type NfseRequest } from "../src/modules/nfse-invoice.js";
16
+
17
+ const runsDir = mkdtempSync(join(tmpdir(), "checkout-nfse-runs-"));
18
+ const events = (run: ScenarioRun) => readFileSync(join(run.bundle_dir, "events.jsonl"), "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l) as { type: string; execution_id?: string | null; actor?: unknown; payload?: Record<string, unknown> });
19
+
20
+ async function scenario(name: string, mode: "human" | "mandate" = "human") {
21
+ const stateDir = mkdtempSync(join(tmpdir(), `checkout-nfse-${name}-`));
22
+ const run = await runScenario(agent, loadScenario(agent, name), { mode, runsDir, stateDir });
23
+ const store = new StateStore(join(stateDir, "state.db"));
24
+ const invoices = new InvoiceBook(store).all();
25
+ const outbox = store.listOutbox().filter((o) => o.kind === "nfse.issue");
26
+ store.close();
27
+ return { run, invoices, outbox, stateDir };
28
+ }
29
+
30
+ const ENV = "CHECKOUT_STUB_ISSUER";
31
+ afterEach(() => {
32
+ delete process.env[ENV];
33
+ });
34
+
35
+ describe("checkout §4: the paid order opens its service invoice, and the invoice never moves the sale", () => {
36
+ it("codespar_invoice is not the model's: it is not in tools.json and no handler answers it (checkout §7.3, point 4)", () => {
37
+ const tools = JSON.parse(readFileSync(join(agent.dir, "tools.json"), "utf8")) as { meta_tools: Array<{ name: string }>; local_tools: Array<{ name: string }> };
38
+ expect([...tools.meta_tools, ...tools.local_tools].map((t) => t.name)).not.toContain("codespar_invoice");
39
+ const s = setup(agent, { rail: "stub", provider: "replay", transcript: "unused", stateDir: mkdtempSync(join(tmpdir(), "checkout-nfse-tools-")), runsDir, say: () => undefined });
40
+ try {
41
+ expect(Object.keys(s.handlers)).not.toContain("codespar_invoice");
42
+ } finally {
43
+ s.close();
44
+ }
45
+ });
46
+
47
+ it("happy-path: one NFS-e per paid order, issued from the order's own data, and the customer is told about the order only", async () => {
48
+ const { run, invoices, outbox } = await scenario("happy-path");
49
+ expect(run.executions.map((e) => e.state)).toEqual(["settled"]);
50
+ expect(invoices).toHaveLength(1);
51
+ const [invoice] = invoices;
52
+ expect(invoice).toMatchObject({ sale_execution_id: run.executions[0]!.id, state: "accepted", attempts: 1, charge_id: run.executions[0]!.charge_ids[0] });
53
+ expect(invoice!.document?.id).toMatch(/^nfse_stub_/);
54
+ expect(invoice!.history.map((h) => h.to)).toEqual(["pending", "issuing", "accepted"]);
55
+ expect(outbox).toEqual([expect.objectContaining({ status: "done", execution_id: invoice!.id })]);
56
+ const all = events(run);
57
+ const dispatched = all.filter((e) => e.type === "invoice.dispatch");
58
+ expect(dispatched).toHaveLength(1);
59
+ // What was PAID is what is invoiced, in the major units the meta-tool reads.
60
+ expect(dispatched[0]!.payload!["services_amount"]).toBe(479.9);
61
+ expect(all.filter((e) => e.type === "message.debtor")).toHaveLength(1);
62
+ expect(all.filter((e) => e.type === "message.attendant")).toHaveLength(0);
63
+ for (const e of all) expect(e.actor).toBeDefined();
64
+ });
65
+
66
+ it("nfse-failed: a refusal ends invoice_refused with the issuer's code; a timeout ends invoice_uncertain and is not sent again; both sales stay paid", async () => {
67
+ for (const mode of ["human", "mandate"] as const) {
68
+ const { run, invoices, outbox } = await scenario("nfse-failed", mode);
69
+ expect(checkScenario(loadScenario(agent, "nfse-failed"), run).failures).toEqual([]);
70
+ expect(run.executions.map((e) => e.state)).toEqual(["settled", "settled"]);
71
+ const [refused, uncertain] = invoices;
72
+ expect(refused).toMatchObject({ state: "failed", reason: "invoice_refused", code: "E0101", attempts: 1 });
73
+ expect(uncertain).toMatchObject({ state: "failed", reason: "invoice_uncertain", code: "timeout", attempts: 1 });
74
+ expect(outbox.map((o) => o.status)).toEqual(["failed", "failed"]);
75
+ const all = events(run);
76
+ // The attendant is told about each failure; the customers are told about their orders, twice in all, never about an invoice.
77
+ expect(all.filter((e) => e.type === "message.attendant").map((e) => e.payload!["reason"])).toEqual(["invoice_refused", "invoice_uncertain"]);
78
+ const told = all.filter((e) => e.type === "message.debtor").map((e) => String(e.payload!["text"]));
79
+ expect(told).toHaveLength(2);
80
+ for (const text of told) expect(text).not.toMatch(/nota|NFS-e|fiscal/i);
81
+ // Nothing moved the sale after it settled.
82
+ const settledAt = all.filter((e) => e.type === "execution.transition" && e.payload!["to"] === "settled").length;
83
+ expect(settledAt).toBe(2);
84
+ expect(all.filter((e) => e.type === "invoice.dispatch")).toHaveLength(2);
85
+ }
86
+ });
87
+
88
+ it("a timed-out issuance is NOT re-sent by resume: the stub issuer did authorize a note, and a second call would mint another", async () => {
89
+ const { invoices, stateDir } = await scenario("nfse-failed");
90
+ const calls: string[] = [];
91
+ const counting: InvoiceRail = { name: "stub-nfse", issue: async (_r, key) => (calls.push(key), { status: "accepted", document_id: "second", document_status: "X", pdf_url: null }) };
92
+ const s = setup(agent, { rail: "stub", provider: "replay", transcript: "unused", stateDir, runsDir, say: () => undefined });
93
+ try {
94
+ const resumed = await resumeInvoices({ store: s.store, engine: s.engine, rail: counting, cart: () => undefined, say: () => undefined });
95
+ expect(resumed).toEqual([]);
96
+ expect(calls).toEqual([]);
97
+ const store = s.store;
98
+ expect(store.getCursor(`stub-nfse:${invoices[1]!.idempotency_key}`)).toBeDefined();
99
+ } finally {
100
+ s.close();
101
+ }
102
+ });
103
+
104
+ it("an attempt proven unsent is retried under the same key, and stops at the bound", async () => {
105
+ process.env[ENV] = "unsent_once";
106
+ const once = await scenario("happy-path");
107
+ expect(once.invoices[0]).toMatchObject({ state: "accepted", attempts: 2 });
108
+ expect(once.invoices[0]!.history.map((h) => h.to)).toEqual(["pending", "issuing", "pending", "issuing", "accepted"]);
109
+
110
+ process.env[ENV] = "unsent";
111
+ const never = await scenario("happy-path");
112
+ expect(never.invoices[0]).toMatchObject({ state: "failed", reason: "invoice_unsent", code: "no_eligible_providers", attempts: 3 });
113
+ expect(never.run.executions.map((e) => e.state)).toEqual(["settled"]);
114
+ });
115
+
116
+ it("a crash after the request left (the record says issuing) is reconciled as uncertain by resume, and nothing is sent", async () => {
117
+ const stateDir = mkdtempSync(join(tmpdir(), "checkout-nfse-crash-"));
118
+ const s = setup(agent, { rail: "stub", provider: "replay", transcript: "unused", stateDir, runsDir, say: () => undefined });
119
+ try {
120
+ const record: InvoiceRecord = { id: "inv_crash", sale_execution_id: "exe_crash", cart_ref: "run/cart-1", charge_id: "chg_1", idempotency_key: "nfse_crash", state: "issuing", attempts: 1, history: [], created_at: "2026-09-23T18:00:00.000Z", updated_at: "2026-09-23T18:00:00.000Z" };
121
+ new InvoiceBook(s.store).save(record);
122
+ s.store.putOutbox({ idempotency_key: "nfse_crash", execution_id: "inv_crash", kind: "nfse.issue", payload: {}, status: "sent", response: undefined, created_at: record.created_at });
123
+ const calls: string[] = [];
124
+ const said: string[] = [];
125
+ const [after] = await resumeInvoices({ store: s.store, engine: s.engine as ExecutionEngine, rail: { name: "stub-nfse", issue: async (_r, k) => (calls.push(k), { status: "accepted", document_id: "x", document_status: "X", pdf_url: null }) }, cart: () => undefined, say: (l) => said.push(l) });
126
+ expect(after).toMatchObject({ state: "failed", reason: "invoice_uncertain", code: "interrupted" });
127
+ expect(calls).toEqual([]);
128
+ expect(said.join(" ")).toContain("INCERTO");
129
+ expect(s.store.getOutbox("nfse_crash")?.status).toBe("failed");
130
+ } finally {
131
+ s.close();
132
+ }
133
+ });
134
+ });
135
+
136
+ describe("the wire, read by structure and code (codespar_invoice through POST /v1/sessions/{id}/execute)", () => {
137
+ it("classifies every answer the route can give", () => {
138
+ expect(classifyExecuteAnswer({ success: true, data: { id: "nf_1", status: "WAITINGSEND" }, error: null })).toMatchObject({ status: "accepted", document_id: "nf_1" });
139
+ // A success that names no document may still have minted one.
140
+ expect(classifyExecuteAnswer({ success: true, data: {}, error: null }).status).toBe("uncertain");
141
+ // provider_error is raised for an issuer 4xx AND for a timeout or a 5xx; the wire does not say which.
142
+ expect(classifyExecuteAnswer({ success: false, data: { error: "provider nfe-io returned status=400", code: "provider_error" }, error: "x", server: "unknown" }).status).toBe("uncertain");
143
+ for (const code of ["no_eligible_providers", "credential_unavailable", "invalid_args", "org_paused", "tool_unknown"]) {
144
+ expect(classifyExecuteAnswer({ success: false, data: { error: "x", code }, error: "x", server: "unknown" }).status).toBe("unsent");
145
+ }
146
+ expect(classifyExecuteAnswer({ success: false, data: { error: "x", code: "scope_missing" }, error: "x", server: "agentgate" }).status).toBe("unsent");
147
+ expect(classifyExecuteAnswer({ success: false, data: null, error: "Tool not registered: codespar_invoice", server: "" }).status).toBe("unsent");
148
+ // An unexpected throw inside the strategy answers output null: nobody can say.
149
+ expect(classifyExecuteAnswer({ success: false, data: null, error: "boom", server: "unknown" }).status).toBe("uncertain");
150
+ // The day the wire carries the provenance, it decides.
151
+ expect(classifyExecuteAnswer({ success: false, data: { code: "provider_error", dispatch: "rejected" }, error: "x", server: "unknown" }).status).toBe("refused");
152
+ expect(classifyExecuteAnswer({ success: false, data: { code: "provider_error", dispatch: "unsent" }, error: "x", server: "unknown" }).status).toBe("unsent");
153
+ });
154
+
155
+ it("the API rail: a session that will not open sent nothing; a 4xx from the route sent nothing; a timeout or a 5xx may have", async () => {
156
+ const request = { action: "issue", type: "nfse" } as NfseRequest;
157
+ const failing = (status: number, onSession = false) =>
158
+ new CodeSparInvoiceRail({
159
+ post: async (path: string) => {
160
+ if (path === "/v1/sessions" && !onSession) return { id: "ses_1" };
161
+ throw new CodesparApiError(`http ${status}`, { status });
162
+ },
163
+ } as never);
164
+ expect((await failing(503, true).issue(request)).status).toBe("unsent");
165
+ expect((await failing(403).issue(request)).status).toBe("unsent");
166
+ expect((await failing(503).issue(request)).status).toBe("uncertain");
167
+ const answered = new CodeSparInvoiceRail({ post: async (path: string, init: { body: { tool?: string; input?: unknown } }) => (path === "/v1/sessions" ? { id: "ses_1" } : { success: true, data: { id: "nf_9", status: "PROCESSING", tool: init.body.tool }, error: null, server: "nfe-io" }) } as never);
168
+ expect(await answered.issue(request)).toMatchObject({ status: "accepted", document_id: "nf_9", document_status: "PROCESSING" });
169
+ });
170
+ });
@@ -0,0 +1,138 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { describe, expect, it } from "vitest";
4
+ import { compositionHash, GuardrailsSchema, itemsHash, type ExecutionItem } from "@codespar/agent-core";
5
+ import { checkOrder, EnvelopeSchema, loadEnvelope, priceCart, type PricedCart } from "../src/pricing.js";
6
+
7
+ const AGENT_DIR = resolve(import.meta.dirname, "..");
8
+ const guardrails = GuardrailsSchema.parse(JSON.parse(readFileSync(resolve(AGENT_DIR, "guardrails.json"), "utf8")));
9
+ const envelope = loadEnvelope(guardrails);
10
+ const TODAY = "2026-09-23";
11
+ const AFTERNOON = new Date("2026-09-23T18:00:00Z"); // 15:00 in Sao Paulo
12
+ const TZ = "America/Sao_Paulo";
13
+
14
+ const codes = (cart: PricedCart) => cart.validation_issues.map((i) => i.code);
15
+ const order = (cart: PricedCart, over: Partial<ExecutionItem> = {}) => {
16
+ const items: ExecutionItem[] = [{ beneficiary: "Marina Costa", payee: "27548613008", amount: cart.total, currency: "BRL", due_date: TODAY, ...over }];
17
+ return { items, total: items.reduce((s, i) => s + i.amount, 0), composition: { ref: "run/cart-1", composition_hash: cart.cart_hash, line_count: cart.composition.length } };
18
+ };
19
+
20
+ describe("checkout §2: the cart the code prices", () => {
21
+ it("the totals are the catalog's: lines priced, subtotal, discount and total computed, not taken", () => {
22
+ const cart = priceCart({ lines: [{ sku: "pacote-10-aulas", quantity: 1 }, { sku: "avaliacao-inicial", quantity: 1 }] }, envelope, TODAY);
23
+ expect(cart.validation_issues).toEqual([]);
24
+ expect(cart.total).toBe(47990);
25
+ expect(cart.line_items.map((l) => [l.sku, l.quantity, l.unit_amount])).toEqual([["pacote-10-aulas", 1, 39000], ["avaliacao-inicial", 1, 8990]]);
26
+ expect(cart.totals.find((t) => t.type === "total")?.amount).toBe(47990);
27
+ });
28
+
29
+ it("a line that carries a price is refused whole (price_not_caller_input), whatever the field is called", () => {
30
+ for (const field of ["price", "unit_price", "amount_minor", "total", "preco"]) {
31
+ const cart = priceCart({ lines: [{ sku: "avaliacao-inicial", quantity: 1, [field]: 1000 }] }, envelope, TODAY);
32
+ expect(codes(cart)).toEqual(["price_not_caller_input"]);
33
+ expect(cart.line_items).toEqual([]);
34
+ expect(cart.total).toBe(0);
35
+ }
36
+ });
37
+
38
+ it("the closed vocabulary: each code for its case, and a bad line is left out rather than guessed", () => {
39
+ expect(codes(priceCart({ lines: [{ sku: "guitarra", quantity: 1 }] }, envelope, TODAY))).toEqual(["sku_unknown"]);
40
+ expect(codes(priceCart({ lines: [{ sku: "masterclass-producao", quantity: 1 }] }, envelope, TODAY))).toEqual(["item_unavailable"]);
41
+ for (const q of [0, -1, 1.5, 11, "2"]) expect(codes(priceCart({ lines: [{ sku: "aula-avulsa", quantity: q }] }, envelope, TODAY))).toEqual(["quantity_invalid"]);
42
+ expect(codes(priceCart({ lines: [{ sku: "ingresso-recital", quantity: 5 }] }, envelope, TODAY))).toEqual(["quantity_above_stock"]);
43
+ expect(codes(priceCart({ lines: [{ sku: "aula-avulsa", quantity: 1 }], coupon: "PRIMEIRACOMPRA30" }, envelope, TODAY))).toEqual(["coupon_unknown"]);
44
+ expect(codes(priceCart({ lines: [{ sku: "aula-avulsa", quantity: 1 }], coupon: "BEMVINDO10" }, envelope, TODAY))).toEqual(["coupon_not_applicable"]);
45
+ expect(codes(priceCart({ lines: [{ sku: "aula-avulsa", quantity: 2 }], coupon: "BEMVINDO10" }, envelope, "2027-01-01"))).toEqual(["coupon_not_applicable"]);
46
+ const mixed = priceCart({ lines: [{ sku: "aula-avulsa", quantity: 2 }, { sku: "guitarra", quantity: 1 }] }, envelope, TODAY);
47
+ expect(codes(mixed)).toEqual(["sku_unknown"]);
48
+ expect(mixed.total).toBe(20000);
49
+ });
50
+
51
+ it("an unknown coupon prices the cart with NO discount, never an equivalent one", () => {
52
+ const cart = priceCart({ lines: [{ sku: "aula-avulsa", quantity: 2 }], coupon: "PRIMEIRACOMPRA30" }, envelope, TODAY);
53
+ expect(cart.coupon).toBeNull();
54
+ expect(cart.order_discounts).toEqual([]);
55
+ expect(cart.total).toBe(20000);
56
+ });
57
+
58
+ it("a coupon in the table applies its own percentage and becomes a line of the composition", () => {
59
+ const cart = priceCart({ lines: [{ sku: "aula-avulsa", quantity: 2 }], coupon: "bemvindo10" }, envelope, TODAY);
60
+ expect(cart.coupon).toBe("BEMVINDO10");
61
+ expect(cart.total).toBe(18000);
62
+ expect(cart.composition.at(-1)).toMatchObject({ ref: "coupon:BEMVINDO10", amount: -2000 });
63
+ });
64
+
65
+ it("cart_hash is compositionHash over the resolved lines: it moves with quantity, SKU and order, and the same total can hash differently", () => {
66
+ const two = priceCart({ lines: [{ sku: "aula-avulsa", quantity: 2 }] }, envelope, TODAY);
67
+ const one = priceCart({ lines: [{ sku: "consultoria-1h", quantity: 1 }] }, envelope, TODAY);
68
+ expect(two.total).toBe(one.total);
69
+ expect(two.cart_hash).not.toBe(one.cart_hash);
70
+ expect(two.cart_hash).toBe(compositionHash(two.composition));
71
+ // The order's single item is the same in both: this is the case items_hash cannot see.
72
+ const item = (c: PricedCart): ExecutionItem => ({ beneficiary: "Marina Costa", payee: "27548613008", amount: c.total, currency: "BRL", due_date: TODAY });
73
+ expect(itemsHash([item(two)])).toBe(itemsHash([item(one)]));
74
+ const ab = priceCart({ lines: [{ sku: "aula-avulsa", quantity: 1 }, { sku: "avaliacao-inicial", quantity: 1 }] }, envelope, TODAY);
75
+ const ba = priceCart({ lines: [{ sku: "avaliacao-inicial", quantity: 1 }, { sku: "aula-avulsa", quantity: 1 }] }, envelope, TODAY);
76
+ expect(ab.cart_hash).not.toBe(ba.cart_hash);
77
+ });
78
+
79
+ it("the envelope schema is strict: an extra key is a load error, not a key ignored", () => {
80
+ expect(() => EnvelopeSchema.parse({ ...envelope, free_shipping: true })).toThrow();
81
+ });
82
+ });
83
+
84
+ describe("checkout §3.3, §5: the gate the core runs at every gate", () => {
85
+ const ok = priceCart({ lines: [{ sku: "pacote-10-aulas", quantity: 1 }, { sku: "avaliacao-inicial", quantity: 1 }] }, envelope, TODAY);
86
+
87
+ it("passes an order that is the cart, at list price, inside the hours, due today", () => {
88
+ expect(checkOrder(order(ok), ok, envelope, AFTERNOON, TZ)).toBeUndefined();
89
+ });
90
+
91
+ it("refuses the discount that would reach an injected price (checkout §5.1), and never names the ceiling", () => {
92
+ const injected = priceCart({ lines: [{ sku: "avaliacao-inicial", quantity: 1, discount_pct: 88.88 }] }, envelope, TODAY);
93
+ expect(injected.total).toBe(1000);
94
+ const verdict = checkOrder(order(injected), injected, envelope, AFTERNOON, TZ);
95
+ expect(verdict?.reason).toBe("outside_envelope");
96
+ expect(verdict?.detail).not.toMatch(/12|%/);
97
+ });
98
+
99
+ it("refuses a line under the margin floor even inside the discount ceiling, without quoting a cost", () => {
100
+ // The package costs 300,00 to deliver: 5% off (370,50) is inside the 12% ceiling and below the 20% margin floor.
101
+ const thin = priceCart({ lines: [{ sku: "pacote-10-aulas", quantity: 1, discount_pct: 5 }] }, envelope, TODAY);
102
+ const verdict = checkOrder(order(thin), thin, envelope, AFTERNOON, TZ);
103
+ expect(verdict?.detail).toContain("piso de margem");
104
+ expect(verdict?.detail).not.toMatch(/300|3\.000|30000/);
105
+ const fine = priceCart({ lines: [{ sku: "aula-avulsa", quantity: 2, discount_pct: 10 }] }, envelope, TODAY);
106
+ expect(checkOrder(order(fine), fine, envelope, AFTERNOON, TZ)).toBeUndefined();
107
+ });
108
+
109
+ it("refuses a negotiated order discount above its ceiling, and a coupon stacked on a negotiated one", () => {
110
+ const over = priceCart({ lines: [{ sku: "aula-avulsa", quantity: 2 }], order_discount_pct: 9 }, envelope, TODAY);
111
+ expect(checkOrder(order(over), over, envelope, AFTERNOON, TZ)?.reason).toBe("outside_envelope");
112
+ const stacked = priceCart({ lines: [{ sku: "aula-avulsa", quantity: 2 }], coupon: "BEMVINDO10", order_discount_pct: 2 }, envelope, TODAY);
113
+ expect(checkOrder(order(stacked), stacked, envelope, AFTERNOON, TZ)?.detail).toContain("nao se somam");
114
+ const coupon = priceCart({ lines: [{ sku: "aula-avulsa", quantity: 2 }], coupon: "BEMVINDO10" }, envelope, TODAY);
115
+ expect(checkOrder(order(coupon), coupon, envelope, AFTERNOON, TZ)).toBeUndefined();
116
+ });
117
+
118
+ it("refuses an order that is not the current cart, a total that is not the cart's, and a missing cart", () => {
119
+ const changed = priceCart({ lines: [{ sku: "aula-avulsa", quantity: 5 }] }, envelope, TODAY);
120
+ expect(checkOrder(order(ok), changed, envelope, AFTERNOON, TZ)?.detail).toContain("carrinho mudou");
121
+ expect(checkOrder({ ...order(ok), total: 100, items: [{ ...order(ok).items[0]!, amount: 100 }] }, ok, envelope, AFTERNOON, TZ)?.reason).toBe("outside_envelope");
122
+ expect(checkOrder(order(ok), undefined, envelope, AFTERNOON, TZ)?.reason).toBe("outside_envelope");
123
+ const { composition: _c, ...bare } = order(ok);
124
+ expect(checkOrder(bare, ok, envelope, AFTERNOON, TZ)?.detail).toContain("nasce de um carrinho");
125
+ });
126
+
127
+ it("refuses outside the service hours in either mode (outside_hours), and a due date outside the window", () => {
128
+ expect(checkOrder(order(ok), ok, envelope, new Date("2026-09-23T23:30:00Z"), TZ)?.reason).toBe("outside_hours");
129
+ expect(checkOrder(order(ok, { due_date: "2026-09-22" }), ok, envelope, AFTERNOON, TZ)?.reason).toBe("outside_envelope");
130
+ expect(checkOrder(order(ok, { due_date: "2026-09-27" }), ok, envelope, AFTERNOON, TZ)?.reason).toBe("outside_envelope");
131
+ expect(checkOrder(order(ok, { due_date: "2026-09-26" }), ok, envelope, AFTERNOON, TZ)).toBeUndefined();
132
+ });
133
+
134
+ it("refuses an order above the ticket ceiling", () => {
135
+ const big = priceCart({ lines: [{ sku: "pacote-10-aulas", quantity: 6 }] }, envelope, TODAY);
136
+ expect(checkOrder(order(big), big, envelope, AFTERNOON, TZ)?.detail).toContain("ticket maximo");
137
+ });
138
+ });
@@ -0,0 +1,113 @@
1
+ import { existsSync, mkdtempSync, readFileSync, readdirSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { describe, expect, it } from "vitest";
5
+ import { ProofBundle } from "@codespar/agent-core";
6
+ import { assembleTimeline, checkScenario, listScenarios, loadScenario, renderText, runScenario, type ScenarioRun } from "@codespar/agent-runtime";
7
+ import { CUSTOMERS } from "../src/catalog.js";
8
+ import { agent } from "../src/kit.js";
9
+
10
+ const runsDir = mkdtempSync(join(tmpdir(), "checkout-runs-"));
11
+ const DOCUMENTS = CUSTOMERS.map((c) => c.document);
12
+ type Artifact = { execution_id: string; approver: { type: string }; items_hash: string; items: Array<{ amount: number; due_date?: string }>; composition?: { ref: string; composition_hash: string; line_count: number } };
13
+ const approvals = (run: ScenarioRun): Artifact[] => {
14
+ const path = join(run.bundle_dir, "approval.json");
15
+ return existsSync(path) ? (JSON.parse(readFileSync(path, "utf8")) as Artifact[]) : [];
16
+ };
17
+ const events = (run: ScenarioRun) => readFileSync(join(run.bundle_dir, "events.jsonl"), "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l) as { type: string; actor?: unknown; payload?: Record<string, unknown> });
18
+
19
+ describe("checkout §6: scenario packs, on the replay provider and the stub rail", () => {
20
+ const required = ["happy-path", "cart-replaced", "cart-recomposed", "price-injected", "coupon-unknown", "payment-claimed", "item-unavailable", "escalated-above-threshold", "charge-expired", "cap-exceeded", "beneficiary-not-allowed", "mandate-revoked", "prompt-injection", "nfse-failed"];
21
+
22
+ it("ships the fourteen scenarios of checkout §6", () => {
23
+ for (const name of required) expect(listScenarios(agent)).toContain(name);
24
+ });
25
+
26
+ for (const name of required) {
27
+ const scenario = loadScenario(agent, name);
28
+ for (const mode of scenario.modes) {
29
+ it(`${name} [${mode}] ends in the states the pack declares`, async () => {
30
+ const run = await runScenario(agent, scenario, { mode, runsDir });
31
+ const check = checkScenario(scenario, run);
32
+ expect(check.failures).toEqual([]);
33
+ expect(check.ok).toBe(true);
34
+ const all = events(run);
35
+ expect(all.length).toBeGreaterThan(0);
36
+ for (const e of all) expect(e.actor).toBeDefined();
37
+ for (const file of readdirSync(join(run.bundle_dir, "receipts"))) {
38
+ const receipt = JSON.parse(readFileSync(join(run.bundle_dir, "receipts", file), "utf8")) as { actor?: unknown; kind?: string; chain: string | null; payment: { payee: string | null; money_moved: boolean } };
39
+ expect(receipt.actor).toBeDefined();
40
+ expect(receipt.kind).toBe("charge");
41
+ expect(receipt.chain).toBeNull();
42
+ expect(receipt.payment.money_moved).toBe(false);
43
+ }
44
+ const snapshot = readFileSync(join(run.bundle_dir, "mandate.snapshot.json"), "utf8");
45
+ for (const doc of DOCUMENTS) expect(snapshot).not.toContain(doc);
46
+ // Every order that reached executing did so under an artifact that carries its composition, and that composition is the one it ran with.
47
+ for (const e of run.executions) {
48
+ if (!e.trail.includes("executing")) continue;
49
+ const artifact = approvals(run).filter((a) => a.execution_id === e.id).at(-1);
50
+ expect(artifact?.composition?.ref).toMatch(/\/cart-\d+$/);
51
+ }
52
+ });
53
+ }
54
+ }
55
+
56
+ it("happy-path: the same records in human and in mandate; the person decided in human, the policy in mandate; the order is due today", async () => {
57
+ const scenario = loadScenario(agent, "happy-path");
58
+ const human = await runScenario(agent, scenario, { mode: "human", runsDir });
59
+ const mandate = await runScenario(agent, scenario, { mode: "mandate", runsDir });
60
+ const [h] = approvals(human);
61
+ const [m] = approvals(mandate);
62
+ expect(h!.approver.type).toBe("person");
63
+ expect(m!.approver.type).toBe("mandate");
64
+ expect(h!.items_hash).toBe(m!.items_hash);
65
+ expect(h!.composition!.composition_hash).toBe(m!.composition!.composition_hash);
66
+ expect(h!.composition!.line_count).toBe(2);
67
+ expect(h!.items).toEqual([expect.objectContaining({ amount: 47990, due_date: "2026-09-23" })]);
68
+ for (const run of [human, mandate]) {
69
+ const all = events(run);
70
+ expect(all.filter((e) => e.type === "charge.instrument" && e.payload?.["payable"] === true)).toHaveLength(1);
71
+ expect(all.filter((e) => e.type === "message.debtor")).toHaveLength(1);
72
+ expect(all.filter((e) => e.type === "commerce.charge.paid")).toHaveLength(1);
73
+ expect(all.filter((e) => e.type === "rail.dispatch")).toHaveLength(1);
74
+ }
75
+ });
76
+
77
+ it("cart-recomposed: the two artifacts share items_hash and differ only in the composition, and one charge goes out", async () => {
78
+ for (const mode of ["human", "mandate"] as const) {
79
+ const run = await runScenario(agent, loadScenario(agent, "cart-recomposed"), { mode, runsDir });
80
+ const [first, second] = approvals(run);
81
+ expect(approvals(run)).toHaveLength(2);
82
+ expect(first!.items_hash).toBe(second!.items_hash);
83
+ expect(first!.composition!.composition_hash).not.toBe(second!.composition!.composition_hash);
84
+ expect(first!.composition!.ref).toBe(second!.composition!.ref);
85
+ expect(run.charges_issued).toBe(1);
86
+ const restated = events(run).filter((e) => e.type === "execution.restated");
87
+ expect(restated).toHaveLength(1);
88
+ expect(restated[0]!.payload!["items_hash"]).toMatchObject({ from: first!.items_hash, to: first!.items_hash });
89
+ }
90
+ });
91
+
92
+ it("npm run inspect reads the composition back from the artifact, beside items_hash", async () => {
93
+ const run = await runScenario(agent, loadScenario(agent, "happy-path"), { mode: "human", runsDir });
94
+ const text = renderText(assembleTimeline(new ProofBundle(runsDir, run.run_id)));
95
+ expect(text).toContain(`composed of 2 line(s) of ${run.run_id}/cart-1 · composition_hash ${approvals(run)[0]!.composition!.composition_hash}`);
96
+ });
97
+
98
+ it("cart-replaced: the order follows the cart, and the charge is for five lessons, never for two", async () => {
99
+ const run = await runScenario(agent, loadScenario(agent, "cart-replaced"), { mode: "human", runsDir });
100
+ const dispatched = events(run).filter((e) => e.type === "rail.dispatch").map((e) => e.payload?.["amount"]);
101
+ expect(dispatched).toEqual([50000]);
102
+ expect(approvals(run).map((a) => a.items[0]!.amount)).toEqual([20000, 50000]);
103
+ });
104
+
105
+ it("coupon-unknown and item-unavailable: the issue is named by code in the cart the model read", async () => {
106
+ for (const [name, code] of [["coupon-unknown", "coupon_unknown"], ["item-unavailable", "item_unavailable"]] as const) {
107
+ const run = await runScenario(agent, loadScenario(agent, name), { mode: "human", runsDir });
108
+ const replaced = events(run).filter((e) => e.type === "cart.replaced");
109
+ expect((replaced[0]!.payload!["validation_issues"] as Array<{ code: string }>).map((i) => i.code)).toEqual([code]);
110
+ expect(run.executions).toEqual([]);
111
+ }
112
+ });
113
+ });
@@ -0,0 +1,171 @@
1
+ /**
2
+ * The checkout-agent on WhatsApp, through the adapter the collections-agent
3
+ * already uses (checkout decision 6). The conversing cases need
4
+ * `dyvit-wa-sim` listening and SKIP when it is not, like the
5
+ * collections-agent's; `npm run whatsapp:gate` fails rather than skipping, and
6
+ * the CI runs it. What the binding and the templates refuse needs no emulator
7
+ * and runs everywhere.
8
+ */
9
+ import { spawnSync } from "node:child_process";
10
+ import { existsSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
11
+ import { tmpdir } from "node:os";
12
+ import { isAbsolute, join, resolve } from "node:path";
13
+ import { describe, expect, it } from "vitest";
14
+ import { parseConversationScript, parseTemplateRegistry, templateArity, type Execution } from "@codespar/agent-core";
15
+ import { handleExecution, setup } from "@codespar/agent-runtime";
16
+ import { agent } from "../src/kit.js";
17
+
18
+ const AGENT_DIR = resolve(import.meta.dirname, "..");
19
+ const NODE = process.execPath;
20
+ const BIN = resolve(AGENT_DIR, "../../packages/agent-runtime/bin.mjs");
21
+
22
+ interface ChannelLine {
23
+ direction: "in" | "out";
24
+ kind: string;
25
+ text?: string;
26
+ refused?: { rule: string };
27
+ }
28
+
29
+ interface Payload {
30
+ executions: Array<{ state: string; reason: string | null; charge_id: string | null }>;
31
+ invoices: Array<{ state: string }>;
32
+ receipts: string[];
33
+ channel: { backend: string; refused: Array<{ rule: string }>; log: string };
34
+ }
35
+
36
+ function start(args: string[], extra: Record<string, string> = {}) {
37
+ const stateDir = mkdtempSync(join(tmpdir(), "checkout-wa-"));
38
+ const result = spawnSync(NODE, [BIN, "start", ...args], {
39
+ cwd: AGENT_DIR,
40
+ env: {
41
+ ...process.env,
42
+ ANTHROPIC_API_KEY: "",
43
+ CODESPAR_API_KEY: "",
44
+ WHATSAPP_PHONE_NUMBER_ID: "",
45
+ WHATSAPP_ACCESS_TOKEN: "",
46
+ WHATSAPP_VERIFY_TOKEN: "",
47
+ WHATSAPP_APP_SECRET: "",
48
+ CHECKOUT_STATE_DIR: stateDir,
49
+ CHECKOUT_RUNS_DIR: join(stateDir, "runs"),
50
+ // A conversation of its own on the emulator (#42): no case inherits another's inbound messages, or the window they anchor.
51
+ WHATSAPP_SIM_PHONE_NUMBER_ID: `9${String(Math.floor(Math.random() * 1e11)).padStart(11, "0")}`,
52
+ ...extra,
53
+ },
54
+ encoding: "utf8",
55
+ timeout: 90_000,
56
+ });
57
+ return { code: result.status, stdout: result.stdout, stderr: result.stderr };
58
+ }
59
+
60
+ const payloadOf = (stdout: string) => JSON.parse(stdout.split("\n").filter(Boolean).pop()!) as Payload;
61
+ const conversationOf = (p: Payload) => {
62
+ const path = isAbsolute(p.channel.log) ? p.channel.log : join(AGENT_DIR, p.channel.log);
63
+ expect(existsSync(path)).toBe(true);
64
+ return readFileSync(path, "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l) as ChannelLine);
65
+ };
66
+
67
+ const EMULATOR = process.env["WHATSAPP_SIM_URL"] ?? "http://127.0.0.1:4290";
68
+ const emulatorUp = await (async () => {
69
+ try {
70
+ return (await fetch(`${EMULATOR}/health`, { signal: AbortSignal.timeout(2000) })).ok;
71
+ } catch {
72
+ return false;
73
+ }
74
+ })();
75
+ if (!emulatorUp) process.stderr.write(`[whatsapp] no emulator at ${EMULATOR}; the conversing cases are skipped. Start it with \`npm run whatsapp:emulator\`.\n`);
76
+
77
+ const NOW = ["--now", "2026-09-23T14:00:00-03:00"];
78
+
79
+ describe.skipIf(!emulatorUp)("the sale closes over the WhatsApp channel", () => {
80
+ it("mandate, nobody at a keyboard: settled, the QR then the copy-and-paste, 'pedido confirmado', and the NFS-e that follows it accepted", () => {
81
+ const out = start(["--channel", "whatsapp", "--conversation", "pedido-marina", "--scripted", "--mode", "mandate", "--simulate-payer", "--json", ...NOW]);
82
+ expect(out.code).toBe(0);
83
+ const p = payloadOf(out.stdout);
84
+ expect(p.executions.map((e) => e.state)).toEqual(["settled"]);
85
+ expect(p.invoices.map((i) => i.state)).toEqual(["accepted"]);
86
+ expect(p.channel.backend).toBe("emulator");
87
+ const outbound = conversationOf(p).filter((l) => l.direction === "out");
88
+ const qr = outbound.findIndex((l) => l.kind === "media");
89
+ expect(outbound[qr + 1]?.kind).toBe("instrument");
90
+ expect(outbound.filter((l) => l.kind === "text" && /pedido confirmado/i.test(l.text ?? ""))).not.toHaveLength(0);
91
+ // The invoice is the attendant's business, never the customer's.
92
+ expect(outbound.some((l) => /nota fiscal|nfs-e/i.test(l.text ?? ""))).toBe(false);
93
+ });
94
+
95
+ it("human: the attendant's question goes to the console and never into the conversation", () => {
96
+ const out = start(["--channel", "whatsapp", "--conversation", "pedido-marina", "--scripted", "--mode", "human", "--approve", "--simulate-payer", "--json", ...NOW]);
97
+ expect(out.code).toBe(0);
98
+ const p = payloadOf(out.stdout);
99
+ expect(p.executions.map((e) => e.state)).toEqual(["settled"]);
100
+ expect(conversationOf(p).some((l) => /atendente|Confirmar este pedido/i.test(l.text ?? ""))).toBe(false);
101
+ });
102
+
103
+ it("checkout §5.4 on the channel: 'ja paguei' moves nothing, the order stays open and the customer hears no confirmation", () => {
104
+ const out = start(["--channel", "whatsapp", "--conversation", "pedido-beatriz", "--scripted", "--mode", "mandate", "--json", ...NOW], { CHECKOUT_STUB_PAYER: "never" });
105
+ expect(out.code).toBe(3);
106
+ const p = payloadOf(out.stdout);
107
+ expect(p.executions).toEqual([expect.objectContaining({ state: "executing", reason: "awaiting_settlement" })]);
108
+ expect(p.invoices).toEqual([]);
109
+ expect(conversationOf(p).some((l) => l.direction === "out" && /pedido confirmado|recebemos/i.test(l.text ?? ""))).toBe(false);
110
+ });
111
+ });
112
+
113
+ describe.skipIf(!emulatorUp)("§46: a tap is a turn on the checkout-agent too", () => {
114
+ it("a tapped 'Falar agora' on the fallback template is handed to the model as the intent the template declared", () => {
115
+ const out = start(["--channel", "whatsapp", "--conversation", "pedido-marina-retomada", "--scripted", "--mode", "mandate", "--transcript", "test/fixtures/tap-falar-agora-marina.transcript.jsonl", "--json", ...NOW]);
116
+ expect(out.code).toBe(0);
117
+ const p = JSON.parse(out.stdout.split("\n").filter(Boolean).pop()!) as { channel: { taps: unknown[]; log: string } };
118
+ expect(p.channel.taps).toEqual([{ id: "falar_agora", turn: true }]);
119
+ const tap = conversationOf(p as unknown as Payload).find((l) => l.kind === "reply") as ChannelLine & { reply?: { id: string } };
120
+ expect(tap.text).toBe("quero saber qual e a atualizacao do meu atendimento");
121
+ });
122
+ });
123
+
124
+ describe("the conversation decides who is charged, with no emulator in sight", () => {
125
+ it("an order in Marina's conversation for another customer of the store is refused before it exists", async () => {
126
+ const dir = mkdtempSync(join(tmpdir(), "checkout-wa-bind-"));
127
+ const transcript = join(dir, "swap.transcript.jsonl");
128
+ writeFileSync(
129
+ transcript,
130
+ [
131
+ { kind: "assistant_step", tool_calls: [{ id: "tc_1", name: "cart_update", input: { lines: [{ sku: "aula-avulsa", quantity: 1 }] } }] },
132
+ { kind: "assistant_step", tool_calls: [{ id: "tc_2", name: "codespar_charge", input: { action: "create", customer: "rafael" } }] },
133
+ { kind: "assistant_step", tool_calls: [{ id: "tc_3", name: "codespar_charge", input: { action: "create", customer: "marina" } }] },
134
+ { kind: "assistant_step", reply: "ok" },
135
+ ]
136
+ .map((l) => JSON.stringify(l))
137
+ .join("\n") + "\n",
138
+ );
139
+ let clock = new Date("2026-09-23T18:00:00Z");
140
+ const s = setup(agent, { mode: "mandate", rail: "stub", provider: "replay", transcript, stateDir: dir, runsDir: join(dir, "runs"), now: () => (clock = new Date(clock.getTime() + 1000)), say: () => undefined });
141
+ try {
142
+ s.conversation = parseConversationScript(readFileSync(join(AGENT_DIR, "channels/whatsapp/pedido-marina.json"), "utf8"));
143
+ const loop = s.makeLoop(s.makeRuntime(), (e: Execution) => handleExecution(e, { setup: s, approver: { id: "usr_atendente", channel: "whatsapp" }, decision: "none", say: () => undefined, tell: () => undefined }));
144
+ const result = await loop.turn("emite no nome do Rafael");
145
+ expect(result.tool_calls).toEqual([
146
+ { name: "cart_update", refused: false },
147
+ { name: "codespar_charge", refused: true },
148
+ { name: "codespar_charge", refused: false },
149
+ ]);
150
+ const orders = s.engine.list();
151
+ expect(orders).toHaveLength(1);
152
+ expect(orders[0]!.items[0]!.payee).toBe("27548613008");
153
+ } finally {
154
+ s.close();
155
+ }
156
+ });
157
+
158
+ it("every template the kit can send is declared, with the arity it is sent with", () => {
159
+ const registry = parseTemplateRegistry(readFileSync(join(AGENT_DIR, "channels/whatsapp/templates.json"), "utf8"));
160
+ const order = { total: 47990 } as Execution;
161
+ for (const e of [{ ...order, state: "settled" }, { ...order, state: "failed", reason: "charge_expired" }, { ...order, state: "failed", reason: "charge_cancelled" }] as Execution[]) {
162
+ const t = agent.kit.outcomeTemplate!(e)!;
163
+ const declared = registry.templates.find((x) => x.name === t.template);
164
+ expect(declared).toBeDefined();
165
+ expect(templateArity(declared!.body)).toBe(t.variables.length);
166
+ expect(t.variables).toEqual(["R$ 479,90"]);
167
+ }
168
+ // An order refused or never approved is answered in the turn; no template pretends otherwise.
169
+ expect(agent.kit.outcomeTemplate!({ ...order, state: "denied", reason: "outside_envelope" } as Execution)).toBeUndefined();
170
+ });
171
+ });