- Slack callback: check data.ok (not data.success) to match backend response
- SlackInstallResponse: use url + state fields (not authorize_url)
- connect-channel.tsx: update all authorize_url refs to url
- BudgetAlert: use current_usd (not current_cost_usd) to match backend Pydantic model
- usage page: update alert.current_cost_usd to alert.current_usd
- Added Recharts cost tracking dashboard at /usage/[tenantId]
- UsageChart, ProviderCostChart, MessageVolumeChart components
- BudgetAlertBadge with ok/warning/exceeded color coding
- TanStack Query hooks for usage summary, provider costs, message volume, budget alerts
- Time range selector (Last 7/30 days, This month, Last 3 months)
- Usage nav link and /usage tenant picker index page
- Installed recharts (was in package.json but missing from node_modules)
- Portal builds cleanly with /usage and /usage/[tenantId] routes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Slack OAuth callback route handler (/api/slack/callback)
- Onboarding wizard: 3-step stepper (connect channel -> configure agent -> test message)
- Connect Channel: Slack OAuth button + WhatsApp manual credentials form
- Configure Agent: links to Agent Designer, Next enabled only with active agent
- Test Message: per-channel test buttons, required step, no separate Go Live button
- BYO API key management settings page at /settings/api-keys
- API Keys nav link in sidebar
- recharts installed (was missing, blocked portal build)
- Create llm_keys.py: GET list (redacted, key_hint only), POST (encrypt + store), DELETE (204 or 404)
- LlmKeyResponse never exposes encrypted_key or raw api_key
- 409 returned on duplicate (tenant_id, provider) key
- Cross-tenant deletion prevented by tenant_id verification in DELETE query
- Update api/__init__.py to export llm_keys_router
- All 5 LLM key CRUD tests passing (32 total unit tests green)
- Add stripe and cryptography to shared pyproject.toml
- Add recharts, @stripe/stripe-js, stripe to portal package.json (submodule)
- Add billing fields to Tenant model (stripe_customer_id, subscription_status, agent_quota, trial_ends_at)
- Add budget_limit_usd to Agent model
- Create TenantLlmKey and StripeEvent models in billing.py (AuditBase and Base respectively)
- Create KeyEncryptionService (MultiFernet encrypt/decrypt/rotate) in crypto.py
- Create compute_budget_status helper in usage.py (threshold logic: ok/warning/exceeded)
- Add platform_encryption_key, stripe_, slack_oauth settings to config.py
- Create Alembic migration 005 with all schema changes, RLS, grants, and composite index
- All 12 tests passing (key encryption roundtrip, rotation, budget thresholds)
- Move key imports to module level in tasks.py for testability and clarity
- Pop WhatsApp extras (phone_number_id, bot_token) in handle_message before model_validate
- Build unified extras dict and extract wa_id from sender.user_id
- Change _process_message signature to accept extras dict
- Add _build_response_extras() helper for channel-aware extras assembly
- Replace all _update_slack_placeholder calls in _process_message with _send_response()
- Add escalation pre-check: skip LLM when Redis escalation_status_key == 'escalated'
- Add escalation post-check: check_escalation_rules after run_agent; call escalate_to_human
when rule matches and agent.escalation_assignee is set
- Add _build_conversation_metadata() helper (billing keyword v1 detection)
- Add channel parameter to build_system_prompt(), build_messages_with_memory(),
build_messages_with_media() for WhatsApp tier-2 business-function scoping
- WhatsApp scoping appends 'You only handle: {topics}' when tool_assignments non-empty
- Pass msg.channel to build_messages_with_memory() in _process_message
- All 26 new tests pass; all existing escalation/WhatsApp tests pass (no regressions)
- Add 02-05-SUMMARY.md with full task documentation and deviations
- Update STATE.md: advance to plan 5 of 5 in phase 02, add decisions
- Update ROADMAP.md: phase 2 now 5/5 plans complete (Complete status)
- Add supports_vision(model_name) to builder.py — detects vision-capable models
(claude-3*, gpt-4o*, gpt-4-vision*, gemini-pro-vision*, gemini-1.5*, gemini-2*)
with provider prefix stripping support
- Add generate_presigned_url(storage_key, expiry=3600) to builder.py — generates
1-hour MinIO presigned URLs via boto3 S3 client
- Add build_messages_with_media() to builder.py — extends build_messages_with_memory()
with media injection: IMAGE -> image_url blocks for vision models / text fallback for
non-vision models, DOCUMENT -> text reference with presigned URL
- image_url blocks use 'detail: auto' per OpenAI/LiteLLM multipart format
- Add 27 unit tests in test_multimodal_messages.py (TDD)
- Summary with decisions, metrics, and self-check
- STATE.md: advance progress to 78%, add decisions, record session
- ROADMAP.md: update phase 2 plan progress (3 of 5 complete)
- REQUIREMENTS.md: mark AGNT-05 complete
- Add escalation pre-check in _process_message: assistant mode for escalated threads
- Add escalation post-check after LLM response: calls escalate_to_human on rule match
- Load Slack bot token unconditionally (needed for escalation DM, not just placeholders)
- Add keyword-based conversation metadata detector (billing keywords, attempt counter)
- Add no-op audit logger stub (replaced by real AuditLogger from Plan 02 when available)
- Add escalation_assignee and natural_language_escalation fields to Agent model
- Add Alembic migration 003 for new Agent columns
- Tests for BUILTIN_TOOLS (4 tools present, correct fields, confirmation flags)
- Tests for get_tools_for_agent filtering and to_litellm_format conversion
- Tests for execute_tool: valid args, invalid args, unknown tool, confirmation flow
- Tests for audit logger called on every invocation
- AuditEvent ORM model with tenant_id, action_type, latency_ms, metadata
- KnowledgeBaseDocument and KBChunk ORM models for vector KB
- Migration 003: audit_events (immutable via REVOKE), kb_documents, kb_chunks
with HNSW index and RLS on all tables
- AuditLogger with log_llm_call, log_tool_call, log_escalation methods
- audit_events immutability enforced at DB level (UPDATE/DELETE rejected)
- [Rule 1 - Bug] Fixed CAST(:metadata AS jsonb) for asyncpg compatibility
- Add MediaType(StrEnum) and MediaAttachment(BaseModel) to shared/models/message.py
- Add media: list[MediaAttachment] field to MessageContent
- Add whatsapp_app_secret, whatsapp_verify_token, and MinIO settings to shared/config.py
- Add normalize_whatsapp_event() to gateway/normalize.py (text, image, document support)
- Create whatsapp.py adapter with verify_whatsapp_signature() and verify_hub_challenge()
- 30 new passing tests (signature verification + normalizer)