Skip to content

refactor(whatsapp): make shopper conversation agent-first - #676

Merged
onerandomdevv merged 3 commits into
devfrom
codex/refactor-whatsapp-shopper-conversation-agent-first
Jul 28, 2026
Merged

refactor(whatsapp): make shopper conversation agent-first#676
onerandomdevv merged 3 commits into
devfrom
codex/refactor-whatsapp-shopper-conversation-agent-first

Conversation

@onerandomdevv

@onerandomdevv onerandomdevv commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Makes WIZZA conversation-first instead of menu-first. Gemini now uses automatic function selection: it may answer bounded shopping conversation or ask a clarifying question, while real Twizrr data and all actions still go through the typed shopper-agent tools and policy layer. Greetings, help, and start no longer force the menu; menu remains an explicit recovery command. The matching runtime contracts and regression tests are updated.

Type of change

  • New feature
  • Bug fix
  • Refactor / cleanup
  • Database migration included
  • Chore / maintenance
  • Documentation

Area affected

  • Backend
  • Web
  • WhatsApp
  • Shared package
  • Database / Prisma
  • GitHub / CI / infrastructure

How to test this

  1. Run pnpm.cmd --filter @twizrr/backend test -- --runInBand whatsapp-intent.service.spec.ts.
  2. Run pnpm.cmd --filter @twizrr/backend test -- --runInBand shopper-agent-orchestrator.service.spec.ts whatsapp.service.spec.ts.
  3. Send conversational messages such as hello, help, or a broad shopping question and confirm WIZZA replies naturally; send menu and confirm the menu still appears.

Expected result: conversational turns do not force a menu or search, explicit menu requests still work, and linked reads/writes, confirmations, account linking, live commerce facts, and policy enforcement remain backend-controlled.

Pre-commit checklist

  • Backend lint/type/build pass when backend is affected
  • Web lint/type/build pass when web is affected (not affected)
  • Shared package build passes when shared is affected (not affected)
  • No console.log left in production code
  • No secrets or .env files committed
  • No new any types added
  • No non-MVP legacy features reintroduced
  • All money values are BigInt kobo, never float (money code not touched)
  • Paystack webhook changes verify HMAC before processing (Paystack webhook not touched)
  • Database migrations are Prisma migrations, not db push (no migration)
  • Database migrations are backward-compatible or risk is documented (no migration)

Screenshots

N/A. Backend WhatsApp behavior and documentation only; no UI changed.

Notes for reviewer

  • Gemini function calling changed from forced ANY mode to AUTO; a no-tool planner result is now a valid conversational decision.
  • Live product, price, stock, cart, account, order, payment, delivery, refund, and dispute facts still require typed backend tools. Gemini does not receive direct Prisma, Redis, Paystack, Shipbubble, or provider access.
  • Existing natural-answer sanitization, shopper-only boundaries, linked-account policy, confirmation requirements, deterministic support/store redirects, and action retry protections remain unchanged.
  • Regression coverage: 51 intent tests and 95 orchestrator/service tests passed.
  • Validation passed: touched-file ESLint, backend typecheck, backend build, git diff --check, cached diff check, and staged secret/private-prompt scan.
  • No web routes, schema/migrations, checkout/payment implementation, payout math, delivery booking, product ranking, image search, store-owner behavior, .env.local, secrets, or private prompt values were changed.

Summary by CodeRabbit

  • New Features
    • Added product-detail requests for recently viewed or searched products, including public product links.
    • Improved WhatsApp conversations with short-lived context, natural replies, and concise clarification for ambiguous products.
    • Expanded support for shopping actions, including discovery, cart, checkout, orders, delivery, disputes, and refunds.
  • Bug Fixes
    • Prevented unsupported internal product information from appearing in shopper responses.
    • Improved fallback messaging when actions cannot be completed.

@codesandbox

codesandbox Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders

Open Preview

@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
twizrr-web Ready Ready Preview, Comment Jul 28, 2026 7:58pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
twizrr-admin Skipped Skipped Jul 28, 2026 7:58pm
twizrr-app Skipped Skipped Jul 28, 2026 7:58pm

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@onerandomdevv, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 35 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3bfcd27c-8bfd-4a02-b131-1cddd9a8fa8e

📥 Commits

Reviewing files that changed from the base of the PR and between cf3b5a0 and 2b62b2b.

📒 Files selected for processing (2)
  • apps/backend/src/channels/whatsapp/whatsapp-product-discovery.service.spec.ts
  • apps/backend/src/channels/whatsapp/whatsapp-product-discovery.service.ts
📝 Walkthrough

Walkthrough

This PR changes WhatsApp intent handling to support bounded Gemini conversation, Redis-backed memory, explicit tool contracts, and recent product-detail retrieval with ambiguity handling and shopper-safe outputs.

Changes

WhatsApp AI interaction flow

