@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/collections-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
26
26
|
import { createServer, type Server } from "node:http";
|
|
27
|
-
import type { ChannelBackend, InboundMessage, OutboundBody, SentMessage } from "../types.js";
|
|
27
|
+
import type { ChannelBackend, DeliveryState, InboundMessage, InboundReply, OutboundBody, SentMessage, StatusUpdate } from "../types.js";
|
|
28
28
|
|
|
29
29
|
export interface CloudApiConfig {
|
|
30
30
|
/**
|
|
@@ -140,23 +140,36 @@ interface CloudApiEnvelope {
|
|
|
140
140
|
value?: {
|
|
141
141
|
messaging_product?: string;
|
|
142
142
|
metadata?: { phone_number_id?: string };
|
|
143
|
-
messages?: Array<{
|
|
144
|
-
|
|
143
|
+
messages?: Array<{
|
|
144
|
+
id?: string;
|
|
145
|
+
from?: string;
|
|
146
|
+
timestamp?: string;
|
|
147
|
+
type?: string;
|
|
148
|
+
text?: { body?: string };
|
|
149
|
+
interactive?: { type?: string; button_reply?: { id?: string; title?: string }; list_reply?: { id?: string; title?: string } };
|
|
150
|
+
button?: { payload?: string; text?: string };
|
|
151
|
+
context?: { id?: string };
|
|
152
|
+
}>;
|
|
153
|
+
statuses?: Array<{ id?: string; status?: string; timestamp?: string; errors?: Array<{ code?: number; title?: string; message?: string; error_data?: { details?: string } }> }>;
|
|
145
154
|
};
|
|
146
155
|
}>;
|
|
147
156
|
}>;
|
|
148
157
|
}
|
|
149
158
|
|
|
150
159
|
/**
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
160
|
+
* What one delivery carries. A delivery holds several entries, each with
|
|
161
|
+
* several changes, each with messages and with STATUS updates about messages
|
|
162
|
+
* we sent. A message is read when it is text, or a tapped quick reply (an
|
|
163
|
+
* interactive `button_reply` / `list_reply`, or a template's `button`), which
|
|
164
|
+
* keeps its id — the channel decides whether that id means anything. Anything
|
|
165
|
+
* else (an image, a location) is dropped with its id named: this channel
|
|
166
|
+
* reads words and taps, and an unreadable message must not become an empty
|
|
167
|
+
* turn.
|
|
156
168
|
*/
|
|
157
|
-
export function parseInbound(payload: unknown): { messages: InboundMessage[]; ignored: Array<{ id: string; type: string }> } {
|
|
169
|
+
export function parseInbound(payload: unknown): { messages: InboundMessage[]; statuses: StatusUpdate[]; ignored: Array<{ id: string; type: string }> } {
|
|
158
170
|
const envelope = payload as CloudApiEnvelope;
|
|
159
171
|
const messages: InboundMessage[] = [];
|
|
172
|
+
const statuses: StatusUpdate[] = [];
|
|
160
173
|
const ignored: Array<{ id: string; type: string }> = [];
|
|
161
174
|
for (const entry of envelope.entry ?? []) {
|
|
162
175
|
for (const change of entry.changes ?? []) {
|
|
@@ -164,15 +177,51 @@ export function parseInbound(payload: unknown): { messages: InboundMessage[]; ig
|
|
|
164
177
|
const id = message.id;
|
|
165
178
|
const from = message.from;
|
|
166
179
|
if (!id || !from) continue;
|
|
180
|
+
const base = { id, from: from.startsWith("+") ? from : `+${from}`, timestamp: Number(message.timestamp ?? 0) };
|
|
181
|
+
const reply = replyOf(message);
|
|
182
|
+
if (reply) {
|
|
183
|
+
messages.push({ ...base, text: reply.title, reply: { ...reply, ...(message.context?.id ? { context_id: message.context.id } : {}) } });
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
167
186
|
if (message.type !== "text" || !message.text?.body) {
|
|
168
|
-
ignored.push({ id, type: message.type ?? "unknown" });
|
|
187
|
+
ignored.push({ id, type: message.type === "interactive" ? `interactive:${message.interactive?.type ?? "unknown"}` : message.type ?? "unknown" });
|
|
169
188
|
continue;
|
|
170
189
|
}
|
|
171
|
-
messages.push({
|
|
190
|
+
messages.push({ ...base, text: message.text.body });
|
|
191
|
+
}
|
|
192
|
+
for (const status of change.value?.statuses ?? []) {
|
|
193
|
+
if (!status.id || !isDeliveryState(status.status)) continue;
|
|
194
|
+
statuses.push({
|
|
195
|
+
message_id: status.id,
|
|
196
|
+
status: status.status,
|
|
197
|
+
timestamp: Number(status.timestamp ?? 0),
|
|
198
|
+
errors: (status.errors ?? []).map((e) => ({
|
|
199
|
+
...(typeof e.code === "number" ? { code: e.code } : {}),
|
|
200
|
+
...(e.title ?? e.message ? { title: e.title ?? e.message } : {}),
|
|
201
|
+
...(e.error_data?.details ? { details: e.error_data.details } : {}),
|
|
202
|
+
})),
|
|
203
|
+
});
|
|
172
204
|
}
|
|
173
205
|
}
|
|
174
206
|
}
|
|
175
|
-
return { messages, ignored };
|
|
207
|
+
return { messages, statuses, ignored };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
type CloudMessage = NonNullable<NonNullable<NonNullable<NonNullable<CloudApiEnvelope["entry"]>[number]["changes"]>[number]["value"]>["messages"]>[number];
|
|
211
|
+
|
|
212
|
+
function replyOf(message: CloudMessage): Omit<InboundReply, "context_id"> | undefined {
|
|
213
|
+
if (message.type === "interactive") {
|
|
214
|
+
const kind = message.interactive?.type;
|
|
215
|
+
const picked = kind === "button_reply" ? message.interactive?.button_reply : kind === "list_reply" ? message.interactive?.list_reply : undefined;
|
|
216
|
+
if ((kind === "button_reply" || kind === "list_reply") && picked?.id) return { type: kind, id: picked.id, title: picked.title ?? picked.id };
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
if (message.type === "button" && message.button?.payload) return { type: "button", id: message.button.payload, title: message.button.text ?? message.button.payload };
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function isDeliveryState(value: unknown): value is DeliveryState {
|
|
224
|
+
return value === "sent" || value === "delivered" || value === "read" || value === "failed";
|
|
176
225
|
}
|
|
177
226
|
|
|
178
227
|
export interface SendRequest {
|
|
@@ -200,10 +249,6 @@ export function buildSendRequest(config: CloudApiConfig, to: string, body: Outbo
|
|
|
200
249
|
method: "POST" as const,
|
|
201
250
|
headers: { authorization: `Bearer ${config.accessToken}`, "content-type": "application/json" },
|
|
202
251
|
};
|
|
203
|
-
// `to` loses its `+`: the Cloud API documents the number in international
|
|
204
|
-
// format without one, and the emulator keys its conversation on the literal
|
|
205
|
-
// string, so a `+` here and none on the inbound side splits one person into
|
|
206
|
-
// two conversations (measured; docs/OPEN_QUESTIONS.md §46c).
|
|
207
252
|
const recipient = { messaging_product: "whatsapp", recipient_type: "individual", to: toGraphNumber(to) };
|
|
208
253
|
switch (body.kind) {
|
|
209
254
|
case "text":
|
|
@@ -219,7 +264,7 @@ export function buildSendRequest(config: CloudApiConfig, to: string, body: Outbo
|
|
|
219
264
|
template: {
|
|
220
265
|
name: body.template,
|
|
221
266
|
language: { code: body.language },
|
|
222
|
-
...(body
|
|
267
|
+
...templateComponents(body),
|
|
223
268
|
},
|
|
224
269
|
}),
|
|
225
270
|
};
|
|
@@ -228,6 +273,49 @@ export function buildSendRequest(config: CloudApiConfig, to: string, body: Outbo
|
|
|
228
273
|
}
|
|
229
274
|
}
|
|
230
275
|
|
|
276
|
+
/**
|
|
277
|
+
* A template's parameters as Meta takes them: the body's variables, and one
|
|
278
|
+
* `quick_reply` button component per declared reply, whose `payload` is the
|
|
279
|
+
* reply id — what comes back as `button.payload` when the person taps it.
|
|
280
|
+
*/
|
|
281
|
+
function templateComponents(body: Extract<OutboundBody, { kind: "template" }>): { components?: unknown[] } {
|
|
282
|
+
const components: unknown[] = [];
|
|
283
|
+
if (body.variables.length) components.push({ type: "body", parameters: body.variables.map((text) => ({ type: "text", text })) });
|
|
284
|
+
for (const [index, button] of (body.buttons ?? []).entries()) components.push({ type: "button", sub_type: "quick_reply", index: String(index), parameters: [{ type: "payload", payload: button.id }] });
|
|
285
|
+
return components.length ? { components } : {};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Meta's error for a free-form message outside the 24 hours after the person's last one. */
|
|
289
|
+
export const WINDOW_CLOSED_ERROR = 131047;
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* What a non-2xx from `/messages` means, read from Meta's error body.
|
|
293
|
+
*
|
|
294
|
+
* One code gets its own rule: 131047 is the provider saying the 24-hour
|
|
295
|
+
* window is shut, which is the SAME rule the channel enforces before a send
|
|
296
|
+
* and is answered the same way — with a template. It reaches here only when
|
|
297
|
+
* the two clocks disagree (ours read the window open, the provider's read it
|
|
298
|
+
* shut), which at the boundary is a race and not a bug, so a caller has to be
|
|
299
|
+
* able to tell it from a refusal it cannot do anything about.
|
|
300
|
+
*/
|
|
301
|
+
export function providerRefusal(status: number, body: string): { rule: string; detail: string } {
|
|
302
|
+
let error: { code?: unknown; message?: unknown; error_data?: { details?: unknown } } | undefined;
|
|
303
|
+
try {
|
|
304
|
+
error = (JSON.parse(body) as { error?: typeof error }).error;
|
|
305
|
+
} catch {
|
|
306
|
+
/* an error without Meta's envelope: the status is all there is */
|
|
307
|
+
}
|
|
308
|
+
const details = typeof error?.error_data?.details === "string" ? error.error_data.details : undefined;
|
|
309
|
+
if (error?.code === WINDOW_CLOSED_ERROR) {
|
|
310
|
+
return {
|
|
311
|
+
rule: "session_window_closed",
|
|
312
|
+
detail: `the provider refused the free-form message (${status}/${WINDOW_CLOSED_ERROR}): its 24h window is shut${details ? ` — ${details}` : ""}`,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
const code = typeof error?.code === "number" ? `/${error.code}` : "";
|
|
316
|
+
return { rule: "provider_refused", detail: `${status}${code} from the Cloud API${details ? ` — ${details}` : ""}` };
|
|
317
|
+
}
|
|
318
|
+
|
|
231
319
|
/* ── the impure half: one fetch over a request the above built ── */
|
|
232
320
|
|
|
233
321
|
/** `+5511987654321` -> `5511987654321`, the only form the Graph API takes. */
|
|
@@ -249,7 +337,10 @@ export class WhatsAppCloudApi implements ChannelBackend {
|
|
|
249
337
|
readonly live: boolean;
|
|
250
338
|
private server: Server | undefined;
|
|
251
339
|
private readonly queue: InboundMessage[] = [];
|
|
340
|
+
/** Message ids already handed on. Meta delivers at-least-once, so the same message can arrive twice. */
|
|
341
|
+
private readonly seen = new Set<string>();
|
|
252
342
|
private waiting: ((m: InboundMessage | undefined) => void) | undefined;
|
|
343
|
+
private statusListener: ((status: StatusUpdate) => void) | undefined;
|
|
253
344
|
private closed = false;
|
|
254
345
|
|
|
255
346
|
constructor(private readonly options: CloudApiOptions) {
|
|
@@ -287,15 +378,16 @@ export class WhatsAppCloudApi implements ChannelBackend {
|
|
|
287
378
|
res.writeHead(401, { "content-type": "application/json" }).end(JSON.stringify({ error: "bad_signature" }));
|
|
288
379
|
return;
|
|
289
380
|
}
|
|
290
|
-
let parsed:
|
|
381
|
+
let parsed: ReturnType<typeof parseInbound>;
|
|
291
382
|
try {
|
|
292
383
|
parsed = parseInbound(JSON.parse(raw));
|
|
293
384
|
} catch {
|
|
294
385
|
res.writeHead(400, { "content-type": "application/json" }).end(JSON.stringify({ error: "invalid_json" }));
|
|
295
386
|
return;
|
|
296
387
|
}
|
|
297
|
-
for (const ignored of parsed.ignored) say(`[whatsapp] ignored ${ignored.type} message ${ignored.id}: this channel reads text`);
|
|
388
|
+
for (const ignored of parsed.ignored) say(`[whatsapp] ignored ${ignored.type} message ${ignored.id}: this channel reads text and taps`);
|
|
298
389
|
for (const message of parsed.messages) this.push(message);
|
|
390
|
+
for (const status of parsed.statuses) this.statusListener?.(status);
|
|
299
391
|
// Always 200 once read: Meta retries on anything else, and a duplicate is not an error.
|
|
300
392
|
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ received: parsed.messages.length }));
|
|
301
393
|
});
|
|
@@ -309,6 +401,13 @@ export class WhatsAppCloudApi implements ChannelBackend {
|
|
|
309
401
|
}
|
|
310
402
|
|
|
311
403
|
private push(message: InboundMessage): void {
|
|
404
|
+
// A redelivery is the same message, not a second turn: answering it twice
|
|
405
|
+
// would put two replies in front of the person for one thing they said.
|
|
406
|
+
if (this.seen.has(message.id)) {
|
|
407
|
+
this.options.say(`[whatsapp] duplicate delivery of ${message.id} dropped`);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
this.seen.add(message.id);
|
|
312
411
|
if (this.waiting) {
|
|
313
412
|
const resolve = this.waiting;
|
|
314
413
|
this.waiting = undefined;
|
|
@@ -318,6 +417,10 @@ export class WhatsAppCloudApi implements ChannelBackend {
|
|
|
318
417
|
this.queue.push(message);
|
|
319
418
|
}
|
|
320
419
|
|
|
420
|
+
onStatus(listener: (status: StatusUpdate) => void): void {
|
|
421
|
+
this.statusListener = listener;
|
|
422
|
+
}
|
|
423
|
+
|
|
321
424
|
async next(): Promise<InboundMessage | undefined> {
|
|
322
425
|
if (this.closed) return undefined;
|
|
323
426
|
const queued = this.queue.shift();
|
|
@@ -335,9 +438,7 @@ export class WhatsAppCloudApi implements ChannelBackend {
|
|
|
335
438
|
const doFetch = this.options.fetchImpl ?? fetch;
|
|
336
439
|
const response = await doFetch(request.url, { method: request.method, headers: request.headers, body: request.body });
|
|
337
440
|
const text = await response.text();
|
|
338
|
-
if (!response.ok) {
|
|
339
|
-
return { id: "", state: "failed", refused: { rule: "provider_refused", detail: `${response.status} from the Cloud API` } };
|
|
340
|
-
}
|
|
441
|
+
if (!response.ok) return { id: "", state: "failed", refused: providerRefusal(response.status, text) };
|
|
341
442
|
let id = "";
|
|
342
443
|
try {
|
|
343
444
|
id = String((JSON.parse(text) as { messages?: Array<{ id?: string }> }).messages?.[0]?.id ?? "");
|
package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts
CHANGED
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
* `npm run whatsapp:emulator` is the command.
|
|
27
27
|
*/
|
|
28
28
|
import type { ConversationScript } from "@codespar/agent-core";
|
|
29
|
-
import type { ChannelBackend, InboundMessage, OutboundBody, SentMessage } from "../types.js";
|
|
30
|
-
import {
|
|
29
|
+
import type { ChannelBackend, InboundMessage, OutboundBody, SentMessage, StatusUpdate } from "../types.js";
|
|
30
|
+
import { WhatsAppCloudApi, type CloudApiOptions } from "./cloud-api.js";
|
|
31
31
|
|
|
32
32
|
export const EMULATOR_ENV = {
|
|
33
33
|
url: "WHATSAPP_SIM_URL",
|
|
@@ -93,18 +93,23 @@ export class EmulatorDriver {
|
|
|
93
93
|
return this.post("/_sim/clock", { advance_hours: hours });
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
/** The person writes. The emulator answers with a signed inbound webhook to our receiver. */
|
|
97
|
-
inbound(options: { phoneNumberId: string; from: string; text: string; sentAt?: Date }): Promise<unknown> {
|
|
96
|
+
/** The person writes, or taps a quick reply. The emulator answers with a signed inbound webhook to our receiver. */
|
|
97
|
+
inbound(options: { phoneNumberId: string; from: string; text: string; sentAt?: Date } | { phoneNumberId: string; from: string; reply: { id: string; title: string }; sentAt?: Date }): Promise<unknown> {
|
|
98
98
|
return this.post("/_sim/inbound", {
|
|
99
99
|
phone_number_id: options.phoneNumberId,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
from: options.from,
|
|
101
|
+
...("reply" in options
|
|
102
|
+
? { type: "interactive", interactive: { type: "button_reply", button_reply: { id: options.reply.id, title: options.reply.title } } }
|
|
103
|
+
: { text: options.text }),
|
|
104
104
|
...(options.sentAt ? { sent_at: options.sentAt.toISOString() } : {}),
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
/** Reports a delivery status for a message the business sent, as the provider would: `read`, or `failed` with a reason. */
|
|
109
|
+
status(options: { status: "read" | "failed"; messageId: string; reason?: string }): Promise<unknown> {
|
|
110
|
+
return this.post("/_sim/status", { status: options.status, message_id: options.messageId, ...(options.reason ? { reason: options.reason } : {}) });
|
|
111
|
+
}
|
|
112
|
+
|
|
108
113
|
/** The priced timeline, which is the emulator's own reason for existing. Read for the console, never asserted on. */
|
|
109
114
|
state(key: string): Promise<{ priced?: { total?: number; currency?: string } } | undefined> {
|
|
110
115
|
return this.call(`/_sim/state?key=${encodeURIComponent(key)}`).then(
|
|
@@ -156,20 +161,21 @@ export class WhatsAppEmulator implements ChannelBackend {
|
|
|
156
161
|
|
|
157
162
|
async next(): Promise<InboundMessage | undefined> {
|
|
158
163
|
if (this.closed) return undefined;
|
|
159
|
-
const
|
|
160
|
-
if (
|
|
161
|
-
// The person writes THROUGH the emulator: it mints the message id
|
|
162
|
-
// the signed webhook our receiver verifies, so a scripted turn
|
|
163
|
-
// same path a real one would.
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
from: this.options.conversation.contact,
|
|
167
|
-
text,
|
|
168
|
-
});
|
|
164
|
+
const turn = await this.nextTurn();
|
|
165
|
+
if (turn === undefined) return undefined;
|
|
166
|
+
// The person writes (or taps) THROUGH the emulator: it mints the message id
|
|
167
|
+
// and posts the signed webhook our receiver verifies, so a scripted turn
|
|
168
|
+
// takes the same path a real one would.
|
|
169
|
+
const who = { phoneNumberId: this.options.config.phoneNumberId, from: this.options.conversation.contact };
|
|
170
|
+
await this.options.driver.inbound(typeof turn === "string" ? { ...who, text: turn } : { ...who, reply: turn });
|
|
169
171
|
return this.api.next();
|
|
170
172
|
}
|
|
171
173
|
|
|
172
|
-
|
|
174
|
+
onStatus(listener: (status: StatusUpdate) => void): void {
|
|
175
|
+
this.api.onStatus(listener);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private async nextTurn(): Promise<string | { id: string; title: string } | undefined> {
|
|
173
179
|
const script = this.options.script;
|
|
174
180
|
if (script) {
|
|
175
181
|
const turn = script.turns[this.turn];
|
|
@@ -178,8 +184,12 @@ export class WhatsAppEmulator implements ChannelBackend {
|
|
|
178
184
|
// The person's own pause, on the emulator's clock. A conversation that
|
|
179
185
|
// happens over days is what closes the 24-hour window.
|
|
180
186
|
if (turn.after_seconds > 0) await this.options.driver.advanceHours(turn.after_seconds / 3600);
|
|
181
|
-
return turn.text;
|
|
187
|
+
return turn.reply ? { id: turn.reply.id, title: turn.reply.title ?? turn.reply.id } : turn.text;
|
|
182
188
|
}
|
|
189
|
+
return this.nextText();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private async nextText(): Promise<string | undefined> {
|
|
183
193
|
if (!this.options.ask) return undefined;
|
|
184
194
|
let answer: string;
|
|
185
195
|
try {
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
* caller and have no path to this object at all — not a check, a shape: there
|
|
14
14
|
* is no method here that an operator line could be handed to.
|
|
15
15
|
*/
|
|
16
|
-
import type
|
|
16
|
+
import { declaredReplies, type ProofBundle, type QuickReply, type WhatsAppTemplate } from "@codespar/agent-core";
|
|
17
17
|
import { checkOutbound, type HoursRule, type RuleContext } from "../rules.js";
|
|
18
|
-
import type { Channel, ChannelBackend, ChannelLogLine, Conversation, InboundMessage, OutboundBody, SentMessage } from "../types.js";
|
|
18
|
+
import type { Channel, ChannelBackend, ChannelLogLine, Conversation, DeliveryState, InboundMessage, OutboundBody, SentMessage, StatusUpdate } from "../types.js";
|
|
19
19
|
import { maskContact } from "../contact.js";
|
|
20
20
|
import { SessionWindow, type SessionState } from "./session.js";
|
|
21
21
|
|
|
@@ -44,6 +44,14 @@ export interface WhatsAppChannelOptions {
|
|
|
44
44
|
* something in front of the person — that is `send`, and it is the only one.
|
|
45
45
|
*/
|
|
46
46
|
render?: ((line: string) => void) | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* The provider reported that a message the agent sent was NOT delivered.
|
|
49
|
+
* Called with the status line, which names the outcome the message told
|
|
50
|
+
* when it told one — that outcome was not told, whatever the send answered.
|
|
51
|
+
*/
|
|
52
|
+
onDeliveryFailed?: ((line: ChannelLogLine) => void) | undefined;
|
|
53
|
+
/** Lines an earlier run wrote for this conversation, so a status about a message it sent can be tied back to what that message was. */
|
|
54
|
+
priorLines?: ReadonlyArray<ChannelLogLine> | undefined;
|
|
47
55
|
}
|
|
48
56
|
|
|
49
57
|
export class WhatsAppChannel implements Channel {
|
|
@@ -52,10 +60,21 @@ export class WhatsAppChannel implements Channel {
|
|
|
52
60
|
private readonly session: SessionWindow;
|
|
53
61
|
private readonly lines: ChannelLogLine[] = [];
|
|
54
62
|
private lastInbound: InboundMessage | undefined;
|
|
63
|
+
private readonly replies: Map<string, QuickReply>;
|
|
64
|
+
/** The latest state the provider reported per message id, this process. */
|
|
65
|
+
private readonly delivery = new Map<string, DeliveryState>();
|
|
66
|
+
/**
|
|
67
|
+
* Failures reported for a message not yet in the log. A provider can post
|
|
68
|
+
* the status before the send that minted the id has answered (the emulator
|
|
69
|
+
* does, synchronously); the failure is acted on once the message is logged
|
|
70
|
+
* and it is known what the message was.
|
|
71
|
+
*/
|
|
72
|
+
private readonly earlyFailures = new Map<string, ChannelLogLine>();
|
|
55
73
|
|
|
56
74
|
constructor(private readonly options: WhatsAppChannelOptions) {
|
|
57
75
|
this.conversation = options.conversation;
|
|
58
76
|
this.session = new SessionWindow(options.templates ?? [], options.session);
|
|
77
|
+
this.replies = declaredReplies(options.templates ?? []);
|
|
59
78
|
}
|
|
60
79
|
|
|
61
80
|
get backend(): string {
|
|
@@ -87,33 +106,125 @@ export class WhatsAppChannel implements Channel {
|
|
|
87
106
|
return this.session.remainingSeconds(this.options.now());
|
|
88
107
|
}
|
|
89
108
|
|
|
109
|
+
/** The registry's fallback template, the one sent for an outcome the kit has no copy for. */
|
|
110
|
+
fallbackTemplate(): string | undefined {
|
|
111
|
+
return (this.options.templates ?? []).find((t) => t.fallback)?.name;
|
|
112
|
+
}
|
|
113
|
+
|
|
90
114
|
/** What the agent declared about a template it is about to send. The language is the registry's, never the sender's guess. */
|
|
91
115
|
declaredTemplate(name: string): WhatsAppTemplate | undefined {
|
|
92
116
|
return this.session.declared(name);
|
|
93
117
|
}
|
|
94
118
|
|
|
95
119
|
async open(): Promise<void> {
|
|
120
|
+
this.options.backend.onStatus?.((status) => this.observeStatus(status));
|
|
96
121
|
await this.options.backend.open();
|
|
97
122
|
}
|
|
98
123
|
|
|
124
|
+
/**
|
|
125
|
+
* The person's next turn. A typed message is the turn. A TAPPED quick reply
|
|
126
|
+
* is a turn only when its id is one a declared template offers, and then the
|
|
127
|
+
* turn is that reply's declared intent, never the button's title and never
|
|
128
|
+
* anything the model makes of it. A tap on an id nobody declared — or on a
|
|
129
|
+
* message this conversation did not send, when the provider names the
|
|
130
|
+
* message — is recorded, reported to the operator and skipped: a button the
|
|
131
|
+
* agent never offered has no meaning to hand on.
|
|
132
|
+
*/
|
|
99
133
|
async next(): Promise<InboundMessage | undefined> {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
134
|
+
for (;;) {
|
|
135
|
+
const message = await this.options.backend.next();
|
|
136
|
+
if (!message) return undefined;
|
|
137
|
+
// Any inbound, tap or text, reopens the 24-hour window: the person wrote.
|
|
138
|
+
this.lastInbound = message;
|
|
139
|
+
this.session.observeInbound(message.timestamp);
|
|
140
|
+
const base = {
|
|
141
|
+
at: this.options.now().toISOString(),
|
|
142
|
+
direction: "in" as const,
|
|
143
|
+
contact: maskContact(message.from),
|
|
144
|
+
message_id: message.id,
|
|
145
|
+
state: "delivered" as const,
|
|
146
|
+
// The provider's clock, which is the only one the 24-hour window is
|
|
147
|
+
// counted on. A later run reads the window back from here.
|
|
148
|
+
provider_timestamp: message.timestamp,
|
|
149
|
+
};
|
|
150
|
+
if (!message.reply) {
|
|
151
|
+
this.record({ ...base, kind: "text", text: message.text });
|
|
152
|
+
return message;
|
|
153
|
+
}
|
|
154
|
+
const tapped = { type: message.reply.type, id: message.reply.id, title: message.reply.title };
|
|
155
|
+
const declared = this.replies.get(message.reply.id);
|
|
156
|
+
const refusal = !declared
|
|
157
|
+
? { rule: "reply_not_offered", detail: `the person tapped ${message.reply.id}, which no template this agent declares offers; it is not a turn` }
|
|
158
|
+
: message.reply.context_id && !this.offeredOn(message.reply.context_id, message.reply.id)
|
|
159
|
+
? { rule: "reply_not_offered", detail: `the tap names message ${message.reply.context_id}, which this conversation did not send with ${message.reply.id}` }
|
|
160
|
+
: undefined;
|
|
161
|
+
if (refusal) {
|
|
162
|
+
this.record({ ...base, kind: "reply", reply: tapped, refused: refusal });
|
|
163
|
+
this.options.say?.(` [whatsapp] toque ignorado (${refusal.rule}): ${refusal.detail}`);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
this.record({ ...base, kind: "reply", reply: tapped, text: declared!.intent });
|
|
167
|
+
return { ...message, text: declared!.intent };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Whether the message the provider named is one this conversation sent carrying that reply. */
|
|
172
|
+
private offeredOn(messageId: string, replyId: string): boolean {
|
|
173
|
+
return [...(this.options.priorLines ?? []), ...this.lines].some((l) => l.direction === "out" && l.message_id === messageId && (l.offered ?? []).includes(replyId));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* A status webhook. Recorded in the conversation; a `failed` is also the
|
|
178
|
+
* operator's business and, when the message told an outcome, the outcome's:
|
|
179
|
+
* the provider just said the person never got it. A `read` is recorded and
|
|
180
|
+
* nothing more — it says a device displayed the message, which is not
|
|
181
|
+
* consent, not acknowledgement of a debt or an order, and not a reply.
|
|
182
|
+
*/
|
|
183
|
+
private observeStatus(status: StatusUpdate): void {
|
|
184
|
+
const sent = [...(this.options.priorLines ?? []), ...this.lines].find((l) => l.direction === "out" && l.message_id === status.message_id);
|
|
185
|
+
this.delivery.set(status.message_id, status.status);
|
|
186
|
+
const line: ChannelLogLine = {
|
|
105
187
|
at: this.options.now().toISOString(),
|
|
106
|
-
direction: "
|
|
107
|
-
contact: maskContact(
|
|
108
|
-
kind: "
|
|
109
|
-
message_id:
|
|
110
|
-
state:
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return
|
|
188
|
+
direction: "status",
|
|
189
|
+
contact: maskContact(this.conversation.contact),
|
|
190
|
+
kind: "status",
|
|
191
|
+
message_id: status.message_id,
|
|
192
|
+
state: status.status,
|
|
193
|
+
provider_timestamp: status.timestamp,
|
|
194
|
+
...(sent?.about ? { about: sent.about } : {}),
|
|
195
|
+
...(status.errors.length ? { errors: status.errors } : {}),
|
|
196
|
+
};
|
|
197
|
+
this.record(line);
|
|
198
|
+
if (status.status !== "failed") return;
|
|
199
|
+
if (!sent) {
|
|
200
|
+
this.earlyFailures.set(status.message_id, line);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
this.reportFailure(line);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private reportFailure(line: ChannelLogLine): void {
|
|
207
|
+
const status = { message_id: line.message_id, errors: line.errors ?? [] };
|
|
208
|
+
const codes = status.errors.map((e) => `${e.code ?? "?"}${e.details ? ` ${e.details}` : ""}`).join("; ") || "no error given";
|
|
209
|
+
this.options.say?.(
|
|
210
|
+
` [operador] ENTREGA FALHOU da mensagem ${status.message_id} (${codes})${line.about ? ` — era o aviso de ${line.about.execution_id} (${line.about.state}): a pessoa NAO foi avisada` : ""}`,
|
|
211
|
+
);
|
|
212
|
+
this.options.onDeliveryFailed?.(line);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** The latest status the provider reported for a message this process saw a status for. */
|
|
216
|
+
deliveryOf(messageId: string): DeliveryState | undefined {
|
|
217
|
+
return this.delivery.get(messageId);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Waits for status webhooks that may still be in flight. A provider reports
|
|
222
|
+
* a failure after the send answered, and a process that exits first never
|
|
223
|
+
* hears it; this is the short, bounded wait a caller that has just told an
|
|
224
|
+
* outcome gives the provider before it reports the outcome as told.
|
|
225
|
+
*/
|
|
226
|
+
async settle(ms: number): Promise<void> {
|
|
227
|
+
if (ms > 0) await new Promise((resolve) => setTimeout(resolve, ms));
|
|
117
228
|
}
|
|
118
229
|
|
|
119
230
|
async send(body: OutboundBody): Promise<SentMessage> {
|
|
@@ -134,9 +245,13 @@ export class WhatsAppChannel implements Channel {
|
|
|
134
245
|
return sent;
|
|
135
246
|
}
|
|
136
247
|
|
|
137
|
-
|
|
248
|
+
// A template carries the quick replies its declaration offers, and only those.
|
|
249
|
+
const buttons = body.kind === "template" ? this.session.declared(body.template)?.buttons : undefined;
|
|
250
|
+
const outgoing: OutboundBody = body.kind === "template" && buttons?.length ? { ...body, buttons: buttons.map((b) => ({ id: b.id, title: b.title })) } : body;
|
|
251
|
+
const sent = await this.options.backend.deliver(to, outgoing);
|
|
138
252
|
if (sent.refused) this.options.say?.(` [whatsapp] recusado pelo backend (${sent.refused.rule}): ${sent.refused.detail}`);
|
|
139
|
-
this.
|
|
253
|
+
if (sent.refused?.rule === "session_window_closed") this.session.observeProviderShut();
|
|
254
|
+
this.logOutbound(outgoing, sent);
|
|
140
255
|
return sent;
|
|
141
256
|
}
|
|
142
257
|
|
|
@@ -154,6 +269,12 @@ export class WhatsAppChannel implements Channel {
|
|
|
154
269
|
}
|
|
155
270
|
|
|
156
271
|
private logOutbound(body: OutboundBody, sent: SentMessage): void {
|
|
272
|
+
const early = sent.id ? this.earlyFailures.get(sent.id) : undefined;
|
|
273
|
+
if (early) {
|
|
274
|
+
this.earlyFailures.delete(sent.id);
|
|
275
|
+
const about = (body.kind === "text" || body.kind === "template") && body.about ? body.about : undefined;
|
|
276
|
+
queueMicrotask(() => this.reportFailure({ ...early, ...(about ? { about } : {}) }));
|
|
277
|
+
}
|
|
157
278
|
this.record({
|
|
158
279
|
at: this.options.now().toISOString(),
|
|
159
280
|
direction: "out",
|
|
@@ -163,6 +284,8 @@ export class WhatsAppChannel implements Channel {
|
|
|
163
284
|
state: sent.state,
|
|
164
285
|
...(textOf(body) !== undefined ? { text: textOf(body)! } : {}),
|
|
165
286
|
...(sent.refused ? { refused: sent.refused } : {}),
|
|
287
|
+
...((body.kind === "text" || body.kind === "template") && body.about ? { about: body.about } : {}),
|
|
288
|
+
...(body.kind === "template" && body.buttons?.length ? { offered: body.buttons.map((b) => b.id) } : {}),
|
|
166
289
|
});
|
|
167
290
|
}
|
|
168
291
|
|
|
@@ -193,6 +316,8 @@ export class WhatsAppChannel implements Channel {
|
|
|
193
316
|
|
|
194
317
|
/** One console line for one message. The refusals are visible: a message nobody got is part of the conversation's story. */
|
|
195
318
|
function draw(line: ChannelLogLine): string {
|
|
319
|
+
if (line.direction === "status") return `(status ${line.message_id}: ${line.state}${line.errors?.length ? ` ${line.errors.map((e) => e.code).join(",")}` : ""})`;
|
|
320
|
+
if (line.kind === "reply") return `${line.contact}: [toque: ${line.reply?.title ?? "?"}]${line.refused ? ` (ignorado: ${line.refused.rule})` : ` -> ${line.text ?? ""}`}`;
|
|
196
321
|
const who = line.direction === "in" ? `${line.contact}:` : "loja:";
|
|
197
322
|
const what =
|
|
198
323
|
line.kind === "instrument"
|
|
@@ -214,7 +339,7 @@ function textOf(body: OutboundBody): string | undefined {
|
|
|
214
339
|
case "media":
|
|
215
340
|
return body.caption;
|
|
216
341
|
case "template":
|
|
217
|
-
return `[template ${body.template}] ${body.variables.join(" | ")}`;
|
|
342
|
+
return `[template ${body.template}] ${body.variables.join(" | ")}${body.buttons?.length ? ` [${body.buttons.map((b) => b.title).join("] [")}]` : ""}`;
|
|
218
343
|
}
|
|
219
344
|
}
|
|
220
345
|
|
|
@@ -15,9 +15,9 @@ import type { ConversationScript, ProofBundle } from "@codespar/agent-core";
|
|
|
15
15
|
import type { Agent } from "../../agent.js";
|
|
16
16
|
import type { Setup } from "../../setup.js";
|
|
17
17
|
import { knownSubjects, loadTemplates } from "../index.js";
|
|
18
|
-
import type { ChannelBackend, Conversation } from "../types.js";
|
|
18
|
+
import type { ChannelBackend, ChannelLogLine, Conversation } from "../types.js";
|
|
19
19
|
import { WhatsAppChannel } from "./index.js";
|
|
20
|
-
import {
|
|
20
|
+
import { WhatsAppCloudApi, loadCloudApiConfig, type CloudApiConfig } from "./cloud-api.js";
|
|
21
21
|
import { EmulatorDriver, EMULATOR_DEFAULTS, EMULATOR_ENV, WhatsAppEmulator } from "./emulator.js";
|
|
22
22
|
import type { SessionState } from "./session.js";
|
|
23
23
|
|
|
@@ -52,6 +52,41 @@ export interface OpenWhatsAppOptions {
|
|
|
52
52
|
session?: SessionState | undefined;
|
|
53
53
|
/** Draws the conversation on the operator's console. */
|
|
54
54
|
render?: ((line: string) => void) | undefined;
|
|
55
|
+
/** What earlier runs wrote for this conversation: a status about one of their messages is tied back to it. */
|
|
56
|
+
priorLines?: ReadonlyArray<ChannelLogLine> | undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* How long a command that has just told an outcome waits for status webhooks
|
|
61
|
+
* before it reports the outcome as told. A provider reports a failed delivery
|
|
62
|
+
* AFTER the send answered, and a process that has exited hears nothing; this
|
|
63
|
+
* bounds the wait. `WHATSAPP_STATUS_GRACE_MS` overrides it (0 turns it off).
|
|
64
|
+
*/
|
|
65
|
+
export function statusGraceMs(env: NodeJS.ProcessEnv = process.env): number {
|
|
66
|
+
const raw = Number(env["WHATSAPP_STATUS_GRACE_MS"]);
|
|
67
|
+
return Number.isFinite(raw) && raw >= 0 && env["WHATSAPP_STATUS_GRACE_MS"]?.trim() ? raw : 1000;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The cursor that says an outcome's message came back `failed`, so a later poll reports it as not told rather than as told. */
|
|
71
|
+
export function undeliveredCursor(executionId: string, state: string): string {
|
|
72
|
+
return `undelivered:${executionId}:${state}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* A `failed` status is an event in the proof bundle, always. When the message
|
|
77
|
+
* told an outcome it is `message.debtor.failed`, and the cursor above is taken:
|
|
78
|
+
* the cursor that says the person was told (`markTold`) stays held — a failed
|
|
79
|
+
* delivery is not retried by itself, because the usual cause (not a WhatsApp
|
|
80
|
+
* number, blocked) fails again — but nothing reports that outcome as told.
|
|
81
|
+
*/
|
|
82
|
+
export function recordDeliveryFailure(s: Setup, line: ChannelLogLine): void {
|
|
83
|
+
const payload = { message_id: line.message_id, errors: line.errors ?? [], channel: "whatsapp" };
|
|
84
|
+
if (!line.about) {
|
|
85
|
+
s.engine.note("message.failed", null, payload);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
s.store.setCursor(undeliveredCursor(line.about.execution_id, line.about.state), JSON.stringify(payload));
|
|
89
|
+
s.engine.note("message.debtor.failed", line.about.execution_id, { ...payload, state: line.about.state });
|
|
55
90
|
}
|
|
56
91
|
|
|
57
92
|
export interface OpenedWhatsApp {
|
|
@@ -102,7 +137,7 @@ export function buildWhatsApp(options: OpenWhatsAppOptions): OpenedWhatsApp | {
|
|
|
102
137
|
webhookPort: Number(env[EMULATOR_ENV.webhookPort]?.trim() || EMULATOR_DEFAULTS.webhookPort),
|
|
103
138
|
};
|
|
104
139
|
driver = new EmulatorDriver(url);
|
|
105
|
-
sessionKey = `${config.phoneNumberId}:${
|
|
140
|
+
sessionKey = `${config.phoneNumberId}:${options.conversation.contact}`;
|
|
106
141
|
backend = new WhatsAppEmulator({
|
|
107
142
|
config,
|
|
108
143
|
conversation: { contact: options.conversation.contact },
|
|
@@ -129,6 +164,8 @@ export function buildWhatsApp(options: OpenWhatsAppOptions): OpenedWhatsApp | {
|
|
|
129
164
|
say,
|
|
130
165
|
render: options.render ?? say,
|
|
131
166
|
...(options.session ? { session: options.session } : {}),
|
|
167
|
+
...(options.priorLines ? { priorLines: options.priorLines } : {}),
|
|
168
|
+
onDeliveryFailed: (line) => recordDeliveryFailure(s, line),
|
|
132
169
|
});
|
|
133
170
|
|
|
134
171
|
return { channel, ...(driver ? { driver } : {}), ...(sessionKey ? { sessionKey } : {}) };
|