@codespar/cli 0.14.1 → 0.17.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 +44 -17
- package/dist/commands/audit-replay.js +267 -0
- package/dist/commands/audit-replay.js.map +1 -0
- package/dist/commands/init.js +28 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/mandate-verify.js +98 -19
- package/dist/commands/mandate-verify.js.map +1 -1
- package/dist/index.js +17 -3
- package/dist/index.js.map +1 -1
- package/dist/mandate-codec.js +31 -20
- package/dist/mandate-codec.js.map +1 -1
- package/dist/surface.js +4 -1
- package/dist/surface.js.map +1 -1
- package/package.json +1 -1
- package/templates/bills-agent/README.md +8 -5
- package/templates/bills-agent/agents/bills-agent/.env.example +3 -3
- 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 +62 -22
- package/templates/bills-agent/agents/bills-agent/SYSTEM_PROMPT.md +1 -1
- package/templates/bills-agent/agents/bills-agent/agent.yaml +2 -2
- package/templates/bills-agent/agents/bills-agent/package.json +14 -11
- package/templates/bills-agent/agents/bills-agent/src/kit.ts +193 -0
- package/templates/bills-agent/agents/bills-agent/test/adversarial.test.ts +8 -7
- package/templates/bills-agent/agents/bills-agent/test/check.test.ts +19 -2
- package/templates/bills-agent/agents/bills-agent/test/cli.test.ts +72 -24
- package/templates/bills-agent/agents/bills-agent/test/fixtures/excursao-300.transcript.jsonl +2 -0
- package/templates/bills-agent/agents/bills-agent/test/fixtures/material-400.transcript.jsonl +2 -0
- package/templates/bills-agent/agents/bills-agent/test/inspect.test.ts +124 -0
- package/templates/bills-agent/agents/bills-agent/test/provider.test.ts +25 -0
- package/templates/bills-agent/agents/bills-agent/test/scenarios.test.ts +10 -9
- package/templates/bills-agent/agents/bills-agent/tsconfig.json +0 -1
- package/templates/bills-agent/package.json +5 -2
- package/templates/bills-agent/packages/agent-core/README.md +2 -0
- package/templates/bills-agent/packages/agent-core/package.json +1 -1
- package/templates/bills-agent/packages/agent-core/src/api/charge-rail.ts +27 -6
- package/templates/bills-agent/packages/agent-core/src/api/rail.ts +16 -0
- package/templates/bills-agent/packages/agent-core/src/api/sandbox-payer.ts +20 -7
- package/templates/bills-agent/packages/agent-core/src/approval.ts +16 -2
- package/templates/bills-agent/packages/agent-core/src/bundle.ts +50 -4
- package/templates/bills-agent/packages/agent-core/src/channels.ts +140 -0
- package/templates/bills-agent/packages/agent-core/src/check.ts +90 -8
- package/templates/bills-agent/packages/agent-core/src/clock.ts +42 -0
- package/templates/bills-agent/packages/agent-core/src/engine.ts +119 -18
- package/templates/bills-agent/packages/agent-core/src/hash.ts +13 -0
- package/templates/bills-agent/packages/agent-core/src/index.ts +3 -0
- package/templates/bills-agent/packages/agent-core/src/manifest.ts +1 -1
- package/templates/bills-agent/packages/agent-core/src/rail.ts +16 -2
- package/templates/bills-agent/packages/agent-core/src/receipt-verification.ts +401 -0
- package/templates/bills-agent/packages/agent-core/src/state-machine.ts +3 -1
- package/templates/bills-agent/packages/agent-core/src/stubs/charge-rail.ts +2 -0
- package/templates/bills-agent/packages/agent-core/src/stubs/rail.ts +19 -2
- package/templates/bills-agent/packages/agent-core/src/types.ts +28 -0
- package/templates/bills-agent/packages/agent-core/test/approval.test.ts +36 -2
- package/templates/bills-agent/packages/agent-core/test/bundle.test.ts +85 -0
- package/templates/bills-agent/packages/agent-core/test/clock.test.ts +25 -0
- package/templates/bills-agent/packages/agent-core/test/engine.test.ts +73 -1
- package/templates/bills-agent/packages/agent-core/test/fixtures/staging-receipt-keys.json +18 -0
- package/templates/bills-agent/packages/agent-core/test/fixtures/staging-receipt.json +24 -0
- package/templates/bills-agent/packages/agent-core/test/helpers.ts +1 -1
- package/templates/bills-agent/packages/agent-core/test/manifest.test.ts +2 -2
- package/templates/bills-agent/packages/agent-core/test/receipt-verification.test.ts +316 -0
- package/templates/bills-agent/packages/agent-core/test/receivables.test.ts +51 -0
- package/templates/bills-agent/packages/agent-runtime/README.md +68 -0
- package/templates/bills-agent/packages/agent-runtime/bin.mjs +32 -0
- package/templates/bills-agent/packages/agent-runtime/package.json +23 -0
- package/templates/bills-agent/{agents/bills-agent → packages/agent-runtime}/src/adversarial.ts +36 -44
- package/templates/bills-agent/packages/agent-runtime/src/agent.ts +68 -0
- package/templates/bills-agent/packages/agent-runtime/src/channels/contact.ts +13 -0
- package/templates/bills-agent/packages/agent-runtime/src/channels/index.ts +72 -0
- package/templates/bills-agent/packages/agent-runtime/src/channels/rules.ts +120 -0
- package/templates/bills-agent/packages/agent-runtime/src/channels/types.ts +124 -0
- package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +358 -0
- package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +206 -0
- package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/evidence.ts +109 -0
- package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +225 -0
- package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +143 -0
- package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/present.ts +47 -0
- package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +113 -0
- package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +113 -0
- package/templates/bills-agent/packages/agent-runtime/src/cli.ts +98 -0
- package/templates/bills-agent/packages/agent-runtime/src/commands/check.ts +15 -0
- package/templates/bills-agent/packages/agent-runtime/src/commands/decide.ts +63 -0
- package/templates/bills-agent/packages/agent-runtime/src/commands/eval.ts +58 -0
- package/templates/bills-agent/packages/agent-runtime/src/commands/inspect.ts +100 -0
- package/templates/bills-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +317 -0
- package/templates/bills-agent/packages/agent-runtime/src/commands/poll.ts +144 -0
- package/templates/bills-agent/packages/agent-runtime/src/commands/reconcile.ts +42 -0
- package/templates/bills-agent/packages/agent-runtime/src/commands/rerun.ts +86 -0
- package/templates/bills-agent/packages/agent-runtime/src/commands/resume.ts +43 -0
- package/templates/bills-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +131 -0
- package/templates/bills-agent/packages/agent-runtime/src/commands/start.ts +245 -0
- package/templates/bills-agent/packages/agent-runtime/src/commands/verify.ts +129 -0
- package/templates/bills-agent/packages/agent-runtime/src/commands/webhook.ts +39 -0
- package/templates/bills-agent/packages/agent-runtime/src/default-kit.ts +83 -0
- package/templates/bills-agent/packages/agent-runtime/src/index.ts +12 -0
- package/templates/bills-agent/packages/agent-runtime/src/inspect.ts +797 -0
- package/templates/bills-agent/packages/agent-runtime/src/kit.ts +206 -0
- package/templates/{collections-agent/agents/collections-agent → bills-agent/packages/agent-runtime}/src/poll.ts +22 -11
- package/templates/{collections-agent/agents/collections-agent → bills-agent/packages/agent-runtime}/src/scenarios.ts +50 -34
- package/templates/bills-agent/{agents/bills-agent → packages/agent-runtime}/src/setup.ts +88 -66
- package/templates/bills-agent/packages/agent-runtime/src/terminal.ts +154 -0
- package/templates/{collections-agent/agents/collections-agent/channels/webhook/index.ts → bills-agent/packages/agent-runtime/src/webhook.ts} +1 -1
- package/templates/bills-agent/packages/agent-runtime/test/inspect.test.ts +336 -0
- package/templates/bills-agent/packages/agent-runtime/test/verify-cli.test.ts +127 -0
- package/templates/bills-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +232 -0
- package/templates/bills-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +204 -0
- package/templates/bills-agent/packages/agent-runtime/test/whatsapp-evidence.test.ts +94 -0
- package/templates/bills-agent/packages/agent-runtime/test/whatsapp-rules.test.ts +174 -0
- package/templates/bills-agent/packages/agent-runtime/test/whatsapp-session.test.ts +135 -0
- package/templates/bills-agent/packages/agent-runtime/tsconfig.json +9 -0
- package/templates/bills-agent/vitest.config.ts +1 -1
- package/templates/collections-agent/README.md +8 -5
- package/templates/collections-agent/agents/collections-agent/.env.example +14 -0
- package/templates/collections-agent/agents/collections-agent/AGENTS.md +6 -4
- package/templates/collections-agent/agents/collections-agent/CLAUDE.md +6 -4
- package/templates/collections-agent/agents/collections-agent/README.md +184 -16
- package/templates/collections-agent/agents/collections-agent/agent.yaml +3 -3
- package/templates/collections-agent/agents/collections-agent/channels/whatsapp/README.md +111 -0
- package/templates/collections-agent/agents/collections-agent/channels/whatsapp/acordo-1042.json +11 -0
- package/templates/collections-agent/agents/collections-agent/channels/whatsapp/acordo-1103.json +11 -0
- package/templates/collections-agent/agents/collections-agent/channels/whatsapp/templates.json +23 -0
- package/templates/collections-agent/agents/collections-agent/package.json +12 -10
- package/templates/collections-agent/agents/collections-agent/runbook.md +52 -1
- package/templates/collections-agent/agents/collections-agent/src/kit.ts +265 -0
- package/templates/collections-agent/agents/collections-agent/test/adversarial.test.ts +9 -8
- package/templates/collections-agent/agents/collections-agent/test/check.test.ts +43 -1
- package/templates/collections-agent/agents/collections-agent/test/cli.test.ts +56 -18
- package/templates/collections-agent/agents/collections-agent/test/fixtures/agreed-1042-awaiting-payer.transcript.jsonl +4 -0
- package/templates/collections-agent/agents/collections-agent/test/scenarios.test.ts +11 -10
- package/templates/collections-agent/agents/collections-agent/test/webhook.test.ts +4 -7
- package/templates/collections-agent/agents/collections-agent/test/whatsapp-poll.test.ts +271 -0
- package/templates/collections-agent/agents/collections-agent/test/whatsapp.test.ts +213 -0
- package/templates/collections-agent/agents/collections-agent/tsconfig.json +0 -1
- package/templates/collections-agent/package.json +3 -1
- package/templates/collections-agent/packages/agent-core/README.md +2 -0
- package/templates/collections-agent/packages/agent-core/package.json +1 -1
- package/templates/collections-agent/packages/agent-core/src/api/charge-rail.ts +27 -6
- package/templates/collections-agent/packages/agent-core/src/api/rail.ts +16 -0
- package/templates/collections-agent/packages/agent-core/src/api/sandbox-payer.ts +20 -7
- package/templates/collections-agent/packages/agent-core/src/approval.ts +16 -2
- package/templates/collections-agent/packages/agent-core/src/bundle.ts +50 -4
- package/templates/collections-agent/packages/agent-core/src/channels.ts +140 -0
- package/templates/collections-agent/packages/agent-core/src/check.ts +90 -8
- package/templates/collections-agent/packages/agent-core/src/clock.ts +42 -0
- package/templates/collections-agent/packages/agent-core/src/engine.ts +119 -18
- package/templates/collections-agent/packages/agent-core/src/hash.ts +13 -0
- package/templates/collections-agent/packages/agent-core/src/index.ts +3 -0
- package/templates/collections-agent/packages/agent-core/src/manifest.ts +1 -1
- package/templates/collections-agent/packages/agent-core/src/rail.ts +16 -2
- package/templates/collections-agent/packages/agent-core/src/receipt-verification.ts +401 -0
- package/templates/collections-agent/packages/agent-core/src/state-machine.ts +3 -1
- package/templates/collections-agent/packages/agent-core/src/stubs/charge-rail.ts +2 -0
- package/templates/collections-agent/packages/agent-core/src/stubs/rail.ts +19 -2
- package/templates/collections-agent/packages/agent-core/src/types.ts +28 -0
- package/templates/collections-agent/packages/agent-core/test/approval.test.ts +36 -2
- package/templates/collections-agent/packages/agent-core/test/bundle.test.ts +85 -0
- package/templates/collections-agent/packages/agent-core/test/clock.test.ts +25 -0
- package/templates/collections-agent/packages/agent-core/test/engine.test.ts +73 -1
- package/templates/collections-agent/packages/agent-core/test/fixtures/staging-receipt-keys.json +18 -0
- package/templates/collections-agent/packages/agent-core/test/fixtures/staging-receipt.json +24 -0
- package/templates/collections-agent/packages/agent-core/test/helpers.ts +1 -1
- package/templates/collections-agent/packages/agent-core/test/manifest.test.ts +2 -2
- package/templates/collections-agent/packages/agent-core/test/receipt-verification.test.ts +316 -0
- package/templates/collections-agent/packages/agent-core/test/receivables.test.ts +51 -0
- package/templates/collections-agent/packages/agent-runtime/README.md +68 -0
- package/templates/collections-agent/packages/agent-runtime/bin.mjs +32 -0
- package/templates/collections-agent/packages/agent-runtime/package.json +23 -0
- package/templates/collections-agent/{agents/collections-agent → packages/agent-runtime}/src/adversarial.ts +29 -50
- package/templates/collections-agent/packages/agent-runtime/src/agent.ts +68 -0
- package/templates/collections-agent/packages/agent-runtime/src/channels/contact.ts +13 -0
- package/templates/collections-agent/packages/agent-runtime/src/channels/index.ts +72 -0
- package/templates/collections-agent/packages/agent-runtime/src/channels/rules.ts +120 -0
- package/templates/collections-agent/packages/agent-runtime/src/channels/types.ts +124 -0
- package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +358 -0
- package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +206 -0
- package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/evidence.ts +109 -0
- package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +225 -0
- package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +143 -0
- package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/present.ts +47 -0
- package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +113 -0
- package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +113 -0
- package/templates/collections-agent/packages/agent-runtime/src/cli.ts +98 -0
- package/templates/collections-agent/packages/agent-runtime/src/commands/check.ts +15 -0
- package/templates/collections-agent/packages/agent-runtime/src/commands/decide.ts +63 -0
- package/templates/collections-agent/packages/agent-runtime/src/commands/eval.ts +58 -0
- package/templates/collections-agent/packages/agent-runtime/src/commands/inspect.ts +100 -0
- package/templates/collections-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +317 -0
- package/templates/collections-agent/packages/agent-runtime/src/commands/poll.ts +144 -0
- package/templates/collections-agent/packages/agent-runtime/src/commands/reconcile.ts +42 -0
- package/templates/collections-agent/packages/agent-runtime/src/commands/rerun.ts +86 -0
- package/templates/collections-agent/packages/agent-runtime/src/commands/resume.ts +43 -0
- package/templates/collections-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +131 -0
- package/templates/collections-agent/packages/agent-runtime/src/commands/start.ts +245 -0
- package/templates/collections-agent/packages/agent-runtime/src/commands/verify.ts +129 -0
- package/templates/collections-agent/packages/agent-runtime/src/commands/webhook.ts +39 -0
- package/templates/collections-agent/packages/agent-runtime/src/default-kit.ts +83 -0
- package/templates/collections-agent/packages/agent-runtime/src/index.ts +12 -0
- package/templates/collections-agent/packages/agent-runtime/src/inspect.ts +797 -0
- package/templates/collections-agent/packages/agent-runtime/src/kit.ts +206 -0
- package/templates/collections-agent/packages/agent-runtime/src/poll.ts +105 -0
- package/templates/{bills-agent/agents/bills-agent → collections-agent/packages/agent-runtime}/src/scenarios.ts +98 -26
- package/templates/collections-agent/packages/agent-runtime/src/setup.ts +229 -0
- package/templates/collections-agent/packages/agent-runtime/src/terminal.ts +154 -0
- package/templates/collections-agent/packages/agent-runtime/src/webhook.ts +131 -0
- package/templates/collections-agent/packages/agent-runtime/test/inspect.test.ts +336 -0
- package/templates/collections-agent/packages/agent-runtime/test/verify-cli.test.ts +127 -0
- package/templates/collections-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +232 -0
- package/templates/collections-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +204 -0
- package/templates/collections-agent/packages/agent-runtime/test/whatsapp-evidence.test.ts +94 -0
- package/templates/collections-agent/packages/agent-runtime/test/whatsapp-rules.test.ts +174 -0
- package/templates/collections-agent/packages/agent-runtime/test/whatsapp-session.test.ts +135 -0
- package/templates/collections-agent/packages/agent-runtime/tsconfig.json +9 -0
- package/templates/collections-agent/vitest.config.ts +1 -1
- package/templates/hello-agent/LICENSE +21 -0
- package/templates/hello-agent/README.md +23 -0
- package/templates/hello-agent/_gitignore +10 -0
- package/templates/hello-agent/agents/hello-agent/.env.example +5 -0
- package/templates/hello-agent/agents/hello-agent/AGENTS.md +15 -0
- package/templates/hello-agent/agents/hello-agent/CLAUDE.md +15 -0
- package/templates/hello-agent/agents/hello-agent/README.md +33 -0
- package/templates/hello-agent/agents/hello-agent/SYSTEM_PROMPT.md +19 -0
- package/templates/hello-agent/agents/hello-agent/agent.yaml +19 -0
- package/templates/hello-agent/agents/hello-agent/evals/adversarial/beneficiary-swap.json +6 -0
- package/templates/hello-agent/agents/hello-agent/evals/adversarial/beneficiary-swap.transcript.jsonl +2 -0
- package/templates/hello-agent/agents/hello-agent/evals/adversarial/exfiltration.json +6 -0
- package/templates/hello-agent/agents/hello-agent/evals/adversarial/exfiltration.transcript.jsonl +2 -0
- package/templates/hello-agent/agents/hello-agent/evals/adversarial/false-authority.json +6 -0
- package/templates/hello-agent/agents/hello-agent/evals/adversarial/false-authority.transcript.jsonl +2 -0
- package/templates/hello-agent/agents/hello-agent/evals/adversarial/fractioning.json +6 -0
- package/templates/hello-agent/agents/hello-agent/evals/adversarial/fractioning.transcript.jsonl +2 -0
- package/templates/hello-agent/agents/hello-agent/evals/adversarial/model-total.json +6 -0
- package/templates/hello-agent/agents/hello-agent/evals/adversarial/model-total.transcript.jsonl +2 -0
- package/templates/hello-agent/agents/hello-agent/evals/adversarial/prompt-injection.json +6 -0
- package/templates/hello-agent/agents/hello-agent/evals/adversarial/prompt-injection.transcript.jsonl +2 -0
- package/templates/hello-agent/agents/hello-agent/evals/adversarial/webhook-replay.json +6 -0
- package/templates/hello-agent/agents/hello-agent/evals/eval.yaml +7 -0
- package/templates/hello-agent/agents/hello-agent/guardrails.json +8 -0
- package/templates/hello-agent/agents/hello-agent/mandate.example.json +17 -0
- package/templates/hello-agent/agents/hello-agent/package.json +19 -0
- package/templates/hello-agent/agents/hello-agent/runbook.md +10 -0
- package/templates/hello-agent/agents/hello-agent/scenarios/happy-path.json +6 -0
- package/templates/hello-agent/agents/hello-agent/scenarios/happy-path.transcript.jsonl +2 -0
- package/templates/hello-agent/agents/hello-agent/scenarios/prompt-injection.json +6 -0
- package/templates/hello-agent/agents/hello-agent/scenarios/prompt-injection.transcript.jsonl +2 -0
- package/templates/hello-agent/agents/hello-agent/src/bills.ts +23 -0
- package/templates/hello-agent/agents/hello-agent/src/kit.ts +33 -0
- package/templates/hello-agent/agents/hello-agent/tools.json +11 -0
- package/templates/hello-agent/agents/hello-agent/tsconfig.json +9 -0
- package/templates/hello-agent/package.json +30 -0
- package/templates/hello-agent/packages/agent-core/README.md +21 -0
- package/templates/hello-agent/packages/agent-core/package.json +24 -0
- package/templates/hello-agent/packages/agent-core/src/agent.ts +125 -0
- package/templates/hello-agent/packages/agent-core/src/api/charge-rail.ts +175 -0
- package/templates/hello-agent/packages/agent-core/src/api/client.ts +53 -0
- package/templates/hello-agent/packages/agent-core/src/api/mandate-status.ts +58 -0
- package/templates/hello-agent/packages/agent-core/src/api/rail.ts +117 -0
- package/templates/hello-agent/packages/agent-core/src/api/sandbox-payer.ts +58 -0
- package/templates/hello-agent/packages/agent-core/src/approval.ts +123 -0
- package/templates/hello-agent/packages/agent-core/src/bundle.ts +159 -0
- package/templates/hello-agent/packages/agent-core/src/channels.ts +140 -0
- package/templates/hello-agent/packages/agent-core/src/check.ts +253 -0
- package/templates/hello-agent/packages/agent-core/src/clock.ts +42 -0
- package/templates/hello-agent/packages/agent-core/src/engine.ts +885 -0
- package/templates/hello-agent/packages/agent-core/src/escalate.ts +85 -0
- package/templates/hello-agent/packages/agent-core/src/events.ts +40 -0
- package/templates/hello-agent/packages/agent-core/src/guardrails.ts +45 -0
- package/templates/hello-agent/packages/agent-core/src/hash.ts +62 -0
- package/templates/hello-agent/packages/agent-core/src/ids.ts +10 -0
- package/templates/hello-agent/packages/agent-core/src/index.ts +32 -0
- package/templates/hello-agent/packages/agent-core/src/mandate.ts +100 -0
- package/templates/hello-agent/packages/agent-core/src/manifest.ts +76 -0
- package/templates/hello-agent/packages/agent-core/src/providers/anthropic.ts +76 -0
- package/templates/hello-agent/packages/agent-core/src/providers/replay.ts +63 -0
- package/templates/hello-agent/packages/agent-core/src/providers/types.ts +39 -0
- package/templates/hello-agent/packages/agent-core/src/rail.ts +101 -0
- package/templates/hello-agent/packages/agent-core/src/receipt-verification.ts +401 -0
- package/templates/hello-agent/packages/agent-core/src/revocation.ts +30 -0
- package/templates/hello-agent/packages/agent-core/src/secrets.ts +30 -0
- package/templates/hello-agent/packages/agent-core/src/state/store.ts +307 -0
- package/templates/hello-agent/packages/agent-core/src/state-machine.ts +148 -0
- package/templates/hello-agent/packages/agent-core/src/stubs/charge-rail.ts +195 -0
- package/templates/hello-agent/packages/agent-core/src/stubs/mandate-status.ts +57 -0
- package/templates/hello-agent/packages/agent-core/src/stubs/rail.ts +141 -0
- package/templates/hello-agent/packages/agent-core/src/tools.ts +55 -0
- package/templates/hello-agent/packages/agent-core/src/types.ts +134 -0
- package/templates/hello-agent/packages/agent-core/test/approval.test.ts +117 -0
- package/templates/hello-agent/packages/agent-core/test/bundle.test.ts +85 -0
- package/templates/hello-agent/packages/agent-core/test/clock.test.ts +25 -0
- package/templates/hello-agent/packages/agent-core/test/engine.test.ts +596 -0
- package/templates/hello-agent/packages/agent-core/test/escalate.test.ts +42 -0
- package/templates/hello-agent/packages/agent-core/test/fixtures/staging-receipt-keys.json +18 -0
- package/templates/hello-agent/packages/agent-core/test/fixtures/staging-receipt.json +24 -0
- package/templates/hello-agent/packages/agent-core/test/helpers.ts +139 -0
- package/templates/hello-agent/packages/agent-core/test/mandate-status.test.ts +153 -0
- package/templates/hello-agent/packages/agent-core/test/manifest.test.ts +45 -0
- package/templates/hello-agent/packages/agent-core/test/receipt-verification.test.ts +316 -0
- package/templates/hello-agent/packages/agent-core/test/receivables.test.ts +316 -0
- package/templates/hello-agent/packages/agent-core/test/secrets.test.ts +28 -0
- package/templates/hello-agent/packages/agent-core/test/state-machine.test.ts +67 -0
- package/templates/hello-agent/packages/agent-core/test/transitions.type-test.ts +59 -0
- package/templates/hello-agent/packages/agent-core/tsconfig.json +10 -0
- package/templates/hello-agent/packages/agent-runtime/README.md +68 -0
- package/templates/hello-agent/packages/agent-runtime/bin.mjs +32 -0
- package/templates/hello-agent/packages/agent-runtime/package.json +23 -0
- package/templates/hello-agent/packages/agent-runtime/src/adversarial.ts +169 -0
- package/templates/hello-agent/packages/agent-runtime/src/agent.ts +68 -0
- package/templates/hello-agent/packages/agent-runtime/src/channels/contact.ts +13 -0
- package/templates/hello-agent/packages/agent-runtime/src/channels/index.ts +72 -0
- package/templates/hello-agent/packages/agent-runtime/src/channels/rules.ts +120 -0
- package/templates/hello-agent/packages/agent-runtime/src/channels/types.ts +124 -0
- package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +358 -0
- package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +206 -0
- package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/evidence.ts +109 -0
- package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +225 -0
- package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +143 -0
- package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/present.ts +47 -0
- package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +113 -0
- package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +113 -0
- package/templates/hello-agent/packages/agent-runtime/src/cli.ts +98 -0
- package/templates/hello-agent/packages/agent-runtime/src/commands/check.ts +15 -0
- package/templates/hello-agent/packages/agent-runtime/src/commands/decide.ts +63 -0
- package/templates/hello-agent/packages/agent-runtime/src/commands/eval.ts +58 -0
- package/templates/hello-agent/packages/agent-runtime/src/commands/inspect.ts +100 -0
- package/templates/hello-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +317 -0
- package/templates/hello-agent/packages/agent-runtime/src/commands/poll.ts +144 -0
- package/templates/hello-agent/packages/agent-runtime/src/commands/reconcile.ts +42 -0
- package/templates/hello-agent/packages/agent-runtime/src/commands/rerun.ts +86 -0
- package/templates/hello-agent/packages/agent-runtime/src/commands/resume.ts +43 -0
- package/templates/hello-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +131 -0
- package/templates/hello-agent/packages/agent-runtime/src/commands/start.ts +245 -0
- package/templates/hello-agent/packages/agent-runtime/src/commands/verify.ts +129 -0
- package/templates/hello-agent/packages/agent-runtime/src/commands/webhook.ts +39 -0
- package/templates/hello-agent/packages/agent-runtime/src/default-kit.ts +83 -0
- package/templates/hello-agent/packages/agent-runtime/src/index.ts +12 -0
- package/templates/hello-agent/packages/agent-runtime/src/inspect.ts +797 -0
- package/templates/hello-agent/packages/agent-runtime/src/kit.ts +206 -0
- package/templates/hello-agent/packages/agent-runtime/src/poll.ts +105 -0
- package/templates/hello-agent/packages/agent-runtime/src/scenarios.ts +268 -0
- package/templates/hello-agent/packages/agent-runtime/src/setup.ts +229 -0
- package/templates/hello-agent/packages/agent-runtime/src/terminal.ts +154 -0
- package/templates/hello-agent/packages/agent-runtime/src/webhook.ts +131 -0
- package/templates/hello-agent/packages/agent-runtime/test/inspect.test.ts +336 -0
- package/templates/hello-agent/packages/agent-runtime/test/verify-cli.test.ts +127 -0
- package/templates/hello-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +232 -0
- package/templates/hello-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +204 -0
- package/templates/hello-agent/packages/agent-runtime/test/whatsapp-evidence.test.ts +94 -0
- package/templates/hello-agent/packages/agent-runtime/test/whatsapp-rules.test.ts +174 -0
- package/templates/hello-agent/packages/agent-runtime/test/whatsapp-session.test.ts +135 -0
- package/templates/hello-agent/packages/agent-runtime/tsconfig.json +9 -0
- package/templates/hello-agent/tsconfig.base.json +16 -0
- package/templates/hello-agent/vitest.config.ts +12 -0
- package/templates/kits.lock.json +60 -12
- package/templates/supplier-payments-agent/LICENSE +21 -0
- package/templates/supplier-payments-agent/README.md +23 -0
- package/templates/supplier-payments-agent/_gitignore +10 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/.env.example +7 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/AGENTS.md +21 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/CLAUDE.md +21 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/README.md +118 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/SYSTEM_PROMPT.md +29 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/agent.yaml +26 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/evals/adversarial/batch-tampering.json +12 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/evals/adversarial/batch-tampering.transcript.jsonl +3 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/evals/adversarial/beneficiary-swap-mandate.json +9 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/evals/adversarial/beneficiary-swap.json +10 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/evals/adversarial/beneficiary-swap.transcript.jsonl +2 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/evals/adversarial/exfiltration.json +13 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/evals/adversarial/exfiltration.transcript.jsonl +3 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/evals/adversarial/false-authority.json +9 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/evals/adversarial/false-authority.transcript.jsonl +3 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/evals/adversarial/fractioning.json +15 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/evals/adversarial/fractioning.transcript.jsonl +6 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/evals/adversarial/model-total.json +10 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/evals/adversarial/model-total.transcript.jsonl +2 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/evals/adversarial/prompt-injection.json +9 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/evals/adversarial/prompt-injection.transcript.jsonl +2 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/evals/adversarial/webhook-replay.json +9 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/evals/eval.yaml +9 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/guardrails.json +15 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/mandate.example.json +70 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/package.json +26 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/runbook.md +19 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/scenarios/beneficiary-not-allowed.json +14 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/scenarios/beneficiary-not-allowed.transcript.jsonl +2 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/scenarios/cap-exceeded.json +12 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/scenarios/cap-exceeded.transcript.jsonl +2 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/scenarios/escalated-above-threshold.json +20 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/scenarios/escalated-above-threshold.transcript.jsonl +7 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/scenarios/happy-path.json +19 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/scenarios/happy-path.transcript.jsonl +3 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/scenarios/mandate-revoked.json +21 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/scenarios/mandate-revoked.transcript.jsonl +9 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/scenarios/partial-batch-failure.json +21 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/scenarios/partial-batch-failure.transcript.jsonl +3 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/scenarios/prompt-injection.json +12 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/scenarios/prompt-injection.transcript.jsonl +2 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/src/kit.ts +176 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/src/modules/batch-payout.ts +345 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/src/modules/pix-out.ts +118 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/src/payables.ts +76 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/test/adversarial.test.ts +53 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/test/batch.test.ts +384 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/test/check.test.ts +109 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/test/scenarios.test.ts +81 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/tools.json +53 -0
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/tsconfig.json +9 -0
- package/templates/supplier-payments-agent/package.json +36 -0
- package/templates/supplier-payments-agent/packages/agent-core/README.md +21 -0
- package/templates/supplier-payments-agent/packages/agent-core/package.json +24 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/agent.ts +125 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/api/charge-rail.ts +175 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/api/client.ts +53 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/api/mandate-status.ts +58 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/api/rail.ts +117 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/api/sandbox-payer.ts +58 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/approval.ts +123 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/bundle.ts +159 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/channels.ts +140 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/check.ts +253 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/clock.ts +42 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/engine.ts +885 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/escalate.ts +85 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/events.ts +40 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/guardrails.ts +45 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/hash.ts +62 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/ids.ts +10 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/index.ts +32 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/mandate.ts +100 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/manifest.ts +76 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/providers/anthropic.ts +76 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/providers/replay.ts +63 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/providers/types.ts +39 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/rail.ts +101 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/receipt-verification.ts +401 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/revocation.ts +30 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/secrets.ts +30 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/state/store.ts +307 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/state-machine.ts +148 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/stubs/charge-rail.ts +195 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/stubs/mandate-status.ts +57 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/stubs/rail.ts +141 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/tools.ts +55 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/types.ts +134 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/approval.test.ts +117 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/bundle.test.ts +85 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/clock.test.ts +25 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/engine.test.ts +596 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/escalate.test.ts +42 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/fixtures/staging-receipt-keys.json +18 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/fixtures/staging-receipt.json +24 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/helpers.ts +139 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/mandate-status.test.ts +153 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/manifest.test.ts +45 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/receipt-verification.test.ts +316 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/receivables.test.ts +316 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/secrets.test.ts +28 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/state-machine.test.ts +67 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/transitions.type-test.ts +59 -0
- package/templates/supplier-payments-agent/packages/agent-core/tsconfig.json +10 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/README.md +68 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/bin.mjs +32 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/package.json +23 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/adversarial.ts +169 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/agent.ts +68 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/contact.ts +13 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/index.ts +72 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/rules.ts +120 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/types.ts +124 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +358 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +206 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/evidence.ts +109 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +225 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +143 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/present.ts +47 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +113 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +113 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/cli.ts +98 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/check.ts +15 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/decide.ts +63 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/eval.ts +58 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/inspect.ts +100 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +317 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/poll.ts +144 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/reconcile.ts +42 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/rerun.ts +86 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/resume.ts +43 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +131 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/start.ts +245 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/verify.ts +129 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/webhook.ts +39 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/default-kit.ts +83 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/index.ts +12 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/inspect.ts +797 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/kit.ts +206 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/poll.ts +105 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/scenarios.ts +268 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/setup.ts +229 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/terminal.ts +154 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/webhook.ts +131 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/test/inspect.test.ts +336 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/test/verify-cli.test.ts +127 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +232 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +204 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-evidence.test.ts +94 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-rules.test.ts +174 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-session.test.ts +135 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/tsconfig.json +9 -0
- package/templates/supplier-payments-agent/tsconfig.base.json +16 -0
- package/templates/supplier-payments-agent/vitest.config.ts +12 -0
- package/templates/bills-agent/agents/bills-agent/channels/terminal/index.ts +0 -108
- package/templates/bills-agent/agents/bills-agent/src/commands/approve.ts +0 -9
- package/templates/bills-agent/agents/bills-agent/src/commands/check.ts +0 -13
- package/templates/bills-agent/agents/bills-agent/src/commands/consent.ts +0 -26
- package/templates/bills-agent/agents/bills-agent/src/commands/decide.ts +0 -45
- package/templates/bills-agent/agents/bills-agent/src/commands/deny.ts +0 -7
- package/templates/bills-agent/agents/bills-agent/src/commands/eval.ts +0 -37
- package/templates/bills-agent/agents/bills-agent/src/commands/reconcile.ts +0 -38
- package/templates/bills-agent/agents/bills-agent/src/commands/rerun.ts +0 -65
- package/templates/bills-agent/agents/bills-agent/src/commands/resume.ts +0 -30
- package/templates/bills-agent/agents/bills-agent/src/main.ts +0 -185
- package/templates/collections-agent/agents/collections-agent/channels/terminal/index.ts +0 -178
- package/templates/collections-agent/agents/collections-agent/src/commands/approve.ts +0 -10
- package/templates/collections-agent/agents/collections-agent/src/commands/check.ts +0 -13
- package/templates/collections-agent/agents/collections-agent/src/commands/decide.ts +0 -48
- package/templates/collections-agent/agents/collections-agent/src/commands/deny.ts +0 -7
- package/templates/collections-agent/agents/collections-agent/src/commands/eval.ts +0 -39
- package/templates/collections-agent/agents/collections-agent/src/commands/poll.ts +0 -35
- package/templates/collections-agent/agents/collections-agent/src/commands/reconcile.ts +0 -38
- package/templates/collections-agent/agents/collections-agent/src/commands/rerun.ts +0 -55
- package/templates/collections-agent/agents/collections-agent/src/commands/resume.ts +0 -30
- package/templates/collections-agent/agents/collections-agent/src/commands/webhook.ts +0 -36
- package/templates/collections-agent/agents/collections-agent/src/main.ts +0 -193
- package/templates/collections-agent/agents/collections-agent/src/setup.ts +0 -250
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rules of collecting over a conversation, as code.
|
|
3
|
+
*
|
|
4
|
+
* The spec names four (section 16): the hours, the secrecy of the debt, no
|
|
5
|
+
* embarrassment, LGPD. Two of those are decidable by a machine and are
|
|
6
|
+
* enforced here, on EVERY outbound message, above the backend — so choosing
|
|
7
|
+
* the simulator or the official API changes nothing about what may be said.
|
|
8
|
+
* The other two are the prompt's and the operator's: no code can read a
|
|
9
|
+
* sentence and tell whether it shames somebody.
|
|
10
|
+
*
|
|
11
|
+
* hours nothing is sent outside the collection window. The
|
|
12
|
+
* envelope already refuses to DRAFT a receivable outside
|
|
13
|
+
* it; this refuses to SPEAK outside it, which is the rule
|
|
14
|
+
* the law actually states.
|
|
15
|
+
* bound contact a message goes to the person the conversation is bound to
|
|
16
|
+
* and to no other number. This is the secrecy rule: the way
|
|
17
|
+
* a debt reaches a third party is by being sent to one.
|
|
18
|
+
* subject a message may not name an agreement other than this
|
|
19
|
+
* conversation's. Same rule, other direction: the debtor
|
|
20
|
+
* learning about somebody else's debt.
|
|
21
|
+
* no document a CPF or a CNPJ never appears in a message. The prompt
|
|
22
|
+
* says so; this makes it true even if the model ignores it.
|
|
23
|
+
*
|
|
24
|
+
* Every rule can only REFUSE. None of them rewrites a message: a message
|
|
25
|
+
* that is edited on the way out is not the message the transcript shows.
|
|
26
|
+
*/
|
|
27
|
+
import { isOutsideHours, localClock } from "@codespar/agent-core";
|
|
28
|
+
import type { Conversation, OutboundBody } from "./types.js";
|
|
29
|
+
|
|
30
|
+
export interface ChannelRefusal {
|
|
31
|
+
rule: string;
|
|
32
|
+
detail: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface HoursRule {
|
|
36
|
+
/** The OPEN window, `HH:MM-HH:MM`, as `guardrails.envelope.collection_hours` spells it. */
|
|
37
|
+
window: string;
|
|
38
|
+
timezone: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface RuleContext {
|
|
42
|
+
conversation: Conversation;
|
|
43
|
+
hours?: HoursRule | undefined;
|
|
44
|
+
now: () => Date;
|
|
45
|
+
/** Agreement aliases this agent knows, so "names another agreement" is decidable. */
|
|
46
|
+
knownSubjects?: readonly string[] | undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** `123.456.789-00`, `12345678900`, `12.345.678/0001-95`, `12345678000195`. */
|
|
50
|
+
const FORMATTED_DOCUMENT = /\b\d{3}\.\d{3}\.\d{3}-\d{2}\b|\b\d{2}\.\d{3}\.\d{3}\/\d{4}-\d{2}\b/;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A bare document is a token of exactly 11 or 14 digits. Token-wise and not
|
|
54
|
+
* substring-wise on purpose: a Pix BR Code is one token of 100+ characters
|
|
55
|
+
* and a substring rule would find a "CPF" inside every payable message.
|
|
56
|
+
*/
|
|
57
|
+
function hasBareDocument(text: string): boolean {
|
|
58
|
+
return text
|
|
59
|
+
.split(/\s+/)
|
|
60
|
+
.map((t) => t.replace(/^[^0-9]+|[^0-9]+$/g, ""))
|
|
61
|
+
.some((t) => /^\d{11}$/.test(t) || /^\d{14}$/.test(t));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The prose a rule reads. The rail's own strings are not prose and are not read. */
|
|
65
|
+
export function proseOf(body: OutboundBody): string[] {
|
|
66
|
+
switch (body.kind) {
|
|
67
|
+
case "text":
|
|
68
|
+
return [body.text];
|
|
69
|
+
case "media":
|
|
70
|
+
return body.caption === undefined ? [] : [body.caption];
|
|
71
|
+
case "template":
|
|
72
|
+
return body.variables;
|
|
73
|
+
case "instrument":
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** `undefined` when the message may go out. The first refusal wins, so the reason is stable. */
|
|
79
|
+
export function checkOutbound(to: string, body: OutboundBody, ctx: RuleContext): ChannelRefusal | undefined {
|
|
80
|
+
if (to !== ctx.conversation.contact) {
|
|
81
|
+
return {
|
|
82
|
+
rule: "bound_contact",
|
|
83
|
+
detail: "a collection message goes to the person who owes and to nobody else; this conversation is bound to another contact",
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (ctx.hours) {
|
|
88
|
+
const [open, close] = ctx.hours.window.split("-") as [string, string];
|
|
89
|
+
// The envelope states the OPEN window; `isOutsideHours` reads a CLOSED one, so it is inverted here the same way the envelope policy inverts it.
|
|
90
|
+
if (isOutsideHours(`${close}-${open}`, ctx.now(), ctx.hours.timezone)) {
|
|
91
|
+
return {
|
|
92
|
+
rule: "collection_hours",
|
|
93
|
+
detail: `fora do horario de cobranca (${ctx.hours.window} ${ctx.hours.timezone}); agora sao ${localClock(ctx.now(), ctx.hours.timezone)}`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const prose = proseOf(body);
|
|
99
|
+
|
|
100
|
+
for (const text of prose) {
|
|
101
|
+
if (FORMATTED_DOCUMENT.test(text) || hasBareDocument(text)) {
|
|
102
|
+
return { rule: "no_document", detail: "a message must not carry a CPF or a CNPJ; the code takes the document from the agreement and never speaks it" };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const subject = ctx.conversation.subject;
|
|
107
|
+
if (subject && ctx.knownSubjects?.length) {
|
|
108
|
+
for (const text of prose) {
|
|
109
|
+
const lowered = text.toLowerCase();
|
|
110
|
+
const other = ctx.knownSubjects.find((s) => s !== subject && lowered.includes(s.toLowerCase()));
|
|
111
|
+
if (other) {
|
|
112
|
+
return { rule: "subject_scope", detail: `this conversation is about ${subject}; a message here may not name ${other}` };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (body.kind === "text" && body.text.trim() === "") return { rule: "empty_message", detail: "an empty message is not a message" };
|
|
118
|
+
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One interface, several backends. What a channel is, from the runner's side:
|
|
3
|
+
* a place a person writes from, a place the agent writes to, and a record of
|
|
4
|
+
* what happened to each message.
|
|
5
|
+
*
|
|
6
|
+
* The shape is deliberately the shape of a MESSAGING channel and not of a
|
|
7
|
+
* terminal, because the two are not the same thing and pretending they are is
|
|
8
|
+
* how an approval question ends up in a debtor's chat. A terminal has one
|
|
9
|
+
* stream and one reader; a channel has a counterparty on one side and an
|
|
10
|
+
* operator somewhere else entirely, and the runner keeps them apart by
|
|
11
|
+
* construction: `send` reaches the counterparty, and the operator's console
|
|
12
|
+
* and questions never touch it.
|
|
13
|
+
*
|
|
14
|
+
* Every backend answers the same five questions, and the ADAPTER above them
|
|
15
|
+
* (`whatsapp/index.ts`) is where the rules live, so a rule cannot be skipped
|
|
16
|
+
* by choosing a different backend.
|
|
17
|
+
*/
|
|
18
|
+
import type { ChannelName } from "@codespar/agent-core";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* What a provider tells us about a message after it left. `queued` is ours
|
|
22
|
+
* (accepted, not yet handed over); the rest are the provider's own, and a
|
|
23
|
+
* backend that cannot observe one never invents it.
|
|
24
|
+
*/
|
|
25
|
+
export type DeliveryState = "queued" | "sent" | "delivered" | "read" | "failed";
|
|
26
|
+
|
|
27
|
+
export interface InboundMessage {
|
|
28
|
+
/** The PROVIDER's message id (`wamid....` on WhatsApp). Never ours. */
|
|
29
|
+
id: string;
|
|
30
|
+
/** Who wrote it, E.164 on WhatsApp. */
|
|
31
|
+
from: string;
|
|
32
|
+
text: string;
|
|
33
|
+
/** Unix seconds on the PROVIDER's clock, which is not this process's clock. */
|
|
34
|
+
timestamp: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* What the agent can put on a channel.
|
|
39
|
+
*
|
|
40
|
+
* `text` is the ordinary message. `media` is a QR, which on WhatsApp is an
|
|
41
|
+
* image and never ASCII art. `template` is the only thing a provider accepts
|
|
42
|
+
* outside the 24-hour session window, which is why it is a separate kind
|
|
43
|
+
* rather than a flag.
|
|
44
|
+
*
|
|
45
|
+
* `instrument` is the Pix copy-and-paste or the boleto line, and it is its own
|
|
46
|
+
* kind for a reason that is not cosmetic: those strings are minted by the rail
|
|
47
|
+
* and are long runs of digits, and the rule that keeps a CPF out of a message
|
|
48
|
+
* would refuse a boleto line (whose last field is fourteen digits) if it read
|
|
49
|
+
* one as prose. Separating them says which strings the conversation composed
|
|
50
|
+
* and which the rail did, and only the first are checked for a document.
|
|
51
|
+
*/
|
|
52
|
+
export type OutboundBody =
|
|
53
|
+
| { kind: "text"; text: string }
|
|
54
|
+
| { kind: "media"; media: "qr"; data: string; caption?: string }
|
|
55
|
+
| { kind: "instrument"; instrument: "pix_copy_paste" | "boleto_bank_line"; value: string }
|
|
56
|
+
| { kind: "template"; template: string; language: string; variables: string[] };
|
|
57
|
+
|
|
58
|
+
export interface SentMessage {
|
|
59
|
+
/** The provider's id when it accepted one, else the id the backend minted. */
|
|
60
|
+
id: string;
|
|
61
|
+
state: DeliveryState;
|
|
62
|
+
/**
|
|
63
|
+
* Set when something REFUSED the message. Nothing left the process: a
|
|
64
|
+
* refusal here is not a delivery failure, it is the message never having
|
|
65
|
+
* been sent. `rule` is the stable name a test and a log both key on.
|
|
66
|
+
*/
|
|
67
|
+
refused?: { rule: string; detail: string };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** What the person on the other side is called, and what the conversation may be about. */
|
|
71
|
+
export interface Conversation {
|
|
72
|
+
/** The contact the conversation is bound to. Every outbound goes here and nowhere else. */
|
|
73
|
+
contact: string;
|
|
74
|
+
/** What the conversation may name — an agreement alias. Absent means the channel enforces no subject rule. */
|
|
75
|
+
subject?: string | undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface ChannelBackend {
|
|
79
|
+
/** `simulator`, `cloud-api`. Named in the console, in the bundle and in the gate's output. */
|
|
80
|
+
readonly name: string;
|
|
81
|
+
/** Whether this backend talks to a real provider. The gate and the tests refuse to run a live one. */
|
|
82
|
+
readonly live: boolean;
|
|
83
|
+
open(): Promise<void>;
|
|
84
|
+
/** The next inbound message, or `undefined` when the conversation is over. */
|
|
85
|
+
next(): Promise<InboundMessage | undefined>;
|
|
86
|
+
/** Hands the message to the provider. Rules have already run: a backend never re-decides policy. */
|
|
87
|
+
deliver(to: string, body: OutboundBody): Promise<SentMessage>;
|
|
88
|
+
close(): Promise<void>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface Channel {
|
|
92
|
+
readonly name: ChannelName;
|
|
93
|
+
readonly backend: string;
|
|
94
|
+
readonly conversation: Conversation;
|
|
95
|
+
open(): Promise<void>;
|
|
96
|
+
next(): Promise<InboundMessage | undefined>;
|
|
97
|
+
/** Applies the channel's rules, then delivers. A refused message is recorded and never sent. */
|
|
98
|
+
send(body: OutboundBody): Promise<SentMessage>;
|
|
99
|
+
/** Convenience for the common case. */
|
|
100
|
+
say(text: string): Promise<SentMessage>;
|
|
101
|
+
close(): Promise<void>;
|
|
102
|
+
/** Every message of this conversation, in order, as the bundle records it. */
|
|
103
|
+
log(): ReadonlyArray<ChannelLogLine>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface ChannelLogLine {
|
|
107
|
+
at: string;
|
|
108
|
+
direction: "in" | "out";
|
|
109
|
+
/** The contact, MASKED. The raw value never reaches a file. */
|
|
110
|
+
contact: string;
|
|
111
|
+
kind: OutboundBody["kind"];
|
|
112
|
+
/** The provider's id, or ours when nothing was sent. */
|
|
113
|
+
message_id: string;
|
|
114
|
+
state: DeliveryState;
|
|
115
|
+
text?: string;
|
|
116
|
+
/**
|
|
117
|
+
* Inbound only: the PROVIDER's own timestamp, unix seconds. Recorded
|
|
118
|
+
* because the 24-hour session window is counted on WhatsApp's clock and
|
|
119
|
+
* `at` is this process's, and a later run has to read the window back from
|
|
120
|
+
* this log rather than from a clock it does not share.
|
|
121
|
+
*/
|
|
122
|
+
provider_timestamp?: number;
|
|
123
|
+
refused?: { rule: string; detail: string };
|
|
124
|
+
}
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The official backend: Meta's WhatsApp Cloud API, behind the same interface
|
|
3
|
+
* the simulator implements. An ADAPTER and nothing more — the rules, the
|
|
4
|
+
* session window and the conversation record are the channel's, above this.
|
|
5
|
+
*
|
|
6
|
+
* Three things about it are deliberate.
|
|
7
|
+
*
|
|
8
|
+
* The credentials are ABSENT by default and this backend refuses to open
|
|
9
|
+
* without them, by name. No fallback, no "development mode" that quietly
|
|
10
|
+
* talks to nobody: a channel that pretends to send is worse than one that
|
|
11
|
+
* says it cannot.
|
|
12
|
+
*
|
|
13
|
+
* Nothing here is called from a test or from the CI, ever. What the tests
|
|
14
|
+
* exercise are the four pure functions — the signature check, the webhook
|
|
15
|
+
* parse, the verification handshake and the request builder — which is
|
|
16
|
+
* exactly the part where a mistake is silent. The send itself is one `fetch`
|
|
17
|
+
* over a request those functions built.
|
|
18
|
+
*
|
|
19
|
+
* And the shapes are written against Meta's published documentation, not
|
|
20
|
+
* against a call we made: nobody on this repo has a Business account, so the
|
|
21
|
+
* first live run belongs to the developer who has one. The README says that
|
|
22
|
+
* in those words, and `docs/OPEN_QUESTIONS.md` names what a live run would
|
|
23
|
+
* settle.
|
|
24
|
+
*/
|
|
25
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
26
|
+
import { createServer, type Server } from "node:http";
|
|
27
|
+
import type { ChannelBackend, InboundMessage, OutboundBody, SentMessage } from "../types.js";
|
|
28
|
+
|
|
29
|
+
export interface CloudApiConfig {
|
|
30
|
+
/**
|
|
31
|
+
* Where the Graph API lives. Meta's own by default, and the local emulator's
|
|
32
|
+
* when the run is against the local emulator — which is the whole point of
|
|
33
|
+
* having it be a field: the two backends are THE SAME CODE with a different
|
|
34
|
+
* base URL, so the simulator exercises the adapter instead of standing in
|
|
35
|
+
* for it. A mock of our own would only ever agree with us.
|
|
36
|
+
*/
|
|
37
|
+
baseUrl: string;
|
|
38
|
+
/** The number the business sends from, as Meta's dashboard names it. */
|
|
39
|
+
phoneNumberId: string;
|
|
40
|
+
accessToken: string;
|
|
41
|
+
/** Echoed back on the `GET` handshake when Meta registers the webhook. */
|
|
42
|
+
verifyToken: string;
|
|
43
|
+
/** The app secret that signs deliveries. Without it no delivery is trusted. */
|
|
44
|
+
appSecret: string;
|
|
45
|
+
apiVersion: string;
|
|
46
|
+
webhookPort: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Meta's own Graph host. Anything else is a stand-in, and the channel treats it as one. */
|
|
50
|
+
export const META_GRAPH_HOST = "graph.facebook.com";
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Whether this base URL is Meta. It decides one thing that matters beyond
|
|
54
|
+
* logging: `live`, which is what the consent-evidence builder refuses on. An
|
|
55
|
+
* act observed by an emulator was observed by nobody.
|
|
56
|
+
*/
|
|
57
|
+
export function isMetaBaseUrl(baseUrl: string): boolean {
|
|
58
|
+
try {
|
|
59
|
+
return new URL(baseUrl).hostname === META_GRAPH_HOST;
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const CLOUD_API_ENV = {
|
|
66
|
+
phoneNumberId: "WHATSAPP_PHONE_NUMBER_ID",
|
|
67
|
+
accessToken: "WHATSAPP_ACCESS_TOKEN",
|
|
68
|
+
verifyToken: "WHATSAPP_VERIFY_TOKEN",
|
|
69
|
+
appSecret: "WHATSAPP_APP_SECRET",
|
|
70
|
+
apiVersion: "WHATSAPP_API_VERSION",
|
|
71
|
+
webhookPort: "WHATSAPP_WEBHOOK_PORT",
|
|
72
|
+
baseUrl: "WHATSAPP_GRAPH_URL",
|
|
73
|
+
} as const;
|
|
74
|
+
|
|
75
|
+
export interface ConfigResult {
|
|
76
|
+
config?: CloudApiConfig;
|
|
77
|
+
/** The variables that are not set. A refusal names them rather than saying "misconfigured". */
|
|
78
|
+
missing: string[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function loadCloudApiConfig(env: NodeJS.ProcessEnv): ConfigResult {
|
|
82
|
+
const read = (name: string) => {
|
|
83
|
+
const v = env[name]?.trim();
|
|
84
|
+
return v ? v : undefined;
|
|
85
|
+
};
|
|
86
|
+
const phoneNumberId = read(CLOUD_API_ENV.phoneNumberId);
|
|
87
|
+
const accessToken = read(CLOUD_API_ENV.accessToken);
|
|
88
|
+
const verifyToken = read(CLOUD_API_ENV.verifyToken);
|
|
89
|
+
const appSecret = read(CLOUD_API_ENV.appSecret);
|
|
90
|
+
const missing = [
|
|
91
|
+
...(phoneNumberId ? [] : [CLOUD_API_ENV.phoneNumberId]),
|
|
92
|
+
...(accessToken ? [] : [CLOUD_API_ENV.accessToken]),
|
|
93
|
+
...(verifyToken ? [] : [CLOUD_API_ENV.verifyToken]),
|
|
94
|
+
...(appSecret ? [] : [CLOUD_API_ENV.appSecret]),
|
|
95
|
+
];
|
|
96
|
+
if (!phoneNumberId || !accessToken || !verifyToken || !appSecret) return { missing };
|
|
97
|
+
return {
|
|
98
|
+
missing,
|
|
99
|
+
config: {
|
|
100
|
+
baseUrl: read(CLOUD_API_ENV.baseUrl) ?? `https://${META_GRAPH_HOST}`,
|
|
101
|
+
phoneNumberId,
|
|
102
|
+
accessToken,
|
|
103
|
+
verifyToken,
|
|
104
|
+
appSecret,
|
|
105
|
+
apiVersion: read(CLOUD_API_ENV.apiVersion) ?? "v21.0",
|
|
106
|
+
webhookPort: Number(read(CLOUD_API_ENV.webhookPort) ?? 3111),
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* ── the pure half: what the tests drive ─────────────────────── */
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* `X-Hub-Signature-256: sha256=<hex>`, HMAC-SHA256 of the RAW body under the
|
|
115
|
+
* app secret. Raw and not re-serialized: a body that round-tripped through
|
|
116
|
+
* `JSON.parse` is a different string, and the signature is over bytes.
|
|
117
|
+
*/
|
|
118
|
+
export function verifyWebhookSignature(appSecret: string, header: string | undefined, rawBody: string): { ok: true } | { ok: false; reason: string } {
|
|
119
|
+
if (!header) return { ok: false, reason: "missing X-Hub-Signature-256" };
|
|
120
|
+
const [algorithm, signature] = header.split("=");
|
|
121
|
+
if (algorithm !== "sha256" || !signature) return { ok: false, reason: "malformed signature header" };
|
|
122
|
+
const expected = createHmac("sha256", appSecret).update(rawBody, "utf8").digest("hex");
|
|
123
|
+
if (expected.length !== signature.length) return { ok: false, reason: "signature mismatch" };
|
|
124
|
+
if (!timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(signature, "hex"))) return { ok: false, reason: "signature mismatch" };
|
|
125
|
+
return { ok: true };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The `GET` handshake Meta makes once, when the webhook URL is registered. */
|
|
129
|
+
export function verifyChallenge(config: CloudApiConfig, query: Record<string, string | undefined>): { status: number; body: string } {
|
|
130
|
+
if (query["hub.mode"] !== "subscribe") return { status: 400, body: "bad mode" };
|
|
131
|
+
if (query["hub.verify_token"] !== config.verifyToken) return { status: 403, body: "bad verify token" };
|
|
132
|
+
return { status: 200, body: query["hub.challenge"] ?? "" };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
interface CloudApiEnvelope {
|
|
136
|
+
object?: string;
|
|
137
|
+
entry?: Array<{
|
|
138
|
+
changes?: Array<{
|
|
139
|
+
field?: string;
|
|
140
|
+
value?: {
|
|
141
|
+
messaging_product?: string;
|
|
142
|
+
metadata?: { phone_number_id?: string };
|
|
143
|
+
messages?: Array<{ id?: string; from?: string; timestamp?: string; type?: string; text?: { body?: string } }>;
|
|
144
|
+
statuses?: Array<{ id?: string; status?: string }>;
|
|
145
|
+
};
|
|
146
|
+
}>;
|
|
147
|
+
}>;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The text messages inside one delivery. A delivery carries several entries,
|
|
152
|
+
* each with several changes, each with several messages — and also status
|
|
153
|
+
* updates, which are not messages. Anything that is not a `text` is dropped
|
|
154
|
+
* with its id named: this channel reads words, and an image or a location the
|
|
155
|
+
* agent cannot read must not be silently treated as an empty message.
|
|
156
|
+
*/
|
|
157
|
+
export function parseInbound(payload: unknown): { messages: InboundMessage[]; ignored: Array<{ id: string; type: string }> } {
|
|
158
|
+
const envelope = payload as CloudApiEnvelope;
|
|
159
|
+
const messages: InboundMessage[] = [];
|
|
160
|
+
const ignored: Array<{ id: string; type: string }> = [];
|
|
161
|
+
for (const entry of envelope.entry ?? []) {
|
|
162
|
+
for (const change of entry.changes ?? []) {
|
|
163
|
+
for (const message of change.value?.messages ?? []) {
|
|
164
|
+
const id = message.id;
|
|
165
|
+
const from = message.from;
|
|
166
|
+
if (!id || !from) continue;
|
|
167
|
+
if (message.type !== "text" || !message.text?.body) {
|
|
168
|
+
ignored.push({ id, type: message.type ?? "unknown" });
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
messages.push({ id, from: from.startsWith("+") ? from : `+${from}`, text: message.text.body, timestamp: Number(message.timestamp ?? 0) });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { messages, ignored };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface SendRequest {
|
|
179
|
+
url: string;
|
|
180
|
+
method: "POST";
|
|
181
|
+
headers: Record<string, string>;
|
|
182
|
+
body: string;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* The `/messages` call, as Meta documents it. `to` loses its `+`: the Cloud
|
|
187
|
+
* API wants the number in international format without the plus.
|
|
188
|
+
*
|
|
189
|
+
* `media` has no branch here and that is the honest answer, not an omission.
|
|
190
|
+
* Sending an image means uploading it first (`POST /{version}/{phone-number-id}/media`,
|
|
191
|
+
* multipart) or handing Meta a public URL, and this repo hosts nothing and
|
|
192
|
+
* renders no PNG — `qrcode-terminal` draws characters. The channel therefore
|
|
193
|
+
* sends the copy-and-paste as its own message, which is the string that
|
|
194
|
+
* actually pays, and the QR is the simulator's. Named in the README and in
|
|
195
|
+
* `docs/OPEN_QUESTIONS.md`.
|
|
196
|
+
*/
|
|
197
|
+
export function buildSendRequest(config: CloudApiConfig, to: string, body: OutboundBody): SendRequest | { unsupported: string } {
|
|
198
|
+
const base = {
|
|
199
|
+
url: `${config.baseUrl.replace(/\/$/, "")}/${config.apiVersion}/${config.phoneNumberId}/messages`,
|
|
200
|
+
method: "POST" as const,
|
|
201
|
+
headers: { authorization: `Bearer ${config.accessToken}`, "content-type": "application/json" },
|
|
202
|
+
};
|
|
203
|
+
// `to` loses its `+`: the Cloud API documents the number in international
|
|
204
|
+
// format without one, and the emulator keys its conversation on the literal
|
|
205
|
+
// string, so a `+` here and none on the inbound side splits one person into
|
|
206
|
+
// two conversations (measured; docs/OPEN_QUESTIONS.md §46c).
|
|
207
|
+
const recipient = { messaging_product: "whatsapp", recipient_type: "individual", to: toGraphNumber(to) };
|
|
208
|
+
switch (body.kind) {
|
|
209
|
+
case "text":
|
|
210
|
+
return { ...base, body: JSON.stringify({ ...recipient, type: "text", text: { preview_url: false, body: body.text } }) };
|
|
211
|
+
case "instrument":
|
|
212
|
+
return { ...base, body: JSON.stringify({ ...recipient, type: "text", text: { preview_url: false, body: body.value } }) };
|
|
213
|
+
case "template":
|
|
214
|
+
return {
|
|
215
|
+
...base,
|
|
216
|
+
body: JSON.stringify({
|
|
217
|
+
...recipient,
|
|
218
|
+
type: "template",
|
|
219
|
+
template: {
|
|
220
|
+
name: body.template,
|
|
221
|
+
language: { code: body.language },
|
|
222
|
+
...(body.variables.length ? { components: [{ type: "body", parameters: body.variables.map((text) => ({ type: "text", text })) }] } : {}),
|
|
223
|
+
},
|
|
224
|
+
}),
|
|
225
|
+
};
|
|
226
|
+
case "media":
|
|
227
|
+
return { unsupported: "media_upload_unimplemented" };
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* ── the impure half: one fetch over a request the above built ── */
|
|
232
|
+
|
|
233
|
+
/** `+5511987654321` -> `5511987654321`, the only form the Graph API takes. */
|
|
234
|
+
export function toGraphNumber(contact: string): string {
|
|
235
|
+
return contact.replace(/^\+/, "");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export interface CloudApiOptions {
|
|
239
|
+
config: CloudApiConfig;
|
|
240
|
+
conversation: { contact: string };
|
|
241
|
+
say: (line: string) => void;
|
|
242
|
+
/** Injected so a test can drive `deliver` without a network. The default is the platform's. */
|
|
243
|
+
fetchImpl?: typeof fetch;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export class WhatsAppCloudApi implements ChannelBackend {
|
|
247
|
+
readonly name: string;
|
|
248
|
+
/** True only against Meta. The emulator is a stand-in and says so. */
|
|
249
|
+
readonly live: boolean;
|
|
250
|
+
private server: Server | undefined;
|
|
251
|
+
private readonly queue: InboundMessage[] = [];
|
|
252
|
+
private waiting: ((m: InboundMessage | undefined) => void) | undefined;
|
|
253
|
+
private closed = false;
|
|
254
|
+
|
|
255
|
+
constructor(private readonly options: CloudApiOptions) {
|
|
256
|
+
this.live = isMetaBaseUrl(options.config.baseUrl);
|
|
257
|
+
this.name = this.live ? "cloud-api" : "emulator";
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** The port the receiver is listening on, which the emulator has to be told about. */
|
|
261
|
+
get webhookPort(): number {
|
|
262
|
+
return this.options.config.webhookPort;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async open(): Promise<void> {
|
|
266
|
+
const { config, say } = this.options;
|
|
267
|
+
this.server = createServer((req, res) => {
|
|
268
|
+
const url = new URL(req.url ?? "/", `http://127.0.0.1:${config.webhookPort}`);
|
|
269
|
+
if (req.method === "GET") {
|
|
270
|
+
const query = Object.fromEntries(url.searchParams.entries());
|
|
271
|
+
const answer = verifyChallenge(config, query);
|
|
272
|
+
res.writeHead(answer.status, { "content-type": "text/plain" }).end(answer.body);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (req.method !== "POST") {
|
|
276
|
+
res.writeHead(405).end();
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
const chunks: Buffer[] = [];
|
|
280
|
+
req.on("data", (c: Buffer) => chunks.push(c));
|
|
281
|
+
req.on("end", () => {
|
|
282
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
283
|
+
const header = req.headers["x-hub-signature-256"];
|
|
284
|
+
const check = verifyWebhookSignature(config.appSecret, Array.isArray(header) ? header[0] : header, raw);
|
|
285
|
+
if (!check.ok) {
|
|
286
|
+
say(`[whatsapp] delivery refused: ${check.reason}`);
|
|
287
|
+
res.writeHead(401, { "content-type": "application/json" }).end(JSON.stringify({ error: "bad_signature" }));
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
let parsed: { messages: InboundMessage[]; ignored: Array<{ id: string; type: string }> };
|
|
291
|
+
try {
|
|
292
|
+
parsed = parseInbound(JSON.parse(raw));
|
|
293
|
+
} catch {
|
|
294
|
+
res.writeHead(400, { "content-type": "application/json" }).end(JSON.stringify({ error: "invalid_json" }));
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
for (const ignored of parsed.ignored) say(`[whatsapp] ignored ${ignored.type} message ${ignored.id}: this channel reads text`);
|
|
298
|
+
for (const message of parsed.messages) this.push(message);
|
|
299
|
+
// Always 200 once read: Meta retries on anything else, and a duplicate is not an error.
|
|
300
|
+
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ received: parsed.messages.length }));
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
await new Promise<void>((resolve) => this.server!.listen(config.webhookPort, resolve));
|
|
304
|
+
say(
|
|
305
|
+
this.live
|
|
306
|
+
? `[whatsapp] cloud API backend: webhook on http://127.0.0.1:${config.webhookPort}/ — expose it and register it with Meta; deliveries are verified against ${CLOUD_API_ENV.appSecret}`
|
|
307
|
+
: `[whatsapp] emulator backend: sending to ${config.baseUrl}/${config.apiVersion}, receiving signed deliveries on http://127.0.0.1:${config.webhookPort}/`,
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private push(message: InboundMessage): void {
|
|
312
|
+
if (this.waiting) {
|
|
313
|
+
const resolve = this.waiting;
|
|
314
|
+
this.waiting = undefined;
|
|
315
|
+
resolve(message);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
this.queue.push(message);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async next(): Promise<InboundMessage | undefined> {
|
|
322
|
+
if (this.closed) return undefined;
|
|
323
|
+
const queued = this.queue.shift();
|
|
324
|
+
if (queued) return queued;
|
|
325
|
+
return new Promise<InboundMessage | undefined>((resolve) => {
|
|
326
|
+
this.waiting = resolve;
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async deliver(to: string, body: OutboundBody): Promise<SentMessage> {
|
|
331
|
+
const request = buildSendRequest(this.options.config, to, body);
|
|
332
|
+
if ("unsupported" in request) {
|
|
333
|
+
return { id: "", state: "failed", refused: { rule: request.unsupported, detail: "this backend cannot upload media: the copy-and-paste text is what pays, and the QR image is the simulator's" } };
|
|
334
|
+
}
|
|
335
|
+
const doFetch = this.options.fetchImpl ?? fetch;
|
|
336
|
+
const response = await doFetch(request.url, { method: request.method, headers: request.headers, body: request.body });
|
|
337
|
+
const text = await response.text();
|
|
338
|
+
if (!response.ok) {
|
|
339
|
+
return { id: "", state: "failed", refused: { rule: "provider_refused", detail: `${response.status} from the Cloud API` } };
|
|
340
|
+
}
|
|
341
|
+
let id = "";
|
|
342
|
+
try {
|
|
343
|
+
id = String((JSON.parse(text) as { messages?: Array<{ id?: string }> }).messages?.[0]?.id ?? "");
|
|
344
|
+
} catch {
|
|
345
|
+
/* a 2xx with an unreadable body: the message went, the id did not come back */
|
|
346
|
+
}
|
|
347
|
+
return { id, state: "sent" };
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async close(): Promise<void> {
|
|
351
|
+
this.closed = true;
|
|
352
|
+
if (this.waiting) {
|
|
353
|
+
this.waiting(undefined);
|
|
354
|
+
this.waiting = undefined;
|
|
355
|
+
}
|
|
356
|
+
await new Promise<void>((resolve) => (this.server ? this.server.close(() => resolve()) : resolve()));
|
|
357
|
+
}
|
|
358
|
+
}
|