@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.
- package/README.md +1 -0
- package/dist/commands/mandate-verify.js +98 -19
- package/dist/commands/mandate-verify.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mandate-codec.js +31 -20
- package/dist/mandate-codec.js.map +1 -1
- package/package.json +1 -1
- package/templates/bills-agent/README.md +1 -1
- package/templates/bills-agent/agents/bills-agent/AGENTS.md +1 -1
- package/templates/bills-agent/agents/bills-agent/CLAUDE.md +1 -1
- package/templates/bills-agent/agents/bills-agent/README.md +6 -5
- package/templates/bills-agent/agents/bills-agent/src/modules/embedded-consent.ts +5 -4
- package/templates/bills-agent/packages/agent-core/README.md +14 -1
- package/templates/bills-agent/packages/agent-core/package.json +1 -1
- package/templates/bills-agent/packages/agent-core/src/agent.ts +39 -1
- package/templates/bills-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
- package/templates/bills-agent/packages/agent-core/src/api/client.ts +28 -5
- package/templates/bills-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
- package/templates/bills-agent/packages/agent-core/src/api/rail.ts +62 -26
- package/templates/bills-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
- package/templates/bills-agent/packages/agent-core/src/approval.ts +14 -2
- package/templates/bills-agent/packages/agent-core/src/bundle.ts +35 -3
- package/templates/bills-agent/packages/agent-core/src/channels.ts +49 -3
- package/templates/bills-agent/packages/agent-core/src/check.ts +25 -2
- package/templates/bills-agent/packages/agent-core/src/engine.ts +235 -46
- package/templates/bills-agent/packages/agent-core/src/hash.ts +57 -1
- package/templates/bills-agent/packages/agent-core/src/index.ts +2 -0
- package/templates/bills-agent/packages/agent-core/src/quote.ts +50 -0
- package/templates/bills-agent/packages/agent-core/src/rail.ts +89 -4
- package/templates/bills-agent/packages/agent-core/src/receipt-chain.ts +270 -0
- package/templates/bills-agent/packages/agent-core/src/receipt-verification.ts +194 -18
- package/templates/bills-agent/packages/agent-core/src/revocation.ts +1 -1
- package/templates/bills-agent/packages/agent-core/src/state-machine.ts +10 -1
- package/templates/bills-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
- package/templates/bills-agent/packages/agent-core/src/stubs/rail.ts +100 -32
- package/templates/bills-agent/packages/agent-core/src/tools.ts +17 -6
- package/templates/bills-agent/packages/agent-core/src/types.ts +54 -1
- package/templates/bills-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
- package/templates/bills-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
- package/templates/bills-agent/packages/agent-core/test/composition.test.ts +204 -0
- package/templates/bills-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
- package/templates/bills-agent/packages/agent-core/test/helpers.ts +4 -1
- package/templates/bills-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
- package/templates/bills-agent/packages/agent-core/test/quote.test.ts +198 -0
- package/templates/bills-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
- package/templates/bills-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
- package/templates/bills-agent/packages/agent-core/test/receivables.test.ts +103 -3
- package/templates/bills-agent/packages/agent-runtime/README.md +12 -0
- package/templates/bills-agent/packages/agent-runtime/src/adversarial.ts +32 -2
- package/templates/bills-agent/packages/agent-runtime/src/channels/types.ts +53 -4
- package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
- package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
- package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
- package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
- package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
- package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
- package/templates/bills-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
- package/templates/bills-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
- package/templates/bills-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
- package/templates/bills-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
- package/templates/bills-agent/packages/agent-runtime/src/commands/start.ts +6 -3
- package/templates/bills-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
- package/templates/bills-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
- package/templates/bills-agent/packages/agent-runtime/src/index.ts +1 -0
- package/templates/bills-agent/packages/agent-runtime/src/inspect.ts +5 -1
- package/templates/bills-agent/packages/agent-runtime/src/kit.ts +25 -2
- package/templates/bills-agent/packages/agent-runtime/src/scenarios.ts +8 -2
- package/templates/bills-agent/packages/agent-runtime/src/setup.ts +13 -3
- package/templates/bills-agent/packages/agent-runtime/src/terminal.ts +127 -18
- package/templates/bills-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
- package/templates/bills-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
- package/templates/bills-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
- package/templates/bills-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
- package/templates/checkout-agent/LICENSE +21 -0
- package/templates/checkout-agent/README.md +28 -0
- package/templates/checkout-agent/_gitignore +10 -0
- package/templates/checkout-agent/agents/checkout-agent/.env.example +5 -0
- package/templates/checkout-agent/agents/checkout-agent/AGENTS.md +20 -0
- package/templates/checkout-agent/agents/checkout-agent/CLAUDE.md +20 -0
- package/templates/checkout-agent/agents/checkout-agent/README.md +94 -0
- package/templates/checkout-agent/agents/checkout-agent/SYSTEM_PROMPT.md +36 -0
- package/templates/checkout-agent/agents/checkout-agent/agent.yaml +30 -0
- package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/README.md +28 -0
- package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/pedido-beatriz.json +11 -0
- package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/pedido-marina-retomada.json +10 -0
- package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/pedido-marina.json +11 -0
- package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/templates.json +49 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/beneficiary-swap-human.json +12 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/beneficiary-swap.json +11 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/beneficiary-swap.transcript.jsonl +5 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/coupon-unknown.json +13 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/coupon-unknown.transcript.jsonl +4 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/exfiltration.json +26 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/exfiltration.transcript.jsonl +3 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/false-authority.json +11 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/false-authority.transcript.jsonl +5 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/fractioning.json +12 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/fractioning.transcript.jsonl +5 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/model-total.json +13 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/model-total.transcript.jsonl +5 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/payment-claimed.json +15 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/payment-claimed.transcript.jsonl +7 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/price-injected.json +12 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/price-injected.transcript.jsonl +5 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/prompt-injection.json +13 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/prompt-injection.transcript.jsonl +4 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped-human.json +16 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped-human.transcript.jsonl +9 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped.json +15 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped.transcript.jsonl +8 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/webhook-replay.json +11 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/eval.yaml +11 -0
- package/templates/checkout-agent/agents/checkout-agent/guardrails.json +25 -0
- package/templates/checkout-agent/agents/checkout-agent/mandate.example.json +28 -0
- package/templates/checkout-agent/agents/checkout-agent/package.json +33 -0
- package/templates/checkout-agent/agents/checkout-agent/runbook.md +19 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/beneficiary-not-allowed.json +42 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/beneficiary-not-allowed.transcript.jsonl +4 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/cap-exceeded.json +32 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/cap-exceeded.transcript.jsonl +4 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-recomposed.json +61 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-recomposed.transcript.jsonl +9 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-replaced.json +61 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-replaced.transcript.jsonl +9 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/charge-expired.json +36 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/charge-expired.transcript.jsonl +6 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/coupon-unknown.json +23 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/coupon-unknown.transcript.jsonl +4 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/escalated-above-threshold.json +72 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/escalated-above-threshold.transcript.jsonl +9 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/happy-path.json +15 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/happy-path.transcript.jsonl +6 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/item-unavailable.json +23 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/item-unavailable.transcript.jsonl +3 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/mandate-revoked.json +42 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/mandate-revoked.transcript.jsonl +12 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/nfse-failed.json +14 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/nfse-failed.transcript.jsonl +9 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/payment-claimed.json +34 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/payment-claimed.transcript.jsonl +7 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/price-injected.json +33 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/price-injected.transcript.jsonl +4 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/prompt-injection.json +32 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/prompt-injection.transcript.jsonl +4 -0
- package/templates/checkout-agent/agents/checkout-agent/src/catalog.ts +92 -0
- package/templates/checkout-agent/agents/checkout-agent/src/kit.ts +338 -0
- package/templates/checkout-agent/agents/checkout-agent/src/modules/bolepix-receivables.ts +179 -0
- package/templates/checkout-agent/agents/checkout-agent/src/modules/nfse-invoice.ts +374 -0
- package/templates/checkout-agent/agents/checkout-agent/src/modules/storefront-cart.ts +220 -0
- package/templates/checkout-agent/agents/checkout-agent/src/pricing.ts +297 -0
- package/templates/checkout-agent/agents/checkout-agent/test/adversarial.test.ts +117 -0
- package/templates/checkout-agent/agents/checkout-agent/test/check.test.ts +67 -0
- package/templates/checkout-agent/agents/checkout-agent/test/cli.test.ts +169 -0
- package/templates/checkout-agent/agents/checkout-agent/test/fixtures/order-marina.transcript.jsonl +5 -0
- package/templates/checkout-agent/agents/checkout-agent/test/fixtures/ordered-marina-awaiting-payer.transcript.jsonl +6 -0
- package/templates/checkout-agent/agents/checkout-agent/test/fixtures/tap-falar-agora-marina.transcript.jsonl +2 -0
- package/templates/checkout-agent/agents/checkout-agent/test/nfse.test.ts +170 -0
- package/templates/checkout-agent/agents/checkout-agent/test/pricing.test.ts +138 -0
- package/templates/checkout-agent/agents/checkout-agent/test/scenarios.test.ts +113 -0
- package/templates/checkout-agent/agents/checkout-agent/test/whatsapp.test.ts +171 -0
- package/templates/checkout-agent/agents/checkout-agent/tools.json +68 -0
- package/templates/checkout-agent/agents/checkout-agent/tsconfig.json +10 -0
- package/templates/checkout-agent/package.json +38 -0
- package/templates/checkout-agent/packages/agent-core/README.md +34 -0
- package/templates/checkout-agent/packages/agent-core/package.json +24 -0
- package/templates/checkout-agent/packages/agent-core/src/agent.ts +163 -0
- package/templates/checkout-agent/packages/agent-core/src/api/charge-rail.ts +195 -0
- package/templates/checkout-agent/packages/agent-core/src/api/client.ts +76 -0
- package/templates/checkout-agent/packages/agent-core/src/api/mandate-status.ts +74 -0
- package/templates/checkout-agent/packages/agent-core/src/api/rail.ts +153 -0
- package/templates/checkout-agent/packages/agent-core/src/api/sandbox-payer.ts +43 -0
- package/templates/checkout-agent/packages/agent-core/src/approval.ts +135 -0
- package/templates/checkout-agent/packages/agent-core/src/bundle.ts +191 -0
- package/templates/checkout-agent/packages/agent-core/src/channels.ts +186 -0
- package/templates/checkout-agent/packages/agent-core/src/check.ts +276 -0
- package/templates/checkout-agent/packages/agent-core/src/clock.ts +42 -0
- package/templates/checkout-agent/packages/agent-core/src/engine.ts +1074 -0
- package/templates/checkout-agent/packages/agent-core/src/escalate.ts +85 -0
- package/templates/checkout-agent/packages/agent-core/src/events.ts +40 -0
- package/templates/checkout-agent/packages/agent-core/src/guardrails.ts +45 -0
- package/templates/checkout-agent/packages/agent-core/src/hash.ts +118 -0
- package/templates/checkout-agent/packages/agent-core/src/ids.ts +10 -0
- package/templates/checkout-agent/packages/agent-core/src/index.ts +34 -0
- package/templates/checkout-agent/packages/agent-core/src/mandate.ts +100 -0
- package/templates/checkout-agent/packages/agent-core/src/manifest.ts +76 -0
- package/templates/checkout-agent/packages/agent-core/src/providers/anthropic.ts +76 -0
- package/templates/checkout-agent/packages/agent-core/src/providers/replay.ts +63 -0
- package/templates/checkout-agent/packages/agent-core/src/providers/types.ts +39 -0
- package/templates/checkout-agent/packages/agent-core/src/quote.ts +50 -0
- package/templates/checkout-agent/packages/agent-core/src/rail.ts +186 -0
- package/templates/checkout-agent/packages/agent-core/src/receipt-chain.ts +270 -0
- package/templates/checkout-agent/packages/agent-core/src/receipt-verification.ts +577 -0
- package/templates/checkout-agent/packages/agent-core/src/revocation.ts +30 -0
- package/templates/checkout-agent/packages/agent-core/src/secrets.ts +30 -0
- package/templates/checkout-agent/packages/agent-core/src/state/store.ts +307 -0
- package/templates/checkout-agent/packages/agent-core/src/state-machine.ts +157 -0
- package/templates/checkout-agent/packages/agent-core/src/stubs/charge-rail.ts +195 -0
- package/templates/checkout-agent/packages/agent-core/src/stubs/mandate-status.ts +58 -0
- package/templates/checkout-agent/packages/agent-core/src/stubs/rail.ts +209 -0
- package/templates/checkout-agent/packages/agent-core/src/tools.ts +66 -0
- package/templates/checkout-agent/packages/agent-core/src/types.ts +187 -0
- package/templates/checkout-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
- package/templates/checkout-agent/packages/agent-core/test/approval.test.ts +117 -0
- package/templates/checkout-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
- package/templates/checkout-agent/packages/agent-core/test/bundle.test.ts +85 -0
- package/templates/checkout-agent/packages/agent-core/test/clock.test.ts +25 -0
- package/templates/checkout-agent/packages/agent-core/test/composition.test.ts +204 -0
- package/templates/checkout-agent/packages/agent-core/test/engine.test.ts +596 -0
- package/templates/checkout-agent/packages/agent-core/test/escalate.test.ts +42 -0
- package/templates/checkout-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
- package/templates/checkout-agent/packages/agent-core/test/fixtures/staging-receipt-keys.json +18 -0
- package/templates/checkout-agent/packages/agent-core/test/fixtures/staging-receipt.json +24 -0
- package/templates/checkout-agent/packages/agent-core/test/helpers.ts +142 -0
- package/templates/checkout-agent/packages/agent-core/test/mandate-status.test.ts +229 -0
- package/templates/checkout-agent/packages/agent-core/test/manifest.test.ts +45 -0
- package/templates/checkout-agent/packages/agent-core/test/quote.test.ts +198 -0
- package/templates/checkout-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
- package/templates/checkout-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
- package/templates/checkout-agent/packages/agent-core/test/receipt-verification.test.ts +316 -0
- package/templates/checkout-agent/packages/agent-core/test/receivables.test.ts +416 -0
- package/templates/checkout-agent/packages/agent-core/test/secrets.test.ts +28 -0
- package/templates/checkout-agent/packages/agent-core/test/state-machine.test.ts +67 -0
- package/templates/checkout-agent/packages/agent-core/test/transitions.type-test.ts +59 -0
- package/templates/checkout-agent/packages/agent-core/tsconfig.json +10 -0
- package/templates/checkout-agent/packages/agent-runtime/README.md +80 -0
- package/templates/checkout-agent/packages/agent-runtime/bin.mjs +32 -0
- package/templates/checkout-agent/packages/agent-runtime/package.json +23 -0
- package/templates/checkout-agent/packages/agent-runtime/src/adversarial.ts +199 -0
- package/templates/checkout-agent/packages/agent-runtime/src/agent.ts +68 -0
- package/templates/checkout-agent/packages/agent-runtime/src/channels/contact.ts +13 -0
- package/templates/checkout-agent/packages/agent-runtime/src/channels/index.ts +72 -0
- package/templates/checkout-agent/packages/agent-runtime/src/channels/rules.ts +120 -0
- package/templates/checkout-agent/packages/agent-runtime/src/channels/types.ts +173 -0
- package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +459 -0
- package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +216 -0
- package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/evidence.ts +109 -0
- package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +350 -0
- package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +180 -0
- package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/present.ts +47 -0
- package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +120 -0
- package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +127 -0
- package/templates/checkout-agent/packages/agent-runtime/src/cli.ts +98 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/check.ts +15 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/decide.ts +63 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/eval.ts +58 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/inspect.ts +100 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +367 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/poll.ts +145 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/reconcile.ts +42 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/rerun.ts +86 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/resume.ts +49 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +134 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/start.ts +248 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/verify.ts +330 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/webhook.ts +40 -0
- package/templates/checkout-agent/packages/agent-runtime/src/default-kit.ts +83 -0
- package/templates/checkout-agent/packages/agent-runtime/src/index.ts +13 -0
- package/templates/checkout-agent/packages/agent-runtime/src/inspect.ts +801 -0
- package/templates/checkout-agent/packages/agent-runtime/src/kit.ts +229 -0
- package/templates/checkout-agent/packages/agent-runtime/src/poll.ts +105 -0
- package/templates/checkout-agent/packages/agent-runtime/src/scenarios.ts +274 -0
- package/templates/checkout-agent/packages/agent-runtime/src/setup.ts +239 -0
- package/templates/checkout-agent/packages/agent-runtime/src/terminal.ts +263 -0
- package/templates/checkout-agent/packages/agent-runtime/src/webhook.ts +131 -0
- package/templates/checkout-agent/packages/agent-runtime/test/inspect.test.ts +336 -0
- package/templates/checkout-agent/packages/agent-runtime/test/verify-cli.test.ts +329 -0
- package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +277 -0
- package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-evidence.test.ts +94 -0
- package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-rules.test.ts +174 -0
- package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-session.test.ts +167 -0
- package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
- package/templates/checkout-agent/packages/agent-runtime/tsconfig.json +9 -0
- package/templates/checkout-agent/scripts/whatsapp-emulator.mjs +84 -0
- package/templates/checkout-agent/tsconfig.base.json +16 -0
- package/templates/checkout-agent/vitest.config.ts +12 -0
- package/templates/collections-agent/README.md +6 -1
- package/templates/collections-agent/agents/collections-agent/README.md +12 -8
- package/templates/collections-agent/agents/collections-agent/channels/whatsapp/README.md +8 -0
- package/templates/collections-agent/agents/collections-agent/channels/whatsapp/acordo-1042-retomada.json +10 -0
- package/templates/collections-agent/agents/collections-agent/channels/whatsapp/templates.json +27 -1
- package/templates/collections-agent/agents/collections-agent/src/kit.ts +2 -0
- package/templates/collections-agent/agents/collections-agent/src/modules/bolepix-receivables.ts +12 -2
- package/templates/collections-agent/agents/collections-agent/test/ambiguous-charge.test.ts +55 -0
- package/templates/collections-agent/agents/collections-agent/test/fixtures/tap-emitir-nova-1042.transcript.jsonl +2 -0
- package/templates/collections-agent/agents/collections-agent/test/whatsapp-poll.test.ts +12 -6
- package/templates/collections-agent/agents/collections-agent/test/whatsapp-status-replies.test.ts +148 -0
- package/templates/collections-agent/agents/collections-agent/test/whatsapp.test.ts +5 -0
- package/templates/collections-agent/package.json +1 -0
- package/templates/collections-agent/packages/agent-core/README.md +14 -1
- package/templates/collections-agent/packages/agent-core/package.json +1 -1
- package/templates/collections-agent/packages/agent-core/src/agent.ts +39 -1
- package/templates/collections-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
- package/templates/collections-agent/packages/agent-core/src/api/client.ts +28 -5
- package/templates/collections-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
- package/templates/collections-agent/packages/agent-core/src/api/rail.ts +62 -26
- package/templates/collections-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
- package/templates/collections-agent/packages/agent-core/src/approval.ts +14 -2
- package/templates/collections-agent/packages/agent-core/src/bundle.ts +35 -3
- package/templates/collections-agent/packages/agent-core/src/channels.ts +49 -3
- package/templates/collections-agent/packages/agent-core/src/check.ts +25 -2
- package/templates/collections-agent/packages/agent-core/src/engine.ts +235 -46
- package/templates/collections-agent/packages/agent-core/src/hash.ts +57 -1
- package/templates/collections-agent/packages/agent-core/src/index.ts +2 -0
- package/templates/collections-agent/packages/agent-core/src/quote.ts +50 -0
- package/templates/collections-agent/packages/agent-core/src/rail.ts +89 -4
- package/templates/collections-agent/packages/agent-core/src/receipt-chain.ts +270 -0
- package/templates/collections-agent/packages/agent-core/src/receipt-verification.ts +194 -18
- package/templates/collections-agent/packages/agent-core/src/revocation.ts +1 -1
- package/templates/collections-agent/packages/agent-core/src/state-machine.ts +10 -1
- package/templates/collections-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
- package/templates/collections-agent/packages/agent-core/src/stubs/rail.ts +100 -32
- package/templates/collections-agent/packages/agent-core/src/tools.ts +17 -6
- package/templates/collections-agent/packages/agent-core/src/types.ts +54 -1
- package/templates/collections-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
- package/templates/collections-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
- package/templates/collections-agent/packages/agent-core/test/composition.test.ts +204 -0
- package/templates/collections-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
- package/templates/collections-agent/packages/agent-core/test/helpers.ts +4 -1
- package/templates/collections-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
- package/templates/collections-agent/packages/agent-core/test/quote.test.ts +198 -0
- package/templates/collections-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
- package/templates/collections-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
- package/templates/collections-agent/packages/agent-core/test/receivables.test.ts +103 -3
- package/templates/collections-agent/packages/agent-runtime/README.md +12 -0
- package/templates/collections-agent/packages/agent-runtime/src/adversarial.ts +32 -2
- package/templates/collections-agent/packages/agent-runtime/src/channels/types.ts +53 -4
- package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
- package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
- package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
- package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
- package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
- package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
- package/templates/collections-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
- package/templates/collections-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
- package/templates/collections-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
- package/templates/collections-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
- package/templates/collections-agent/packages/agent-runtime/src/commands/start.ts +6 -3
- package/templates/collections-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
- package/templates/collections-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
- package/templates/collections-agent/packages/agent-runtime/src/index.ts +1 -0
- package/templates/collections-agent/packages/agent-runtime/src/inspect.ts +5 -1
- package/templates/collections-agent/packages/agent-runtime/src/kit.ts +25 -2
- package/templates/collections-agent/packages/agent-runtime/src/scenarios.ts +8 -2
- package/templates/collections-agent/packages/agent-runtime/src/setup.ts +13 -3
- package/templates/collections-agent/packages/agent-runtime/src/terminal.ts +127 -18
- package/templates/collections-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
- package/templates/collections-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
- package/templates/collections-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
- package/templates/collections-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
- package/templates/collections-agent/scripts/whatsapp-emulator.mjs +84 -0
- package/templates/hello-agent/README.md +1 -1
- package/templates/hello-agent/packages/agent-core/README.md +14 -1
- package/templates/hello-agent/packages/agent-core/package.json +1 -1
- package/templates/hello-agent/packages/agent-core/src/agent.ts +39 -1
- package/templates/hello-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
- package/templates/hello-agent/packages/agent-core/src/api/client.ts +28 -5
- package/templates/hello-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
- package/templates/hello-agent/packages/agent-core/src/api/rail.ts +62 -26
- package/templates/hello-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
- package/templates/hello-agent/packages/agent-core/src/approval.ts +14 -2
- package/templates/hello-agent/packages/agent-core/src/bundle.ts +35 -3
- package/templates/hello-agent/packages/agent-core/src/channels.ts +49 -3
- package/templates/hello-agent/packages/agent-core/src/check.ts +25 -2
- package/templates/hello-agent/packages/agent-core/src/engine.ts +235 -46
- package/templates/hello-agent/packages/agent-core/src/hash.ts +57 -1
- package/templates/hello-agent/packages/agent-core/src/index.ts +2 -0
- package/templates/hello-agent/packages/agent-core/src/quote.ts +50 -0
- package/templates/hello-agent/packages/agent-core/src/rail.ts +89 -4
- package/templates/hello-agent/packages/agent-core/src/receipt-chain.ts +270 -0
- package/templates/hello-agent/packages/agent-core/src/receipt-verification.ts +194 -18
- package/templates/hello-agent/packages/agent-core/src/revocation.ts +1 -1
- package/templates/hello-agent/packages/agent-core/src/state-machine.ts +10 -1
- package/templates/hello-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
- package/templates/hello-agent/packages/agent-core/src/stubs/rail.ts +100 -32
- package/templates/hello-agent/packages/agent-core/src/tools.ts +17 -6
- package/templates/hello-agent/packages/agent-core/src/types.ts +54 -1
- package/templates/hello-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
- package/templates/hello-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
- package/templates/hello-agent/packages/agent-core/test/composition.test.ts +204 -0
- package/templates/hello-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
- package/templates/hello-agent/packages/agent-core/test/helpers.ts +4 -1
- package/templates/hello-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
- package/templates/hello-agent/packages/agent-core/test/quote.test.ts +198 -0
- package/templates/hello-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
- package/templates/hello-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
- package/templates/hello-agent/packages/agent-core/test/receivables.test.ts +103 -3
- package/templates/hello-agent/packages/agent-runtime/README.md +12 -0
- package/templates/hello-agent/packages/agent-runtime/src/adversarial.ts +32 -2
- package/templates/hello-agent/packages/agent-runtime/src/channels/types.ts +53 -4
- package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
- package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
- package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
- package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
- package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
- package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
- package/templates/hello-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
- package/templates/hello-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
- package/templates/hello-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
- package/templates/hello-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
- package/templates/hello-agent/packages/agent-runtime/src/commands/start.ts +6 -3
- package/templates/hello-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
- package/templates/hello-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
- package/templates/hello-agent/packages/agent-runtime/src/index.ts +1 -0
- package/templates/hello-agent/packages/agent-runtime/src/inspect.ts +5 -1
- package/templates/hello-agent/packages/agent-runtime/src/kit.ts +25 -2
- package/templates/hello-agent/packages/agent-runtime/src/scenarios.ts +8 -2
- package/templates/hello-agent/packages/agent-runtime/src/setup.ts +13 -3
- package/templates/hello-agent/packages/agent-runtime/src/terminal.ts +127 -18
- package/templates/hello-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
- package/templates/hello-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
- package/templates/hello-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
- package/templates/hello-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
- package/templates/kits.lock.json +29 -6
- package/templates/supplier-payments-agent/README.md +1 -1
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/AGENTS.md +1 -1
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/CLAUDE.md +1 -1
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/README.md +11 -9
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/src/kit.ts +1 -1
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/src/modules/batch-payout.ts +67 -20
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/test/batch.test.ts +504 -5
- package/templates/supplier-payments-agent/packages/agent-core/README.md +14 -1
- package/templates/supplier-payments-agent/packages/agent-core/package.json +1 -1
- package/templates/supplier-payments-agent/packages/agent-core/src/agent.ts +39 -1
- package/templates/supplier-payments-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
- package/templates/supplier-payments-agent/packages/agent-core/src/api/client.ts +28 -5
- package/templates/supplier-payments-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
- package/templates/supplier-payments-agent/packages/agent-core/src/api/rail.ts +62 -26
- package/templates/supplier-payments-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
- package/templates/supplier-payments-agent/packages/agent-core/src/approval.ts +14 -2
- package/templates/supplier-payments-agent/packages/agent-core/src/bundle.ts +35 -3
- package/templates/supplier-payments-agent/packages/agent-core/src/channels.ts +49 -3
- package/templates/supplier-payments-agent/packages/agent-core/src/check.ts +25 -2
- package/templates/supplier-payments-agent/packages/agent-core/src/engine.ts +235 -46
- package/templates/supplier-payments-agent/packages/agent-core/src/hash.ts +57 -1
- package/templates/supplier-payments-agent/packages/agent-core/src/index.ts +2 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/quote.ts +50 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/rail.ts +89 -4
- package/templates/supplier-payments-agent/packages/agent-core/src/receipt-chain.ts +270 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/receipt-verification.ts +194 -18
- package/templates/supplier-payments-agent/packages/agent-core/src/revocation.ts +1 -1
- package/templates/supplier-payments-agent/packages/agent-core/src/state-machine.ts +10 -1
- package/templates/supplier-payments-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
- package/templates/supplier-payments-agent/packages/agent-core/src/stubs/rail.ts +100 -32
- package/templates/supplier-payments-agent/packages/agent-core/src/tools.ts +17 -6
- package/templates/supplier-payments-agent/packages/agent-core/src/types.ts +54 -1
- package/templates/supplier-payments-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/composition.test.ts +204 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/helpers.ts +4 -1
- package/templates/supplier-payments-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
- package/templates/supplier-payments-agent/packages/agent-core/test/quote.test.ts +198 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/receivables.test.ts +103 -3
- package/templates/supplier-payments-agent/packages/agent-runtime/README.md +12 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/adversarial.ts +32 -2
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/types.ts +53 -4
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/start.ts +6 -3
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
- package/templates/supplier-payments-agent/packages/agent-runtime/src/index.ts +1 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/inspect.ts +5 -1
- package/templates/supplier-payments-agent/packages/agent-runtime/src/kit.ts +25 -2
- package/templates/supplier-payments-agent/packages/agent-runtime/src/scenarios.ts +8 -2
- package/templates/supplier-payments-agent/packages/agent-runtime/src/setup.ts +13 -3
- package/templates/supplier-payments-agent/packages/agent-runtime/src/terminal.ts +127 -18
- package/templates/supplier-payments-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
- package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
- package/templates/bills-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
- package/templates/collections-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
- package/templates/hello-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
- package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
package/templates/checkout-agent/agents/checkout-agent/test/fixtures/order-marina.transcript.jsonl
ADDED
|
@@ -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,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
|
+
});
|