fix(notifications): skip permission request if already denied by browser
CognxSafeTrack
84fb6f6
fix(crm): normalize Excel column names before bulk contact upload
CognxSafeTrack
3d1299e
sheet_to_json returns raw column headers (Téléphone, Mobile, Phone…)
but the API expects exactly `phoneNumber`. Added normalizeContacts()
that matches common phone/name column variants case-insensitively and
maps them to the schema — remaining columns go into `attributes`.
Also: show a clear error if no phone column is found, and fix the
`Error: [object Object]` message that was hiding the real API error.
fix(admin): replace vague Meta hints with direct clickable links in Direct Setup modal
CognxSafeTrack
295c327
- WABA ID → business.facebook.com/settings/whatsapp-business-accounts
- Meta Business ID → business.facebook.com/settings/info
- Token → business.facebook.com/settings/system-users (new hint added)
- Updated i18n labels in en/fr/pt
feat(admin): add Direct Setup button for super admins on orgs without phone number
CognxSafeTrack
a3df350
- Add "Direct Setup" button (super admin only) on /clients for orgs without a WhatsApp number
- Previously the setup modal was only reachable via the Reconfigure button (orgs with a phone)
- Add i18n keys: clients.actions.direct_setup (en/fr/pt)
- Add scratch scripts for org configuration and DB queries
- Add doc: configurer-token-nouvelle-org.md — how to configure a Meta token on a new org
fix(clients): resolve file import dialog error and SSE 401
CognxSafeTrack
87dcd87
File import (Bug 1):
- CrmAIAssistant had its own <input type="file"> that called onFileUpload
onChange, which then called .click() on #crm-file-upload — a second
programmatic file dialog triggered from an onChange handler. Chrome 126+
blocks this as it lacks a direct "user activation".
- Fix: replace the embedded <input> with <label htmlFor="crm-file-upload">
so the click routes directly to FileImporter's input. Remove the
now-unused onFileUpload prop entirely.
SSE 401 (Bug 2):
- EventSource (browser API) cannot send custom headers — the JWT token
never reached the server, causing a 401 on every SSE connection.
- Fix: pass the token as ?token= query param in the EventSource URL
(MainLayout + CrmConversationalDashboard).
- Backend: verifyJwt now injects the query param token into the
Authorization header before calling jwtVerify(), so no route changes
are needed.
feat(observability): log Meta Graph API version at startup
CognxSafeTrack
d66ae60
Both API and Worker now emit a ✅ info log at boot showing the active
META_GRAPH_API_VERSION, or a ⚠️ warn if the version is below v21.0
(which signals a likely-deprecated version). Visible directly in
Railway logs without digging into the code.
fix: upgrade Meta Graph API from deprecated v18/v19 to v22.0
CognxSafeTrack
b25d16e
All hardcoded v18.0 and v19.0 references replaced with a centralized
META_GRAPH_API_VERSION env var (default: v22.0). v19.0 was deprecated
in February 2026 causing messaging_limit_tier to silently disappear
from the /clients daily-limit card.
Also improves error visibility: the catch block in fetchMetaStatus now
logs the full error instead of swallowing it silently, and a warn is
emitted when messaging_limit_tier is absent even on a successful response
(signals a missing BUSINESS_MANAGEMENT permission).
feat(i18n): complete super-admin i18n — all 11 pages fully translated
CognxSafeTrack
b92ea37
All 11 super-admin pages now use useTranslation for every visible string.
Adds rel_today/rel_yesterday/rel_days_ago keys for WhatsAppProfiles relativeDate,
tpl_cat_*_desc keys for template category descriptions, and fixes the
SuperAdminLayout NAV_ITEMS destructuring (labelKey vs label).
feat(i18n): complete admin app internationalization across all pages
CognxSafeTrack
d80fec4
Replace all hardcoded French strings with t() calls on 14 pages and
sync 130+ new keys across fr/en/pt locale files. Language switching
now works everywhere in the admin interface.
Pages covered: TrackDaysPage, TrackFormPage, TrackListPage, UserListPage,
KnowledgeBasePage, SettingsPage, AnalyticsPage, AIAgentSetup,
OnboardingWizard, ResetPasswordPage, ClientsManagementView,
ContactsPage, TemplatesPage, TrainingLab.
fix: restore Limite Quotidienne and complete admin i18n
CognxSafeTrack
4f90920
Fix messaging_limit_tier disappearing in /clients page:
- fetchMetaStatus was querying WhatsAppPhoneNumber separately, which is
tenant-filtered by Prisma extension — returns null when called from
another org's JWT context (super-admin viewing all orgs)
- Fix: include phoneNumbers in the Organization query (excluded from
tenant filtering) and use org.phoneNumbers[0].id directly
i18n — pages fully translated (fr/en/pt):
- LoginPage: org ID, email, password, subtitles, button, footer
- ResetPasswordPage: all 4 states (request/sent/new/success)
- BillingPage: 62 keys — wallet, transactions, chart, AI assistant,
credit packs, QUICK_QUESTIONS, TX_LABELS, FEATURE_LABELS
- ClientsManagementView (/clients): 20 key groups — status badges,
WABA labels, tier/quality info, all modals (billing, credits, create
org, personality, WA setup), validation errors, toasts
Fix TemplatesPage status badges showing raw key strings:
- Add 5 missing keys to all 3 locale files:
status_approved, sta…
fix(audit): resolve all remaining technical debt issues
CognxSafeTrack
a888244
C2 - analytics SQL injection: UNION detection, ALLOWED_TABLES allowlist,
remove SQL from error responses to prevent schema leakage
H1/H2 - override-feedback: move organizationId validation to top of handler
before any DB mutation; scope enrollment.findFirst to org
H6 - UserListPage: add pagination controls (Précédent/Suivant, 20 per page)
L4 - TrackDaysPage: await load() before closing editor to prevent stale data race
L5 - admin.ts: replace getOrganizationId() with req.organizationId at 3 call sites,
remove now-unused import
M1 - EntityResolver: replace any[] types with proper Prisma payload types
(ResolvedUser/Contact/Organization/Enrollment with exact include shapes)
L8 - AI command fuzzy org match: return pending_confirmation when org found
by partial name match, inject orgId for unambiguous confirmation retry
fix(security): resolve critical and high technical debt from audit
CognxSafeTrack
a8e18d6
CRITICAL:
- Exclude passwordHash from enrollment list response (admin.ts)
- Add sanitizeOrg() to strip API keys/tokens from all org GET responses
- Fix CORS wildcard on SSE stream; use request origin instead
- Restrict super-admin routes to SUPER_ADMIN role only (was also allowing ADMIN)
- Replace all Zod .parse() with .safeParse() → proper 400s instead of 500s (ai.ts, admin.ts)
HIGH:
- Increase ExerciseHandler Redis dedup lock from 30s to 300s
- Add take:500 to unbounded trainingData.findMany (OOM risk)
- Add take limits to message/contact findMany queries
- Add Redis lock in EnrollHandler to prevent duplicate enrollments (TOCTOU)
- Wrap override-feedback in try/catch; return 404 on P2025
- Use atomic increment for wallet credits (was TOCTOU race condition)
- Scope messages/send to requesting org (was cross-org accessible)
MEDIUM:
- decryptSecrets returns new object instead of mutating argument in-place
- GeminiProvider.generateStructuredData now validates response with Zod schema
-…
fix(super-admin): guard null wabaId, org.id, amount in profiles/templates/billing/AI pages
CognxSafeTrack
b5dcbdb
chore(scripts): clean up copy-wa-config utility script
CognxSafeTrack
338c360
feat(super-admin): WhatsApp OTP registration and template creation
CognxSafeTrack
170eb5d
- WhatsApp service: add registerPhoneNumber() and verifyPhoneNumber() methods
- Super-admin routes: POST /whatsapp/numbers/register and /verify for Meta OTP flow
- Super-admin routes: POST /whatsapp/templates to submit templates to Meta for approval
- WhatsAppNumbers page: 2-step modal (orgId + phoneNumberId + PIN → OTP code)
- WhatsAppTemplates page: full creation modal with category cards, char counters, live preview bubble
feat(super-admin): org delete, user password reset, complete CRUD coverage
CognxSafeTrack
b8629ec
Backend:
- DELETE /v1/super-admin/organizations/:id — soft-delete via
subscriptionStatus='DELETED' + isHardStopped=true
- POST /v1/super-admin/users/:id/reset-password — generates 1h JWT
reset token, emails link via scheduleEmail (same flow as /forgot-password)
Frontend:
- OrganizationsManager: red trash icon per row calls DELETE; confirmation
dialog before destructive action; suspend icon color changed to amber
- UsersManager: key icon per row calls reset-password endpoint; guards
against users with no email address
feat: complete super-admin interface + enforce Day 11 photo requirement
CognxSafeTrack
8f2286f
Super-admin (3 missing pages + 3 new backend routes):
- AuditLogs page: date-range / action filter, paginated table with
inline JSON detail expansion
- WhatsAppTemplates page: org directory with lazy per-org template
fetch; category/status badges; expandable template panel per row
- WhatsAppProfiles page: card grid showing org branding (logo, color,
WABA ID, token age); inline edit form with live logo preview and
color picker; PATCH /v1/super-admin/whatsapp/profiles/:id
- Backend: GET /audit-logs, GET/PATCH /whatsapp/profiles,
GET /whatsapp/templates added to super-admin.ts
- SuperAdminLayout: 10 nav items (added Templates WA, Profils WA,
Audit Logs); SuperAdminRouter wired to all new routes
Day 11 photo enforcement (tasks/todo.md item resolved):
- FeedbackHandler: if currentDay === 11 and no imageUrl, send
multilingual photo prompt and return early — exercise cannot be
validated without a team member photo
feat: CSV export, monthly report, and quota alert plan filter
CognxSafeTrack
a24fb7f
- BillingPage: add "CSV" download button that exports all wallet
transactions (date, type, description, amount, balance) as UTF-8 CSV
- scheduler.ts: add startMonthlyReportScheduler() — cron on 1st of month
07:30 UTC; sends per-org recap of AI calls, WhatsApp messages, top-3
features, credits recharged/spent, and current balance
- index.ts: register startMonthlyReportScheduler() alongside existing crons
- usage-tracker.ts: quota alert (85% threshold) now only fires for GROWTH,
SCALE, and ENTERPRISE plans; STARTER alerts were high-noise / low-value
chore: add api/worker docker-compose services and route validation tests
CognxSafeTrack
862b2b1
- docker-compose.yml: add `api` (port 7860) and `whatsapp-worker` (port 8082)
services with healthchecks on postgres/redis, SERVICES env var to select
PM2 process, and RAILWAY_INTERNAL_URL wired between containers
- test/routes.test.ts: 31 tests covering campaign schema validation (send,
broadcast, generate), analytics SQL injection pattern guard, and auth route
unauthenticated access — all pure logic, no DB required
feat(settings): expose branding logoUrl and primaryColor fields
CognxSafeTrack
ec1b111
Adds a Branding section to SettingsPage that reads/writes
org.brandingData.logoUrl and org.brandingData.primaryColor.
Includes a live logo preview and a color picker + hex input.
test(security): add 15 SQL injection and auth tests
Pure unit checks for the DANGEROUS_PATTERNS set used in the
text-to-SQL guard (UNION, DELETE, DROP, comments, stacked queries).
Integration auth checks for super-admin JWT enforcement.
fix(super-admin): move routes to JWT-only scope to bypass tenant extension
CognxSafeTrack
ea8815c
Super-admin routes need cross-org Prisma access. The previous registration
inside the tenant-guarded scope called runWithTenant('xamle-admin-org'),
which caused the Prisma extension to silently filter all User, Message,
WalletTransaction and other queries to a single org.
Moving to a separate scope with JWT-only auth (no runWithTenant) means
getOrganizationId() returns null inside these handlers, so the tenant
extension skips filtering — enabling true cross-org stats and queries.
When selectedOrgId === 'xamle-admin-org', the admin switches to a full
platform management dashboard instead of the normal org layout.
Backend (apps/api):
- New /v1/super-admin/* routes (SUPER_ADMIN/ADMIN role required):
platform stats, organizations CRUD + suspend + impersonate,
cross-org users + role change, WhatsApp numbers, billing
transactions + manual credits, monitoring health, AI agentic command
- Fixed SQL injection in analytics text-to-SQL (UNION/DROP/comment blocking)
- Removed JWT secret fallback — crashes on startup if JWT_SECRET is missing
- Added ORG_ADMIN role guard to all /v1/admin/* routes
Frontend (apps/admin):
- SuperAdminRouter: nested routes under /platform/*
- SuperAdminLayout: dark sidebar (slate-950/violet), 7 nav items
- PlatformDashboard: 6 KPI cards, system health, active alerts
- OrganizationsManager: searchable table, suspend/edit drawer, create modal
- UsersManager: cross-org table with inline role-change
- WhatsAppNumbers: all registered numbers …
feat(whatsapp-setup): make token optional when org already has one stored
CognxSafeTrack
de46926
If an org already has a systemUserToken, the /whatsapp-setup route now
reuses it automatically. Admins can reconfigure WABA ID + Phone Number ID
without re-entering the token — useful for adding a new phone number to
an existing connected account.
feat(settings): add WhatsApp reconfiguration form to SettingsPage
CognxSafeTrack
ec8f103
Business owners can now connect or reconnect their WhatsApp account
directly from Settings without going through the onboarding wizard.
Form accepts WABA ID, System User Token, Phone Number ID, and phone number.
fix(crm-inbox): unwrap paginated messages response to restore conversation list
CognxSafeTrack
0da3feb
The GET /messages endpoint was changed to return {data, total, limit, skip}
but the frontend still consumed it as an array, causing setInboxMessages
to receive an object and the conversation list to always show empty.
fix(crm-agent): replace generateText+JSON.parse with generateStructuredData for intent detection
CognxSafeTrack
3b3d6cd
The CRM command agent was always falling back to GENERAL_CHAT and
showing the menu again because LLMs wrap JSON in markdown fences,
breaking JSON.parse. Switch to generateStructuredData with a Zod schema
which guarantees structured output regardless of LLM quirks.
Also exposes generateStructuredData as a public method on AIService.
fix(audit): resolve remaining TypeScript any types, string enums, and Json validation
CognxSafeTrack+1: Claude Sonnet 4.6
2a3e2a1
TypeScript types (ai-sdk):
- Define AIServicePrismaClient structural interface — replaces prisma: any
- Define TenantSecrets interface — replaces getTenantSecrets: () => Promise<any>
- Define BusinessProfileContext interface — replaces businessProfile?: any in 3 methods
- Define BusinessProfileExtraction interface — extractBusinessProfile now fully typed
- Define ContactData interface — replaces contact: any in generateCrmCampaign
- exerciseCriteria? typed as Record<string, unknown> instead of any
- searchResults now typed as SearchResult[] instead of any[]
DB enums:
- CampaignHistory.status: String → CampaignStatus enum (SENT | DELIVERED | READ | FAILED)
- AnalyticsLog.eventType: String → AnalyticsEventType enum (CLICK | READ | RESPONSE)
- Migration 20260514000001 applied to production DB
Zod validation:
- TrackDaySchema: add exerciseCriteria (z.record) and badges (z.array(z.string))
- ai.ts /crm/generate-campaign: enforce contact.phoneNumber (required) via Zod
Runtime safety:
- wh…
fix(audit): resolve remaining minor technical debt issues
CognxSafeTrack+1: Claude Sonnet 4.6
d74c1b3
AI/BYOK:
- generateText now respects tenant BYOK keys via getProvidersForTenant() (was using global registry)
- generateText now applies exponential backoff retry via AIService.withRetry()
Backend:
- notifications.ts: return 503 instead of 200+undefined when VAPID_PUBLIC_KEY absent
- BillingPage: support WA number now reads from VITE_SUPPORT_WA_NUMBER env var (was hardcoded fake number)
- KnowledgeBasePage: show toast.error on delete failure (was silently swallowed)
DB:
- Add @@index([organizationId, exerciseStatus]) on UserProgress for live-feed query performance
- Add @@index([createdAt]) on AuditLog for time-range admin queries
- Migration 20260513000005 applied to production DB
i18n:
- Add knowledge.delete_error translation key to all 4 locales (fr, en, es, pt)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(audit): resolve 15 critical and major technical debt issues
CognxSafeTrack+1: Claude Sonnet 4.6
97f541f
Security:
- campaigns.ts: prevent cross-tenant write by using req.organizationId instead of URL param
- billing.ts: restrict org mode change to SUPER_ADMIN only (was incorrectly allowing ADMIN)
- CommandHandler: guard SEED command with NODE_ENV !== 'production'
- search.ts: return [] instead of fabricated mock data when SERPER_API_KEY absent
- scheduler: filter out hard-stopped organizations from nudge and lesson delivery
- payments.ts: HMAC signature verification for Wave and Orange Money webhooks + fix track.priceAmount
Data integrity:
- schema: TrackDay.dayNumber Float → Int, Enrollment.currentDay Float → Int
- schema: add @@unique([userId, trackId]) to Enrollment to prevent race condition double-enrollment
- Migration 20260513000004 applied to production DB
Performance:
- admin.ts /live-feed: fix N+1 queries with batch findMany + in-memory map
- organizations.ts /:id/messages: add pagination (was unbounded, OOM risk)
- crm-service.ts listMessages: add skip/limit/total support
Co…
fix(jour11): real Gemini vision for team member extraction + correct mediaType
CognxSafeTrack+1: Claude Sonnet 4.6
9d56788
- ai-sdk: add extractTeamMemberFromImage() — uses callWithFailover with VISION
capability (Gemini base64 inlineData), structured schema {name, role, bio}
- FeedbackHandler: replace generateText() (text-only) with extractTeamMemberFromImage()
so Day 11 photos are actually analyzed visually, not just via URL in text prompt
- MediaHandler: fix hardcoded mediaType AUDIO → IMAGE for image mimeTypes; remove
duplicate isImage declaration in spinner block
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat: gold standards tests (15 days + STT + vision) & botName editable in AI setup
CognxSafeTrack+1: Claude Sonnet 4.6
1c602ea
- ai-gold-standards: 40 test cases covering all 15 lesson days, STT smoke, vision
(day 11 imageUrl), CRM broadcast/conversation, curriculum (FR+EN), pitch deck EN,
business profile extraction days 1-6 with quality assertions
- AIAgentSetup: botName field now editable (was hardcoded 'Agent IA'); loaded from
personality API on mount, saved with PATCH; falls back to 'Agent IA' if blank
- i18n: add bot_name_label/placeholder/hint keys to FR, EN, ES, PT locales
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Delete stripe.ts route file, remove import and /v1/stripe registration from app.ts,
drop stripe npm dependency, remove deprecated stripeWebhookRoute stub from payments.ts,
and remove stripeCustomerId from admin Organization interface.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Analytics: POST /v1/analytics/query — natural language → GPT-4o-mini → SELECT → results (Text-to-SQL)
- Analytics UI: example chips, SQL results table, 'Voir SQL' toggle
- Billing: role guard on change_organization_mode (ADMIN+ only)
- Nav: /ai-setup accessible to all org members (not just super admin)
- Scheduler: startPedagogyAdvisor() — Wednesday 08:30 UTC cron, at-risk nudges (5 languages) + email report
- S1 security: organizationId always from JWT for non-super-admin (never from header)
- A6: InboundHandler passes user.language to Whisper transcription (Wolof hint: 'fr')
- A2: Redis 6h cache for personalized lessons keyed by (trackDayId, activity, language)
- P1: sendLessonDay() parallelized from 4 sequential DB queries to 2 (user+trackDay batch, userProgress parallel)
- D2: Redis 30s dedup lock on exercise submission to prevent double-submit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(jour11+schema): Jour 11 photoUrl on TeamMember + audit doc updates
CognxSafeTrack+1: Claude Sonnet 4.6
61fd7f5
- TeamMember.photoUrl: new optional field stores the original WhatsApp image URL alongside the AI-extracted name/role/bio
- extractAndSaveTeamMember(): now persists photoUrl field
- Migration 20260513000003_add_team_member_photo_url
- Audit doc: mark Semaine 3-4 fully complete, add Stripe + Jour 11 items
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(admin): migrate raw fetch() to api client in ClientsManagementView
CognxSafeTrack+1: Claude Sonnet 4.6
60c3938
Removes the last manual fetch() call outside lib/api.ts — billing/admin/allocate now
goes through api.post() which injects Authorization and Content-Type automatically.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat: Claude Sonnet 4.6 provider + Stripe billing automation
CognxSafeTrack+1: Claude Sonnet 4.6
4a0c3ba
AI:
- packages/ai-sdk: ClaudeProvider implements LLMProvider via tool_use structured outputs + zod-to-json-schema
- Claude registered at priority 120 (TEXT only), supersedes Gemini for feedback/lessons/chat — -40% cost, better French
- ANTHROPIC_API_KEY added to API and worker env validation
Stripe:
- apps/api/src/routes/stripe.ts: POST /v1/stripe/webhook handles checkout.session.completed, subscription.updated/deleted, invoice.payment_failed/succeeded
- Schema: stripeCustomerId + stripeSubscriptionId added to Organization
- tenant.ts: 402 gate for isHardStopped orgs (x-api-key calls always pass)
- ClientsManagementView: SUSPENDU badge on hard-stopped orgs
- STRIPE_SECRET_KEY + STRIPE_WEBHOOK_SECRET added to config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(crm-ux): tag management, unread badge, handoff release UI
CognxSafeTrack+1: Claude Sonnet 4.6
42a2598
- ContactsPage: tag filter chips, per-contact tag editing with inline input, add/remove tags via PATCH API, colSpan fix for new Tags column
- MainLayout: SSE-powered unread message badge on conversations nav item, resets on navigation
- UserListPage: soft-delete button, handoff status fetch + "Libérer l'IA" button in conversation modal
- admin routes: DELETE /users/:id (soft-delete), GET/DELETE /users/:id/handoff endpoints
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- resolveOrgId now uses findFirst with OR [slug, id] so literal custom IDs
like "default-org-id" (the seed org XAMLÉ Global) are resolved correctly
instead of returning 404
- ContactsPage.tsx: pass selectedOrgId to analytics/usage call — was missing
the x-organization-id header, causing systematic 400 on the contacts page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(agentic-week2-4): 5 major features — AI content generator, handoff, tags, soft-delete, SSE
CognxSafeTrack+1: Claude Sonnet 4.6
99f6bca
## Agent Créateur de Contenu (Semaine 3)
- AIService.generateCurriculum() — structured GPT-4o prompt generates full Track + TrackDays
- POST /v1/organizations/:id/content/ai-generate — persists curriculum directly to DB
- TrackListPage: "Générer avec IA" button + modal (description, numDays, language, audience)
- Auto-redirects to the generated program days after creation
## Human Handoff Detection (Semaine 4)
- AIAgentHandler: detects 15 multilingual handoff keywords (FR/EN/ES)
- Sets Redis flag handoff:{userId}:{orgId} (24h) — pauses AI responses
- Emails org admin with contact info + link to admin UI
- Handles subsequent messages while in handoff mode gracefully
## Contact Segmentation by Tags (Semaine 2)
- Contact.tags String[] added to Prisma schema (migration + db:push applied)
- GET /contacts/tags — list all unique tags in org
- PATCH /contacts/:id/tags — set tags on a contact
- GET /contacts?tags=tag1,tag2 — filter contacts by tags (hasSome)
## SSE Real-time Updates (Semaine…
- Add missing /conversations route to App.tsx (was nav-linked but unregistered)
- Apply resolveOrgId() to all 5 KB routes so slug-based org IDs (e.g. xamle-admin-org) no longer cause 404/400
- TemplatesPage: detect WABA-not-configured 400 and show amber banner instead of silently failing
- KnowledgeBasePage: show toast on index-kb 400 (no KB URL) with actionable message
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- AIAgentHandler: Redis sliding window (20 entries, TTL 24h) for conversation
history injected into system prompt — AI_AGENT now remembers context
- IndexingService.searchRelevantContext: cosine threshold 0.70 — returns '' if
no chunk is relevant so agent responds honestly instead of hallucinating
- add-hnsw-index.ts: one-shot HNSW index script (m=16, ef=64) for ~10x faster
pgvector cosine search on large knowledge bases
- scheduler.ts: hourly wallet alert (email via Brevo if < 3 days runway,
6h Redis suppression) + weekly report every Monday 07:00 UTC with trend vs
previous week and color-coded wallet status
- queue.ts + campaigns.ts: sendAt ISO-8601 parameter on broadcast/campaign
routes — BullMQ delay option schedules jobs natively, no cron needed
- docs/agentic/: roadmap updated with Semaine 1 marked complete
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(admin-chat): make AI copilot a full super-admin via platform knowledge + agentic tools
CognxSafeTrack+1: Claude Sonnet 4.6
9061927
- Inject PLATFORM_KNOWLEDGE constant into all /chat system prompts: detailed
descriptions of all 4 modes (EDTECH/CRM_MARKETING/AI_AGENT/CUSTOMER_SERVICE),
plans & credit pricing, WhatsApp setup (WABA ID/Business ID/token), template
rules (variables, MARKETING vs UTILITY), KB indexing, wallet/hard-stop logic
- Remove "contactez le support" fallback — AI now answers all platform questions
- Add OpenAI function-calling tools for agentic admin actions:
- get_organization_settings: read full org config
- change_organization_mode: switch org mode (EDTECH/CRM_MARKETING/AI_AGENT/CUSTOMER_SERVICE)
- update_ai_agent_personality: update coreMission, tone, temperature
- Implement two-pass completion loop: tool calls executed against Prisma, results
fed back for final natural-language answer with actionPerformed metadata
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Phases 1-5 : masquage JSON avancé, chat IA global (AdminChat),
onboarding WhatsApp guidé, templates assistés IA, agent IA setup guidé
- i18n exhaustif : ~70 nouvelles clés en FR/EN/ES/PT pour tous les
composants ajoutés (AdminChat, AIAgentSetup, SettingsPage,
TemplatesPage, OnboardingWizard)
- Chat IA multilingue : l'assistant répond désormais dans la langue
de l'utilisateur (FR/EN/ES/PT) sur toutes les pages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Frontend (BillingPage):
- Renommage "Wallet" → "Crédits disponibles" (langage non-technique)
- Badge projection dynamique "À ce rythme : encore ~X jours" calculé
depuis les transactions des 7 derniers jours
- Questions suggérées cliquables (quick-reply chips) avant le 1er message
- Suppression "Tokens entrants/sortants" (jargon incompréhensible)
- Transactions filtrées : BYOK masqué, layout liste simple vs tableau
- Section "Où vont vos crédits IA ?" remplace le jargon feature/type
- Indicateur de frappe animé (3 points) dans le chat
- Packs recharge avec explication "≈ N messages IA" et badge populaire
- Texte WhatsApp clarifié : "Gratuit pour vous — Meta facture directement"
Backend (billing.ts /chat):
- Wallet balance + isHardStopped ajoutés dans le contexte IA
→ l'assistant peut maintenant répondre "combien de crédits me reste-t-il"
- Burn rate (débit moyen 7j) calculé et injecté : "À ce rythme,
votre solde durera encore X jours"
- Statut wallet (SUSPENDU / bas / actif) expl…
H4: Replace per-contact prisma.message.create() inside the loop with
a batch prisma.message.createMany() after each page — reduces N DB
round-trips to 1 per page of 200 contacts.
H5: Replace include:{contacts:true} (loads all contacts into memory) with
cursor-based pagination (PAGE_SIZE=200) using broadcastLists relation
filter. Large lists no longer OOM the worker.
H6 (usage check): Move UsageService.checkAndIncrement() from per-contact
to per-page with amount=page.length — reduces Redis INCRBY calls from
N to N/200.
H11: Add job.updateProgress() after each page in CampaignHandler so BullMQ's
stall detector sees the job as alive during long campaigns; also provides
live progress in BullBoard (0–99%).
H10: Confirmed resolved — wallet:ok:{orgId} cache key is consistent across
API middleware and worker since credit-guard.ts deletion in Sprint 2.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
refactor(worker): Sprint 2 — dead code removal and queue consolidation
CognxSafeTrack+1: Claude Sonnet 4.6
37fb9ce
H1: Delete credit-guard.ts (worker) and credit-cache.ts (api) — both fully
superseded by wallet.ts/requireCredits.ts since the wallet migration.
H6: Introduce lib/queues.ts with shared whatsappQueue and notificationQueue
instances. Replace all 10 inline `new Queue(...)` calls across wallet.ts,
usage-tracker.ts, whatsapp-logic.ts, scheduler.ts, and 4 handlers.
Removes 10 redundant Redis connections; drops `q.close()` calls on
ephemeral instances that were killing the shared connection.
Shutdown handler now explicitly closes both shared queues.
Scratch: Move apps/api/scratch/ one-shot DB scripts to scripts/db-ops/
so they don't ship in the production API bundle.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(security): Sprint 1 — five critical debt fixes
CognxSafeTrack+1: Claude Sonnet 4.6
fae3e96
C1: Await debitWallet() in usage-tracker and broadcast/campaign handlers
so billing failures are caught and logged, not silently swallowed.
C2+C3: Rewrite debitWallet() as a single Prisma interactive transaction:
balanceAfter now set atomically; isHardStopped flag set in the same
transaction as the balance decrement — no window between debit and stop.
C4: Add X-Hub-Signature-256 HMAC-SHA256 verification to POST /webhook.
Uses preParsing hook for raw body capture; fails open when
WHATSAPP_APP_SECRET is not configured.
H3: Fix payment webhook race condition — use updateMany(where: PENDING)
as atomic idempotency guard; only the request that flips PENDING→COMPLETED
proceeds to enrollment creation.
H7: Replace $queryRawUnsafe/$executeRawUnsafe with $queryRaw/$executeRaw
Prisma.sql template literals in indexing.ts; user-controlled values
(organizationId, limit) are properly parametrized.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(billing): close 3 remaining wallet gaps from implementation plan
CognxSafeTrack+1: Claude Sonnet 4.6
f5126bc
⑫ sendTextMessage() wallet debit coverage:
- Add organizationId?: string to sendTextMessage() config — calls trackWhatsAppSent() automatically after each successful send
- NudgeHandler, ContentHandler, EnrollHandler, MediaHandler, AdminHandler: pass organizationId in tenantConfig so every outbound message triggers the debit without touching each callsite individually
⑯ GET /billing/summary now includes wallet block:
- wallet: { balance, isHardStopped, creditToFcfa, balanceFcfa } appended to every summary response
⑱ BillingPage: wallet transaction table:
- Table showing last 20 transactions (date, type, description, amount, balance after)
- Type labels with emoji (recharge vs debit), BYOK badge, red/green amount colouring
- Inserted between feature breakdown and chat widget
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>