@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
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `codespar-agent eval [--json] [--case <name>]`: the adversarial suite of
|
|
3
|
+
* section 9 plus every scenario of section 12 in every mode it declares. Runs
|
|
4
|
+
* on the replay provider and the stub rail, with no model and no network. A
|
|
5
|
+
* regression blocks merge.
|
|
6
|
+
*/
|
|
7
|
+
import { stderr, stdout } from "node:process";
|
|
8
|
+
import type { Agent } from "../agent.js";
|
|
9
|
+
import { listAdversarialCases, loadAdversarialCase, runAdversarialCase, type AdversarialResult } from "../adversarial.js";
|
|
10
|
+
import { checkScenario, listScenarios, loadScenario, runScenario, type ScenarioCheck } from "../scenarios.js";
|
|
11
|
+
|
|
12
|
+
export async function runEval(agent: Agent, argv: string[]): Promise<number> {
|
|
13
|
+
const json = argv.includes("--json");
|
|
14
|
+
const only = argv.includes("--case") ? argv[argv.indexOf("--case") + 1] : undefined;
|
|
15
|
+
const say = (l: string) => stderr.write(l + "\n");
|
|
16
|
+
const awaitsPayer = agent.settlement === "await-payer";
|
|
17
|
+
|
|
18
|
+
const adversarial = [];
|
|
19
|
+
for (const name of listAdversarialCases(agent)) {
|
|
20
|
+
if (only && only !== name) continue;
|
|
21
|
+
const result = await runAdversarialCase(agent, loadAdversarialCase(agent, name));
|
|
22
|
+
adversarial.push(result);
|
|
23
|
+
say(agent.kit.evalAdversarialLine?.(result) ?? adversarialLine(result));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const scenarios = [];
|
|
27
|
+
for (const name of listScenarios(agent)) {
|
|
28
|
+
if (only && only !== name) continue;
|
|
29
|
+
const scenario = loadScenario(agent, name);
|
|
30
|
+
if (!scenario.rails.includes("stub")) continue;
|
|
31
|
+
for (const mode of scenario.modes) {
|
|
32
|
+
const check = checkScenario(scenario, await runScenario(agent, scenario, { mode, rail: "stub" }));
|
|
33
|
+
scenarios.push({
|
|
34
|
+
name,
|
|
35
|
+
mode,
|
|
36
|
+
ok: check.ok,
|
|
37
|
+
failures: check.failures,
|
|
38
|
+
states: check.run.executions.map((e) => e.state),
|
|
39
|
+
receipts: check.run.receipts,
|
|
40
|
+
...(awaitsPayer ? { charges_issued: check.run.charges_issued, debtor_messages: check.run.debtor_messages } : {}),
|
|
41
|
+
});
|
|
42
|
+
say(agent.kit.evalScenarioLine?.(check) ?? scenarioLine(check));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const ok = adversarial.every((r) => r.ok) && scenarios.every((r) => r.ok);
|
|
47
|
+
if (json) stdout.write(JSON.stringify({ ok, adversarial, scenarios }) + "\n");
|
|
48
|
+
say(ok ? `eval ok: ${adversarial.length} adversarial case(s), ${scenarios.length} scenario run(s)` : "eval FAILED");
|
|
49
|
+
return ok ? 0 : 1;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function adversarialLine(result: AdversarialResult): string {
|
|
53
|
+
return `${result.ok ? "ok " : "FAIL"} adversarial/${result.name} — ${result.attack} — states ${JSON.stringify(result.states)}${result.failures.length ? ` — ${result.failures.join("; ")}` : ""}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function scenarioLine(check: ScenarioCheck): string {
|
|
57
|
+
return `${check.ok ? "ok " : "FAIL"} scenario/${check.run.scenario} [${check.mode}] — states ${JSON.stringify(check.run.executions.map((e) => e.state))}, ${check.run.receipts} receipt(s)${check.failures.length ? ` — ${check.failures.join("; ")}` : ""}`;
|
|
58
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `codespar-agent inspect <run-id> [--json] [--html <path>]`: the proof bundle
|
|
3
|
+
* of section 11 read back as a timeline. Defaults to the terminal rendering;
|
|
4
|
+
* `--json` follows the rule of section 14.5 — machine data on stdout, valid
|
|
5
|
+
* JSON and nothing else, human messages on stderr.
|
|
6
|
+
*
|
|
7
|
+
* It reads the bundle and nothing else, so it works on any agent's run, and
|
|
8
|
+
* on a folder copied off the machine that produced it.
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, readdirSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { resolve } from "node:path";
|
|
12
|
+
import { stderr, stdout } from "node:process";
|
|
13
|
+
import { ProofBundle } from "@codespar/agent-core";
|
|
14
|
+
import type { Agent } from "../agent.js";
|
|
15
|
+
import { runsDir } from "../setup.js";
|
|
16
|
+
import { assembleTimeline, renderHtml, renderText } from "../inspect.js";
|
|
17
|
+
|
|
18
|
+
const USAGE = "usage: npm run inspect <run-id> [--json] [--html <file>]";
|
|
19
|
+
|
|
20
|
+
export function inspect(agent: Agent, argv: string[]): number {
|
|
21
|
+
const say = (line: string) => stderr.write(line + "\n");
|
|
22
|
+
let json = false;
|
|
23
|
+
let html: string | undefined;
|
|
24
|
+
let runId: string | undefined;
|
|
25
|
+
|
|
26
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
27
|
+
const a = argv[i]!;
|
|
28
|
+
if (a === "--json") json = true;
|
|
29
|
+
else if (a === "--html") {
|
|
30
|
+
const value = argv[i + 1];
|
|
31
|
+
if (value === undefined || value.startsWith("--")) {
|
|
32
|
+
say(`--html needs a file path\n${USAGE}`);
|
|
33
|
+
return 2;
|
|
34
|
+
}
|
|
35
|
+
html = value;
|
|
36
|
+
i += 1;
|
|
37
|
+
} else if (a === "--help" || a === "-h") {
|
|
38
|
+
say(USAGE);
|
|
39
|
+
return 0;
|
|
40
|
+
} else if (a.startsWith("-")) {
|
|
41
|
+
say(`unknown argument ${a}\n${USAGE}`);
|
|
42
|
+
return 2;
|
|
43
|
+
} else if (runId === undefined) runId = a;
|
|
44
|
+
else {
|
|
45
|
+
say(`inspect takes one run id (got ${runId} and ${a})\n${USAGE}`);
|
|
46
|
+
return 2;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const runs = runsDir(agent);
|
|
51
|
+
if (!runId) {
|
|
52
|
+
say(USAGE);
|
|
53
|
+
say(available(runs));
|
|
54
|
+
return 2;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// An unknown run id is a refusal a person can act on, never a stack trace.
|
|
58
|
+
const bundle = ProofBundle.open(runs, runId);
|
|
59
|
+
if (!bundle) {
|
|
60
|
+
say(`no proof bundle for run ${runId}: nothing at ${resolve(runs, runId)}`);
|
|
61
|
+
say(available(runs));
|
|
62
|
+
return 1;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// A bundle half-written by a run that was killed is exactly when somebody looks at it: say which file is unreadable, do not throw a stack at them.
|
|
66
|
+
let report;
|
|
67
|
+
try {
|
|
68
|
+
report = assembleTimeline(bundle);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
say(`the bundle at ${bundle.dir} could not be read: ${err instanceof Error ? err.message : String(err)}`);
|
|
71
|
+
say("a file in it is missing or not valid JSON; the folder is the run's own output and is safe to delete and re-run");
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (html) {
|
|
76
|
+
try {
|
|
77
|
+
writeFileSync(html, renderHtml(report));
|
|
78
|
+
} catch (err) {
|
|
79
|
+
say(`could not write ${resolve(html)}: ${err instanceof Error ? err.message : String(err)}`);
|
|
80
|
+
return 1;
|
|
81
|
+
}
|
|
82
|
+
say(`wrote ${resolve(html)} — one file, no network, opens from disk`);
|
|
83
|
+
}
|
|
84
|
+
if (json) stdout.write(JSON.stringify(report) + "\n");
|
|
85
|
+
else stdout.write(renderText(report));
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** What the person can inspect instead. Bounded: a long-lived agent has many runs and this is an error message, not a listing command. */
|
|
90
|
+
function available(runs: string): string {
|
|
91
|
+
if (!existsSync(runs)) return `no runs yet: ${runs} does not exist. Run the agent once, then inspect the run id it printed.`;
|
|
92
|
+
const ids = readdirSync(runs, { withFileTypes: true })
|
|
93
|
+
.filter((e) => e.isDirectory())
|
|
94
|
+
.map((e) => e.name)
|
|
95
|
+
.sort()
|
|
96
|
+
.reverse();
|
|
97
|
+
if (!ids.length) return `no runs yet in ${runs}. Run the agent once, then inspect the run id it printed.`;
|
|
98
|
+
const shown = ids.slice(0, 10);
|
|
99
|
+
return `runs in ${runs}${ids.length > shown.length ? ` (newest ${shown.length} of ${ids.length})` : ""}:\n${shown.map((id) => ` ${id}`).join("\n")}`;
|
|
100
|
+
}
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `codespar-agent poll --channel whatsapp`: the conversation's loop-closer.
|
|
3
|
+
*
|
|
4
|
+
* The terminal already had one, and the reason the channel needs its own is
|
|
5
|
+
* the whole of issue #25. Over a terminal the operator and the counterparty
|
|
6
|
+
* are the same pair of eyes and the run never really ends. Over WhatsApp the
|
|
7
|
+
* debtor agrees on Tuesday and pays on Friday: the run that issued the charge
|
|
8
|
+
* exited days ago, and by the time the money lands the 24-hour customer
|
|
9
|
+
* service window has shut, so "recebemos, acordo quitado" can only go out as
|
|
10
|
+
* a template Meta approved in advance. Without this command that case cannot
|
|
11
|
+
* be run at all, which is why the confirmation in every recorded run so far
|
|
12
|
+
* happened inside the turn that issued the charge.
|
|
13
|
+
*
|
|
14
|
+
* What it does: reopens a conversation from the RECORD — the bundle of the
|
|
15
|
+
* run that held it, plus `state.db` — looks at the receivable the way the
|
|
16
|
+
* terminal poll does, and puts the outcome back into that same conversation.
|
|
17
|
+
* Nothing about which message may be sent is decided here: the channel's
|
|
18
|
+
* rules and the session window decide, and this only ASKS the window which of
|
|
19
|
+
* the two carriers it may use, because composing a message in order to watch
|
|
20
|
+
* it be refused is not a decision.
|
|
21
|
+
*
|
|
22
|
+
* Three things it deliberately does not do. It does not invent a conversation
|
|
23
|
+
* when there is nothing waiting for a payer — that is a no-op and exits 0,
|
|
24
|
+
* because a cron that fails when there is no work is a cron somebody turns
|
|
25
|
+
* off. It does not send a second confirmation when it runs twice, because the
|
|
26
|
+
* once-only cursor is in `state.db` and outlives the process. And it does not
|
|
27
|
+
* pin the emulator's clock, unlike the run that started the conversation:
|
|
28
|
+
* rewinding the provider's clock would reopen a window that really is shut.
|
|
29
|
+
*/
|
|
30
|
+
import { stdout } from "node:process";
|
|
31
|
+
import { relative } from "node:path";
|
|
32
|
+
import { ProofBundle, type ChargeInstrument, type Execution } from "@codespar/agent-core";
|
|
33
|
+
import type { Agent } from "../agent.js";
|
|
34
|
+
import { runsDir, type Setup } from "../setup.js";
|
|
35
|
+
import { followUp, waitForPayer } from "../terminal.js";
|
|
36
|
+
import { resolveConversation } from "../channels/index.js";
|
|
37
|
+
import { buildWhatsApp, simulatedCost, statusGraceMs, undeliveredCursor, type WhatsAppBackendName } from "../channels/whatsapp/open.js";
|
|
38
|
+
import { EmulatorUnreachableError } from "../channels/whatsapp/emulator.js";
|
|
39
|
+
import { sessionStateFromChannelLog, type SessionState } from "../channels/whatsapp/session.js";
|
|
40
|
+
import { instrumentBodies } from "../channels/whatsapp/present.js";
|
|
41
|
+
import type { WhatsAppChannel } from "../channels/whatsapp/index.js";
|
|
42
|
+
import type { ChannelLogLine, OutboundBody, SentMessage } from "../channels/types.js";
|
|
43
|
+
|
|
44
|
+
export interface PollWhatsAppOptions {
|
|
45
|
+
agent: Agent;
|
|
46
|
+
setup: Setup;
|
|
47
|
+
conversation: string | undefined;
|
|
48
|
+
backend: WhatsAppBackendName;
|
|
49
|
+
json: boolean;
|
|
50
|
+
waitSeconds?: number | undefined;
|
|
51
|
+
simulatePayer?: boolean | undefined;
|
|
52
|
+
/** Stub only: what the fixture payer does with the receivables THIS conversation is waiting on. */
|
|
53
|
+
payer?: "pays" | "expires" | "never" | undefined;
|
|
54
|
+
now: () => Date;
|
|
55
|
+
say: (line: string) => void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** How the outcome reached the person, or why it did not. */
|
|
59
|
+
type Delivery =
|
|
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
|
+
|
|
63
|
+
interface Polled {
|
|
64
|
+
id: string;
|
|
65
|
+
state: string;
|
|
66
|
+
reason: string | null;
|
|
67
|
+
rounds: number;
|
|
68
|
+
seconds: number;
|
|
69
|
+
timed_out: boolean;
|
|
70
|
+
session_open: boolean;
|
|
71
|
+
delivery: Delivery;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function pollWhatsApp(options: PollWhatsAppOptions): Promise<number> {
|
|
75
|
+
const { agent, setup: s, say, now } = options;
|
|
76
|
+
|
|
77
|
+
let script;
|
|
78
|
+
try {
|
|
79
|
+
script = resolveConversation(agent, options.conversation);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
say(err instanceof Error ? err.message : String(err));
|
|
82
|
+
return 2;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Which open executions belong to THIS conversation. The binding is the
|
|
86
|
+
// conversation's subject — the agreement alias it is allowed to name — and
|
|
87
|
+
// that is not a convenience: sending one debtor's outcome into another
|
|
88
|
+
// debtor's chat is the secrecy rule broken, and the same `subject` is what
|
|
89
|
+
// the channel already refuses other agreements by. A conversation that
|
|
90
|
+
// declares no subject binds to nothing and says so, rather than taking
|
|
91
|
+
// every open execution and hoping.
|
|
92
|
+
const open = s.engine.list({ state: "executing" }).filter((e) => e.reason === "awaiting_settlement");
|
|
93
|
+
const subject = script.subject;
|
|
94
|
+
// EVERY item, not some: an execution that mixes this conversation's
|
|
95
|
+
// agreement with another's belongs to neither, and confirming it here would
|
|
96
|
+
// put somebody else's debt in front of this person.
|
|
97
|
+
const mine = subject ? open.filter((e) => e.items.length > 0 && e.items.every((i) => i.alias === subject || i.payee === subject)) : [];
|
|
98
|
+
|
|
99
|
+
if (!subject && open.length) {
|
|
100
|
+
say(`a conversa ${script.name} nao declara subject: nao da para dizer quais execucoes sao dela, e mandar o desfecho de outra pessoa nesta conversa e exatamente o que a regra de sigilo proibe`);
|
|
101
|
+
return 2;
|
|
102
|
+
}
|
|
103
|
+
if (!mine.length) {
|
|
104
|
+
// A clean no-op: nothing is waiting, so nothing is opened. The emulator is
|
|
105
|
+
// not even contacted, which is the point — a poll with no work does no work.
|
|
106
|
+
if (options.json) stdout.write(JSON.stringify({ polled: [], conversation: script.name, channel: null }) + "\n");
|
|
107
|
+
else say(`nada aguardando pagador em ${script.name}`);
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// The conversation as it was left: the bundle of the run that held it. The
|
|
112
|
+
// window is counted from the person's last message on the PROVIDER's clock,
|
|
113
|
+
// which only that record holds, and the confirmation is appended THERE, so
|
|
114
|
+
// one conversation stays one record instead of ending in a second folder.
|
|
115
|
+
const { bundle, session, lines: priorLines } = conversationRecord(agent, mine);
|
|
116
|
+
if (!bundle) {
|
|
117
|
+
say(`nenhum bundle encontrado para ${mine.map((e) => e.run_id).join(", ")}: a conversa nao pode ser retomada sem o registro dela`);
|
|
118
|
+
return 1;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const built = buildWhatsApp({
|
|
122
|
+
agent,
|
|
123
|
+
setup: s,
|
|
124
|
+
conversation: { contact: script.contact, subject: script.subject },
|
|
125
|
+
backend: options.backend,
|
|
126
|
+
now,
|
|
127
|
+
say,
|
|
128
|
+
bundle,
|
|
129
|
+
// Nobody writes in a poll: the person already said what they had to say,
|
|
130
|
+
// and this run is here because the PAYER acted, not because the person did.
|
|
131
|
+
session,
|
|
132
|
+
priorLines,
|
|
133
|
+
});
|
|
134
|
+
if ("refusal" in built) {
|
|
135
|
+
for (const line of built.refusal) say(line);
|
|
136
|
+
return 1;
|
|
137
|
+
}
|
|
138
|
+
const { channel, driver, sessionKey } = built;
|
|
139
|
+
|
|
140
|
+
// Scoped to the receivables of THIS conversation: `behave` set the default
|
|
141
|
+
// for new ones, and this rewrites the fate of the ones already issued —
|
|
142
|
+
// another conversation's charge is none of this command's business.
|
|
143
|
+
if (options.payer) {
|
|
144
|
+
for (const execution of mine) {
|
|
145
|
+
for (const outcome of execution.outcomes) if (outcome.status === "accepted") s.payer?.decideFor?.(outcome.attempt_id, options.payer);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
await channel.open();
|
|
151
|
+
} catch (err) {
|
|
152
|
+
if (err instanceof EmulatorUnreachableError) {
|
|
153
|
+
say(err.message);
|
|
154
|
+
return 1;
|
|
155
|
+
}
|
|
156
|
+
throw err;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const results: Polled[] = [];
|
|
160
|
+
try {
|
|
161
|
+
const remaining = channel.sessionRemainingSeconds;
|
|
162
|
+
say(
|
|
163
|
+
`${s.manifest.manifest.name} ${s.manifest.manifest.version} — poll no canal whatsapp (${channel.backend}) — conversa ${script.name} — janela de 24h ${
|
|
164
|
+
channel.sessionOpen ? `ABERTA (${Math.round(remaining / 60)} min restantes): mensagem livre` : "FECHADA: so template aprovado"
|
|
165
|
+
}`,
|
|
166
|
+
);
|
|
167
|
+
for (const execution of mine) {
|
|
168
|
+
const sessionOpen = channel.sessionOpen;
|
|
169
|
+
const result = await waitForPayer(execution.id, {
|
|
170
|
+
setup: s,
|
|
171
|
+
approver: { id: s.kit.labels.defaultUser, channel: "whatsapp" },
|
|
172
|
+
say,
|
|
173
|
+
waitSeconds: options.waitSeconds,
|
|
174
|
+
simulatePayer: options.simulatePayer,
|
|
175
|
+
presentInstrument: (e, n, id, instrument) => presentInstrument(channel, s, e, n, id, instrument),
|
|
176
|
+
});
|
|
177
|
+
const closed = result.execution;
|
|
178
|
+
if (closed.state !== "executing") await s.engine.collectReceipts(closed.id);
|
|
179
|
+
const delivery = await tellOutcome(channel, s, closed);
|
|
180
|
+
await followUp(closed, s, say);
|
|
181
|
+
results.push({
|
|
182
|
+
id: closed.id,
|
|
183
|
+
state: closed.state,
|
|
184
|
+
reason: closed.reason ?? null,
|
|
185
|
+
rounds: result.rounds,
|
|
186
|
+
seconds: result.seconds,
|
|
187
|
+
timed_out: result.timed_out,
|
|
188
|
+
session_open: sessionOpen,
|
|
189
|
+
delivery,
|
|
190
|
+
});
|
|
191
|
+
say(
|
|
192
|
+
`${closed.id}: ${closed.state}${closed.reason ? ` (${closed.reason})` : ""} apos ${result.rounds} consulta(s), ${result.seconds}s — ${
|
|
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}` : ""})`
|
|
194
|
+
}`,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
} finally {
|
|
198
|
+
await channel.close();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const cost = await simulatedCost(driver, sessionKey);
|
|
202
|
+
const log = channel.log();
|
|
203
|
+
const refused = log.filter((l) => l.refused).map((l) => ({ rule: l.refused!.rule, detail: l.refused!.detail }));
|
|
204
|
+
const channelSummary = {
|
|
205
|
+
name: "whatsapp",
|
|
206
|
+
backend: channel.backend,
|
|
207
|
+
conversation: script.name,
|
|
208
|
+
session_open: channel.sessionOpen,
|
|
209
|
+
messages_out: log.filter((l) => l.direction === "out" && !l.refused).length,
|
|
210
|
+
refused,
|
|
211
|
+
log: relative(process.cwd(), `${bundle.dir}/channel.jsonl`),
|
|
212
|
+
...(cost ? { simulated_cost: cost } : {}),
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
if (options.json) stdout.write(JSON.stringify({ polled: results, conversation: script.name, channel: channelSummary }) + "\n");
|
|
216
|
+
else say(`conversa em ${channelSummary.log} — ${channelSummary.messages_out} enviada(s)${refused.length ? `, ${refused.length} recusada(s)` : ""}`);
|
|
217
|
+
|
|
218
|
+
if (results.some((r) => r.timed_out)) return 3;
|
|
219
|
+
// A cycle that closed and a person who was not told is a failure worth an
|
|
220
|
+
// exit code: the record says the agreement is settled and the debtor does
|
|
221
|
+
// not know it. The other two are not failures — `already_told` is this
|
|
222
|
+
// command running twice, which is the thing it is built to be safe under,
|
|
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.
|
|
225
|
+
return results.some((r) => !r.delivery.told && r.delivery.reason !== "already_told" && r.delivery.reason !== "still_open") ? 1 : 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* The bundle the conversation lives in, and where its window stood. When
|
|
230
|
+
* several runs touched the same conversation the LATEST one holds the last
|
|
231
|
+
* inbound, so that is the record the confirmation is appended to; the window
|
|
232
|
+
* itself is read across all of them, because the person's last message is the
|
|
233
|
+
* person's last message whichever run recorded it.
|
|
234
|
+
*/
|
|
235
|
+
function conversationRecord(agent: Agent, executions: readonly Execution[]): { bundle: ProofBundle | undefined; session: SessionState; lines: ChannelLogLine[] } {
|
|
236
|
+
const runs = runsDir(agent);
|
|
237
|
+
const seen = new Set<string>();
|
|
238
|
+
let bundle: ProofBundle | undefined;
|
|
239
|
+
let lastInboundAt: number | undefined;
|
|
240
|
+
const all: ChannelLogLine[] = [];
|
|
241
|
+
// `list()` orders by creation, so the last execution that carries a log is the most recent record of the conversation.
|
|
242
|
+
for (const execution of executions) {
|
|
243
|
+
if (seen.has(execution.run_id)) continue;
|
|
244
|
+
seen.add(execution.run_id);
|
|
245
|
+
const opened = ProofBundle.open(runs, execution.run_id);
|
|
246
|
+
if (!opened) continue;
|
|
247
|
+
const lines = opened.readChannel();
|
|
248
|
+
if (!lines.length) continue;
|
|
249
|
+
all.push(...(lines as unknown as ChannelLogLine[]));
|
|
250
|
+
bundle = opened;
|
|
251
|
+
const state = sessionStateFromChannelLog(lines);
|
|
252
|
+
if (state.lastInboundAt !== undefined && (lastInboundAt === undefined || state.lastInboundAt > lastInboundAt)) lastInboundAt = state.lastInboundAt;
|
|
253
|
+
}
|
|
254
|
+
return { bundle, session: { lastInboundAt }, lines: all };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* The payable artifact, the way a person can use it: the QR as an image and
|
|
259
|
+
* the copy-and-paste as its OWN message. The same shape `converse` sends,
|
|
260
|
+
* because a payer told how to pay by a poll is told the same way a payer told
|
|
261
|
+
* by a run is.
|
|
262
|
+
*
|
|
263
|
+
* It is only reached for an instrument nobody has shown yet — `markShown` is a
|
|
264
|
+
* cursor in `state.db` — so in the ordinary resumed conversation nothing is
|
|
265
|
+
* sent here at all. When it IS reached with the window shut the channel
|
|
266
|
+
* refuses it, and that refusal is the record: WhatsApp would not have carried
|
|
267
|
+
* it either.
|
|
268
|
+
*/
|
|
269
|
+
async function presentInstrument(channel: WhatsAppChannel, s: Setup, execution: Execution, instalment: number, chargeId: string, instrument: ChargeInstrument): Promise<void> {
|
|
270
|
+
for (const body of instrumentBodies(execution, instalment, chargeId, instrument, s.mandate.currency)) await channel.send(body);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* The one message per outcome, carried by whichever of the two the window
|
|
275
|
+
* allows. Both halves share `markTold`, the cursor in `state.db`, so a second
|
|
276
|
+
* poll sends nothing whichever carrier the first one used.
|
|
277
|
+
*
|
|
278
|
+
* The order matters and is deliberate: the cursor is taken BEFORE the send.
|
|
279
|
+
* Taking it after would mean a process killed between the send and the write
|
|
280
|
+
* tells the person twice, and on a channel that is a second "recebemos" for
|
|
281
|
+
* one payment. A send that is then refused is reported instead, with the
|
|
282
|
+
* rule, and it is the exit code of the command — the record says settled and
|
|
283
|
+
* the debtor does not know.
|
|
284
|
+
*/
|
|
285
|
+
async function tellOutcome(channel: WhatsAppChannel, s: Setup, execution: Execution): Promise<Delivery> {
|
|
286
|
+
// Nothing closed, so there is no outcome to carry. Not a failure and not a
|
|
287
|
+
// silence to explain: the wait ran out and the receivable is still open.
|
|
288
|
+
if (execution.state === "executing") return { told: false, reason: "still_open" };
|
|
289
|
+
|
|
290
|
+
if (channel.sessionOpen) {
|
|
291
|
+
// The kit writes lines, the channel sends messages. Joined rather than
|
|
292
|
+
// last-one-wins, so a kit that says two sentences does not lose one.
|
|
293
|
+
const lines: string[] = [];
|
|
294
|
+
const told = s.kit.announceOutcome?.(execution, s, (line) => void lines.push(line)) ?? false;
|
|
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 });
|
|
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
|
+
}
|
|
310
|
+
|
|
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);
|
|
341
|
+
if (!chosen) {
|
|
342
|
+
return {
|
|
343
|
+
told: false,
|
|
344
|
+
reason: "no_template_for_outcome",
|
|
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`,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
const declared = channel.declaredTemplate(chosen.template);
|
|
349
|
+
if (!declared) {
|
|
350
|
+
return { told: false, reason: "no_template_for_outcome", detail: `o kit pediu o template ${chosen.template}, que channels/whatsapp/templates.json nao declara` };
|
|
351
|
+
}
|
|
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;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function deliveryOf(sent: SentMessage, carrier: "text" | "template", template?: string, fallback?: boolean): Delivery {
|
|
364
|
+
if (sent.refused) return { told: false, reason: "refused", detail: `${sent.refused.rule}: ${sent.refused.detail}` };
|
|
365
|
+
if (sent.state === "failed") return { told: false, reason: "refused", detail: "o provedor nao aceitou a mensagem" };
|
|
366
|
+
return { told: true, carrier, ...(template ? { template } : {}), ...(fallback ? { fallback: true as const } : {}) };
|
|
367
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `codespar-agent poll [--channel terminal|whatsapp] [--wait <seconds>] [--simulate-payer] [--json]`:
|
|
3
|
+
* keeps looking at every receivable still waiting for its payer until it
|
|
4
|
+
* closes or the wait runs out. This is the loop-closer for a run that has no
|
|
5
|
+
* webhook URL; `codespar-agent webhook` is the other. Presents an instrument
|
|
6
|
+
* not yet shown, tells the payer the outcome once, fetches the paid record.
|
|
7
|
+
*
|
|
8
|
+
* `--channel` is on THIS command rather than in a second one, and the reason
|
|
9
|
+
* is that only one thing differs between the two: where the outcome message
|
|
10
|
+
* goes, and which carriers that place allows. The looking, the once-only
|
|
11
|
+
* presentation, the sandbox payer, the receipts and the terminal states are
|
|
12
|
+
* one loop — `pollUntilClosed` — and a second command would have meant a
|
|
13
|
+
* second copy of it, which is exactly how a channel ends up closing a cycle
|
|
14
|
+
* differently from the terminal.
|
|
15
|
+
*/
|
|
16
|
+
import { stderr, stdout } from "node:process";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { loadManifest, resolveFixedClock, type ChannelName } from "@codespar/agent-core";
|
|
19
|
+
import type { Agent } from "../agent.js";
|
|
20
|
+
import { setup } from "../setup.js";
|
|
21
|
+
import { announceOutcome, followUp, waitForPayer } from "../terminal.js";
|
|
22
|
+
import { pollWhatsApp } from "./poll-whatsapp.js";
|
|
23
|
+
import type { WhatsAppBackendName } from "../channels/whatsapp/open.js";
|
|
24
|
+
|
|
25
|
+
const USAGE = `codespar-agent poll [options]
|
|
26
|
+
--channel terminal|whatsapp where the outcome is told. Default terminal.
|
|
27
|
+
--conversation <name> which channels/whatsapp/<name>.json to come back to (--channel whatsapp)
|
|
28
|
+
--backend simulator|cloud-api default simulator, the local emulator (--channel whatsapp)
|
|
29
|
+
--wait <seconds> how long to keep looking
|
|
30
|
+
--simulate-payer let the sandbox payer act once the receivable is payable
|
|
31
|
+
--payer pays|expires|never what the fixture payer does when it acts (stub rail only)
|
|
32
|
+
--now <ISO 8601> pin the run's clock; env CODESPAR_AGENT_NOW is the same thing
|
|
33
|
+
--json machine output on stdout, everything else on stderr`;
|
|
34
|
+
|
|
35
|
+
interface Args {
|
|
36
|
+
channel: ChannelName;
|
|
37
|
+
conversation?: string;
|
|
38
|
+
backend?: WhatsAppBackendName;
|
|
39
|
+
wait?: number;
|
|
40
|
+
simulatePayer: boolean;
|
|
41
|
+
payer?: "pays" | "expires" | "never";
|
|
42
|
+
now?: string;
|
|
43
|
+
json: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function parsePollArgs(argv: string[]): Args {
|
|
47
|
+
const args: Args = { channel: "terminal", simulatePayer: false, json: false };
|
|
48
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
49
|
+
const a = argv[i]!;
|
|
50
|
+
const next = () => {
|
|
51
|
+
const v = argv[i + 1];
|
|
52
|
+
if (v === undefined) throw new Error(`${a} needs a value`);
|
|
53
|
+
i += 1;
|
|
54
|
+
return v;
|
|
55
|
+
};
|
|
56
|
+
if (a === "--json") args.json = true;
|
|
57
|
+
else if (a === "--simulate-payer") args.simulatePayer = true;
|
|
58
|
+
else if (a === "--wait") {
|
|
59
|
+
const v = Number(next());
|
|
60
|
+
if (!Number.isFinite(v) || v < 0) throw new Error("--wait must be a number of seconds");
|
|
61
|
+
args.wait = v;
|
|
62
|
+
} else if (a === "--channel") {
|
|
63
|
+
const v = next();
|
|
64
|
+
if (v !== "terminal" && v !== "whatsapp") throw new Error("--channel must be terminal or whatsapp");
|
|
65
|
+
args.channel = v;
|
|
66
|
+
} else if (a === "--backend") {
|
|
67
|
+
const v = next();
|
|
68
|
+
if (v !== "simulator" && v !== "cloud-api") throw new Error("--backend must be simulator or cloud-api");
|
|
69
|
+
args.backend = v;
|
|
70
|
+
} else if (a === "--payer") {
|
|
71
|
+
const v = next();
|
|
72
|
+
if (v !== "pays" && v !== "expires" && v !== "never") throw new Error("--payer must be pays, expires or never");
|
|
73
|
+
args.payer = v;
|
|
74
|
+
} else if (a === "--conversation") args.conversation = next();
|
|
75
|
+
else if (a === "--now") args.now = next();
|
|
76
|
+
else throw new Error(`unknown argument ${a}`);
|
|
77
|
+
}
|
|
78
|
+
if (args.channel !== "whatsapp" && (args.backend !== undefined || args.conversation !== undefined)) {
|
|
79
|
+
throw new Error("--backend and --conversation belong to --channel whatsapp");
|
|
80
|
+
}
|
|
81
|
+
return args;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function poll(agent: Agent, argv: string[]): Promise<number> {
|
|
85
|
+
const say = (l: string) => stderr.write(l + "\n");
|
|
86
|
+
let args: Args;
|
|
87
|
+
let now: (() => Date) | undefined;
|
|
88
|
+
try {
|
|
89
|
+
args = parsePollArgs(argv);
|
|
90
|
+
now = resolveFixedClock(args.now, process.env);
|
|
91
|
+
} catch (err) {
|
|
92
|
+
say(`${err instanceof Error ? err.message : String(err)}\n${USAGE}`);
|
|
93
|
+
return 2;
|
|
94
|
+
}
|
|
95
|
+
if (args.channel === "whatsapp" && !loadManifest(join(agent.dir, "agent.yaml")).manifest.channels.includes("whatsapp")) {
|
|
96
|
+
say(`${agent.slug} does not declare the whatsapp channel in agent.yaml`);
|
|
97
|
+
return 2;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const s = setup(agent, { say, runId: `run_poll_${Date.now().toString(36)}`, ...(now ? { now } : {}) });
|
|
101
|
+
// The same flag `start` has, and it means more here: a receivable nobody
|
|
102
|
+
// paid reaches its due date BETWEEN two runs, never inside one, so "what
|
|
103
|
+
// happens when it expires" is a question only a poll can be asked.
|
|
104
|
+
if (args.payer) s.payer?.behave(args.payer);
|
|
105
|
+
try {
|
|
106
|
+
if (args.channel === "whatsapp") {
|
|
107
|
+
return await pollWhatsApp({
|
|
108
|
+
agent,
|
|
109
|
+
setup: s,
|
|
110
|
+
conversation: args.conversation,
|
|
111
|
+
backend: args.backend ?? "simulator",
|
|
112
|
+
json: args.json,
|
|
113
|
+
waitSeconds: args.wait,
|
|
114
|
+
simulatePayer: args.simulatePayer,
|
|
115
|
+
...(args.payer ? { payer: args.payer } : {}),
|
|
116
|
+
now: now ?? (() => new Date()),
|
|
117
|
+
say,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
// `behave` only changes the default for receivables the rail has not
|
|
121
|
+
// looked at, and by now it has looked at these; `decideFor` is what
|
|
122
|
+
// rewrites the fate of one that already exists.
|
|
123
|
+
if (args.payer) {
|
|
124
|
+
for (const open of s.engine.list({ state: "executing" })) {
|
|
125
|
+
for (const outcome of open.outcomes) if (outcome.status === "accepted") s.payer?.decideFor?.(outcome.attempt_id, args.payer);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const tell = (l: string) => void (args.json ? stderr : stdout).write(l + "\n");
|
|
129
|
+
const results = [];
|
|
130
|
+
for (const open of s.engine.list({ state: "executing" })) {
|
|
131
|
+
if (open.reason !== "awaiting_settlement") continue;
|
|
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
|
+
announceOutcome(r.execution, s, tell);
|
|
134
|
+
if (r.execution.state !== "executing") await s.engine.collectReceipts(r.execution.id);
|
|
135
|
+
await followUp(r.execution, s, say);
|
|
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 });
|
|
137
|
+
say(`${r.execution.id}: ${r.execution.state}${r.execution.reason ? ` (${r.execution.reason})` : ""} after ${r.rounds} look(s), ${r.seconds}s`);
|
|
138
|
+
}
|
|
139
|
+
if (args.json) stdout.write(JSON.stringify({ polled: results }) + "\n");
|
|
140
|
+
else if (!results.length) say("nothing waiting for a payer");
|
|
141
|
+
return results.some((r) => r.timed_out) ? 3 : 0;
|
|
142
|
+
} finally {
|
|
143
|
+
s.close();
|
|
144
|
+
}
|
|
145
|
+
}
|