Skip to content

refactor(whatsapp): introduce shopper agent tool contract - #656

Merged
onerandomdevv merged 2 commits into
devfrom
codex/refactor-whatsapp-shopper-agent-tool-contract
Jul 26, 2026
Merged

refactor(whatsapp): introduce shopper agent tool contract#656
onerandomdevv merged 2 commits into
devfrom
codex/refactor-whatsapp-shopper-agent-tool-contract

Conversation

@onerandomdevv

@onerandomdevv onerandomdevv commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Introduces the typed shopper-agent foundation for WIZZA so Gemini intent output is converted into a validated tool or conversation plan before existing WhatsApp handlers run. It adds stable tool input/output contracts, action categories, consent/account-link policy evaluation, shopper-safe output validation, and structured planning analytics while preserving the current text/image discovery, recent-product selection, consent, account-linking, and placeholder behavior. No new commerce mutation is enabled in this PR.

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 cd apps/backend && pnpm exec jest --runInBand channels/whatsapp.
  2. Run pnpm exec eslint src/channels/whatsapp, pnpm run typecheck, and pnpm run build from apps/backend.
  3. Exercise an existing product search, a natural-answer response, and an unlinked order-status request through the WhatsApp service tests.

Expected result: all existing discovery paths behave as before; guest tools run only after consent; linked tools initiate the existing account-linking flow for unlinked shoppers; unknown Gemini actions fall back safely; confirmation-required tools are classified but do not execute a new write.

Pre-commit checklist

  • Backend lint/type/build pass when backend is affected
  • Web lint/type/build pass when web is affected (N/A: no web files changed)
  • Shared package build passes when shared is affected (N/A: no shared files changed)
  • 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 (N/A: no money values changed)
  • Paystack webhook changes verify HMAC before processing (N/A: Paystack was not touched)
  • Database migrations are Prisma migrations, not db push (N/A: no schema or migration changes)
  • Database migrations are backward-compatible or risk is documented (N/A: no database changes)

Screenshots

N/A. This PR changes backend WhatsApp orchestration contracts only and has no UI changes.

Notes for reviewer

Foundation added

  • Added typed tool input/output maps and four policy categories: guest, linked-read, linked-write, and confirmation-required.
  • Added a registry that owns Gemini declarations, executor keys, runtime input normalization, and per-tool safe output fields.
  • Added recursive rejection of private supplier/source, cost/margin, linked-order, ranking, embedding, and vector fields in shopper-agent tool outputs.
  • Added a policy service that enforces consent first and active account linking for non-guest tools.
  • Added an orchestrator that converts Gemini output into a deterministic plan and records only safe action/category/decision metadata.
  • Registered the foundation through WhatsAppSharedModule and integrated planning into the existing WhatsAppService dispatch path.

Behavior deliberately preserved

  • Text discovery, image discovery, discovery follow-ups, and recent product selection still run through their existing services and ordering.
  • Existing hybrid ranking, image embedding, canonical product URLs, account linking, and analytics persistence are unchanged.
  • get_order_status and confirm_delivery retain their existing linked-account placeholder behavior.
  • confirm_delivery is now marked confirmation-required, but this PR does not add or execute a delivery-confirmation write.
  • No cart, checkout, payment, order-state, delivery, payout, store-management, Prisma, Redis, Paystack, Shipbubble, Gemini-provider, or Meta-provider behavior was changed.

Validation

  • Focused shopper-agent/intent/service tests: 5 suites, 112 tests passed.
  • Full WhatsApp channel tests: 22 suites, 301 tests passed.
  • WhatsApp channel ESLint: passed.
  • Backend typecheck: passed before and after rebase onto latest dev.
  • Backend build: passed.
  • git diff --check: passed.
  • Pre-commit lint-staged ESLint, security sentinel, Prettier, and backend typecheck: passed.
  • Staged secret-like assignment scan: 0 findings.
  • Staged real WhatsApp invite URL scan: 0 findings.

No schema/migrations, .env.local, secrets, private prompt values, frontend routes/UI, checkout/cart/payment behavior, payout math, delivery booking, Shipbubble, search ranking, image-search architecture, public product URLs, or store-owner behavior were touched.

Summary by CodeRabbit

  • New Features

    • Added shopper-agent planning for WhatsApp interactions.
    • Added support for product searches, order-status requests, natural conversations, and friendly fallbacks.
    • Added consent, account-linking, and confirmation-aware access decisions.
    • Added safeguards to prevent sensitive or unsupported information from appearing in tool results.
    • Added audit details for planned actions and denied requests.
  • Bug Fixes

    • Improved handling of unsupported actions and unauthorized account-specific requests with clear fallback responses.
  • Tests

    • Added coverage for tool validation, policy decisions, safe outputs, planning behavior, and WhatsApp integration.

