@codespar/cli 0.17.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 (480) hide show
  1. package/README.md +1 -0
  2. package/package.json +1 -1
  3. package/templates/bills-agent/README.md +1 -1
  4. package/templates/bills-agent/agents/bills-agent/AGENTS.md +1 -1
  5. package/templates/bills-agent/agents/bills-agent/CLAUDE.md +1 -1
  6. package/templates/bills-agent/agents/bills-agent/README.md +6 -5
  7. package/templates/bills-agent/agents/bills-agent/src/modules/embedded-consent.ts +5 -4
  8. package/templates/bills-agent/packages/agent-core/README.md +14 -1
  9. package/templates/bills-agent/packages/agent-core/package.json +1 -1
  10. package/templates/bills-agent/packages/agent-core/src/agent.ts +39 -1
  11. package/templates/bills-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
  12. package/templates/bills-agent/packages/agent-core/src/api/client.ts +28 -5
  13. package/templates/bills-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
  14. package/templates/bills-agent/packages/agent-core/src/api/rail.ts +62 -26
  15. package/templates/bills-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
  16. package/templates/bills-agent/packages/agent-core/src/approval.ts +14 -2
  17. package/templates/bills-agent/packages/agent-core/src/bundle.ts +35 -3
  18. package/templates/bills-agent/packages/agent-core/src/channels.ts +49 -3
  19. package/templates/bills-agent/packages/agent-core/src/check.ts +25 -2
  20. package/templates/bills-agent/packages/agent-core/src/engine.ts +235 -46
  21. package/templates/bills-agent/packages/agent-core/src/hash.ts +57 -1
  22. package/templates/bills-agent/packages/agent-core/src/index.ts +2 -0
  23. package/templates/bills-agent/packages/agent-core/src/quote.ts +50 -0
  24. package/templates/bills-agent/packages/agent-core/src/rail.ts +89 -4
  25. package/templates/bills-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  26. package/templates/bills-agent/packages/agent-core/src/receipt-verification.ts +194 -18
  27. package/templates/bills-agent/packages/agent-core/src/revocation.ts +1 -1
  28. package/templates/bills-agent/packages/agent-core/src/state-machine.ts +10 -1
  29. package/templates/bills-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
  30. package/templates/bills-agent/packages/agent-core/src/stubs/rail.ts +100 -32
  31. package/templates/bills-agent/packages/agent-core/src/tools.ts +17 -6
  32. package/templates/bills-agent/packages/agent-core/src/types.ts +54 -1
  33. package/templates/bills-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  34. package/templates/bills-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  35. package/templates/bills-agent/packages/agent-core/test/composition.test.ts +204 -0
  36. package/templates/bills-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  37. package/templates/bills-agent/packages/agent-core/test/helpers.ts +4 -1
  38. package/templates/bills-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
  39. package/templates/bills-agent/packages/agent-core/test/quote.test.ts +198 -0
  40. package/templates/bills-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  41. package/templates/bills-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  42. package/templates/bills-agent/packages/agent-core/test/receivables.test.ts +103 -3
  43. package/templates/bills-agent/packages/agent-runtime/README.md +12 -0
  44. package/templates/bills-agent/packages/agent-runtime/src/adversarial.ts +32 -2
  45. package/templates/bills-agent/packages/agent-runtime/src/channels/types.ts +53 -4
  46. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
  47. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
  48. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
  49. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
  50. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
  51. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
  52. package/templates/bills-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
  53. package/templates/bills-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
  54. package/templates/bills-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
  55. package/templates/bills-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
  56. package/templates/bills-agent/packages/agent-runtime/src/commands/start.ts +6 -3
  57. package/templates/bills-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
  58. package/templates/bills-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
  59. package/templates/bills-agent/packages/agent-runtime/src/index.ts +1 -0
  60. package/templates/bills-agent/packages/agent-runtime/src/inspect.ts +5 -1
  61. package/templates/bills-agent/packages/agent-runtime/src/kit.ts +25 -2
  62. package/templates/bills-agent/packages/agent-runtime/src/scenarios.ts +8 -2
  63. package/templates/bills-agent/packages/agent-runtime/src/setup.ts +13 -3
  64. package/templates/bills-agent/packages/agent-runtime/src/terminal.ts +127 -18
  65. package/templates/bills-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
  66. package/templates/bills-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
  67. package/templates/bills-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
  68. package/templates/bills-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  69. package/templates/checkout-agent/LICENSE +21 -0
  70. package/templates/checkout-agent/README.md +28 -0
  71. package/templates/checkout-agent/_gitignore +10 -0
  72. package/templates/checkout-agent/agents/checkout-agent/.env.example +5 -0
  73. package/templates/checkout-agent/agents/checkout-agent/AGENTS.md +20 -0
  74. package/templates/checkout-agent/agents/checkout-agent/CLAUDE.md +20 -0
  75. package/templates/checkout-agent/agents/checkout-agent/README.md +94 -0
  76. package/templates/checkout-agent/agents/checkout-agent/SYSTEM_PROMPT.md +36 -0
  77. package/templates/checkout-agent/agents/checkout-agent/agent.yaml +30 -0
  78. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/README.md +28 -0
  79. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/pedido-beatriz.json +11 -0
  80. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/pedido-marina-retomada.json +10 -0
  81. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/pedido-marina.json +11 -0
  82. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/templates.json +49 -0
  83. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/beneficiary-swap-human.json +12 -0
  84. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/beneficiary-swap.json +11 -0
  85. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/beneficiary-swap.transcript.jsonl +5 -0
  86. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/coupon-unknown.json +13 -0
  87. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/coupon-unknown.transcript.jsonl +4 -0
  88. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/exfiltration.json +26 -0
  89. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/exfiltration.transcript.jsonl +3 -0
  90. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/false-authority.json +11 -0
  91. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/false-authority.transcript.jsonl +5 -0
  92. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/fractioning.json +12 -0
  93. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/fractioning.transcript.jsonl +5 -0
  94. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/model-total.json +13 -0
  95. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/model-total.transcript.jsonl +5 -0
  96. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/payment-claimed.json +15 -0
  97. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/payment-claimed.transcript.jsonl +7 -0
  98. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/price-injected.json +12 -0
  99. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/price-injected.transcript.jsonl +5 -0
  100. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/prompt-injection.json +13 -0
  101. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/prompt-injection.transcript.jsonl +4 -0
  102. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped-human.json +16 -0
  103. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped-human.transcript.jsonl +9 -0
  104. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped.json +15 -0
  105. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped.transcript.jsonl +8 -0
  106. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/webhook-replay.json +11 -0
  107. package/templates/checkout-agent/agents/checkout-agent/evals/eval.yaml +11 -0
  108. package/templates/checkout-agent/agents/checkout-agent/guardrails.json +25 -0
  109. package/templates/checkout-agent/agents/checkout-agent/mandate.example.json +28 -0
  110. package/templates/checkout-agent/agents/checkout-agent/package.json +33 -0
  111. package/templates/checkout-agent/agents/checkout-agent/runbook.md +19 -0
  112. package/templates/checkout-agent/agents/checkout-agent/scenarios/beneficiary-not-allowed.json +42 -0
  113. package/templates/checkout-agent/agents/checkout-agent/scenarios/beneficiary-not-allowed.transcript.jsonl +4 -0
  114. package/templates/checkout-agent/agents/checkout-agent/scenarios/cap-exceeded.json +32 -0
  115. package/templates/checkout-agent/agents/checkout-agent/scenarios/cap-exceeded.transcript.jsonl +4 -0
  116. package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-recomposed.json +61 -0
  117. package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-recomposed.transcript.jsonl +9 -0
  118. package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-replaced.json +61 -0
  119. package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-replaced.transcript.jsonl +9 -0
  120. package/templates/checkout-agent/agents/checkout-agent/scenarios/charge-expired.json +36 -0
  121. package/templates/checkout-agent/agents/checkout-agent/scenarios/charge-expired.transcript.jsonl +6 -0
  122. package/templates/checkout-agent/agents/checkout-agent/scenarios/coupon-unknown.json +23 -0
  123. package/templates/checkout-agent/agents/checkout-agent/scenarios/coupon-unknown.transcript.jsonl +4 -0
  124. package/templates/checkout-agent/agents/checkout-agent/scenarios/escalated-above-threshold.json +72 -0
  125. package/templates/checkout-agent/agents/checkout-agent/scenarios/escalated-above-threshold.transcript.jsonl +9 -0
  126. package/templates/checkout-agent/agents/checkout-agent/scenarios/happy-path.json +15 -0
  127. package/templates/checkout-agent/agents/checkout-agent/scenarios/happy-path.transcript.jsonl +6 -0
  128. package/templates/checkout-agent/agents/checkout-agent/scenarios/item-unavailable.json +23 -0
  129. package/templates/checkout-agent/agents/checkout-agent/scenarios/item-unavailable.transcript.jsonl +3 -0
  130. package/templates/checkout-agent/agents/checkout-agent/scenarios/mandate-revoked.json +42 -0
  131. package/templates/checkout-agent/agents/checkout-agent/scenarios/mandate-revoked.transcript.jsonl +12 -0
  132. package/templates/checkout-agent/agents/checkout-agent/scenarios/nfse-failed.json +14 -0
  133. package/templates/checkout-agent/agents/checkout-agent/scenarios/nfse-failed.transcript.jsonl +9 -0
  134. package/templates/checkout-agent/agents/checkout-agent/scenarios/payment-claimed.json +34 -0
  135. package/templates/checkout-agent/agents/checkout-agent/scenarios/payment-claimed.transcript.jsonl +7 -0
  136. package/templates/checkout-agent/agents/checkout-agent/scenarios/price-injected.json +33 -0
  137. package/templates/checkout-agent/agents/checkout-agent/scenarios/price-injected.transcript.jsonl +4 -0
  138. package/templates/checkout-agent/agents/checkout-agent/scenarios/prompt-injection.json +32 -0
  139. package/templates/checkout-agent/agents/checkout-agent/scenarios/prompt-injection.transcript.jsonl +4 -0
  140. package/templates/checkout-agent/agents/checkout-agent/src/catalog.ts +92 -0
  141. package/templates/checkout-agent/agents/checkout-agent/src/kit.ts +338 -0
  142. package/templates/checkout-agent/agents/checkout-agent/src/modules/bolepix-receivables.ts +179 -0
  143. package/templates/checkout-agent/agents/checkout-agent/src/modules/nfse-invoice.ts +374 -0
  144. package/templates/checkout-agent/agents/checkout-agent/src/modules/storefront-cart.ts +220 -0
  145. package/templates/checkout-agent/agents/checkout-agent/src/pricing.ts +297 -0
  146. package/templates/checkout-agent/agents/checkout-agent/test/adversarial.test.ts +117 -0
  147. package/templates/checkout-agent/agents/checkout-agent/test/check.test.ts +67 -0
  148. package/templates/checkout-agent/agents/checkout-agent/test/cli.test.ts +169 -0
  149. package/templates/checkout-agent/agents/checkout-agent/test/fixtures/order-marina.transcript.jsonl +5 -0
  150. package/templates/checkout-agent/agents/checkout-agent/test/fixtures/ordered-marina-awaiting-payer.transcript.jsonl +6 -0
  151. package/templates/checkout-agent/agents/checkout-agent/test/fixtures/tap-falar-agora-marina.transcript.jsonl +2 -0
  152. package/templates/checkout-agent/agents/checkout-agent/test/nfse.test.ts +170 -0
  153. package/templates/checkout-agent/agents/checkout-agent/test/pricing.test.ts +138 -0
  154. package/templates/checkout-agent/agents/checkout-agent/test/scenarios.test.ts +113 -0
  155. package/templates/checkout-agent/agents/checkout-agent/test/whatsapp.test.ts +171 -0
  156. package/templates/checkout-agent/agents/checkout-agent/tools.json +68 -0
  157. package/templates/checkout-agent/agents/checkout-agent/tsconfig.json +10 -0
  158. package/templates/checkout-agent/package.json +38 -0
  159. package/templates/checkout-agent/packages/agent-core/README.md +34 -0
  160. package/templates/checkout-agent/packages/agent-core/package.json +24 -0
  161. package/templates/checkout-agent/packages/agent-core/src/agent.ts +163 -0
  162. package/templates/checkout-agent/packages/agent-core/src/api/charge-rail.ts +195 -0
  163. package/templates/checkout-agent/packages/agent-core/src/api/client.ts +76 -0
  164. package/templates/checkout-agent/packages/agent-core/src/api/mandate-status.ts +74 -0
  165. package/templates/checkout-agent/packages/agent-core/src/api/rail.ts +153 -0
  166. package/templates/checkout-agent/packages/agent-core/src/api/sandbox-payer.ts +43 -0
  167. package/templates/checkout-agent/packages/agent-core/src/approval.ts +135 -0
  168. package/templates/checkout-agent/packages/agent-core/src/bundle.ts +191 -0
  169. package/templates/checkout-agent/packages/agent-core/src/channels.ts +186 -0
  170. package/templates/checkout-agent/packages/agent-core/src/check.ts +276 -0
  171. package/templates/checkout-agent/packages/agent-core/src/clock.ts +42 -0
  172. package/templates/checkout-agent/packages/agent-core/src/engine.ts +1074 -0
  173. package/templates/checkout-agent/packages/agent-core/src/escalate.ts +85 -0
  174. package/templates/checkout-agent/packages/agent-core/src/events.ts +40 -0
  175. package/templates/checkout-agent/packages/agent-core/src/guardrails.ts +45 -0
  176. package/templates/checkout-agent/packages/agent-core/src/hash.ts +118 -0
  177. package/templates/checkout-agent/packages/agent-core/src/ids.ts +10 -0
  178. package/templates/checkout-agent/packages/agent-core/src/index.ts +34 -0
  179. package/templates/checkout-agent/packages/agent-core/src/mandate.ts +100 -0
  180. package/templates/checkout-agent/packages/agent-core/src/manifest.ts +76 -0
  181. package/templates/checkout-agent/packages/agent-core/src/providers/anthropic.ts +76 -0
  182. package/templates/checkout-agent/packages/agent-core/src/providers/replay.ts +63 -0
  183. package/templates/checkout-agent/packages/agent-core/src/providers/types.ts +39 -0
  184. package/templates/checkout-agent/packages/agent-core/src/quote.ts +50 -0
  185. package/templates/checkout-agent/packages/agent-core/src/rail.ts +186 -0
  186. package/templates/checkout-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  187. package/templates/checkout-agent/packages/agent-core/src/receipt-verification.ts +577 -0
  188. package/templates/checkout-agent/packages/agent-core/src/revocation.ts +30 -0
  189. package/templates/checkout-agent/packages/agent-core/src/secrets.ts +30 -0
  190. package/templates/checkout-agent/packages/agent-core/src/state/store.ts +307 -0
  191. package/templates/checkout-agent/packages/agent-core/src/state-machine.ts +157 -0
  192. package/templates/checkout-agent/packages/agent-core/src/stubs/charge-rail.ts +195 -0
  193. package/templates/checkout-agent/packages/agent-core/src/stubs/mandate-status.ts +58 -0
  194. package/templates/checkout-agent/packages/agent-core/src/stubs/rail.ts +209 -0
  195. package/templates/checkout-agent/packages/agent-core/src/tools.ts +66 -0
  196. package/templates/checkout-agent/packages/agent-core/src/types.ts +187 -0
  197. package/templates/checkout-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  198. package/templates/checkout-agent/packages/agent-core/test/approval.test.ts +117 -0
  199. package/templates/checkout-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  200. package/templates/checkout-agent/packages/agent-core/test/bundle.test.ts +85 -0
  201. package/templates/checkout-agent/packages/agent-core/test/clock.test.ts +25 -0
  202. package/templates/checkout-agent/packages/agent-core/test/composition.test.ts +204 -0
  203. package/templates/checkout-agent/packages/agent-core/test/engine.test.ts +596 -0
  204. package/templates/checkout-agent/packages/agent-core/test/escalate.test.ts +42 -0
  205. package/templates/checkout-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  206. package/templates/checkout-agent/packages/agent-core/test/fixtures/staging-receipt-keys.json +18 -0
  207. package/templates/checkout-agent/packages/agent-core/test/fixtures/staging-receipt.json +24 -0
  208. package/templates/checkout-agent/packages/agent-core/test/helpers.ts +142 -0
  209. package/templates/checkout-agent/packages/agent-core/test/mandate-status.test.ts +229 -0
  210. package/templates/checkout-agent/packages/agent-core/test/manifest.test.ts +45 -0
  211. package/templates/checkout-agent/packages/agent-core/test/quote.test.ts +198 -0
  212. package/templates/checkout-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  213. package/templates/checkout-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  214. package/templates/checkout-agent/packages/agent-core/test/receipt-verification.test.ts +316 -0
  215. package/templates/checkout-agent/packages/agent-core/test/receivables.test.ts +416 -0
  216. package/templates/checkout-agent/packages/agent-core/test/secrets.test.ts +28 -0
  217. package/templates/checkout-agent/packages/agent-core/test/state-machine.test.ts +67 -0
  218. package/templates/checkout-agent/packages/agent-core/test/transitions.type-test.ts +59 -0
  219. package/templates/checkout-agent/packages/agent-core/tsconfig.json +10 -0
  220. package/templates/checkout-agent/packages/agent-runtime/README.md +80 -0
  221. package/templates/checkout-agent/packages/agent-runtime/bin.mjs +32 -0
  222. package/templates/checkout-agent/packages/agent-runtime/package.json +23 -0
  223. package/templates/checkout-agent/packages/agent-runtime/src/adversarial.ts +199 -0
  224. package/templates/checkout-agent/packages/agent-runtime/src/agent.ts +68 -0
  225. package/templates/checkout-agent/packages/agent-runtime/src/channels/contact.ts +13 -0
  226. package/templates/checkout-agent/packages/agent-runtime/src/channels/index.ts +72 -0
  227. package/templates/checkout-agent/packages/agent-runtime/src/channels/rules.ts +120 -0
  228. package/templates/checkout-agent/packages/agent-runtime/src/channels/types.ts +173 -0
  229. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +459 -0
  230. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +216 -0
  231. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/evidence.ts +109 -0
  232. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +350 -0
  233. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +180 -0
  234. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/present.ts +47 -0
  235. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +120 -0
  236. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +127 -0
  237. package/templates/checkout-agent/packages/agent-runtime/src/cli.ts +98 -0
  238. package/templates/checkout-agent/packages/agent-runtime/src/commands/check.ts +15 -0
  239. package/templates/checkout-agent/packages/agent-runtime/src/commands/decide.ts +63 -0
  240. package/templates/checkout-agent/packages/agent-runtime/src/commands/eval.ts +58 -0
  241. package/templates/checkout-agent/packages/agent-runtime/src/commands/inspect.ts +100 -0
  242. package/templates/checkout-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +367 -0
  243. package/templates/checkout-agent/packages/agent-runtime/src/commands/poll.ts +145 -0
  244. package/templates/checkout-agent/packages/agent-runtime/src/commands/reconcile.ts +42 -0
  245. package/templates/checkout-agent/packages/agent-runtime/src/commands/rerun.ts +86 -0
  246. package/templates/checkout-agent/packages/agent-runtime/src/commands/resume.ts +49 -0
  247. package/templates/checkout-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +134 -0
  248. package/templates/checkout-agent/packages/agent-runtime/src/commands/start.ts +248 -0
  249. package/templates/checkout-agent/packages/agent-runtime/src/commands/verify.ts +330 -0
  250. package/templates/checkout-agent/packages/agent-runtime/src/commands/webhook.ts +40 -0
  251. package/templates/checkout-agent/packages/agent-runtime/src/default-kit.ts +83 -0
  252. package/templates/checkout-agent/packages/agent-runtime/src/index.ts +13 -0
  253. package/templates/checkout-agent/packages/agent-runtime/src/inspect.ts +801 -0
  254. package/templates/checkout-agent/packages/agent-runtime/src/kit.ts +229 -0
  255. package/templates/checkout-agent/packages/agent-runtime/src/poll.ts +105 -0
  256. package/templates/checkout-agent/packages/agent-runtime/src/scenarios.ts +274 -0
  257. package/templates/checkout-agent/packages/agent-runtime/src/setup.ts +239 -0
  258. package/templates/checkout-agent/packages/agent-runtime/src/terminal.ts +263 -0
  259. package/templates/checkout-agent/packages/agent-runtime/src/webhook.ts +131 -0
  260. package/templates/checkout-agent/packages/agent-runtime/test/inspect.test.ts +336 -0
  261. package/templates/checkout-agent/packages/agent-runtime/test/verify-cli.test.ts +329 -0
  262. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +277 -0
  263. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-evidence.test.ts +94 -0
  264. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-rules.test.ts +174 -0
  265. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-session.test.ts +167 -0
  266. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  267. package/templates/checkout-agent/packages/agent-runtime/tsconfig.json +9 -0
  268. package/templates/checkout-agent/scripts/whatsapp-emulator.mjs +84 -0
  269. package/templates/checkout-agent/tsconfig.base.json +16 -0
  270. package/templates/checkout-agent/vitest.config.ts +12 -0
  271. package/templates/collections-agent/README.md +6 -1
  272. package/templates/collections-agent/agents/collections-agent/README.md +12 -8
  273. package/templates/collections-agent/agents/collections-agent/channels/whatsapp/README.md +8 -0
  274. package/templates/collections-agent/agents/collections-agent/channels/whatsapp/acordo-1042-retomada.json +10 -0
  275. package/templates/collections-agent/agents/collections-agent/channels/whatsapp/templates.json +27 -1
  276. package/templates/collections-agent/agents/collections-agent/src/kit.ts +2 -0
  277. package/templates/collections-agent/agents/collections-agent/src/modules/bolepix-receivables.ts +12 -2
  278. package/templates/collections-agent/agents/collections-agent/test/ambiguous-charge.test.ts +55 -0
  279. package/templates/collections-agent/agents/collections-agent/test/fixtures/tap-emitir-nova-1042.transcript.jsonl +2 -0
  280. package/templates/collections-agent/agents/collections-agent/test/whatsapp-poll.test.ts +12 -6
  281. package/templates/collections-agent/agents/collections-agent/test/whatsapp-status-replies.test.ts +148 -0
  282. package/templates/collections-agent/agents/collections-agent/test/whatsapp.test.ts +5 -0
  283. package/templates/collections-agent/package.json +1 -0
  284. package/templates/collections-agent/packages/agent-core/README.md +14 -1
  285. package/templates/collections-agent/packages/agent-core/package.json +1 -1
  286. package/templates/collections-agent/packages/agent-core/src/agent.ts +39 -1
  287. package/templates/collections-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
  288. package/templates/collections-agent/packages/agent-core/src/api/client.ts +28 -5
  289. package/templates/collections-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
  290. package/templates/collections-agent/packages/agent-core/src/api/rail.ts +62 -26
  291. package/templates/collections-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
  292. package/templates/collections-agent/packages/agent-core/src/approval.ts +14 -2
  293. package/templates/collections-agent/packages/agent-core/src/bundle.ts +35 -3
  294. package/templates/collections-agent/packages/agent-core/src/channels.ts +49 -3
  295. package/templates/collections-agent/packages/agent-core/src/check.ts +25 -2
  296. package/templates/collections-agent/packages/agent-core/src/engine.ts +235 -46
  297. package/templates/collections-agent/packages/agent-core/src/hash.ts +57 -1
  298. package/templates/collections-agent/packages/agent-core/src/index.ts +2 -0
  299. package/templates/collections-agent/packages/agent-core/src/quote.ts +50 -0
  300. package/templates/collections-agent/packages/agent-core/src/rail.ts +89 -4
  301. package/templates/collections-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  302. package/templates/collections-agent/packages/agent-core/src/receipt-verification.ts +194 -18
  303. package/templates/collections-agent/packages/agent-core/src/revocation.ts +1 -1
  304. package/templates/collections-agent/packages/agent-core/src/state-machine.ts +10 -1
  305. package/templates/collections-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
  306. package/templates/collections-agent/packages/agent-core/src/stubs/rail.ts +100 -32
  307. package/templates/collections-agent/packages/agent-core/src/tools.ts +17 -6
  308. package/templates/collections-agent/packages/agent-core/src/types.ts +54 -1
  309. package/templates/collections-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  310. package/templates/collections-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  311. package/templates/collections-agent/packages/agent-core/test/composition.test.ts +204 -0
  312. package/templates/collections-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  313. package/templates/collections-agent/packages/agent-core/test/helpers.ts +4 -1
  314. package/templates/collections-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
  315. package/templates/collections-agent/packages/agent-core/test/quote.test.ts +198 -0
  316. package/templates/collections-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  317. package/templates/collections-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  318. package/templates/collections-agent/packages/agent-core/test/receivables.test.ts +103 -3
  319. package/templates/collections-agent/packages/agent-runtime/README.md +12 -0
  320. package/templates/collections-agent/packages/agent-runtime/src/adversarial.ts +32 -2
  321. package/templates/collections-agent/packages/agent-runtime/src/channels/types.ts +53 -4
  322. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
  323. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
  324. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
  325. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
  326. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
  327. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
  328. package/templates/collections-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
  329. package/templates/collections-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
  330. package/templates/collections-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
  331. package/templates/collections-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
  332. package/templates/collections-agent/packages/agent-runtime/src/commands/start.ts +6 -3
  333. package/templates/collections-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
  334. package/templates/collections-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
  335. package/templates/collections-agent/packages/agent-runtime/src/index.ts +1 -0
  336. package/templates/collections-agent/packages/agent-runtime/src/inspect.ts +5 -1
  337. package/templates/collections-agent/packages/agent-runtime/src/kit.ts +25 -2
  338. package/templates/collections-agent/packages/agent-runtime/src/scenarios.ts +8 -2
  339. package/templates/collections-agent/packages/agent-runtime/src/setup.ts +13 -3
  340. package/templates/collections-agent/packages/agent-runtime/src/terminal.ts +127 -18
  341. package/templates/collections-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
  342. package/templates/collections-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
  343. package/templates/collections-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
  344. package/templates/collections-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  345. package/templates/collections-agent/scripts/whatsapp-emulator.mjs +84 -0
  346. package/templates/hello-agent/README.md +1 -1
  347. package/templates/hello-agent/packages/agent-core/README.md +14 -1
  348. package/templates/hello-agent/packages/agent-core/package.json +1 -1
  349. package/templates/hello-agent/packages/agent-core/src/agent.ts +39 -1
  350. package/templates/hello-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
  351. package/templates/hello-agent/packages/agent-core/src/api/client.ts +28 -5
  352. package/templates/hello-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
  353. package/templates/hello-agent/packages/agent-core/src/api/rail.ts +62 -26
  354. package/templates/hello-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
  355. package/templates/hello-agent/packages/agent-core/src/approval.ts +14 -2
  356. package/templates/hello-agent/packages/agent-core/src/bundle.ts +35 -3
  357. package/templates/hello-agent/packages/agent-core/src/channels.ts +49 -3
  358. package/templates/hello-agent/packages/agent-core/src/check.ts +25 -2
  359. package/templates/hello-agent/packages/agent-core/src/engine.ts +235 -46
  360. package/templates/hello-agent/packages/agent-core/src/hash.ts +57 -1
  361. package/templates/hello-agent/packages/agent-core/src/index.ts +2 -0
  362. package/templates/hello-agent/packages/agent-core/src/quote.ts +50 -0
  363. package/templates/hello-agent/packages/agent-core/src/rail.ts +89 -4
  364. package/templates/hello-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  365. package/templates/hello-agent/packages/agent-core/src/receipt-verification.ts +194 -18
  366. package/templates/hello-agent/packages/agent-core/src/revocation.ts +1 -1
  367. package/templates/hello-agent/packages/agent-core/src/state-machine.ts +10 -1
  368. package/templates/hello-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
  369. package/templates/hello-agent/packages/agent-core/src/stubs/rail.ts +100 -32
  370. package/templates/hello-agent/packages/agent-core/src/tools.ts +17 -6
  371. package/templates/hello-agent/packages/agent-core/src/types.ts +54 -1
  372. package/templates/hello-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  373. package/templates/hello-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  374. package/templates/hello-agent/packages/agent-core/test/composition.test.ts +204 -0
  375. package/templates/hello-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  376. package/templates/hello-agent/packages/agent-core/test/helpers.ts +4 -1
  377. package/templates/hello-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
  378. package/templates/hello-agent/packages/agent-core/test/quote.test.ts +198 -0
  379. package/templates/hello-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  380. package/templates/hello-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  381. package/templates/hello-agent/packages/agent-core/test/receivables.test.ts +103 -3
  382. package/templates/hello-agent/packages/agent-runtime/README.md +12 -0
  383. package/templates/hello-agent/packages/agent-runtime/src/adversarial.ts +32 -2
  384. package/templates/hello-agent/packages/agent-runtime/src/channels/types.ts +53 -4
  385. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
  386. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
  387. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
  388. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
  389. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
  390. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
  391. package/templates/hello-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
  392. package/templates/hello-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
  393. package/templates/hello-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
  394. package/templates/hello-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
  395. package/templates/hello-agent/packages/agent-runtime/src/commands/start.ts +6 -3
  396. package/templates/hello-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
  397. package/templates/hello-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
  398. package/templates/hello-agent/packages/agent-runtime/src/index.ts +1 -0
  399. package/templates/hello-agent/packages/agent-runtime/src/inspect.ts +5 -1
  400. package/templates/hello-agent/packages/agent-runtime/src/kit.ts +25 -2
  401. package/templates/hello-agent/packages/agent-runtime/src/scenarios.ts +8 -2
  402. package/templates/hello-agent/packages/agent-runtime/src/setup.ts +13 -3
  403. package/templates/hello-agent/packages/agent-runtime/src/terminal.ts +127 -18
  404. package/templates/hello-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
  405. package/templates/hello-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
  406. package/templates/hello-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
  407. package/templates/hello-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  408. package/templates/kits.lock.json +29 -6
  409. package/templates/supplier-payments-agent/README.md +1 -1
  410. package/templates/supplier-payments-agent/agents/supplier-payments-agent/AGENTS.md +1 -1
  411. package/templates/supplier-payments-agent/agents/supplier-payments-agent/CLAUDE.md +1 -1
  412. package/templates/supplier-payments-agent/agents/supplier-payments-agent/README.md +11 -9
  413. package/templates/supplier-payments-agent/agents/supplier-payments-agent/src/kit.ts +1 -1
  414. package/templates/supplier-payments-agent/agents/supplier-payments-agent/src/modules/batch-payout.ts +67 -20
  415. package/templates/supplier-payments-agent/agents/supplier-payments-agent/test/batch.test.ts +504 -5
  416. package/templates/supplier-payments-agent/packages/agent-core/README.md +14 -1
  417. package/templates/supplier-payments-agent/packages/agent-core/package.json +1 -1
  418. package/templates/supplier-payments-agent/packages/agent-core/src/agent.ts +39 -1
  419. package/templates/supplier-payments-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
  420. package/templates/supplier-payments-agent/packages/agent-core/src/api/client.ts +28 -5
  421. package/templates/supplier-payments-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
  422. package/templates/supplier-payments-agent/packages/agent-core/src/api/rail.ts +62 -26
  423. package/templates/supplier-payments-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
  424. package/templates/supplier-payments-agent/packages/agent-core/src/approval.ts +14 -2
  425. package/templates/supplier-payments-agent/packages/agent-core/src/bundle.ts +35 -3
  426. package/templates/supplier-payments-agent/packages/agent-core/src/channels.ts +49 -3
  427. package/templates/supplier-payments-agent/packages/agent-core/src/check.ts +25 -2
  428. package/templates/supplier-payments-agent/packages/agent-core/src/engine.ts +235 -46
  429. package/templates/supplier-payments-agent/packages/agent-core/src/hash.ts +57 -1
  430. package/templates/supplier-payments-agent/packages/agent-core/src/index.ts +2 -0
  431. package/templates/supplier-payments-agent/packages/agent-core/src/quote.ts +50 -0
  432. package/templates/supplier-payments-agent/packages/agent-core/src/rail.ts +89 -4
  433. package/templates/supplier-payments-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  434. package/templates/supplier-payments-agent/packages/agent-core/src/receipt-verification.ts +194 -18
  435. package/templates/supplier-payments-agent/packages/agent-core/src/revocation.ts +1 -1
  436. package/templates/supplier-payments-agent/packages/agent-core/src/state-machine.ts +10 -1
  437. package/templates/supplier-payments-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
  438. package/templates/supplier-payments-agent/packages/agent-core/src/stubs/rail.ts +100 -32
  439. package/templates/supplier-payments-agent/packages/agent-core/src/tools.ts +17 -6
  440. package/templates/supplier-payments-agent/packages/agent-core/src/types.ts +54 -1
  441. package/templates/supplier-payments-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  442. package/templates/supplier-payments-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  443. package/templates/supplier-payments-agent/packages/agent-core/test/composition.test.ts +204 -0
  444. package/templates/supplier-payments-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  445. package/templates/supplier-payments-agent/packages/agent-core/test/helpers.ts +4 -1
  446. package/templates/supplier-payments-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
  447. package/templates/supplier-payments-agent/packages/agent-core/test/quote.test.ts +198 -0
  448. package/templates/supplier-payments-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  449. package/templates/supplier-payments-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  450. package/templates/supplier-payments-agent/packages/agent-core/test/receivables.test.ts +103 -3
  451. package/templates/supplier-payments-agent/packages/agent-runtime/README.md +12 -0
  452. package/templates/supplier-payments-agent/packages/agent-runtime/src/adversarial.ts +32 -2
  453. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/types.ts +53 -4
  454. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
  455. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
  456. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
  457. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
  458. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
  459. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
  460. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
  461. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
  462. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
  463. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
  464. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/start.ts +6 -3
  465. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
  466. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
  467. package/templates/supplier-payments-agent/packages/agent-runtime/src/index.ts +1 -0
  468. package/templates/supplier-payments-agent/packages/agent-runtime/src/inspect.ts +5 -1
  469. package/templates/supplier-payments-agent/packages/agent-runtime/src/kit.ts +25 -2
  470. package/templates/supplier-payments-agent/packages/agent-runtime/src/scenarios.ts +8 -2
  471. package/templates/supplier-payments-agent/packages/agent-runtime/src/setup.ts +13 -3
  472. package/templates/supplier-payments-agent/packages/agent-runtime/src/terminal.ts +127 -18
  473. package/templates/supplier-payments-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
  474. package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
  475. package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
  476. package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  477. package/templates/bills-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
  478. package/templates/collections-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
  479. package/templates/hello-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
  480. package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Runs the WhatsApp Cloud API emulator the channel's `simulator` backend talks
