feat(credits): gate scheduled agent runs on credits + Telegram alert on shortfall

SW
4mo ago
+304-13

What

runAgentWorkflow charged credits after the turn with no pre-gate, so an unfunded account (out of credits, no saved card to auto-recharge) kept running scheduled turns into a deep negative balance — WAVS Digital hit −1,563 credits (recoupable/chat#1885, "Split out — billing & engagement integrity").

This wires the existing request-path credit gate into the workflow so scheduled runs enforce the same rules as /api/research and the social-scrape routes, and alerts the team on shortfall.

Changes

  • gateWorkflowCredits (app/lib/workflows/) — a "use step" run BEFORE runAgentStep. Reuses ensureCreditsOrShortCircuit (which attempts a silent auto-recharge against a saved card) with a 1-credit floor. On shortfall it fires a Telegram alert and returns hasCredits:false; the workflow skips the run before any model token is spent. Fails open on a transient Stripe/Supabase error so an infra blip never breaks every run.
  • alertCreditShortfall (lib/credits/) — formats + sends the team Telegram alert (account/chat/session), swallowing errors so alerting never breaks the workflow (mirrors sendSalesNotification).
  • runAgentWorkflow — gate first, inside the existing try, so finally still runs cleanup (clear active stream, close writable, revoke ephemeral key) when a run is skipped. Accounts with a saved card top up transparently and proceed unchanged.

Covers both the headless /api/chat/runs path (the scheduled burners this targets) and the interactive /api/chat/workflow path — funded/carded accounts are unaffected either way.

Scope notes

  • The optional floor on deduct_credits_with_audit is intentionally left out — it's a database/ migration (cross-submodule) and this pre-gate already stops the drain at its source. Recommend a follow-up in the billing-integrity tracker.

Verification

  • TDD RED→GREEN for all three units:
    • alertCreditShortfall — sends alert w/ account+chat; never throws on Telegram failure.
    • gateWorkflowCredits — available → hasCredits:true no alert; shortfall → hasCredits:false + alert; 1-credit floor; fail-open on throw.
    • runAgentWorkflow — gates before the step; skips step/billing/auto-commit + no id minted on block; cleanup + ephemeral-key revoke still run on block.
  • Full domain suites green: lib/credits + app/lib/workflows — 140 tests passing.
  • tsc --noEmit: no new errors in changed files (2 pre-existing baseline errors in runAgentWorkflow.ts/.test.ts are unchanged, only line-shifted).
  • eslint clean on all changed files.

Targets main.

Relates to recoupable/chat#1885 (split-out billing & engagement integrity).

🤖 Generated with Claude Code


Summary by cubic

Add a pre-run credit gate to scheduled and interactive agent runs to block unfunded accounts before any tokens are used. On shortfall, we send a Telegram alert; carded accounts auto‑recharge and proceed.

  • New Features
    • Gate checks a 1‑credit floor before runAgentStep; on shortfall it skips the run and alerts Telegram. Cleanup still runs (stream clear/close, ephemeral key revoke).
    • Reuses the existing ensureCreditsOrShortCircuit path; silently tops up if a card is saved and fails open on transient Stripe/Supabase errors to avoid false blocks.