@codespar/cli 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- 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/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/index.ts
CHANGED
|
@@ -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
|
|
package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/open.ts
CHANGED
|
@@ -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 } : {}) };
|
package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/run.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { handleExecution, type TerminalOptions } from "../../terminal.js";
|
|
|
20
20
|
import type { Setup } from "../../setup.js";
|
|
21
21
|
import type { OutboundBody } from "../types.js";
|
|
22
22
|
import { instrumentBodies } from "./present.js";
|
|
23
|
+
import { statusGraceMs } from "./open.js";
|
|
23
24
|
import type { WhatsAppChannel } from "./index.js";
|
|
24
25
|
|
|
25
26
|
export interface ConverseOptions {
|
|
@@ -63,6 +64,7 @@ class Outbox {
|
|
|
63
64
|
export async function converse(options: ConverseOptions): Promise<ConverseResult> {
|
|
64
65
|
const { setup: s, channel, approver, say } = options;
|
|
65
66
|
const outbox = new Outbox(channel);
|
|
67
|
+
let toldOutcome = false;
|
|
66
68
|
const replies: string[] = [];
|
|
67
69
|
const toolCalls: Array<{ name: string; refused: boolean }> = [];
|
|
68
70
|
let turns = 0;
|
|
@@ -77,7 +79,10 @@ export async function converse(options: ConverseOptions): Promise<ConverseResult
|
|
|
77
79
|
setup: s,
|
|
78
80
|
approver,
|
|
79
81
|
say,
|
|
80
|
-
tell: (line: string) =>
|
|
82
|
+
tell: (line: string, about?: Execution) => {
|
|
83
|
+
if (about) toldOutcome = true;
|
|
84
|
+
outbox.push({ kind: "text", text: line, ...(about ? { about: { execution_id: about.id, state: about.state } } : {}) });
|
|
85
|
+
},
|
|
81
86
|
presentInstrument,
|
|
82
87
|
...(options.decision ? { decision: options.decision } : {}),
|
|
83
88
|
...(options.ask ? { ask: options.ask } : {}),
|
|
@@ -105,6 +110,8 @@ export async function converse(options: ConverseOptions): Promise<ConverseResult
|
|
|
105
110
|
replies.push(result.reply);
|
|
106
111
|
}
|
|
107
112
|
await outbox.drain();
|
|
113
|
+
// An outcome told in this run may still come back failed; give the provider its bounded moment before the receiver closes.
|
|
114
|
+
if (toldOutcome) await channel.settle(statusGraceMs());
|
|
108
115
|
} finally {
|
|
109
116
|
await channel.close();
|
|
110
117
|
}
|
package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/session.ts
CHANGED
|
@@ -33,6 +33,8 @@ export type TemplateRefusal = { rule: string; detail: string } | undefined;
|
|
|
33
33
|
export class SessionWindow {
|
|
34
34
|
private readonly templates: Map<string, WhatsAppTemplate>;
|
|
35
35
|
private lastInboundAt: number | undefined;
|
|
36
|
+
/** The provider said the window is shut (131047) after ours read it open. Holds until the person writes again. */
|
|
37
|
+
private shutByProvider = false;
|
|
36
38
|
|
|
37
39
|
constructor(templates: readonly WhatsAppTemplate[], initial?: SessionState) {
|
|
38
40
|
this.templates = new Map(templates.map((t) => [t.name, t]));
|
|
@@ -40,12 +42,24 @@ export class SessionWindow {
|
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
observeInbound(timestamp: number): void {
|
|
43
|
-
if (this.lastInboundAt === undefined || timestamp > this.lastInboundAt)
|
|
45
|
+
if (this.lastInboundAt === undefined || timestamp > this.lastInboundAt) {
|
|
46
|
+
this.lastInboundAt = timestamp;
|
|
47
|
+
this.shutByProvider = false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The provider refused a free-form message because its window is shut. Its
|
|
53
|
+
* clock is the one the 24 hours are counted on, so from here the window
|
|
54
|
+
* reads shut whatever ours says, and the next message is a template.
|
|
55
|
+
*/
|
|
56
|
+
observeProviderShut(): void {
|
|
57
|
+
this.shutByProvider = true;
|
|
44
58
|
}
|
|
45
59
|
|
|
46
60
|
/** Seconds left before free-form messages stop being allowed; 0 when the window is shut or was never opened. */
|
|
47
61
|
remainingSeconds(now: Date): number {
|
|
48
|
-
if (this.lastInboundAt === undefined) return 0;
|
|
62
|
+
if (this.lastInboundAt === undefined || this.shutByProvider) return 0;
|
|
49
63
|
return Math.max(0, this.lastInboundAt + SESSION_WINDOW_SECONDS - Math.floor(now.getTime() / 1000));
|
|
50
64
|
}
|
|
51
65
|
|
package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts
CHANGED
|
@@ -32,14 +32,14 @@ import { relative } from "node:path";
|
|
|
32
32
|
import { ProofBundle, type ChargeInstrument, type Execution } from "@codespar/agent-core";
|
|
33
33
|
import type { Agent } from "../agent.js";
|
|
34
34
|
import { runsDir, type Setup } from "../setup.js";
|
|
35
|
-
import { waitForPayer } from "../terminal.js";
|
|
35
|
+
import { followUp, waitForPayer } from "../terminal.js";
|
|
36
36
|
import { resolveConversation } from "../channels/index.js";
|
|
37
|
-
import { buildWhatsApp, simulatedCost, type WhatsAppBackendName } from "../channels/whatsapp/open.js";
|
|
37
|
+
import { buildWhatsApp, simulatedCost, statusGraceMs, undeliveredCursor, type WhatsAppBackendName } from "../channels/whatsapp/open.js";
|
|
38
38
|
import { EmulatorUnreachableError } from "../channels/whatsapp/emulator.js";
|
|
39
39
|
import { sessionStateFromChannelLog, type SessionState } from "../channels/whatsapp/session.js";
|
|
40
40
|
import { instrumentBodies } from "../channels/whatsapp/present.js";
|
|
41
41
|
import type { WhatsAppChannel } from "../channels/whatsapp/index.js";
|
|
42
|
-
import type { SentMessage } from "../channels/types.js";
|
|
42
|
+
import type { ChannelLogLine, OutboundBody, SentMessage } from "../channels/types.js";
|
|
43
43
|
|
|
44
44
|
export interface PollWhatsAppOptions {
|
|
45
45
|
agent: Agent;
|
|
@@ -57,8 +57,8 @@ export interface PollWhatsAppOptions {
|
|
|
57
57
|
|
|
58
58
|
/** How the outcome reached the person, or why it did not. */
|
|
59
59
|
type Delivery =
|
|
60
|
-
| { told: true; carrier: "text" | "template"; template?: string }
|
|
61
|
-
| { told: false; reason: "already_told" | "still_open" | "no_template_for_outcome" | "refused"; detail?: string };
|
|
60
|
+
| { told: true; carrier: "text" | "template"; template?: string; fallback?: true }
|
|
61
|
+
| { told: false; reason: "already_told" | "still_open" | "no_template_for_outcome" | "refused" | "delivery_failed"; detail?: string };
|
|
62
62
|
|
|
63
63
|
interface Polled {
|
|
64
64
|
id: string;
|
|
@@ -112,7 +112,7 @@ export async function pollWhatsApp(options: PollWhatsAppOptions): Promise<number
|
|
|
112
112
|
// window is counted from the person's last message on the PROVIDER's clock,
|
|
113
113
|
// which only that record holds, and the confirmation is appended THERE, so
|
|
114
114
|
// one conversation stays one record instead of ending in a second folder.
|
|
115
|
-
const { bundle, session } = conversationRecord(agent, mine);
|
|
115
|
+
const { bundle, session, lines: priorLines } = conversationRecord(agent, mine);
|
|
116
116
|
if (!bundle) {
|
|
117
117
|
say(`nenhum bundle encontrado para ${mine.map((e) => e.run_id).join(", ")}: a conversa nao pode ser retomada sem o registro dela`);
|
|
118
118
|
return 1;
|
|
@@ -129,6 +129,7 @@ export async function pollWhatsApp(options: PollWhatsAppOptions): Promise<number
|
|
|
129
129
|
// Nobody writes in a poll: the person already said what they had to say,
|
|
130
130
|
// and this run is here because the PAYER acted, not because the person did.
|
|
131
131
|
session,
|
|
132
|
+
priorLines,
|
|
132
133
|
});
|
|
133
134
|
if ("refusal" in built) {
|
|
134
135
|
for (const line of built.refusal) say(line);
|
|
@@ -176,6 +177,7 @@ export async function pollWhatsApp(options: PollWhatsAppOptions): Promise<number
|
|
|
176
177
|
const closed = result.execution;
|
|
177
178
|
if (closed.state !== "executing") await s.engine.collectReceipts(closed.id);
|
|
178
179
|
const delivery = await tellOutcome(channel, s, closed);
|
|
180
|
+
await followUp(closed, s, say);
|
|
179
181
|
results.push({
|
|
180
182
|
id: closed.id,
|
|
181
183
|
state: closed.state,
|
|
@@ -188,7 +190,7 @@ export async function pollWhatsApp(options: PollWhatsAppOptions): Promise<number
|
|
|
188
190
|
});
|
|
189
191
|
say(
|
|
190
192
|
`${closed.id}: ${closed.state}${closed.reason ? ` (${closed.reason})` : ""} apos ${result.rounds} consulta(s), ${result.seconds}s — ${
|
|
191
|
-
delivery.told ? `avisado por ${delivery.carrier === "template" ? `template ${delivery.template}` : "mensagem livre"}` : `nao avisado (${delivery.reason}${delivery.detail ? `: ${delivery.detail}` : ""})`
|
|
193
|
+
delivery.told ? `avisado por ${delivery.carrier === "template" ? `template ${delivery.template}${delivery.fallback ? " (reserva: o kit nao tem copia para este desfecho)" : ""}` : "mensagem livre"}` : `nao avisado (${delivery.reason}${delivery.detail ? `: ${delivery.detail}` : ""})`
|
|
192
194
|
}`,
|
|
193
195
|
);
|
|
194
196
|
}
|
|
@@ -219,6 +221,7 @@ export async function pollWhatsApp(options: PollWhatsAppOptions): Promise<number
|
|
|
219
221
|
// not know it. The other two are not failures — `already_told` is this
|
|
220
222
|
// command running twice, which is the thing it is built to be safe under,
|
|
221
223
|
// and `still_open` is a wait that ran out, which the 3 above already said.
|
|
224
|
+
// `delivery_failed` is the provider saying the person never got it, and is the same failure as a refusal.
|
|
222
225
|
return results.some((r) => !r.delivery.told && r.delivery.reason !== "already_told" && r.delivery.reason !== "still_open") ? 1 : 0;
|
|
223
226
|
}
|
|
224
227
|
|
|
@@ -229,11 +232,12 @@ export async function pollWhatsApp(options: PollWhatsAppOptions): Promise<number
|
|
|
229
232
|
* itself is read across all of them, because the person's last message is the
|
|
230
233
|
* person's last message whichever run recorded it.
|
|
231
234
|
*/
|
|
232
|
-
function conversationRecord(agent: Agent, executions: readonly Execution[]): { bundle: ProofBundle | undefined; session: SessionState } {
|
|
235
|
+
function conversationRecord(agent: Agent, executions: readonly Execution[]): { bundle: ProofBundle | undefined; session: SessionState; lines: ChannelLogLine[] } {
|
|
233
236
|
const runs = runsDir(agent);
|
|
234
237
|
const seen = new Set<string>();
|
|
235
238
|
let bundle: ProofBundle | undefined;
|
|
236
239
|
let lastInboundAt: number | undefined;
|
|
240
|
+
const all: ChannelLogLine[] = [];
|
|
237
241
|
// `list()` orders by creation, so the last execution that carries a log is the most recent record of the conversation.
|
|
238
242
|
for (const execution of executions) {
|
|
239
243
|
if (seen.has(execution.run_id)) continue;
|
|
@@ -242,11 +246,12 @@ function conversationRecord(agent: Agent, executions: readonly Execution[]): { b
|
|
|
242
246
|
if (!opened) continue;
|
|
243
247
|
const lines = opened.readChannel();
|
|
244
248
|
if (!lines.length) continue;
|
|
249
|
+
all.push(...(lines as unknown as ChannelLogLine[]));
|
|
245
250
|
bundle = opened;
|
|
246
251
|
const state = sessionStateFromChannelLog(lines);
|
|
247
252
|
if (state.lastInboundAt !== undefined && (lastInboundAt === undefined || state.lastInboundAt > lastInboundAt)) lastInboundAt = state.lastInboundAt;
|
|
248
253
|
}
|
|
249
|
-
return { bundle, session: { lastInboundAt } };
|
|
254
|
+
return { bundle, session: { lastInboundAt }, lines: all };
|
|
250
255
|
}
|
|
251
256
|
|
|
252
257
|
/**
|
|
@@ -287,31 +292,76 @@ async function tellOutcome(channel: WhatsAppChannel, s: Setup, execution: Execut
|
|
|
287
292
|
// last-one-wins, so a kit that says two sentences does not lose one.
|
|
288
293
|
const lines: string[] = [];
|
|
289
294
|
const told = s.kit.announceOutcome?.(execution, s, (line) => void lines.push(line)) ?? false;
|
|
290
|
-
if (!told) return
|
|
291
|
-
const sent = await channel.send({ kind: "text", text: lines.join("\n") });
|
|
292
|
-
|
|
295
|
+
if (!told) return toldBefore(s, execution);
|
|
296
|
+
const sent = await channel.send({ kind: "text", text: lines.join("\n"), about: aboutOf(execution) });
|
|
297
|
+
// Our clock read the window open and the provider's read it shut (131047).
|
|
298
|
+
// The provider's is the one the window is counted on, so the message goes
|
|
299
|
+
// the way it would have gone had we known: as the template. The cursor is
|
|
300
|
+
// already this call's, taken by `announceOutcome` above.
|
|
301
|
+
if (sent.refused?.rule !== "session_window_closed") return confirmed(channel, sent, deliveryOf(sent, "text"));
|
|
302
|
+
return tellByTemplate(channel, s, execution, { cursorHeld: true });
|
|
293
303
|
}
|
|
304
|
+
return tellByTemplate(channel, s, execution, { cursorHeld: false });
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function aboutOf(execution: Execution): NonNullable<Extract<OutboundBody, { kind: "text" }>["about"]> {
|
|
308
|
+
return { execution_id: execution.id, state: execution.state };
|
|
309
|
+
}
|
|
294
310
|
|
|
295
|
-
|
|
311
|
+
/** The cursor was taken by an earlier run. Told, unless the provider said since that the message never arrived. */
|
|
312
|
+
function toldBefore(s: Setup, execution: Execution): Delivery {
|
|
313
|
+
const failed = s.store.getCursor(undeliveredCursor(execution.id, execution.state));
|
|
314
|
+
return failed ? { told: false, reason: "delivery_failed", detail: `an earlier message came back failed: ${failed}` } : { told: false, reason: "already_told" };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* A send that answered is not yet a message that arrived. The provider may
|
|
319
|
+
* report a failure a moment later; the channel records it (and the bundle,
|
|
320
|
+
* and the operator hear of it), and this gives it the bounded wait before
|
|
321
|
+
* saying the person was told.
|
|
322
|
+
*/
|
|
323
|
+
async function confirmed(channel: WhatsAppChannel, sent: SentMessage, delivery: Delivery): Promise<Delivery> {
|
|
324
|
+
if (!delivery.told || !sent.id) return delivery;
|
|
325
|
+
await channel.settle(statusGraceMs());
|
|
326
|
+
if (channel.deliveryOf(sent.id) !== "failed") return delivery;
|
|
327
|
+
const line = channel.log().find((l: ChannelLogLine) => l.direction === "status" && l.message_id === sent.id && l.state === "failed");
|
|
328
|
+
return { told: false, reason: "delivery_failed", detail: `the provider reported ${sent.id} failed${line?.errors?.length ? ` (${line.errors.map((e) => e.code).join(", ")})` : ""}` };
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* The outcome as a template. The kit's own copy when it has one; the
|
|
333
|
+
* registry's FALLBACK when it has none — a template that says there is news
|
|
334
|
+
* and asks the person to answer, and states nothing about the outcome, so it
|
|
335
|
+
* cannot be the wrong news (`npm run check` requires exactly one, taking no
|
|
336
|
+
* variables). A kit that names a template the registry does not declare is a
|
|
337
|
+
* defect, reported as one and not papered over by the fallback.
|
|
338
|
+
*/
|
|
339
|
+
async function tellByTemplate(channel: WhatsAppChannel, s: Setup, execution: Execution, options: { cursorHeld: boolean }): Promise<Delivery> {
|
|
340
|
+
const chosen: { template: string; variables: string[]; fallback?: true } | undefined = s.kit.outcomeTemplate?.(execution) ?? fallbackOf(channel);
|
|
296
341
|
if (!chosen) {
|
|
297
342
|
return {
|
|
298
343
|
told: false,
|
|
299
344
|
reason: "no_template_for_outcome",
|
|
300
|
-
detail: `a janela de 24h esta fechada e o agente nao declara template para ${execution.state}${execution.reason ? ` (${execution.reason})` : ""}`,
|
|
345
|
+
detail: `a janela de 24h esta fechada e o agente nao declara template para ${execution.state}${execution.reason ? ` (${execution.reason})` : ""}, nem um template de reserva`,
|
|
301
346
|
};
|
|
302
347
|
}
|
|
303
348
|
const declared = channel.declaredTemplate(chosen.template);
|
|
304
349
|
if (!declared) {
|
|
305
350
|
return { told: false, reason: "no_template_for_outcome", detail: `o kit pediu o template ${chosen.template}, que channels/whatsapp/templates.json nao declara` };
|
|
306
351
|
}
|
|
307
|
-
if (!s.engine.markTold(execution.id, execution.state)) return
|
|
308
|
-
const sent = await channel.send({ kind: "template", template: declared.name, language: declared.language, variables: chosen.variables });
|
|
309
|
-
s.engine.note("message.debtor", execution.id, { state: execution.state, reason: execution.reason ?? null, template: declared.name, variables: chosen.variables });
|
|
310
|
-
return deliveryOf(sent, "template", declared.name);
|
|
352
|
+
if (!options.cursorHeld && !s.engine.markTold(execution.id, execution.state)) return toldBefore(s, execution);
|
|
353
|
+
const sent = await channel.send({ kind: "template", template: declared.name, language: declared.language, variables: chosen.variables, about: aboutOf(execution) });
|
|
354
|
+
s.engine.note("message.debtor", execution.id, { state: execution.state, reason: execution.reason ?? null, template: declared.name, variables: chosen.variables, ...(chosen.fallback ? { fallback: true } : {}) });
|
|
355
|
+
return confirmed(channel, sent, deliveryOf(sent, "template", declared.name, chosen.fallback));
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function fallbackOf(channel: WhatsAppChannel): { template: string; variables: string[]; fallback: true } | undefined {
|
|
359
|
+
const name = channel.fallbackTemplate();
|
|
360
|
+
return name ? { template: name, variables: [], fallback: true } : undefined;
|
|
311
361
|
}
|
|
312
362
|
|
|
313
|
-
function deliveryOf(sent: SentMessage, carrier: "text" | "template", template?: string): Delivery {
|
|
363
|
+
function deliveryOf(sent: SentMessage, carrier: "text" | "template", template?: string, fallback?: boolean): Delivery {
|
|
314
364
|
if (sent.refused) return { told: false, reason: "refused", detail: `${sent.refused.rule}: ${sent.refused.detail}` };
|
|
315
365
|
if (sent.state === "failed") return { told: false, reason: "refused", detail: "o provedor nao aceitou a mensagem" };
|
|
316
|
-
return { told: true, carrier, ...(template ? { template } : {}) };
|
|
366
|
+
return { told: true, carrier, ...(template ? { template } : {}), ...(fallback ? { fallback: true as const } : {}) };
|
|
317
367
|
}
|
|
@@ -18,7 +18,7 @@ import { join } from "node:path";
|
|
|
18
18
|
import { loadManifest, resolveFixedClock, type ChannelName } from "@codespar/agent-core";
|
|
19
19
|
import type { Agent } from "../agent.js";
|
|
20
20
|
import { setup } from "../setup.js";
|
|
21
|
-
import { announceOutcome, waitForPayer } from "../terminal.js";
|
|
21
|
+
import { announceOutcome, followUp, waitForPayer } from "../terminal.js";
|
|
22
22
|
import { pollWhatsApp } from "./poll-whatsapp.js";
|
|
23
23
|
import type { WhatsAppBackendName } from "../channels/whatsapp/open.js";
|
|
24
24
|
|
|
@@ -132,6 +132,7 @@ export async function poll(agent: Agent, argv: string[]): Promise<number> {
|
|
|
132
132
|
const r = await waitForPayer(open.id, { setup: s, approver: { id: s.kit.labels.defaultUser, channel: "terminal" }, say, tell, waitSeconds: args.wait, simulatePayer: args.simulatePayer });
|
|
133
133
|
announceOutcome(r.execution, s, tell);
|
|
134
134
|
if (r.execution.state !== "executing") await s.engine.collectReceipts(r.execution.id);
|
|
135
|
+
await followUp(r.execution, s, say);
|
|
135
136
|
results.push({ id: r.execution.id, state: r.execution.state, reason: r.execution.reason ?? null, rounds: r.rounds, seconds: r.seconds, timed_out: r.timed_out });
|
|
136
137
|
say(`${r.execution.id}: ${r.execution.state}${r.execution.reason ? ` (${r.execution.reason})` : ""} after ${r.rounds} look(s), ${r.seconds}s`);
|
|
137
138
|
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `codespar-agent resume [--json]`: after a crash or a restart. Executions
|
|
3
|
-
* left in `executing` are reconciled against the rail
|
|
4
|
-
* attempt_id
|
|
3
|
+
* left in `executing` are reconciled against the rail, never repeated: a
|
|
4
|
+
* lookup presents the same explicit attempt_id, which the API answers from
|
|
5
|
+
* its record of that attempt without paying again (ent#1671). Stale open
|
|
6
|
+
* ones expire.
|
|
5
7
|
*/
|
|
6
8
|
import { stderr, stdout } from "node:process";
|
|
7
9
|
import type { Execution } from "@codespar/agent-core";
|
|
8
10
|
import type { Agent } from "../agent.js";
|
|
9
11
|
import { setup } from "../setup.js";
|
|
12
|
+
import { followUp } from "../terminal.js";
|
|
10
13
|
|
|
11
14
|
export async function resume(agent: Agent, argv: string[]): Promise<number> {
|
|
12
15
|
const json = argv.includes("--json");
|
|
@@ -21,6 +24,9 @@ export async function resume(agent: Agent, argv: string[]): Promise<number> {
|
|
|
21
24
|
closed.push(e);
|
|
22
25
|
say(e.state === "executing" ? s.kit.labels.stillExecuting(e) : `${e.id}: executing -> ${e.state}`);
|
|
23
26
|
}
|
|
27
|
+
for (const e of closed) if (e.state !== "executing") await followUp(e, s, say);
|
|
28
|
+
// What a crash left half-done after an outcome (a service invoice opened and never sent, or sent and never answered).
|
|
29
|
+
await s.kit.resumeFollowUps?.(s, say);
|
|
24
30
|
const expired = s.engine.expireStale().map((e) => ({ id: e.id, state: e.state }));
|
|
25
31
|
for (const e of expired) say(`${e.id}: -> expired`);
|
|
26
32
|
const open = s.engine.list({ state: ["awaiting_approval", "approved"] }).map((e) => ({ id: e.id, state: e.state }));
|
package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/start-whatsapp.ts
CHANGED
|
@@ -109,6 +109,9 @@ export async function startWhatsApp(options: StartWhatsAppOptions): Promise<numb
|
|
|
109
109
|
messages_in: log.filter((l) => l.direction === "in").length,
|
|
110
110
|
messages_out: log.filter((l) => l.direction === "out" && !l.refused).length,
|
|
111
111
|
refused,
|
|
112
|
+
// What the provider reported undelivered after it left, and which outcome each one was telling.
|
|
113
|
+
delivery_failed: log.filter((l) => l.direction === "status" && l.state === "failed").map((l) => ({ message_id: l.message_id, errors: l.errors ?? [], ...(l.about ? { about: l.about } : {}) })),
|
|
114
|
+
taps: log.filter((l) => l.kind === "reply").map((l) => ({ id: l.reply?.id ?? null, turn: !l.refused, ...(l.refused ? { refused: l.refused.rule } : {}) })),
|
|
112
115
|
log: relative(process.cwd(), `${s.bundle.dir}/channel.jsonl`),
|
|
113
116
|
...(cost ? { simulated_cost: cost } : {}),
|
|
114
117
|
};
|