4
+ * to: `dyvit-wa-sim`, published as `@dyvit/whatsapp-simulator-cli` (MIT) from
5
+ * https://github.com/fabianocruz/whatsapp-simulator. It is not ours and it is
6
+ * not a dependency of this workspace.
7
+ *
8
+ * WHY NOT A DEPENDENCY. It is a development tool. Nothing in `packages/` or
9
+ * `agents/` imports it, so `npm ci` has no reason to fetch it and a contributor
10
+ * who never runs the WhatsApp gate never downloads it. `npx` fetches the exact
11
+ * version into its own cache the first time it is needed; nothing is vendored,
12
+ * nothing is installed globally, and the repository's tree is untouched.
13
+ *
14
+ * WHY PINNED TO A VERSION. A moving `latest` would make our gate fail on
15
+ * somebody else's release. `--version` overrides, and there is no `latest`.
16
+ * The spec handed to `npx` is exact, so it cannot resolve anything else.
17
+ *
18
+ * Usage:
19
+ * node scripts/whatsapp-emulator.mjs start it (foreground)
20
+ * node scripts/whatsapp-emulator.mjs --prepare fetch only
21
+ * node scripts/whatsapp-emulator.mjs --version <v> a different release
22
+ */
23
+ import { spawn, spawnSync } from "node:child_process";
24
+ import { dirname, resolve } from "node:path";
25
+ import { fileURLToPath } from "node:url";
26
+
27
+ const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
28
+
29
+ export const EMULATOR_PACKAGE = "@dyvit/whatsapp-simulator-cli";
30
+ /** The release the gate is measured against. Bump deliberately, never automatically. */
31
+ export const EMULATOR_VERSION = "0.3.0";
32
+ export const EMULATOR_PORT = 4290;
33
+ /** Must match `WHATSAPP_SIM_APP_SECRET`. A local development value, not a credential. */
34
+ export const EMULATOR_APP_SECRET = "dev";
35
+ /** Where the channel's own receiver listens; the emulator posts its webhooks there. */
36
+ export const EMULATOR_WEBHOOK_PORT = 4399;
37
+
38
+ function spec(version) {
39
+ return `${EMULATOR_PACKAGE}@${version}`;
40
+ }
41
+
42
+ /** Warms `npx`'s cache so the start below does not race a download. */
43
+ export function prepare(version = EMULATOR_VERSION, say = (l) => process.stderr.write(l + "\n")) {
44
+ say(`whatsapp emulator: fetching ${spec(version)}`);
45
+ const result = spawnSync("npx", ["--yes", spec(version), "--help"], { stdio: ["ignore", "ignore", "inherit"] });
46
+ if (result.error) throw result.error;
47
+ if (result.status !== 0) {
48
+ throw new Error(`could not fetch ${spec(version)} (npx exited ${result.status}). Is the version published, and is the network reachable?`);
49
+ }
50
+ return spec(version);
51
+ }
52
+
53
+ function serve(version, webhookUrl) {
54
+ const child = spawn(
55
+ "npx",
56
+ ["--yes", spec(version), "serve", "--port", String(EMULATOR_PORT), "--webhook", webhookUrl, "--app-secret", EMULATOR_APP_SECRET],
57
+ // Its own process group: `npx` is a wrapper around the real server, and a
58
+ // signal sent to the wrapper alone leaves the port held by an orphan.
59
+ { cwd: ROOT, stdio: "inherit", detached: true },
60
+ );
61
+ const stop = () => {
62
+ try {
63
+ process.kill(-child.pid, "SIGTERM");
64
+ } catch {
65
+ child.kill("SIGTERM");
66
+ }
67
+ };
68
+ process.once("SIGINT", stop);
69
+ process.once("SIGTERM", stop);
70
+ child.on("exit", (code) => process.exit(code ?? 0));
71
+ }
72
+
73
+ if (process.argv[1] && import.meta.url === new URL(`file://${process.argv[1]}`).href) {
74
+ const argv = process.argv.slice(2);
75
+ const version = argv.includes("--version") ? argv[argv.indexOf("--version") + 1] : EMULATOR_VERSION;
76
+ const webhookUrl = argv.includes("--webhook") ? argv[argv.indexOf("--webhook") + 1] : `http://127.0.0.1:${EMULATOR_WEBHOOK_PORT}/`;
77
+ if (argv.includes("--prepare")) {
78
+ prepare(version);
79
+ process.stderr.write(`whatsapp emulator ready: ${spec(version)}\n`);
80
+ } else {
81
+ process.stderr.write(`whatsapp emulator: ${spec(version)} on :${EMULATOR_PORT}, webhooks to ${webhookUrl}\n`);
82
+ serve(version, webhookUrl);
83
+ }
84
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "strict": true,
7
+ "exactOptionalPropertyTypes": true,
8
+ "noUncheckedIndexedAccess": true,
9
+ "noImplicitOverride": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "resolveJsonModule": true,
13
+ "noEmit": true,
14
+ "types": ["node"]
15
+ }
16
+ }
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ["packages/*/test/**/*.test.ts", "agents/*/test/**/*.test.ts", "test/**/*.test.ts"],
6
+ pool: "forks",
7
+ maxWorkers: 1,
8
+ minWorkers: 1,
9
+ fileParallelism: false,
10
+ testTimeout: 30000,
11
+ },
12
+ });
@@ -1,7 +1,7 @@
1
1
  # {{name}}
