@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
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { createInterface } from "node:readline/promises";
|
|
9
9
|
import { stdin, stderr, stdout } from "node:process";
|
|
10
10
|
import { relative } from "node:path";
|
|
11
|
-
import type { AgentRuntime, ChargeInstrument, Execution } from "@codespar/agent-core";
|
|
11
|
+
import type { AgentRuntime, BatchGesture, BatchPresentation, ChargeInstrument, Execution } from "@codespar/agent-core";
|
|
12
12
|
import { pollUntilClosed, type PollResult } from "./poll.js";
|
|
13
13
|
import type { Setup } from "./setup.js";
|
|
14
14
|
|
|
@@ -23,8 +23,12 @@ export interface TerminalOptions {
|
|
|
23
23
|
simulatePayer?: boolean | undefined;
|
|
24
24
|
say?: ((line: string) => void) | undefined;
|
|
25
25
|
ask?: ((question: string) => Promise<string>) | undefined;
|
|
26
|
-
/**
|
|
27
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Where the lines for the counterparty go (the conversation); `say` is the
|
|
28
|
+
* operator's console. `about` is the execution whose OUTCOME the line tells,
|
|
29
|
+
* so a channel can tie a failed delivery of it back to that outcome.
|
|
30
|
+
*/
|
|
31
|
+
tell?: ((line: string, about?: Execution) => void) | undefined;
|
|
28
32
|
/**
|
|
29
33
|
* How a payable receivable is put in front of the counterparty. Defaults to
|
|
30
34
|
* the kit's own, which writes lines to a console. A channel overrides it,
|
|
@@ -32,8 +36,18 @@ export interface TerminalOptions {
|
|
|
32
36
|
* message, and neither of those is a line of text.
|
|
33
37
|
*/
|
|
34
38
|
presentInstrument?: ((execution: Execution, instalment: number, chargeId: string, instrument: ChargeInstrument, tell: (line: string) => void) => void | Promise<void>) | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* The gestures taken on whole batches in this session, by batch ref. When a
|
|
41
|
+
* line of a batch arrives and its ref is here, the line is decided by the
|
|
42
|
+
* gesture instead of by a question; see `presentBatch`.
|
|
43
|
+
*/
|
|
44
|
+
gestures?: BatchGestures | undefined;
|
|
35
45
|
}
|
|
36
46
|
|
|
47
|
+
/** One gesture per batch ref: the list it was taken on, and the positions vetoed. */
|
|
48
|
+
export type BatchGestures = Map<string, BatchGestureRecord>;
|
|
49
|
+
type BatchGestureRecord = { batch_hash: string; count: number; vetoed: Set<number> };
|
|
50
|
+
|
|
37
51
|
export function describeExecution(execution: Execution, setup: Setup): string[] {
|
|
38
52
|
return setup.kit.describeExecution(execution, setup);
|
|
39
53
|
}
|
|
@@ -43,6 +57,16 @@ export function announceOutcome(execution: Execution, setup: Setup, tell: (line:
|
|
|
43
57
|
return setup.kit.announceOutcome?.(execution, setup, tell) ?? false;
|
|
44
58
|
}
|
|
45
59
|
|
|
60
|
+
/** The kit's follow-up to an outcome. Never throws: what follows a paid order cannot un-pay it. */
|
|
61
|
+
export async function followUp(execution: Execution, setup: Setup, say: (line: string) => void): Promise<void> {
|
|
62
|
+
if (!setup.kit.followUp) return;
|
|
63
|
+
try {
|
|
64
|
+
await setup.kit.followUp(execution, setup, say);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
say(` follow-up of ${execution.id} failed and changed nothing about it: ${err instanceof Error ? err.message : String(err)}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
46
70
|
function receiptLines(execution: Execution, setup: Setup, say: (line: string) => void): void {
|
|
47
71
|
for (const outcome of execution.outcomes) {
|
|
48
72
|
if (outcome.receipt_id) say(` ${setup.kit.labels.receiptWord}: ${relative(process.cwd(), `${setup.bundle.dir}/receipts/${outcome.receipt_id}.json`)}`);
|
|
@@ -61,23 +85,29 @@ export async function handleExecution(execution: Execution, options: TerminalOpt
|
|
|
61
85
|
|
|
62
86
|
let current = execution;
|
|
63
87
|
if (current.state === "awaiting_approval") {
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
decision
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
88
|
+
const gesture = options.decision === undefined && current.batch ? options.gestures?.get(current.batch.ref) : undefined;
|
|
89
|
+
if (gesture) {
|
|
90
|
+
current = decideByGesture(current, gesture, setup, approver);
|
|
91
|
+
} else {
|
|
92
|
+
let decision = options.decision;
|
|
93
|
+
if (!decision) {
|
|
94
|
+
if (current.blocking_reasons.length > 0) say(" (o unico desfecho possivel e negar; aperte Enter)");
|
|
95
|
+
const ask = options.ask ?? defaultAsk;
|
|
96
|
+
const answer = (await ask(labels.approveQuestion)).trim().toLowerCase();
|
|
97
|
+
decision = answer === "s" || answer === "sim" || answer === "y" || answer === "yes" ? "approve" : "deny";
|
|
98
|
+
}
|
|
99
|
+
if (decision === "approve") current = engine.approve(current.id, approver);
|
|
100
|
+
else if (decision === "deny") current = engine.deny(current.id, approver);
|
|
101
|
+
else {
|
|
102
|
+
say(" deixado em awaiting_approval (rode de novo com --approve ou --deny)");
|
|
103
|
+
return current;
|
|
104
|
+
}
|
|
76
105
|
}
|
|
77
106
|
say(` -> ${current.state}${current.reason ? ` (${current.reason})` : ""}`);
|
|
78
107
|
}
|
|
79
108
|
|
|
80
|
-
|
|
109
|
+
// An agent that issues on request (`executeOnApproval: false`) leaves the approved execution for its own tool to issue.
|
|
110
|
+
if (current.state === "approved" && setup.kit.executeOnApproval !== false) {
|
|
81
111
|
current = await engine.execute(current.id);
|
|
82
112
|
say(` -> ${current.state}${current.reason ? ` (${current.reason})` : ""}`);
|
|
83
113
|
if (setup.settlement === "immediate") {
|
|
@@ -95,11 +125,87 @@ export async function handleExecution(execution: Execution, options: TerminalOpt
|
|
|
95
125
|
} else if (current.state === "executing") {
|
|
96
126
|
say(labels.uncertainDispatch);
|
|
97
127
|
}
|
|
98
|
-
|
|
128
|
+
const told = current;
|
|
129
|
+
announceOutcome(told, setup, (line) => tell(line, told));
|
|
130
|
+
await followUp(current, setup, say);
|
|
99
131
|
}
|
|
100
132
|
return current;
|
|
101
133
|
}
|
|
102
134
|
|
|
135
|
+
/**
|
|
136
|
+
* A line of a batch the person already decided on as a list. The gesture
|
|
137
|
+
* approves or vetoes a POSITION in ONE list: a line that names another list
|
|
138
|
+
* under the same ref is not covered by it, and "todas" must not become a yes
|
|
139
|
+
* to a list nobody was shown. `approve` still runs the policy and still mints
|
|
140
|
+
* this line's own artifact, which carries the `batch_hash` the gesture was
|
|
141
|
+
* taken on — that is what makes the gesture attested rather than asserted.
|
|
142
|
+
*/
|
|
143
|
+
function decideByGesture(execution: Execution, gesture: BatchGestureRecord, setup: Setup, approver: TerminalOptions["approver"]): Execution {
|
|
144
|
+
const engine = setup.engine;
|
|
145
|
+
const { batch_hash, index, count } = execution.batch!;
|
|
146
|
+
if (batch_hash !== gesture.batch_hash || count !== gesture.count) {
|
|
147
|
+
return engine.deny(execution.id, approver, `the list changed after the gesture: it was taken on ${gesture.count} line(s) hashing to ${gesture.batch_hash}, and this line belongs to ${count} hashing to ${batch_hash}`);
|
|
148
|
+
}
|
|
149
|
+
if (gesture.vetoed.has(index)) return engine.deny(execution.id, approver, `vetoed in the batch gesture (line ${index + 1} of ${count})`);
|
|
150
|
+
return engine.approve(execution.id, approver);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* What the person answered to a whole list, or `undefined` when the answer
|
|
155
|
+
* cannot be read as one. Lines are numbered from 1, as they were shown.
|
|
156
|
+
* "todas" / "all", "todas exceto 3,7" / "all except 3,7", and "nenhuma" /
|
|
157
|
+
* "none" (also an empty answer: the default is no, as it is per line). A
|
|
158
|
+
* number outside the list is unreadable rather than ignored, because a typo
|
|
159
|
+
* that silently vetoed nothing would pay the line the person meant to stop.
|
|
160
|
+
*/
|
|
161
|
+
export function parseBatchGesture(answer: string, count: number): { vetoed: number[] } | undefined {
|
|
162
|
+
const text = answer.trim().toLowerCase();
|
|
163
|
+
const all = Array.from({ length: count }, (_, i) => i);
|
|
164
|
+
if (text === "" || text === "nenhuma" || text === "none" || text === "n" || text === "nao") return { vetoed: all };
|
|
165
|
+
if (text === "todas" || text === "all") return { vetoed: [] };
|
|
166
|
+
const except = /^(?:todas|all)\s+(?:exceto|except)\s+([\d\s,]+)$/.exec(text);
|
|
167
|
+
if (!except) return undefined;
|
|
168
|
+
const numbers = except[1]!.split(/[\s,]+/).filter(Boolean).map(Number);
|
|
169
|
+
if (numbers.length === 0 || numbers.some((n) => !Number.isInteger(n) || n < 1 || n > count)) return undefined;
|
|
170
|
+
return { vetoed: [...new Set(numbers.map((n) => n - 1))].sort((a, b) => a - b) };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Section 3, for a list: the person sees every line, the total and the hash
|
|
175
|
+
* prefix, and answers once. What they answer is recorded in the bundle as a
|
|
176
|
+
* `batch.gesture` event and kept for the lines that follow; each line is then
|
|
177
|
+
* decided by `decideByGesture` when it arrives, and each one it approves mints
|
|
178
|
+
* its own artifact as before.
|
|
179
|
+
*/
|
|
180
|
+
export async function presentBatch(batch: BatchPresentation, options: TerminalOptions & { gestures: BatchGestures }): Promise<BatchGesture> {
|
|
181
|
+
const { setup, approver } = options;
|
|
182
|
+
const say = options.say ?? ((l: string) => stderr.write(l + "\n"));
|
|
183
|
+
const ask = options.ask ?? defaultAsk;
|
|
184
|
+
say(` lote ${batch.ref} — ${batch.label}: ${batch.count} linha(s), total ${batch.total}, batch_hash ${batch.batch_hash.slice(0, 19)}…`);
|
|
185
|
+
for (const line of batch.lines) {
|
|
186
|
+
const note =
|
|
187
|
+
line.status === "already_settled"
|
|
188
|
+
? " (ja paga; nao roda de novo)"
|
|
189
|
+
: line.status === "in_progress"
|
|
190
|
+
? " (em andamento; nao roda de novo)"
|
|
191
|
+
: line.status === "attempt_id_conflict"
|
|
192
|
+
? " (tentativa presa a outro pagamento; nao roda de novo)"
|
|
193
|
+
: "";
|
|
194
|
+
say(` ${line.index + 1}. ${line.beneficiary}: ${line.amount}${note}`);
|
|
195
|
+
}
|
|
196
|
+
let parsed: { vetoed: number[] } | undefined;
|
|
197
|
+
while (!parsed) {
|
|
198
|
+
parsed = parseBatchGesture(await ask(" Aprovar a lista? [todas / todas exceto 3,7 / nenhuma] "), batch.count);
|
|
199
|
+
if (!parsed) say(` nao entendi; responda todas, todas exceto <numeros de 1 a ${batch.count}> ou nenhuma`);
|
|
200
|
+
}
|
|
201
|
+
const vetoed = new Set(parsed.vetoed);
|
|
202
|
+
const gesture: BatchGesture = { batch_hash: batch.batch_hash, approved: batch.lines.map((l) => l.index).filter((i) => !vetoed.has(i)), vetoed: parsed.vetoed };
|
|
203
|
+
options.gestures.set(batch.ref, { batch_hash: batch.batch_hash, count: batch.count, vetoed });
|
|
204
|
+
setup.engine.note("batch.gesture", null, { batch_ref: batch.ref, batch_hash: batch.batch_hash, count: batch.count, total_minor: batch.total_minor, approved: gesture.approved, vetoed: gesture.vetoed, approver: { type: "human", id: approver.id, channel: approver.channel } });
|
|
205
|
+
say(` -> lista ${gesture.vetoed.length === 0 ? "aprovada inteira" : gesture.approved.length === 0 ? "negada inteira" : `aprovada exceto ${gesture.vetoed.map((i) => i + 1).join(", ")}`}`);
|
|
206
|
+
return gesture;
|
|
207
|
+
}
|
|
208
|
+
|
|
103
209
|
export async function waitForPayer(executionId: string, options: TerminalOptions): Promise<PollResult> {
|
|
104
210
|
const { setup } = options;
|
|
105
211
|
const say = options.say ?? ((l: string) => stderr.write(l + "\n"));
|
|
@@ -133,7 +239,10 @@ export async function interactive(options: TerminalOptions & { runtime: AgentRun
|
|
|
133
239
|
const { setup } = options;
|
|
134
240
|
const say = options.say ?? ((l: string) => stderr.write(l + "\n"));
|
|
135
241
|
const labels = setup.kit.labels;
|
|
136
|
-
|
|
242
|
+
// Interactive only: a person at the keyboard can decide a list in one go. The one-shot and the scenario runner pass one decision to every line and never reach this.
|
|
243
|
+
const gestures: BatchGestures = new Map();
|
|
244
|
+
const withGestures = { ...options, gestures };
|
|
245
|
+
const loop = setup.makeLoop(options.runtime, (execution) => handleExecution(execution, withGestures), (batch) => presentBatch(batch, withGestures));
|
|
137
246
|
say(`${setup.manifest.manifest.name} ${setup.manifest.manifest.version} — approval: ${setup.mode} — trilho: ${setup.railKind} — ${labels.mandateWord} ${setup.mandate.id}`);
|
|
138
247
|
say(`run ${setup.runId} — bundle em ${relative(process.cwd(), setup.bundle.dir)}`);
|
|
139
248
|
say(labels.intro);
|
|
@@ -7,12 +7,15 @@
|
|
|
7
7
|
* verifier is someone who has the receipt file and nothing else. The key set
|
|
8
8
|
* is a local copy, so no test opens a socket.
|
|
9
9
|
*/
|
|
10
|
-
import { spawnSync } from "node:child_process";
|
|
11
|
-
import { generateKeyPairSync, sign as signDetached } from "node:crypto";
|
|
12
|
-
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
11
|
+
import { createHash, generateKeyPairSync, sign as signDetached } from "node:crypto";
|
|
12
|
+
import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
13
|
+
import { createServer, type Server } from "node:http";
|
|
14
|
+
import type { AddressInfo } from "node:net";
|
|
13
15
|
import { tmpdir } from "node:os";
|
|
14
16
|
import { join, resolve } from "node:path";
|
|
15
|
-
import {
|
|
17
|
+
import { canonicalize, itemsHash, maskPayee, type ExecutionItem } from "@codespar/agent-core";
|
|
18
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
16
19
|
|
|
17
20
|
const BIN = resolve(import.meta.dirname, "../bin.mjs");
|
|
18
21
|
const KID = "did:web:id.codespar.dev#1";
|
|
@@ -55,13 +58,15 @@ function run(args: string[], cwd: string) {
|
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
describe("codespar-agent verify", () => {
|
|
58
|
-
it("
|
|
61
|
+
it("checks a run's copy from a directory with no agent and no key, and says it proves the signature only (exit 7)", () => {
|
|
59
62
|
const { dir, receiptPath, keysPath } = fixture();
|
|
60
63
|
const out = run([receiptPath, "--keys", keysPath], dir);
|
|
61
|
-
expect(out.code).toBe(
|
|
62
|
-
expect(out.stdout).toContain("
|
|
64
|
+
expect(out.code).toBe(7);
|
|
65
|
+
expect(out.stdout).toContain("SIGNATURE_ONLY");
|
|
63
66
|
expect(out.stdout).toContain(RECEIPT_ID);
|
|
64
|
-
expect(out.
|
|
67
|
+
expect(out.stdout).toContain("read_required");
|
|
68
|
+
expect(out.stderr).toContain("carries CodeSpar's signature");
|
|
69
|
+
expect(out.stderr).toContain("--from-api");
|
|
65
70
|
});
|
|
66
71
|
|
|
67
72
|
it("puts valid JSON and nothing else on stdout with --json; the sentence goes to stderr", () => {
|
|
@@ -70,7 +75,7 @@ describe("codespar-agent verify", () => {
|
|
|
70
75
|
const lines = out.stdout.split("\n").filter(Boolean);
|
|
71
76
|
expect(lines).toHaveLength(1);
|
|
72
77
|
const report = JSON.parse(lines[0]!) as { verdict: string; kid: string; signing_string: string; message: string };
|
|
73
|
-
expect(report.verdict).toBe("
|
|
78
|
+
expect(report.verdict).toBe("signature_only");
|
|
74
79
|
expect(report.kid).toBe(KID);
|
|
75
80
|
expect(report.signing_string).toBe(`codespar-receipt:v1:${RECEIPT_ID}:${CHAIN}`);
|
|
76
81
|
expect(out.stderr).toContain(report.message);
|
|
@@ -125,3 +130,200 @@ describe("codespar-agent verify", () => {
|
|
|
125
130
|
expect(run(["--help"], dir).code).toBe(0);
|
|
126
131
|
});
|
|
127
132
|
});
|
|
133
|
+
|
|
134
|
+
/*
|
|
135
|
+
* The body and the approval (OPEN_QUESTIONS §3, §47). A v4 receipt read, its
|
|
136
|
+
* chain written out link by link and signed with a key generated here, under
|
|
137
|
+
* the recipe production published on 2026-09-26.
|
|
138
|
+
*/
|
|
139
|
+
const PUBLISHED = JSON.parse(readFileSync(resolve(import.meta.dirname, "../../agent-core/test/fixtures/production-receipt-keys.json"), "utf8")) as Record<string, unknown>;
|
|
140
|
+
const V4_KID = "did:web:id.codespar.dev#production-2";
|
|
141
|
+
const PAYEE = "escola@exemplo.com.br";
|
|
142
|
+
const ITEMS: ExecutionItem[] = [{ alias: "escola", beneficiary: "Escola Aurora", payee: PAYEE, amount: 185000, currency: "BRL", description: "outubro" }];
|
|
143
|
+
const sha256 = (v: string) => createHash("sha256").update(v, "utf8").digest("hex");
|
|
144
|
+
|
|
145
|
+
function v4Fixture(sealedItemsHash = itemsHash(ITEMS)) {
|
|
146
|
+
const dir = mkdtempSync(join(tmpdir(), "verify-cli-v4-"));
|
|
147
|
+
const { privateKey, publicKey } = generateKeyPairSync("ed25519");
|
|
148
|
+
const x = Buffer.from(publicKey.export({ format: "der", type: "spki" }).subarray(-32)).toString("base64url");
|
|
149
|
+
const mandate = { id: "cm_test_1", nonce: "nonce-1", scope: "contas do mes", currency: "BRL", sig_sha256: sha256("s".repeat(64)) };
|
|
150
|
+
const quote = { seller: "Escola Aurora", resource: "outubro", price_minor: 185000, payee: PAYEE, session_id: null, sig: "q".repeat(64), at: "2026-09-26T18:00:00.000Z" };
|
|
151
|
+
const approval = { items_hash: sealedItemsHash, batch_hash: null };
|
|
152
|
+
const payment = { rail: "pix-consent", provider: "pix", tx_id: "tx_1", amount_minor: 185000, amount_atomic: null, sandbox: true, attempt_id: "att_1_0", money_moved: false, at: "2026-09-26T18:00:01.000Z" };
|
|
153
|
+
const chain = sha256(
|
|
154
|
+
canonicalize({
|
|
155
|
+
v: 4,
|
|
156
|
+
links: [
|
|
157
|
+
mandate,
|
|
158
|
+
quote,
|
|
159
|
+
approval,
|
|
160
|
+
{ rail: payment.rail, provider: payment.provider, tx_id: payment.tx_id, amount_minor: payment.amount_minor, sandbox: true, attempt_id: payment.attempt_id, money_moved: false, at: payment.at },
|
|
161
|
+
],
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
164
|
+
const signature = signDetached(null, Buffer.from(`codespar-receipt:v1:${RECEIPT_ID}:${chain}`, "utf8"), privateKey).toString("base64url");
|
|
165
|
+
const read = { receipt_id: RECEIPT_ID, state: "paid", chain_version: 4, mandate, quote, approval, payment, delivery: null, chain, receipt_sig: "hmac-not-read-here", receipt_sig_ed25519: signature, receipt_sig_kid: V4_KID, exceptions: [] };
|
|
166
|
+
const keys = { ...PUBLISHED, keys: [{ kid: V4_KID, kty: "OKP", crv: "Ed25519", x, alg: "EdDSA", use: "sig", status: "active" }] };
|
|
167
|
+
const artifact = { approval_id: "apr_1", execution_id: "exe_1", items: ITEMS, items_hash: itemsHash(ITEMS), approved_at: "2026-09-26T18:00:00.000Z" };
|
|
168
|
+
|
|
169
|
+
// A run's bundle, as the engine writes it: the masked copy naming its artifact, and approval.json beside receipts/.
|
|
170
|
+
const run = join(dir, "runs", "run_1");
|
|
171
|
+
mkdirSync(join(run, "receipts"), { recursive: true });
|
|
172
|
+
const copy = {
|
|
173
|
+
receipt_id: RECEIPT_ID,
|
|
174
|
+
state: "paid",
|
|
175
|
+
mandate: { id: mandate.id },
|
|
176
|
+
payment: { amount_minor: 185000, payee: maskPayee(PAYEE), attempt_id: payment.attempt_id, money_moved: false, sandbox: true, at: payment.at },
|
|
177
|
+
chain,
|
|
178
|
+
receipt_sig: "hmac-not-read-here",
|
|
179
|
+
receipt_sig_ed25519: signature,
|
|
180
|
+
receipt_sig_kid: V4_KID,
|
|
181
|
+
actor: { type: "agent", agent: "bills-agent@0.1.0", on_behalf_of: "usr_demo" },
|
|
182
|
+
approval_id: artifact.approval_id,
|
|
183
|
+
};
|
|
184
|
+
const paths = {
|
|
185
|
+
read: join(dir, "receipt-read.json"),
|
|
186
|
+
keys: join(dir, "codespar-receipt-keys.json"),
|
|
187
|
+
artifact: join(dir, "approval.json"),
|
|
188
|
+
copy: join(run, "receipts", `${RECEIPT_ID}.json`),
|
|
189
|
+
runApprovals: join(run, "approval.json"),
|
|
190
|
+
};
|
|
191
|
+
writeFileSync(paths.read, JSON.stringify(read, null, 2) + "\n");
|
|
192
|
+
writeFileSync(paths.keys, JSON.stringify(keys, null, 2) + "\n");
|
|
193
|
+
writeFileSync(paths.artifact, JSON.stringify([artifact], null, 2) + "\n");
|
|
194
|
+
writeFileSync(paths.copy, JSON.stringify(copy, null, 2) + "\n");
|
|
195
|
+
writeFileSync(paths.runApprovals, JSON.stringify([artifact], null, 2) + "\n");
|
|
196
|
+
return { dir, read, keys, copy, artifact, paths };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
describe("codespar-agent verify, the body and the approval of a v4 receipt read", () => {
|
|
200
|
+
it("exits 0 when the chain recomputes from the read and the sealed approval is the artifact's", () => {
|
|
201
|
+
const f = v4Fixture();
|
|
202
|
+
const out = run([f.paths.read, "--keys", f.paths.keys, "--approval", f.paths.artifact], f.dir);
|
|
203
|
+
expect(out.code).toBe(0);
|
|
204
|
+
expect(out.stdout).toContain("VERIFIED");
|
|
205
|
+
expect(out.stdout).toContain("body recomputed (chain v4)");
|
|
206
|
+
expect(out.stdout).toContain("approval matched");
|
|
207
|
+
expect(out.stderr).toContain("paid against the list artifact apr_1 approved");
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("exits 9, approval_mismatch, when the receipt sealed another list than the artifact's", () => {
|
|
211
|
+
const f = v4Fixture(`sha256:${"9".repeat(64)}`);
|
|
212
|
+
const out = run([f.paths.read, "--keys", f.paths.keys, "--approval", f.paths.artifact, "--json"], f.dir);
|
|
213
|
+
expect(out.code).toBe(9);
|
|
214
|
+
const report = JSON.parse(out.stdout) as { verdict: string; reason: string; approval_check: { status: string } };
|
|
215
|
+
expect(report).toMatchObject({ verdict: "approval_mismatch", reason: "items_hash_differs", approval_check: { status: "mismatch" } });
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("exits 8, chain_mismatch, when the payee beside a genuine signature was changed", () => {
|
|
219
|
+
const f = v4Fixture();
|
|
220
|
+
writeFileSync(f.paths.read, JSON.stringify({ ...f.read, quote: { ...f.read.quote, payee: "outra@exemplo.com.br" } }));
|
|
221
|
+
const out = run([f.paths.read, "--keys", f.paths.keys, "--approval", f.paths.artifact], f.dir);
|
|
222
|
+
expect(out.code).toBe(8);
|
|
223
|
+
expect(out.stdout).toContain("CHAIN_MISMATCH");
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("exits 7 for a v2 read, and says the chain is not recomputable without the mandate signature", () => {
|
|
227
|
+
const f = v4Fixture();
|
|
228
|
+
writeFileSync(f.paths.read, JSON.stringify({ ...f.read, chain_version: 2 }));
|
|
229
|
+
const out = run([f.paths.read, "--keys", f.paths.keys], f.dir);
|
|
230
|
+
expect(out.code).toBe(7);
|
|
231
|
+
expect(out.stderr).toContain("not recomputable without the mandate signature");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("refuses an --approval file with more than one artifact unless --approval-id names one", () => {
|
|
235
|
+
const f = v4Fixture();
|
|
236
|
+
writeFileSync(f.paths.artifact, JSON.stringify([f.artifact, { ...f.artifact, approval_id: "apr_2" }]));
|
|
237
|
+
expect(run([f.paths.read, "--keys", f.paths.keys, "--approval", f.paths.artifact], f.dir).code).toBe(2);
|
|
238
|
+
expect(run([f.paths.read, "--keys", f.paths.keys, "--approval", f.paths.artifact, "--approval-id", "apr_1"], f.dir).code).toBe(0);
|
|
239
|
+
expect(run([f.paths.read, "--keys", f.paths.keys, "--approval-id", "apr_1"], f.dir).code).toBe(2);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
/*
|
|
244
|
+
* `--from-api`: a run's masked copy, bound through the API's read. A local
|
|
245
|
+
* server plays the deployment — the receipt route and its well-known key set —
|
|
246
|
+
* so the read and the key set come from ONE origin, as the command requires.
|
|
247
|
+
*/
|
|
248
|
+
describe("codespar-agent verify --from-api", () => {
|
|
249
|
+
let server: Server;
|
|
250
|
+
let baseUrl: string;
|
|
251
|
+
let served: { read: unknown; keys: unknown } = { read: {}, keys: {} };
|
|
252
|
+
const asked: string[] = [];
|
|
253
|
+
|
|
254
|
+
beforeAll(async () => {
|
|
255
|
+
server = createServer((req, res) => {
|
|
256
|
+
asked.push(`${req.method} ${req.url} ${req.headers["authorization"] ? "auth" : "anon"}`);
|
|
257
|
+
const body = req.url === "/.well-known/codespar-receipt-keys.json" ? served.keys : req.url === `/v1/consumers/receipts/${RECEIPT_ID}` ? served.read : undefined;
|
|
258
|
+
res.writeHead(body === undefined ? 404 : 200, { "content-type": "application/json" });
|
|
259
|
+
res.end(JSON.stringify(body ?? { error: { code: "not_found", message: "no" } }));
|
|
260
|
+
});
|
|
261
|
+
await new Promise<void>((ok) => server.listen(0, "127.0.0.1", ok));
|
|
262
|
+
baseUrl = `http://127.0.0.1:${(server.address() as AddressInfo).port}`;
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
afterAll(async () => {
|
|
266
|
+
server.closeAllConnections();
|
|
267
|
+
await new Promise<void>((ok) => server.close(() => ok()));
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
/** The server answers while the command runs, so the process is awaited, never spawned synchronously. */
|
|
271
|
+
function runAsync(args: string[], cwd: string): Promise<{ code: number | null; stdout: string; stderr: string }> {
|
|
272
|
+
return new Promise((done) => {
|
|
273
|
+
const child = spawn(process.execPath, [BIN, "verify", ...args], {
|
|
274
|
+
cwd,
|
|
275
|
+
// The placeholder passes the csk_test_ guard and is the one test-key-shaped string the secret scan allows.
|
|
276
|
+
env: { ...process.env, CODESPAR_API_KEY: "csk_test_your_key_here", CODESPAR_API_URL: baseUrl, ANTHROPIC_API_KEY: "" },
|
|
277
|
+
});
|
|
278
|
+
let stdout = "";
|
|
279
|
+
let stderr = "";
|
|
280
|
+
child.stdout.on("data", (d: Buffer) => (stdout += d.toString()));
|
|
281
|
+
child.stderr.on("data", (d: Buffer) => (stderr += d.toString()));
|
|
282
|
+
child.on("close", (code) => done({ code, stdout, stderr }));
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
it("binds the copy's body through the read and checks the run's own artifact: exit 0, and nothing unmasked is written", async () => {
|
|
287
|
+
const f = v4Fixture();
|
|
288
|
+
served = { read: f.read, keys: f.keys };
|
|
289
|
+
asked.length = 0;
|
|
290
|
+
const out = await runAsync([f.paths.copy, "--from-api"], f.dir);
|
|
291
|
+
expect(out.code).toBe(0);
|
|
292
|
+
expect(out.stdout).toContain("approval matched");
|
|
293
|
+
expect(asked).toEqual([`GET /.well-known/codespar-receipt-keys.json anon`, `GET /v1/consumers/receipts/${RECEIPT_ID} auth`]);
|
|
294
|
+
expect(readFileSync(f.paths.copy, "utf8")).not.toContain(PAYEE);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it("exits 9 when the run's artifact is not the list the receipt sealed", async () => {
|
|
298
|
+
const f = v4Fixture();
|
|
299
|
+
served = { read: f.read, keys: f.keys };
|
|
300
|
+
writeFileSync(f.paths.runApprovals, JSON.stringify([{ ...f.artifact, items: [{ ...ITEMS[0]!, amount: 1 }], items_hash: itemsHash([{ ...ITEMS[0]!, amount: 1 }]) }]));
|
|
301
|
+
const out = await runAsync([f.paths.copy, "--from-api", "--json"], f.dir);
|
|
302
|
+
expect(out.code).toBe(9);
|
|
303
|
+
expect(JSON.parse(out.stdout)).toMatchObject({ verdict: "approval_mismatch", reason: "items_hash_differs" });
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("exits 8 when the copy was edited after it was written: the signature does not cover its body, the read does", async () => {
|
|
307
|
+
const f = v4Fixture();
|
|
308
|
+
served = { read: f.read, keys: f.keys };
|
|
309
|
+
writeFileSync(f.paths.copy, JSON.stringify({ ...f.copy, payment: { ...f.copy.payment, amount_minor: 1 } }));
|
|
310
|
+
const out = await runAsync([f.paths.copy, "--from-api", "--json"], f.dir);
|
|
311
|
+
expect(out.code).toBe(8);
|
|
312
|
+
expect(JSON.parse(out.stdout)).toMatchObject({ verdict: "chain_mismatch", reason: "copy_differs_from_read" });
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("answers signature_only, never a failure, when the read cannot be fetched", async () => {
|
|
316
|
+
const f = v4Fixture();
|
|
317
|
+
served = { read: undefined as unknown, keys: f.keys };
|
|
318
|
+
const out = await runAsync([f.paths.copy, "--from-api", "--json"], f.dir);
|
|
319
|
+
expect(out.code).toBe(7);
|
|
320
|
+
expect(JSON.parse(out.stdout)).toMatchObject({ verdict: "signature_only", reason: "read_unavailable" });
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it("refuses a key set from another deployment than the read", async () => {
|
|
324
|
+
const f = v4Fixture();
|
|
325
|
+
const out = await runAsync([f.paths.copy, "--from-api", "--url", "https://api.codespar.dev/.well-known/codespar-receipt-keys.json"], f.dir);
|
|
326
|
+
expect(out.code).toBe(2);
|
|
327
|
+
expect(out.stderr).toContain("same deployment");
|
|
328
|
+
});
|
|
329
|
+
});
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
isMetaBaseUrl,
|
|
21
21
|
loadCloudApiConfig,
|
|
22
22
|
parseInbound,
|
|
23
|
+
providerRefusal,
|
|
23
24
|
verifyChallenge,
|
|
24
25
|
verifyWebhookSignature,
|
|
25
26
|
WhatsAppCloudApi,
|
|
@@ -230,3 +231,47 @@ describe("deliver, with the network replaced by an assertion", () => {
|
|
|
230
231
|
expect(sent.refused?.rule).toBe("provider_refused");
|
|
231
232
|
});
|
|
232
233
|
});
|
|
234
|
+
|
|
235
|
+
describe("what Meta's error body says, read rather than collapsed into a status", () => {
|
|
236
|
+
const body = (error: unknown) => JSON.stringify({ error });
|
|
237
|
+
|
|
238
|
+
it("reads 131047 as the window rule it is, so a caller can answer it with a template", () => {
|
|
239
|
+
const refusal = providerRefusal(400, body({ code: 131047, message: "(#131047) Message failed to send", error_data: { details: "the 24h customer service window closed" } }));
|
|
240
|
+
expect(refusal.rule).toBe("session_window_closed");
|
|
241
|
+
expect(refusal.detail).toContain("400/131047");
|
|
242
|
+
expect(refusal.detail).toContain("the 24h customer service window closed");
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("keeps any other code a provider refusal, with the code named", () => {
|
|
246
|
+
expect(providerRefusal(400, body({ code: 131026, message: "Message undeliverable" }))).toEqual({ rule: "provider_refused", detail: "400/131026 from the Cloud API" });
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("does not mistake a body without Meta's envelope for anything but the status", () => {
|
|
250
|
+
expect(providerRefusal(502, "<html>bad gateway</html>")).toEqual({ rule: "provider_refused", detail: "502 from the Cloud API" });
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe("a redelivered message is the same message", () => {
|
|
255
|
+
it("hands a message on once however many times it arrives, and says it dropped the rest", async () => {
|
|
256
|
+
const lines: string[] = [];
|
|
257
|
+
const port = 40000 + Math.floor(Math.random() * 20000);
|
|
258
|
+
const backend = new WhatsAppCloudApi({ config: { ...CONFIG, webhookPort: port }, conversation: { contact: "+5511987654321" }, say: (l) => lines.push(l) });
|
|
259
|
+
await backend.open();
|
|
260
|
+
try {
|
|
261
|
+
const raw = JSON.stringify({
|
|
262
|
+
entry: [{ changes: [{ value: { messages: [{ id: "wamid.ONCE", from: "5511987654321", timestamp: "1758643200", type: "text", text: { body: "fechado" } }] } }] }],
|
|
263
|
+
});
|
|
264
|
+
for (let i = 0; i < 2; i++) {
|
|
265
|
+
const response = await fetch(`http://127.0.0.1:${port}/`, { method: "POST", headers: { "x-hub-signature-256": sign(raw) }, body: raw });
|
|
266
|
+
// Still 200: Meta retries anything else, and a duplicate is not an error.
|
|
267
|
+
expect(response.status).toBe(200);
|
|
268
|
+
}
|
|
269
|
+
expect((await backend.next())?.id).toBe("wamid.ONCE");
|
|
270
|
+
const second = await Promise.race([backend.next(), new Promise((resolve) => setTimeout(() => resolve("nothing"), 200))]);
|
|
271
|
+
expect(second).toBe("nothing");
|
|
272
|
+
expect(lines).toContain("[whatsapp] duplicate delivery of wamid.ONCE dropped");
|
|
273
|
+
} finally {
|
|
274
|
+
await backend.close();
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
});
|
|
@@ -104,6 +104,38 @@ describe("a conversation a later process comes back to", () => {
|
|
|
104
104
|
});
|
|
105
105
|
});
|
|
106
106
|
|
|
107
|
+
describe("when the provider's clock disagrees with ours", () => {
|
|
108
|
+
/** A provider whose window is shut whatever ours says: free text is refused the way Meta refuses it, templates go. */
|
|
109
|
+
class ShutProvider extends Recorder {
|
|
110
|
+
override async deliver(to: string, body: OutboundBody): Promise<SentMessage> {
|
|
111
|
+
if (body.kind === "template") return super.deliver(to, body);
|
|
112
|
+
return { id: "", state: "failed", refused: { rule: "session_window_closed", detail: "the provider refused the free-form message (400/131047)" } };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
it("believes the provider: after a 131047 the window reads shut, and a template still goes", async () => {
|
|
117
|
+
const { channel: c, backend } = channel(TUESDAY, Math.floor(TUESDAY.getTime() / 1000) - 3600, new ShutProvider());
|
|
118
|
+
expect(c.sessionOpen).toBe(true);
|
|
119
|
+
const sent = await c.say("Recebemos, acordo quitado.");
|
|
120
|
+
expect(sent.refused?.rule).toBe("session_window_closed");
|
|
121
|
+
expect(c.sessionOpen).toBe(false);
|
|
122
|
+
expect(c.sessionRemainingSeconds).toBe(0);
|
|
123
|
+
// Refused by our own rule from here, so a second free-form message never reaches the provider.
|
|
124
|
+
expect((await c.say("de novo")).refused?.detail).toContain("more than 24h");
|
|
125
|
+
const template = await c.send({ kind: "template", template: "acordo_quitado", language: "pt_BR", variables: ["acordo-1042"] });
|
|
126
|
+
expect(template.refused).toBeUndefined();
|
|
127
|
+
expect(backend.delivered).toHaveLength(1);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("reopens when the person writes again, because that is what opens a window at Meta too", () => {
|
|
131
|
+
const window = new SessionWindow([], { lastInboundAt: Math.floor(TUESDAY.getTime() / 1000) - 3600 });
|
|
132
|
+
window.observeProviderShut();
|
|
133
|
+
expect(window.open(TUESDAY)).toBe(false);
|
|
134
|
+
window.observeInbound(Math.floor(TUESDAY.getTime() / 1000));
|
|
135
|
+
expect(window.open(TUESDAY)).toBe(true);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
107
139
|
describe("the local registry refuses what Meta would refuse", () => {
|
|
108
140
|
it("a name the agent never declared", async () => {
|
|
109
141
|
const { channel: c, backend } = channel(FRIDAY, Math.floor(TUESDAY.getTime() / 1000));
|