@codesandbox

codesandbox Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders

Open Preview

@vercel

vercel Bot commented Jul 26, 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 26, 2026 9:59am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
twizrr-admin Skipped Skipped Jul 26, 2026 9:59am
twizrr-app Skipped Skipped Jul 26, 2026 9:59am

@coderabbitai

coderabbitai Bot commented Jul 26, 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: 21 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: fb2e10f1-13e4-40b7-9640-1852a22a54ac

📥 Commits

Reviewing files that changed from the base of the PR and between 1b2fa4a and 6b1c802.

📒 Files selected for processing (7)
  • apps/backend/src/channels/whatsapp/agent/shopper-agent-policy.service.spec.ts
  • apps/backend/src/channels/whatsapp/agent/shopper-agent-policy.service.ts
  • 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.service.spec.ts
  • apps/backend/src/channels/whatsapp/whatsapp.service.ts
📝 Walkthrough

Walkthrough

Introduces typed shopper-agent tools, policy evaluation, audited intent planning, safe output validation, and WhatsApp integration for conversation routing and denied-tool handling.

Changes

Shopper agent planning and WhatsApp integration

Layer / File(s) Summary
Agent contracts and tool registry
apps/backend/src/channels/whatsapp/agent/shopper-agent.types.ts, apps/backend/src/channels/whatsapp/agent/shopper-agent-tool.registry.ts, apps/backend/src/channels/whatsapp/whatsapp.constants.ts
Defines typed tool requests and plans, centralizes tool metadata and Gemini declarations, normalizes inputs, and rejects unsafe outputs including nested forbidden fields.
Policy evaluation and intent planning
apps/backend/src/channels/whatsapp/agent/shopper-agent-policy.service.ts, apps/backend/src/channels/whatsapp/agent/shopper-agent-orchestrator.service.ts, apps/backend/src/channels/whatsapp/agent/*.spec.ts
Evaluates consent, linked-account, and confirmation requirements, then produces audited tool or conversation plans with fallback handling.
WhatsApp planning and denial handling
apps/backend/src/channels/whatsapp/whatsapp.service.ts, apps/backend/src/channels/whatsapp/whatsapp-shared.module.ts, apps/backend/src/channels/whatsapp/*.spec.ts
Registers the shopper-agent services, applies planned intent and audit data during message processing, and routes denied tools to account linking or fallback responses.

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

Sequence Diagram(s)

sequenceDiagram
  participant WhatsAppService
  participant ShopperAgentOrchestrator
  participant ShopperAgentPolicyService
  participant Analytics
  WhatsAppService->>ShopperAgentOrchestrator: planIntent(parsed intent and consent context)
  ShopperAgentOrchestrator->>ShopperAgentPolicyService: evaluate tool request
  ShopperAgentPolicyService-->>ShopperAgentOrchestrator: allowed or denied decision
  ShopperAgentOrchestrator-->>WhatsAppService: tool or conversation plan
  WhatsAppService->>Analytics: apply shopper-agent audit
  WhatsAppService-->>WhatsAppService: handle denied tool or continue planned flow
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: introducing the shopper agent tool contract for WhatsApp.
Description check ✅ Passed The description follows the template closely and includes the required overview, change type, affected area, testing steps, 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-agent-tool-contract

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1b2fa4a387

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

: {};
analytics.parsedFilters = {
...existing,
shopperAgent: {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep planner audit data out of parsed filters

Every message that reaches intent planning, including menus, support requests, and natural answers, now persists a shopperAgent object in parsedFilters. This field represents discovery-derived filter signals used for analytics and future recommendation training, so non-search conversations will now appear to contain parsed filters and pollute that dataset; record planning decisions in a separate analytics/audit field instead.

AGENTS.md reference: apps/backend/src/channels/whatsapp/AGENTS.md:L603-L606

Useful? React with 👍 / 👎.

Comment on lines +191 to +195
for (const key of Object.keys(output)) {
if (!allowedFields.has(key)) {
throw new Error(`Unsafe ${name} tool output field: ${key}`);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate output values before narrowing the type

This loop rejects unknown field names but never verifies required fields or their runtime values, so outputs such as {} or { status: "sent", productsShownCount: "two" } pass and are then narrowed to ShopperAgentToolOutputMap[Name]. When an executor returns malformed data, downstream code can therefore rely on fields that are absent or have the wrong type; validate each tool's required fields, status union, arrays, and scalar types before making this assertion.

Useful? React with 👍 / 👎.

Comment on lines +26 to +30
if (request.category === "guest") {
return {
allowed: true,
reason: "allowed",
confirmationRequired: false,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Derive access categories from trusted tool definitions

The policy trusts the caller-supplied request.category, while the exported ShopperAgentToolRequest type permits every tool name to carry any category. Any caller that constructs or deserializes { name: "get_order_status", category: "guest", ... } therefore receives an allowed decision without a linked account; the current registry happens to create the correct pairing, but the authorization boundary itself does not enforce it. Derive the category from the trusted definition for request.name, or bind each name to its literal category in the request type.

AGENTS.md reference: apps/backend/src/channels/whatsapp/AGENTS.md:L509-L511

Useful? React with 👍 / 👎.

@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.

🧹 Nitpick comments (2)
apps/backend/src/channels/whatsapp/agent/shopper-agent-tool.registry.ts (2)

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

Use a NestJS exception instead of plain Error.

These output-safety guards throw raw Error objects. Per coding guidelines, domain-failure throws should use NestJS built-in exceptions.

As per coding guidelines, "Use NestJS built-in exceptions and preserve the documented error codes for validation, authorization, payment, and domain failures."

♻️ Proposed fix
-import { Injectable } from "`@nestjs/common`";
+import { Injectable, InternalServerErrorException } from "`@nestjs/common`";
     if (!this.isRecord(output)) {
-      throw new Error(`Unsafe ${name} tool output`);
+      throw new InternalServerErrorException(`Unsafe ${name} tool output`);
     }
       if (FORBIDDEN_SAFE_OUTPUT_FIELDS.has(key)) {
-        throw new Error(`Unsafe shopper-agent output field: ${key}`);
+        throw new InternalServerErrorException(
+          `Unsafe shopper-agent output field: ${key}`,
+        );
       }

Also applies to: 229-245

🤖 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/agent/shopper-agent-tool.registry.ts`
around lines 175 - 196, Update assertSafeOutput and the additional output-safety
guard at the referenced later section to throw the appropriate NestJS built-in
exception instead of plain Error, while preserving the existing unsafe-output
messages and behavior.

Source: Coding guidelines


129-141: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add a guard test for forbidden/safe field overlap.

Nothing currently asserts that FORBIDDEN_SAFE_OUTPUT_FIELDS never intersects a tool's safeOutputFields. A future addition to a tool's allowlist using a name that coincides with the blocklist would silently pass assertSafeOutput (allowlist check happens after the forbidden-field check, but a field present in both sets would still be rejected only by luck of check order — worth locking this down explicitly).

🤖 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/agent/shopper-agent-tool.registry.ts`
around lines 129 - 141, Add a guard test around FORBIDDEN_SAFE_OUTPUT_FIELDS
that iterates every tool’s safeOutputFields and asserts none overlap with the
forbidden set. Ensure the test fails if any allowlisted field is later added to
the blocklist, while preserving the existing assertSafeOutput behavior.
🤖 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.

Nitpick comments:
In `@apps/backend/src/channels/whatsapp/agent/shopper-agent-tool.registry.ts`:
- Around line 175-196: Update assertSafeOutput and the additional output-safety
guard at the referenced later section to throw the appropriate NestJS built-in
exception instead of plain Error, while preserving the existing unsafe-output
messages and behavior.
- Around line 129-141: Add a guard test around FORBIDDEN_SAFE_OUTPUT_FIELDS that
iterates every tool’s safeOutputFields and asserts none overlap with the
forbidden set. Ensure the test fails if any allowlisted field is later added to
the blocklist, while preserving the existing assertSafeOutput behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7bf3d99f-49ee-4fe1-bfed-256a4c103ae1

📥 Commits

Reviewing files that changed from the base of the PR and between 5ff9ab6 and 1b2fa4a.

📒 Files selected for processing (12)
  • apps/backend/src/channels/whatsapp/agent/shopper-agent-orchestrator.service.spec.ts
  • apps/backend/src/channels/whatsapp/agent/shopper-agent-orchestrator.service.ts
  • apps/backend/src/channels/whatsapp/agent/shopper-agent-policy.service.spec.ts
  • apps/backend/src/channels/whatsapp/agent/shopper-agent-policy.service.ts
  • 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-intent.service.spec.ts
  • apps/backend/src/channels/whatsapp/whatsapp-shared.module.ts
  • apps/backend/src/channels/whatsapp/whatsapp.constants.ts
  • apps/backend/src/channels/whatsapp/whatsapp.service.spec.ts
  • apps/backend/src/channels/whatsapp/whatsapp.service.ts

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