2
2
 
3
3
  Scaffolded by `codespar init --template collections-agent` from the `collections-agent` starter kit
4
- (https://github.com/codespar/agent-starter-kits at `8f130b7cdc30510c929e6f7ebb07184f763e6ce4`, agent 0.1.0).
4
+ (https://github.com/codespar/agent-starter-kits at `b766575306a71c9135c0d70e23d591e6523dc2f8`, agent 0.1.0).
5
5
 
6
6
  The merchant's agent that collects: agrees terms with the payer inside a negotiation envelope, issues one bolepix per instalment with an idempotency key, presents the QR and the copy-and-paste in the conversation, and closes the cycle on commerce.charge.paid or commerce.charge.expired.
7
7
 
@@ -18,6 +18,11 @@ against are vendored here and linked as npm workspaces; the agent's own
18
18
  npm start
19
19
  ```
20
20
 
21
+ The kits root scripts this agent's channels use are carried too, verbatim, and
22
+ run at this root as they do in the kits repo:
23
+
24
+ - `npm run whatsapp:emulator` — `scripts/whatsapp-emulator.mjs`
25
+
21
26
  The agent's guide is [`agents/collections-agent/README.md`](agents/collections-agent/README.md); its commands run at this root
22
27
  (`npm run check`, `npm run eval`, `npm test`) or inside `agents/collections-agent/`. The manifest pins
23
28
  `cli: "@codespar/cli@0.14.0"`, the CLI version whose `agent run`/`eval` this agent was written for.
@@ -71,9 +71,11 @@ Cloud API's own response shape and posts back the same signed
71
71
  `x-hub-signature-256` webhooks, so what runs against it is the adapter itself.
72
72
  It also has a clock we can move (`POST /_sim/clock`), which a replay finishing
73
73
  in seconds cannot otherwise have. No Meta account, no credential, and no
74
- traffic that leaves the machine. What it does NOT cover is measured, with the
75
- exact payloads, in `docs/OPEN_QUESTIONS.md` §46 — chief among them that it
76
- prices the 24-hour window but does not enforce it.
74
+ traffic that leaves the machine. What it did not cover at 0.1.1 was measured, with
75
+ the exact payloads, in `docs/OPEN_QUESTIONS.md` §46; 0.2.0 closed
76
+ all five, chief among them that it ENFORCES the 24-hour window with Meta's
77
+ own 400/131047 instead of only pricing it, and the pinned 0.3.0 corrects the
78
+ four places 0.2.0 differed from its release note.
77
79
 
78
80
  `--scripted` replays the debtor's turns from `channels/whatsapp/<name>.json`,
79
81
  which is what an agent ships for this channel: the contact the conversation is
@@ -179,11 +181,13 @@ nothing. It runs in the CI, which starts the emulator in its own step.
179
181
 
180
182
  A fourth run covers the case the other three cannot reach: agree, move the
181
183
  conversation's clock 26 hours, let the sandbox payer pay, poll. It asserts the
182
- execution settled and the confirmation went out as a TEMPLATE. Read that
183
- precisely: the emulator PRICES the 24-hour window and does not ENFORCE it
184
- (`docs/OPEN_QUESTIONS.md` §46a), so what passes is OUR choice of carrier and
185
- not the provider refusing the alternative. The day the emulator enforces the
186
- window, that run gets stronger without changing.
184
+ execution settled and the confirmation went out as a TEMPLATE, and it asks the
185
+ emulator directly whether it would have carried the free-form alternative: it
186
+ must answer 400/131047, as Meta does (`docs/OPEN_QUESTIONS.md` §46a). A fifth
187
+ run moves the two clocks apart — the conversation's 26 hours, the agent's one —
188
+ so our window reads open while the provider's is shut; the free-form
189
+ confirmation is refused with 131047 and the poll tells the person by the
190
+ template instead, once.
187
191
 
188
192
  ## The sandbox payer
189
193
 
@@ -109,3 +109,11 @@ credential, nothing leaving the machine. `npm run whatsapp:gate` at the
109
109
  repository root runs the whole cycle over it three times from a clean state
110
110
  and asserts the final state each time, plus a fourth run that agrees now, moves
111
111
  the conversation's clock past 24 hours, pays, and polls.
112
+
113
+ ## Taps, failed deliveries and the fallback (OPEN_QUESTIONS §46)
114
+
115
+ A template may declare quick replies `{ id, title, intent }`. When the person taps one, the model is handed the `intent`, never the button's title, and a tap on an id no template declares is recorded and skipped. `acordo_cobranca_vencida` offers "Emitir nova" / "Agora nao", and `acordo-1042-retomada.json` is Joana tapping "Emitir nova" the day after.
116
+
117
+ A delivery the provider reports `failed` (131026: not on WhatsApp, blocked) is recorded, becomes `message.debtor.failed` when it told an outcome, reaches the operator's console, and makes the poll answer `delivery_failed` instead of "told". A `read` is recorded and nothing more.
118
+
119
+ `atendimento_atualizacao` is the registry's FALLBACK, the one template a poll sends for an outcome the agent has no copy for. It states nothing about the outcome and asks the person to answer. `npm run check` requires exactly one fallback, taking no variables.
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "acordo-1042-retomada",
3
+ "description": "The day after: the charge for order #1042 expired, the poll told Joana with the acordo_cobranca_vencida template, and she answers by TAPPING its 'Emitir nova' button instead of typing. The tap is a turn whose text is the intent the template declares for that button, never the button's title and never the model's reading of it; the agent issues again and the payer pays.",
4
+ "channel": "whatsapp",
5
+ "contact": "+5511987654321",
6
+ "subject": "acordo-1042",
7
+ "turns": [
8
+ { "reply": { "id": "emitir_nova", "title": "Emitir nova" } }
9
+ ]
10
+ }
@@ -11,13 +11,39 @@
11
11
  "name": "acordo_cobranca_vencida",
12
12
  "language": "pt_BR",
13
13
  "description": "The receivable reached its due date unpaid. The same message the terminal sends free-form, and it has to be its own template: a debtor whose charge expired must not be told the agreement is settled. {{1}} is the agreement alias.",
14
- "body": "Oi! A cobranca do {{1}} venceu sem pagamento. Se quiser, emito uma nova dentro das mesmas condicoes."
14
+ "body": "Oi! A cobranca do {{1}} venceu sem pagamento. Se quiser, emito uma nova dentro das mesmas condicoes.",
15
+ "buttons": [
16
+ {
17
+ "id": "emitir_nova",
18
+ "title": "Emitir nova",
19
+ "intent": "quero que voce emita uma nova cobranca para o meu acordo"
20
+ },
21
+ {
22
+ "id": "agora_nao",
23
+ "title": "Agora nao",
24
+ "intent": "agora nao quero uma nova cobranca para o meu acordo"
25
+ }
26
+ ]
15
27
  },
16
28
  {
17
29
  "name": "acordo_cobranca_cancelada",
18
30
  "language": "pt_BR",
19
31
  "description": "The receivable was cancelled, so nothing was paid and nothing is owed on it. {{1}} is the agreement alias.",
20
32
  "body": "Oi! A cobranca do {{1}} foi cancelada e nada foi pago. Qualquer coisa, e so falar por aqui."
33
+ },
34
+ {
35
+ "name": "atendimento_atualizacao",
36
+ "language": "pt_BR",
37
+ "description": "The FALLBACK: sent by a poll, outside the 24-hour window, when an outcome closed that the agent has no template of its own for (a charge the rail failed to issue, a proposal denied or expired days later). It states NOTHING about the outcome, so it can never be the wrong news; it asks the person to answer, and an answer reopens the window, inside which the agent says what happened in free text. Takes no variables.",
38
+ "body": "Oi! Temos uma atualizacao sobre o seu atendimento. Responda esta mensagem para continuarmos por aqui.",
39
+ "fallback": true,
40
+ "buttons": [
41
+ {
42
+ "id": "falar_agora",
43
+ "title": "Falar agora",
44
+ "intent": "quero saber qual e a atualizacao do meu atendimento"
45
+ }
46
+ ]
21
47
  }
22
48
  ]
23
49
  }
@@ -184,6 +184,8 @@ whatsapp: --backend simulator|cloud-api default simulator, which is the local e
184
184
  if (execution.state === "settled") text = `Recebemos, acordo quitado. Obrigado! (${execution.outcomes.map((o) => o.transaction_id).filter(Boolean).join(", ")})`;
185
185
  else if (execution.reason === "charge_expired") text = "A cobranca venceu sem pagamento. Se quiser, emito uma nova dentro das mesmas condicoes.";
186
186
  else if (execution.reason === "charge_cancelled") text = "A cobranca foi cancelada. Nada foi pago.";
187
+ // Failed or refused, the same fact: a charge was issued and nobody can yet say whether it was paid. Never "nada foi cobrado".
188
+ else if (execution.reason === "charge_reference_ambiguous") text = "A cobranca deste acordo ja foi emitida e estamos conferindo a situacao dela. Nao pague de novo; te aviso assim que estiver conferida.";
187
189
  else if (execution.state === "failed") text = `Nao consegui emitir a cobranca (${execution.reason ?? "falha no trilho"}). Nada foi cobrado.`;
188
190
  else if (execution.state === "denied") text = `Nao posso emitir nesses termos (${execution.reason ?? "recusado"}).`;
189
191
  else text = `A proposta expirou sem decisao (${execution.state}).`;
@@ -22,6 +22,9 @@ export function makeHandlers(envelope: Envelope): Record<string, ToolHandler> {
22
22
  const listAgreements: ToolHandler = async (_input, ctx) => {
23
23
  const settled = ctx.engine.list({ state: "settled" });
24
24
  const open = ctx.engine.list({ state: "executing" });
25
+ // Issued, then its reference turned ambiguous: the charge exists and may be paid. Not open, and the core refuses another until it is reconciled.
26
+ const unreconciled = ctx.engine.list({ state: "failed" }).filter((e) => e.reason === "charge_reference_ambiguous");
27
+ const unreconciledAliases = new Set(unreconciled.flatMap((e) => e.items.map((i) => i.alias)).filter(Boolean));
25
28
  const settledAliases = new Set(settled.flatMap((e) => e.items.map((i) => i.alias)).filter(Boolean));
26
29
  const issuedAliases = new Set(open.flatMap((e) => e.items.map((i) => i.alias)).filter(Boolean));
27
30
  return {
@@ -32,7 +35,13 @@ export function makeHandlers(envelope: Envelope): Record<string, ToolHandler> {
32
35
  principal: formatBRL(a.principal_minor),
33
36
  origin: a.origin,
34
37
  opened_at: a.opened_at,
35
- status: settledAliases.has(a.alias) ? "quitado" : issuedAliases.has(a.alias) ? "cobranca emitida, aguardando pagamento" : "em aberto",
38
+ status: settledAliases.has(a.alias)
39
+ ? "quitado"
40
+ : issuedAliases.has(a.alias)
41
+ ? "cobranca emitida, aguardando pagamento"
42
+ : unreconciledAliases.has(a.alias)
43
+ ? "cobranca emitida, em conferencia: nao emitir outra"
44
+ : "em aberto",
36
45
  })),
37
46
  envelope: {
38
47
  max_discount_pct: envelope.max_discount_pct,
@@ -98,7 +107,8 @@ function describe(execution: import("@codespar/agent-core").Execution) {
98
107
  ...(execution.reason ? { reason: execution.reason, detail: execution.detail } : {}),
99
108
  ...(execution.escalation ? { escalated_by: execution.escalation.trigger } : {}),
100
109
  charges,
101
- issued: charges.length > 0 && charges.every((c) => c.status !== "failed"),
110
+ issued: execution.reason === "charge_reference_ambiguous" ? execution.state === "failed" : charges.length > 0 && charges.every((c) => c.status !== "failed"),
111
+ ...(execution.reason === "charge_reference_ambiguous" ? { reconcile_before_reissue: true } : {}),
102
112
  paid: execution.state === "settled",
103
113
  expired: execution.state === "failed" && execution.reason === "charge_expired",
104
114
  };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * An issued receivable whose reference turned ambiguous (`charge_reference_ambiguous`)
3
+ * may still be paid. The agent must not report the agreement as open, must not tell the
4
+ * debtor nothing was charged, and must not issue a second charge for the same debt.
5
+ */
6
+ import { mkdtempSync } from "node:fs";
7
+ import { tmpdir } from "node:os";
8
+ import { join } from "node:path";
9
+ import { describe, expect, it } from "vitest";
10
+ import type { Execution } from "@codespar/agent-core";
11
+ import { announceOutcome, setup } from "@codespar/agent-runtime";
12
+ import { agent } from "../src/kit.js";
13
+
14
+ describe("a receivable closed charge_reference_ambiguous: reconcile, never reissue", () => {
15
+ it("list_agreements does not reopen it, a new charge for it is refused before the rail, and the debtor is not told nothing was charged", async () => {
16
+ let clock = new Date("2026-09-23T18:00:00Z");
17
+ const s = setup(agent, { mode: "mandate", rail: "stub", provider: "replay", transcript: "unused", stateDir: mkdtempSync(join(tmpdir(), "collections-ambiguous-")), runsDir: mkdtempSync(join(tmpdir(), "collections-ambiguous-runs-")), now: () => (clock = new Date(clock.getTime() + 1000)), say: () => undefined });
18
+ try {
19
+ s.payer!.behave("never");
20
+ const d = await s.engine.draft({ items: [{ payee: "acordo-1042", amount: 108000, due_date: "2026-09-30" }] });
21
+ if (!d.ok) throw new Error("refused");
22
+ const issued = await s.engine.execute(d.execution.id);
23
+ expect(issued.state).toBe("executing");
24
+ // The stub rail cannot answer 409; the core's own test drives that read. Here the closed execution is what the agent reads.
25
+ const chargeId = issued.outcomes[0]!.transaction_id!;
26
+ const closed: Execution = {
27
+ ...issued,
28
+ state: "failed",
29
+ reason: "charge_reference_ambiguous",
30
+ detail: "charge_reference_ambiguous: the reference matches more than one charge",
31
+ outcomes: [{ index: 0, attempt_id: issued.outcomes[0]!.attempt_id, status: "failed", code: "charge_reference_ambiguous", error: "charge_reference_ambiguous" }],
32
+ history: [...issued.history, { from: "executing", to: "failed", at: clock.toISOString(), actor: issued.actor, reason: "charge_reference_ambiguous" }],
33
+ };
34
+ s.store.saveExecution(closed);
35
+
36
+ const handlers = s.kit.handlers(s);
37
+ const ctx = { engine: s.engine, onExecution: async (e: Execution) => e };
38
+ const listed = (await handlers["list_agreements"]!({}, ctx)) as { agreements: Array<{ alias: string; status: string }> };
39
+ expect(listed.agreements.find((a) => a.alias === "acordo-1042")?.status).toBe("cobranca emitida, em conferencia: nao emitir outra");
40
+
41
+ const again = (await handlers["codespar_charge"]!({ action: "create", agreement: "acordo-1042", instalments: [{ amount_minor: 108000, due_date: "2026-10-15" }] }, ctx)) as Record<string, unknown>;
42
+ expect(again).toMatchObject({ status: "denied", reason: "charge_reference_ambiguous", issued: false, charges: [] });
43
+ expect(s.engine.list({ state: "executing" })).toHaveLength(0);
44
+ expect(s.engine.list().flatMap((e) => e.outcomes).filter((o) => o.transaction_id && o.transaction_id !== chargeId)).toEqual([]);
45
+
46
+ const told: string[] = [];
47
+ announceOutcome(closed, s, (l) => told.push(l));
48
+ expect(told).toHaveLength(1);
49
+ expect(told[0]).toContain("ja foi emitida");
50
+ expect(told[0]).not.toContain("Nada foi cobrado");
51
+ } finally {
52
+ s.close();
53
+ }
54
+ });
55
+ });
@@ -0,0 +1,2 @@
1
+ {"kind": "assistant_step", "tool_calls": [{"id": "tc_1", "name": "codespar_charge", "input": {"action": "create", "agreement": "acordo-1042", "instalments": [{"amount_minor": 108000, "due_date": "2026-09-30"}], "total_minor": 108000}}]}
2
+ {"kind": "assistant_step", "reply": "Pronto, Joana: emiti uma nova cobranca de R$ 1.080,00, vencendo em 30/09. O codigo esta acima."}
@@ -11,11 +11,11 @@
11
11
  * what the guardrails and the window comparison read.
12
12
  *
13
13
  * What is asserted is which CARRIER the confirmation went out on and that the
14
- * cycle closed, never the wording. Read the template assertions precisely:
15
- * the emulator prices the 24-hour window and does not enforce it
16
- * (docs/OPEN_QUESTIONS.md §46a), so what passes here is OUR choice of
17
- * carrier, made by the session window, and not the provider refusing the
18
- * alternative.
14
+ * cycle closed, never the wording. Here the carrier is chosen by the session
15
+ * window with both clocks moved together; since 0.2.0 the emulator also
16
+ * refuses the free-form alternative with 131047 (docs/OPEN_QUESTIONS.md
17
+ * §46a), and `npm run whatsapp:gate` asks it to, and drives the case where
18
+ * the two clocks disagree.
19
19
  *
20
20
  * The conversing cases SKIP without the emulator, like the rest of the
21
21
  * channel suite; `npm run whatsapp:gate` fails rather than skipping, so the
@@ -72,9 +72,15 @@ interface PollPayload {
72
72
  channel: { backend: string; session_open: boolean; messages_out: number; refused: Array<{ rule: string }>; log: string } | null;
73
73
  }
74
74
 
75
+ /**
76
+ * One scratch per case: its state, its runs, and its own conversation on the
77
+ * emulator (#42). The agree and the poll of a case share it, which is the
78
+ * point; two cases never do.
79
+ */
75
80
  function scratch(label: string) {
76
81
  const stateDir = mkdtempSync(join(tmpdir(), `collections-wa-poll-${label}-`));
77
- return { COLLECTIONS_STATE_DIR: stateDir, COLLECTIONS_RUNS_DIR: join(stateDir, "runs") };
82
+ const phoneNumberId = `9${String(Math.floor(Math.random() * 1e11)).padStart(11, "0")}`;
83
+ return { COLLECTIONS_STATE_DIR: stateDir, COLLECTIONS_RUNS_DIR: join(stateDir, "runs"), WHATSAPP_SIM_PHONE_NUMBER_ID: phoneNumberId };
78
84
  }
79
85
 
80
86
  function agent(args: string[], env: Record<string, string>) {
@@ -0,0 +1,148 @@
1
+ /**
2
+ * §46's three product gaps on the collections-agent, in real processes
3
+ * against the emulator: the confirmation of a paid agreement that the provider
4
+ * reports FAILED is not an agreement the debtor was told about; an outcome the
5
+ * kit has no template for goes out as the registry's fallback; and a tapped
6
+ * "Emitir nova" is a turn, as the intent the template declared. Each case runs
7
+ * in a conversation of its own (#42). They SKIP without the emulator; the gate
8
+ * does not.
9
+ */
10
+ import { spawn, spawnSync } from "node:child_process";
11
+ import { mkdtempSync, readFileSync } from "node:fs";
12
+ import { tmpdir } from "node:os";
13
+ import { isAbsolute, join, resolve } from "node:path";
14
+ import { DatabaseSync } from "node:sqlite";
15
+ import { describe, expect, it } from "vitest";
16
+ import { pollWhatsApp, setup } from "@codespar/agent-runtime";
17
+ import { agent as collections } from "../src/kit.js";
18
+
19
+ const AGENT_DIR = resolve(import.meta.dirname, "..");
20
+ const NODE = process.execPath;
21
+ const BIN = resolve(AGENT_DIR, "../../packages/agent-runtime/bin.mjs");
22
+ const EMULATOR = process.env["WHATSAPP_SIM_URL"] ?? "http://127.0.0.1:4290";
23
+ const TUESDAY = "2026-09-23T14:00:00-03:00";
24
+ const AFTER_WINDOW = new Date(new Date(TUESDAY).getTime() + 26 * 3600_000).toISOString();
25
+ const AGREED = "test/fixtures/agreed-1042-awaiting-payer.transcript.jsonl";
26
+ const CONTACT = "5511987654321";
27
+
28
+ const emulatorUp = await (async () => {
29
+ try {
30
+ return (await fetch(`${EMULATOR}/health`, { signal: AbortSignal.timeout(2000) })).ok;
31
+ } catch {
32
+ return false;
33
+ }
34
+ })();
35
+
36
+ function scratch(label: string) {
37
+ const stateDir = mkdtempSync(join(tmpdir(), `collections-wa-sr-${label}-`));
38
+ return { COLLECTIONS_STATE_DIR: stateDir, COLLECTIONS_RUNS_DIR: join(stateDir, "runs"), WHATSAPP_SIM_PHONE_NUMBER_ID: `9${String(Math.floor(Math.random() * 1e11)).padStart(11, "0")}` };
39
+ }
40
+
41
+ const baseEnv = (env: Record<string, string>) => ({ ...process.env, ANTHROPIC_API_KEY: "", CODESPAR_API_KEY: "", WHATSAPP_PHONE_NUMBER_ID: "", WHATSAPP_ACCESS_TOKEN: "", WHATSAPP_VERIFY_TOKEN: "", WHATSAPP_APP_SECRET: "", ...env });
42
+
43
+ function run(args: string[], env: Record<string, string>) {
44
+ const r = spawnSync(NODE, [BIN, ...args], { cwd: AGENT_DIR, env: baseEnv(env), encoding: "utf8", timeout: 90_000 });
45
+ return { code: r.status, stdout: r.stdout, stderr: r.stderr };
46
+ }
47
+
48
+ const lastJson = (stdout: string) => JSON.parse(stdout.split("\n").filter(Boolean).pop()!) as Record<string, unknown>;
49
+
50
+ async function sim(path: string, body?: unknown) {
51
+ const r = await fetch(`${EMULATOR}${path}`, body === undefined ? {} : { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
52
+ return (await r.json()) as Record<string, unknown>;
53
+ }
54
+
55
+ function events(env: Record<string, string>, type: string): Array<{ execution_id: string | null; payload: Record<string, unknown> }> {
56
+ const db = new DatabaseSync(join(env["COLLECTIONS_STATE_DIR"]!, "state.db"));
57
+ try {
58
+ return (db.prepare("SELECT execution_id, payload FROM events WHERE type = ?").all(type) as Array<{ execution_id: string | null; payload: string }>).map((r) => ({ execution_id: r.execution_id, payload: JSON.parse(r.payload) as Record<string, unknown> }));
59
+ } finally {
60
+ db.close();
61
+ }
62
+ }
63
+
64
+ /** A log path is relative to the cwd of whoever wrote it: the agent's directory for a child process, this process's for an in-process poll. */
65
+ function channelLog(logPath: string, base = AGENT_DIR): Array<Record<string, unknown>> {
66
+ const path = isAbsolute(logPath) ? logPath : resolve(base, logPath);
67
+ return readFileSync(path, "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l) as Record<string, unknown>);
68
+ }
69
+
70
+ describe.skipIf(!emulatorUp)("§46 on the collections-agent, against the emulator", () => {
71
+ it("the paid-agreement template the provider reports FAILED (131026) is not an agreement the debtor was told about", { timeout: 60_000 }, async () => {
72
+ const env = scratch("failed");
73
+ expect(run(["start", "--channel", "whatsapp", "--conversation", "acordo-1042", "--scripted", "--mode", "mandate", "--transcript", AGREED, "--now", TUESDAY, "--json"], { ...env, COLLECTIONS_STUB_PAYER: "never" }).code).toBe(3);
74
+ await sim("/_sim/clock", { advance_hours: 26 });
75
+
76
+ // The poll, in its own process, with a long enough wait for the status the provider reports after the send.
77
+ const child = spawn(NODE, [BIN, "poll", "--channel", "whatsapp", "--conversation", "acordo-1042", "--simulate-payer", "--now", AFTER_WINDOW, "--json"], { cwd: AGENT_DIR, env: baseEnv({ ...env, WHATSAPP_STATUS_GRACE_MS: "6000" }) });
78
+ let stdout = "";
79
+ child.stdout.on("data", (c: Buffer) => (stdout += c.toString()));
80
+ const exit = new Promise<number | null>((done) => child.on("exit", (code) => done(code)));
81
+
82
+ // As soon as the confirmation template exists at the provider, the provider reports it failed.
83
+ const key = `${env["WHATSAPP_SIM_PHONE_NUMBER_ID"]}:${CONTACT}`;
84
+ let template: string | undefined;
85
+ for (let i = 0; i < 200 && !template; i += 1) {
86
+ const state = (await sim(`/_sim/state?key=${key}`)) as { messages?: Array<{ id: string; direction: string; kind: string }> };
87
+ template = state.messages?.find((m) => m.direction === "business_to_user" && m.kind === "template")?.id;
88
+ if (!template) await new Promise((r) => setTimeout(r, 50));
89
+ }
90
+ expect(template).toBeDefined();
91
+ await sim("/_sim/status", { status: "failed", message_id: template, reason: "not on whatsapp" });
92
+
93
+ expect(await exit).toBe(1);
94
+ const polled = (lastJson(stdout)["polled"] as Array<{ state: string; delivery: { told: boolean; reason?: string } }>)[0]!;
95
+ // The cycle closed: the money is where the rail says it is. The telling of it did not happen.
96
+ expect(polled.state).toBe("settled");
97
+ expect(polled.delivery).toMatchObject({ told: false, reason: "delivery_failed" });
98
+ const failed = events(env, "message.debtor.failed");
99
+ expect(failed).toHaveLength(1);
100
+ expect(failed[0]!.payload).toMatchObject({ message_id: template, state: "settled" });
101
+ expect((failed[0]!.payload["errors"] as Array<{ code: number }>)[0]!.code).toBe(131026);
102
+ });
103
+
104
+ it("an outcome the kit has no template for goes out as the registry's fallback, which states no outcome", { timeout: 60_000 }, async () => {
105
+ const env = scratch("fallback");
106
+ expect(run(["start", "--channel", "whatsapp", "--conversation", "acordo-1042", "--scripted", "--mode", "mandate", "--transcript", AGREED, "--now", TUESDAY, "--json"], { ...env, COLLECTIONS_STUB_PAYER: "never" }).code).toBe(3);
107
+ await sim("/_sim/clock", { advance_hours: 26 });
108
+
109
+ // The same agent with no copy for any outcome: what a kit that never wrote one looks like to the poll.
110
+ const bare = { ...collections, kit: { ...collections.kit, outcomeTemplate: () => undefined } };
111
+ const saved = { ...process.env };
112
+ Object.assign(process.env, { ...env, COLLECTIONS_STATE_DIR: env["COLLECTIONS_STATE_DIR"], ANTHROPIC_API_KEY: "", CODESPAR_API_KEY: "" });
113
+ const written: string[] = [];
114
+ const write = process.stdout.write.bind(process.stdout);
115
+ process.stdout.write = ((chunk: string) => (written.push(String(chunk)), true)) as typeof process.stdout.write;
116
+ const s = setup(bare, { mode: "mandate", rail: "stub", stateDir: env["COLLECTIONS_STATE_DIR"]!, runsDir: env["COLLECTIONS_RUNS_DIR"]!, now: () => new Date(AFTER_WINDOW), say: () => undefined });
117
+ let code: number;
118
+ try {
119
+ code = await pollWhatsApp({ agent: bare, setup: s, conversation: "acordo-1042", backend: "simulator", json: true, simulatePayer: true, now: () => new Date(AFTER_WINDOW), say: () => undefined });
120
+ } finally {
121
+ process.stdout.write = write;
122
+ s.close();
123
+ for (const k of Object.keys(process.env)) if (!(k in saved)) delete process.env[k];
124
+ Object.assign(process.env, saved);
125
+ }
126
+ expect(code).toBe(0);
127
+ const payload = JSON.parse(written.join("").trim().split("\n").pop()!) as { polled: Array<{ delivery: Record<string, unknown> }>; channel: { log: string } };
128
+ expect(payload.polled[0]!.delivery).toEqual({ told: true, carrier: "template", template: "atendimento_atualizacao", fallback: true });
129
+ const last = channelLog(payload.channel.log, process.cwd()).filter((l) => l["direction"] === "out").at(-1)!;
130
+ expect(String(last["text"])).toContain("atendimento_atualizacao");
131
+ expect(last["offered"]).toEqual(["falar_agora"]);
132
+ expect(events(env, "message.debtor").at(-1)!.payload).toMatchObject({ template: "atendimento_atualizacao", fallback: true });
133
+ });
134
+
135
+ it("a tapped 'Emitir nova' is a turn, and the model is handed the intent the template declared for it", { timeout: 60_000 }, () => {
136
+ const env = scratch("tap");
137
+ const out = run(["start", "--channel", "whatsapp", "--conversation", "acordo-1042-retomada", "--scripted", "--mode", "mandate", "--transcript", "test/fixtures/tap-emitir-nova-1042.transcript.jsonl", "--simulate-payer", "--now", TUESDAY, "--json"], env);
138
+ expect(out.code).toBe(0);
139
+ const payload = lastJson(out.stdout) as { run_id: string; executions: Array<{ state: string }>; channel: { taps: unknown[]; log: string } };
140
+ expect(payload.executions.map((e) => e.state)).toEqual(["settled"]);
141
+ expect(payload.channel.taps).toEqual([{ id: "emitir_nova", turn: true }]);
142
+ const tap = channelLog(payload.channel.log).find((l) => l["kind"] === "reply")!;
143
+ expect(tap).toMatchObject({ direction: "in", reply: { id: "emitir_nova", title: "Emitir nova" }, text: "quero que voce emita uma nova cobranca para o meu acordo" });
144
+ // What the MODEL was handed: the declared intent, verbatim, as the person's turn.
145
+ const transcript = readFileSync(join(env["COLLECTIONS_RUNS_DIR"]!, payload.run_id, "transcript.jsonl"), "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l) as { kind: string; text?: string });
146
+ expect(transcript.filter((t) => t.kind === "user").map((t) => t.text)).toEqual(["quero que voce emita uma nova cobranca para o meu acordo"]);
147
+ });
148
+ });
@@ -43,6 +43,9 @@ interface Payload {
43
43
  channel: { backend: string; conversation: string; turns: number; messages_in: number; messages_out: number; refused: Array<{ rule: string }>; log: string };
44
44
  }
45
45
 
46
+ /** The emulator keys a conversation on `phone_number_id:contact`; a fresh id is a fresh conversation for the same contact. */
47
+ const freshPhoneNumberId = () => `9${String(Math.floor(Math.random() * 1e11)).padStart(11, "0")}`;
48
+
46
49
  function run(args: string[], extra: Record<string, string> = {}) {
47
50
  const stateDir = mkdtempSync(join(tmpdir(), "collections-wa-"));
48
51
  const result = spawnSync(NODE, [BIN, "start", ...args], {
@@ -58,6 +61,8 @@ function run(args: string[], extra: Record<string, string> = {}) {
58
61
  WHATSAPP_APP_SECRET: "",
59
62
  COLLECTIONS_STATE_DIR: stateDir,
60
63
  COLLECTIONS_RUNS_DIR: join(stateDir, "runs"),
64
+ // A conversation of its own on the emulator (#42): no case inherits another's inbound messages, or the window they anchor.
65
+ WHATSAPP_SIM_PHONE_NUMBER_ID: freshPhoneNumberId(),
61
66
  ...extra,
62
67
  },
63
68
  encoding: "utf8",
@@ -15,6 +15,7 @@
15
15
  "check": "npm run check --workspaces --if-present --",
16
16
  "test": "vitest run --pool=forks --maxWorkers=1",
17
17
  "typecheck": "tsc --noEmit -p packages/agent-core/tsconfig.json && tsc --noEmit -p packages/agent-runtime/tsconfig.json && tsc --noEmit -p agents/collections-agent/tsconfig.json",
18
+ "whatsapp:emulator": "node scripts/whatsapp-emulator.mjs",
18
19
  "approve": "npm run approve --workspace=agents/collections-agent --",
19
20
  "deny": "npm run deny --workspace=agents/collections-agent --",
20
21
  "eval": "npm run eval --workspace=agents/collections-agent --",
@@ -12,10 +12,23 @@ This package is the decisions. The RUNNER around them — the terminal channel,
12
12
 
13
13
  `ExecutionEngine.policy()` runs at draft, at a human's approval and again immediately before `executing`, inside the transaction that writes the outbox row. Mandate status, allowlist, per-transaction cap, the window cap (counting settled, in flight AND awaiting executions as reservations), `escalate_above` and the artifact's `items_hash` are all re-checked at the last gate. A person's approval satisfies `escalate_above` and nothing else.
14
14
 
15
+ ## What the artifact binds besides the items
16
+
17
+ `items_hash` covers the execution's items: payee, amount, currency and a receivable's due date. Two optional bindings cover what those fields cannot see, and each is **omitted, never null**, when it does not apply, so an artifact without them signs the same bytes it signed before they existed (`test/composition.test.ts` pins that against an artifact hashed on `main` before `composition` was added):
18
+
19
+ - `batch` — the set a line was approved inside (`batch_hash` over the ordered lines, the line's `index`, the `count`). One execution per line, N lines.
20
+ - `composition` — what ONE amount is made of (`{ ref, composition_hash, line_count }`, with `compositionHash` over the resolved lines). One execution, N lines behind it: a sale is one charge, so a cart of three lines is one item whose amount is the order's total. Two units at 100 and one unit at 200 are the same item and the same `items_hash`; they are not the same `composition_hash`. `compositionHash` uses `itemsHash`'s canonicalisation over `ref`, `quantity`, `unit_amount`, `amount` and `currency`, not `itemsHash` itself, because ten units at a 10% discount and nine at list price have the same line amount.
21
+
22
+ `checkApprovalArtifact` compares both, and the last gate sends a mismatch back to `awaiting_approval` with `items_hash_mismatch`, like a changed list. `ExecutionEngine.restate()` is how an OPEN execution (`awaiting_approval`, `approved`) follows the proposal behind it — a customer who changes the cart after the order was approved — without a transition: items, total, `items_hash` and composition are replaced, the state, the artifact and the idempotency key are kept, and the last gate does the refusing. The payees of a restatement cannot change.
23
+
24
+ ## What of the artifact reaches the receipt
25
+
26
+ Every spend carries `approval: { items_hash, batch_hash? }`, the artifact's own hashes (`spendApprovalOf`), and the API seals them into the receipt's chain as a link of their own (chain v4, ent#1670). Both rails refuse a spend without them before any call. `composition` is not sent: the API takes the two hashes and nothing else, and only charges carry a composition today. `receipt-chain.ts` recomputes a chain from the receipt read under the `chain_recipe` the key document publishes (RFC 8785 implemented locally, `node:crypto` only), and `verifyReceiptRead` holds the signature, the body and the sealed approval link against the artifact, in that order. It proves WHAT was approved to anybody holding the read; WHO approved stays in the artifact, under the local key. `docs/OPEN_QUESTIONS.md` §3 and §47.
27
+
15
28
  ## Reconcile never re-dispatches
16
29
 
17
30
  An execution left in `executing` is looked up on the rail, attempt by attempt. A recorded settled or failed outcome closes it; `in_flight`, `uncertain` or unknown leaves it in `executing` with `reason: rail_uncertain` for a human. The single exception is an outbox row still `pending` (the state changed, no call was ever made): those attempts go out once, under the same ids.
18
31
 
19
32
  ## Stubs
20
33
 
21
- `stubs/mandate-status.ts` (the section 4.7 status source for runs without a key: revocation, pause and the organization kill switch, over the local state.db) and `stubs/rail.ts` (the sandbox rail) are stand-ins for the CI, the scenarios and `rerun`, marked as such. With a test key the engine reads the mandate status from the API instead (`api/mandate-status.ts`, `GET /v1/mandates/{id}`), fail-closed: anything but `active` refuses `executing`, and a read that does not answer is `mandate_status_unavailable`, never "assume active". The approval artifact is signed with a local development key (`approval.ts`); the API does not sign approval lists.
34
+ `stubs/mandate-status.ts` (the section 4.7 status source for runs without a key: revocation, pause and the organization kill switch, over the local state.db) and `stubs/rail.ts` (the sandbox rail) are stand-ins for the CI, the scenarios and `rerun`, marked as such. With a test key the engine reads the mandate status from the API instead (`api/mandate-status.ts`, `GET /v1/mandates/{id}`), fail-closed: anything but `active` refuses `executing`, `org_paused: true` (the organization kill switch, ent#1648) refuses it whatever `status` says, and a read that does not answer, or answers without `org_paused`, is `mandate_status_unavailable`, never "assume active". The approval artifact is signed with a local development key (`approval.ts`); the API does not sign approval lists.
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "dependencies": {
19
19
  "@anthropic-ai/sdk": "0.128.0",
20
- "@codespar/sdk": "0.16.6",
20
+ "@codespar/sdk": "0.16.10",
21
21
  "yaml": "2.9.1",
22
22
  "zod": "3.25.76"
23
23
  }