Layer / File(s) Summary
Tool contract and safe product-detail shape
TWIZRR_WHATSAPP_AI_CONTRACT.md, apps/backend/src/channels/whatsapp/AGENTS.md, apps/backend/src/channels/whatsapp/agent/*
Gemini tools now cover commerce actions, exclude deterministic-only routes, and add validated get_product_details inputs and outputs.
Conversation memory storage
apps/backend/src/channels/whatsapp/whatsapp-conversation-memory.service.ts, apps/backend/src/channels/whatsapp/whatsapp-conversation-memory.service.spec.ts, apps/backend/src/channels/whatsapp/whatsapp-shared.module.ts
Redis stores sanitized natural turns for up to eight entries with a 30-minute TTL, validation, and fail-closed reads.
Hybrid intent and natural-answer routing
apps/backend/src/channels/whatsapp/whatsapp-intent.service.ts, apps/backend/src/channels/whatsapp/whatsapp-intent.service.spec.ts, apps/backend/src/integrations/ai/gemini.client.ts
Intent parsing includes conversation history, supports natural answers when Gemini returns no function call, and uses automatic function-calling mode.
Recent product-detail dispatch
apps/backend/src/channels/whatsapp/whatsapp-product-discovery.service.ts, apps/backend/src/channels/whatsapp/whatsapp.service.ts, apps/backend/src/channels/whatsapp/*spec.ts
Recent product references produce no-context, ambiguous, sent, or not-found outcomes; exact matches are revalidated and limited to shopper-safe fields.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Shopper
  participant WhatsAppService
  participant WhatsAppIntentService
  participant GeminiClient
  participant WhatsAppProductDiscoveryService
  Shopper->>WhatsAppService: Send product-detail request
  WhatsAppService->>WhatsAppIntentService: Parse intent with phone context
  WhatsAppIntentService->>GeminiClient: Select tool or natural answer
  GeminiClient-->>WhatsAppIntentService: get_product_details
  WhatsAppIntentService-->>WhatsAppService: Product-detail intent
  WhatsAppService->>WhatsAppProductDiscoveryService: Resolve recent product
  WhatsAppProductDiscoveryService-->>WhatsAppService: Shopper-safe details or clarification
  WhatsAppService-->>Shopper: Send response
Loading

Possibly related PRs

Suggested reviewers: saheed2010

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: making the WhatsApp shopper experience conversation-first.
Description check ✅ Passed The description matches the template well, covering what changed, type, area, testing, checklist, screenshots, and reviewer notes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/refactor-whatsapp-shopper-conversation-agent-first

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@onerandomdevv

Copy link
Copy Markdown
Collaborator Author

Follow-up hardening pushed in cf3b5a0. Added a shopper-safe get_product_details tool backed by revalidated recent-result context; bounded Redis natural-conversation memory (8 turns, 30-minute TTL, sanitized/untrusted); clearer ambiguous-reference handling; truthful non-retryable failure copy; and removed support/store-management routes from Gemini-visible declarations while keeping deterministic runtime handling. Updated WhatsApp contracts and added focused coverage. Validation: 215 focused tests passed, touched-file ESLint passed, backend TypeScript check passed, backend build passed, git diff checks and staged secret/private-output scans passed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/backend/src/channels/whatsapp/whatsapp-intent.service.ts (1)

104-282: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

"help"/"start" become dead-ends when Gemini is unavailable.

basicKeywordMatch is the deterministic fallback used both when Gemini isn't configured and when Gemini throws mid-request. The greeting branch (Lines 269-279) only matches "hi"|"hello"|"hey"|"good morning"|"good evening" exactly; "help" and "start" now match nothing and fall through to friendly_fallback (Line 281). Previously these keywords deterministically routed to the menu, giving shoppers a recovery path during AI outages. Now, precisely when AI is down (the scenario this fallback exists for), asking for "help" gives no pointer to the menu command.

🩹 Proposed fix
     if (
       ["hi", "hello", "hey", "good morning", "good evening"].includes(lower)
     ) {
       return {
         functionName: "natural_answer",
         params: {
           answer:
             "Hi, I am WIZZA. Tell me what you are shopping for, your budget, or who you are buying for, and I will help you find a good match on Twizrr.",
         },
       };
     }
 
+    if (lower === "help" || lower === "start") {
+      return {
+        functionName: "natural_answer",
+        params: {
+          answer:
+            "Tell me what you are shopping for, your budget, or who you are buying for. You can also type menu to see options.",
+        },
+      };
+    }
+
     return { functionName: "friendly_fallback", params: {} };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/backend/src/channels/whatsapp/whatsapp-intent.service.ts` around lines
104 - 282, Update the exact-match greeting branch in basicKeywordMatch to also
recognize “help” and “start”, routing both to the existing menu/recovery
response rather than friendly_fallback. Preserve the current behavior for the
existing greeting keywords and keep the deterministic fallback path unchanged
otherwise.
🧹 Nitpick comments (2)
apps/backend/src/channels/whatsapp/whatsapp-conversation-memory.service.spec.ts (1)

18-18: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid any for the mocked RedisService.

Use the double-cast idiom already used elsewhere in this PR instead of any.

♻️ Proposed fix
-    service = new WhatsAppConversationMemoryService(redisService as any);
+    service = new WhatsAppConversationMemoryService(
+      redisService as unknown as RedisService,
+    );

As per coding guidelines, "Use TypeScript strict mode and never use any; use unknown with type guards when necessary."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/backend/src/channels/whatsapp/whatsapp-conversation-memory.service.spec.ts`
at line 18, Replace the any cast in the WhatsAppConversationMemoryService test
setup with the double-cast idiom already established elsewhere in the PR, using
unknown as the intermediate type when casting the mocked redisService to
RedisService.

Source: Coding guidelines

apps/backend/src/channels/whatsapp/whatsapp-intent.service.spec.ts (1)

20-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid any for the mocked conversationMemoryService.

Line 40 uses as any while the adjacent constructor args use the as unknown as X idiom. Prefer consistency.

♻️ Proposed fix
     service = new WhatsAppIntentService(
       configService as unknown as ConfigService,
       geminiClient as unknown as GeminiClient,
-      conversationMemoryService as any,
+      conversationMemoryService as unknown as WhatsAppConversationMemoryService,
     );

As per coding guidelines, "Use TypeScript strict mode and never use any; use unknown with type guards when necessary."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/backend/src/channels/whatsapp/whatsapp-intent.service.spec.ts` around
lines 20 - 41, Replace the `as any` cast for `conversationMemoryService` in the
`beforeEach` setup with the established `as unknown as
ConversationMemoryService` pattern, importing or referencing the correct service
type while leaving the mock behavior unchanged.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/backend/src/channels/whatsapp/whatsapp-product-discovery.service.ts`:
- Around line 748-758: Update the flow around sendProductDetails and the
returned publicProductUrl to derive the shopper URL from the revalidated product
record’s current public storeHandle and productCode, rather than
selected.productUrl from Redis. Use the canonical URL builder for the fetched
record and omit publicProductUrl when the required current values are
unavailable.

---

Outside diff comments:
In `@apps/backend/src/channels/whatsapp/whatsapp-intent.service.ts`:
- Around line 104-282: Update the exact-match greeting branch in
basicKeywordMatch to also recognize “help” and “start”, routing both to the
existing menu/recovery response rather than friendly_fallback. Preserve the
current behavior for the existing greeting keywords and keep the deterministic
fallback path unchanged otherwise.

---

Nitpick comments:
In
`@apps/backend/src/channels/whatsapp/whatsapp-conversation-memory.service.spec.ts`:
- Line 18: Replace the any cast in the WhatsAppConversationMemoryService test
setup with the double-cast idiom already established elsewhere in the PR, using
unknown as the intermediate type when casting the mocked redisService to
RedisService.

In `@apps/backend/src/channels/whatsapp/whatsapp-intent.service.spec.ts`:
- Around line 20-41: Replace the `as any` cast for `conversationMemoryService`
in the `beforeEach` setup with the established `as unknown as
ConversationMemoryService` pattern, importing or referencing the correct service
type while leaving the mock behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5ade4ea4-fe2b-4a85-91b4-a2079e63808d

📥 Commits

Reviewing files that changed from the base of the PR and between 0902fc5 and cf3b5a0.

📒 Files selected for processing (15)
  • TWIZRR_WHATSAPP_AI_CONTRACT.md
  • apps/backend/src/channels/whatsapp/AGENTS.md
  • apps/backend/src/channels/whatsapp/agent/shopper-agent-tool.registry.spec.ts
  • apps/backend/src/channels/whatsapp/agent/shopper-agent-tool.registry.ts
  • apps/backend/src/channels/whatsapp/agent/shopper-agent.types.ts
  • apps/backend/src/channels/whatsapp/whatsapp-conversation-memory.service.spec.ts
  • apps/backend/src/channels/whatsapp/whatsapp-conversation-memory.service.ts
  • apps/backend/src/channels/whatsapp/whatsapp-intent.service.spec.ts
  • apps/backend/src/channels/whatsapp/whatsapp-intent.service.ts
  • apps/backend/src/channels/whatsapp/whatsapp-product-discovery.service.spec.ts
  • apps/backend/src/channels/whatsapp/whatsapp-product-discovery.service.ts
  • apps/backend/src/channels/whatsapp/whatsapp-shared.module.ts
  • apps/backend/src/channels/whatsapp/whatsapp.service.spec.ts
  • apps/backend/src/channels/whatsapp/whatsapp.service.ts
  • apps/backend/src/integrations/ai/gemini.client.ts

Comment thread apps/backend/src/channels/whatsapp/whatsapp-product-discovery.service.ts Outdated
@vercel
vercel Bot temporarily deployed to Preview – twizrr-app July 28, 2026 19:58 Inactive
@vercel
vercel Bot temporarily deployed to Preview – twizrr-admin July 28, 2026 19:58 Inactive
@onerandomdevv
onerandomdevv merged commit 613bd4d into dev Jul 28, 2026
14 checks passed
@onerandomdevv
onerandomdevv deleted the codex/refactor-whatsapp-shopper-conversation-agent-first branch July 28, 2026 20:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant