# Project export: Procure

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2026
- Tagline: The agentic procurement platform built to help small businesses source, negotiate, and settle vendor deals faster and smarter.
- Devpost: https://devpost.com/software/procure-jafrn0
- GitHub: https://github.com/mk-sriram/treehacks2026
- Video: https://player.vimeo.com/video/1165160449?byline=0&portrait=0&title=0#t=
- Team: 3 GitHub contributor(s) — Abhay Bhat (12 commits), Sriram MK (11 commits), Andrew Fong (9 commits)

## Devpost submission (written by the team)

### Inspiration

With the recent rise of agentic capabilities, we saw an opportunity to empower small businesses with all of their online procurement needs. In times of financial turmoil, we often witness the shutdown of our favorite local cafes, barbershops, and other community centers due to costs that are too high and revenues that are too low. We hope to change that. Our product does more than vendor calls: it remembers historical vendor behavior, negotiates across competing offers, and fights for the best deals globally. It also enables near-instant cross-border settlement via stablecoins, so businesses can pay and get paid in real time—protecting cash flow when every day matters.

### What it does

Procure is an agentic procurement platform that automates vendor sourcing, quote negotiation, and deal closure. It combines web-scale vendor discovery, memory-aware negotiation, and transparent interaction tracking to help businesses secure better terms faster, with rapid global settlement and clear next-step summaries for approval. AI-powered vendor discovery: Searches the internet to identify reliable, verifiable vendor contacts. Automated quote collection and negotiation: Voice agents call vendors, gather quotes, compare offers, and follow up until target terms are reached. Behavior-aware vendor intelligence: Uses semantic and lexical retrieval over vendor records to evaluate consistency, reliability, and pricing behavior over time. Transparent, auditable workflows: Logs and records all interactions for verification and accountability. Fast global settlement: Enables near-instant cross-border payments once deals are approved. Actionable decision support: Generates concise, LLM-powered deal summaries and recommended next steps for user review.

### How we built it

Frontend: Built with Next.js 16 and React 19, styled with Tailwind CSS 4 and shadcn/ui. The dashboard streams real-time progress via Server-Sent Events (SSE), letting users watch the agent work live. Recharts powers quote comparison visualizations. Backend & Database: Next.js API Routes handle backend logic. Prisma 7 connects to a Neon serverless PostgreSQL database storing runs, vendors, offers, and call transcripts. Vendor Discovery: Perplexity Sonar Pro runs multiple search angles per request to find real suppliers with verified contact info and citations — replacing hours of manual sourcing. Voice AI Calls: ElevenLabs Conversational AI makes autonomous phone calls to vendors via Twilio. Three specialized agents handle initial quoting (Round 1), competitive negotiation (Round 2), and order confirmation (Round 3). Intelligent Memory: Elasticsearch provides hybrid retrieval combining BM25 keyword search with semantic vector search, ranked via Reciprocal Rank Fusion (RRF). The agent remembers what each vendor said and uses that intel to negotiate better deals. LLM Processing: OpenAI GPT-4o-mini extracts structured offer data from call transcripts, generates per-vendor negotiation strategies, and parses natural-language requirements into structured RFQ specs. Email Communication: AgentMail sends confirmation emails to winning vendors and listens for invoice replies via webhooks, closing the procurement loop automatically. Payment: We use ElevenLabs server tool calling integrated with a Turnkey-based crypto wallet implementation. During the Round 3 confirmation call, the voice agent autonomously initiates a USDC stablecoin payment on the Base network to the vendor's wallet address and generates an invoice. Note that the payment is simulated for demo purposes to avoid sending real funds, but the full pipeline is wired end-to-end.

### Challenges we ran into

Concurrency and call-state race conditions: Our workflow depends on parallel outbound calls and multi-round follow-ups. Coordinating state transitions across simultaneous call events introduced race conditions (e.g., conflicting status updates, missed callbacks, duplicate transitions), which occasionally caused dropped opportunities and inconsistent call flow. Complex real-time orchestration across frontend + SSE + voice webhooks: Integrating live UI updates with server-sent events and asynchronous voice-provider webhooks was more complex than expected. We had to handle out-of-order events, retries, and idempotency to keep the interface consistent with backend truth in real time. Voice-agent alignment and information fidelity: Getting voice agents to sound natural while reliably extracting structured procurement details (price, MOQ, lead time, payment terms, delivery constraints) required substantial prompt and policy iteration. Small phrasing changes often affected both conversation quality and extraction accuracy. Vendor data normalization across heterogeneous sources: Vendor information arrived in inconsistent formats across web sources and call transcripts. Standardizing entities (company identity, unit pricing, terms, and reliability signals) into a comparable schema was a non-trivial data quality challenge. Negotiation strategy tuning under uncertainty: We needed agents to negotiate assertively without becoming repetitive or adversarial, while adapting to vendor-specific behavior across rounds. Balancing win-rate, final pricing quality, and conversation length required careful policy tuning and guardrails. Accomplishments we’re proud of Enabled parallel vendor outreach and multi-round negotiation in real time Implemented memory-aware vendor comparison across historical and live quote data Delivered fast cross-border settlement flow after deal approval Shipped a clean, intuitive interface for rapid operator review and control Designed a scalable architecture that supports multiple concurrent procurement workflows Generated concise, actionable AI summaries to speed up final decision-making

### What we learned

Asynchronous state-machine orchestration: Architected a resilient state machine with Next.js and Postgres to manage long-running, multi-stage procurement workflows across days of async vendor interactions and webhook callbacks. Hybrid search implementation: Engineered a hybrid Elasticsearch strategy combining semantic vector retrieval with lexical constraint matching for high-recall, high-precision vendor discovery. Real-time event streaming: Implemented an SSE pipeline to stream background workflow updates to the frontend in real time, eliminating polling for call progress and negotiation outcomes. Audio-to-structured-data pipelines: Built fault-tolerant extraction workflows that transform noisy phone audio into strict JSON schemas for pricing, inventory, and terms using multi-pass LLM inference. Latency-sensitive voice integration: Optimized the Twilio–ElevenLabs handshake for sub-second conversational latency while injecting retrieved context to support interruptions and complex live negotiation logic.

### What's next

Live context injection: Upgrading the voice pipeline to support mid-call context updates so agents can reference newly discovered competitor pricing or inventory in real time and apply immediate negotiation leverage. Adaptive agent personas: Implementing learning loops from historical interaction logs to tailor agent style (e.g., aggressive vs. consultative) based on which strategies consistently produce better terms for specific vendors. Domain-specific knowledge graphs: Integrating vertical knowledge bases (e.g., aerospace, medical devices) so agents use correct industry terminology, improve vendor trust, and reduce hallucinations in technical conversations.

## README (from the GitHub repository)

# Procure — AI-Powered Procurement Agent

The agentic procurement platform built to help small businesses source, negotiate, and settle vendor deals faster and smarter.

## Inspiration
With the recent rise of agentic capabilities, we saw an opportunity to empower small businesses with all of their online procurement needs. In times of financial turmoil, we often witness the shutdown of our favorite local cafes, barbershops, and other community centers due to costs that are too high and revenues that are too low. We hope to change that.

Our product does more than vendor calls: it remembers historical vendor behavior, negotiates across competing offers, and fights for the best deals globally. It also enables near-instant cross-border settlement via stablecoins, so businesses can pay and get paid in real time—protecting cash flow when every day matters.

## What it does
Procure is an agentic procurement platform that automates vendor sourcing, quote negotiation, and deal closure. It combines web-scale vendor discovery, memory-aware negotiation, and transparent interaction tracking to help businesses secure better terms faster, with rapid global settlement and clear next-step summaries for approval.

- **AI-powered vendor discovery:** Searches the internet to identify reliable, verifiable vendor contacts.  
- **Automated quote collection and negotiation:** Voice agents call vendors, gather quotes, compare offers, and follow up until target terms are reached.  
- **Behavior-aware vendor intelligence:** Uses semantic and lexical retrieval over vendor records to evaluate consistency, reliability, and pricing behavior over time.  
- **Transparent, auditable workflows:** Logs and records all interactions for verification and accountability.  
- **Fast global settlement:** Enables near-instant cross-border payments once deals are approved.  
- **Actionable decision support:** Generates concise, LLM-powered deal summaries and recommended next steps for user review.

## YC Track: Reimagining [Weave](https://www.ycombinator.com/companies/weave) (YC W14)

Weave built a customer communication and payments platform — threading together data, software, and communication channels to strengthen business relationships at the point of contact. It was founded in 2011 and went through YC in Winter 2014, well before the AI era.

**Procure reimagines this for 2026:** instead of giving humans better tools to communicate with vendors, we replace the human entirely. Procure is an autonomous AI agent that handles end-to-end B2B procurement — it discovers suppliers, calls them on the phone, negotiates prices across multiple rounds using competitive intelligence, picks a winner, sends confirmation emails, and closes the deal with a stablecoin payment. Where Weave made human-to-business communication more efficient, Procure makes it fully autonomous.

## How we built it
- **Frontend:** Built with Next.js 16 and React 19, styled with Tailwind CSS 4 and shadcn/ui. The dashboard streams real-time progress via Server-Sent Events (SSE), letting users watch the agent work live. Recharts powers quote comparison visualizations.  
- **Backend & Database:** Next.js API Routes handle backend logic. Prisma 7 connects to a Neon serverless PostgreSQL database storing runs, vendors, offers, and call transcripts.  
- **Vendor Discovery:** Perplexity Sonar Pro runs multiple search angles per request to find real suppliers with verified contact info and citations — replacing hours of manual sourcing.  
- **Voice AI Calls:** ElevenLabs Conversational AI makes autonomous phone calls to vendors via Twilio. Three specialized agents handle initial quoting (Round 1), competitive negotiation (Round 2), and order confirmation (Round 3).  
- **Intelligent Memory:** Elasticsearch provides hybrid retrieval combining BM25 keyword search with semantic vector search, ranked via Reciprocal Rank Fusion (RRF). The agent remembers what each vendor said and uses that intel to negotiate better deals.  
- **LLM Processing:** OpenAI GPT-4o-mini extracts structured offer data from call transcripts, generates per-vendor negotiation strategies, and parses natural-language requirements into structured RFQ specs.  
- **Email Communication:** AgentMail sends confirmation emails to winning vendors and listens for invoice replies via webhooks, closing the procurement loop automatically.  
- **Payment:** We use ElevenLabs server tool calling integrated with a Turnkey-based crypto wallet implementation. During the Round 3 confirmation call, the voice agent autonomously initiates a USDC stablecoin payment on the Base network to the vendor's wallet address and generates an invoice. Note that the payment is simulated for demo purposes to avoid sending real funds, but the full pipeline is wired end-to-end.

## Challenges we ran into
- **Concurrency and call-state race conditions:** Our workflow depends on parallel outbound calls and multi-round follow-ups. Coordinating state transitions across simultaneous call events introduced race conditions (e.g., conflicting status updates, missed callbacks, duplicate transitions), which occasionally caused dropped opportunities and inconsistent call flow.
- **Complex real-time orchestration across frontend + SSE + voice webhooks:** Integrating live UI updates with server-sent events and asynchronous voice-provider webhooks was more complex than expected. We had to handle out-of-order events, retries, and idempotency to keep the interface consistent with backend truth in real time.
- **Voice-agent alignment and information fidelity:** Getting voice agents to sound natural while reliably extracting structured procurement details (price, MOQ, lead time, payment terms, delivery constraints) required substantial prompt and policy iteration. Small phrasing changes often affected both conversation quality and extraction accuracy.
- **Vendor data normalization across heterogeneous sources:** Vendor information arrived in inconsistent formats across web sources and call transcripts. Standardizing entities (company identity, unit pricing, terms, and reliability signals) into a comparable schema was a non-trivial data quality challenge.
- **Negotiation strategy tuning under uncertainty:** We needed agents to negotiate assertively without becoming repetitive or adversarial, while adapting to vendor-specific behavior across rounds. Balancing win-rate, final pricing quality, and conversation length required careful policy tuning and guardrails.

## Accomplishments we're proud of
- Enabled parallel vendor outreach and multi-round negotiation in real time  
- Implemented memory-aware vendor comparison across historical and live quote data  
- Delivered fast cross-border settlement flow after deal approval  
- Shipped a clean, intuitive interface for rapid operator review and control  
- Designed a scalable architecture that supports multiple concurrent procurement workflows  
- Generated concise, actionable AI summaries to speed up final decision-making  

## What we learned
- **Asynchronous state-machine orchestration:** Architected a resilient state machine with Next.js and Postgres to manage long-running, multi-stage procurement workflows across days of async vendor interactions and webhook callbacks.  
- **Hybrid search implementation:** Engineered a hybrid Elasticsearch strategy combining semantic vector retrieval with lexical constraint matching for high-recall, high-precision vendor discovery.  
- **Real-time event streaming:** Implemented an SSE pipeline to stream background workflow updates to the frontend in real time, eliminating polling for call progress and negotiation outcomes.  
- **Audio-to-structured-data pipelines:** Built fault-tolerant extraction workflows that transform noisy phone audio into strict JSON schemas for pricing, inventory, and terms using multi-pass LLM inference.  
- **Latency-sensitive voice integration:** Optimized the Twilio–ElevenLabs handshake for sub-second conversati

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 110 recognized source files, 881 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- OpenAI (technology) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (120 of 122)

```
.gitignore
.npmrc
agent_info.json
components.json
eslint.config.mjs
jsconfig.json
next.config.mjs
package.json
plans/elevenlabs_turnkey_stablecoin_payments.md
plans/implementation_plan_v1.md
plans/procurement_voice_agents_plan.md
plans/Voice_call_agent_plan.md
pnpm-workspace.yaml
postcss.config.mjs
prisma.config.ts
prisma/migrations/20260214222512_init/migration.sql
prisma/migrations/migration_lock.toml
prisma/schema.prisma
prompts/negotiation_agent_prompt.md
prompts/sonar_prompt.md
README.md
scripts/quick-tunnel.sh
src/app/api/extract-requirements/route.ts
src/app/api/run/[runId]/events/route.ts
src/app/api/run/route.ts
src/app/api/setup/route.ts
src/app/api/test-finalize/route.ts
src/app/api/tools/send-payment/route.ts
src/app/api/webhooks/agentmail/route.ts
src/app/api/webhooks/elevenlabs/route.ts
src/app/globals.css
src/app/layout.js
src/app/page.js
src/components/activity-feed.jsx
src/components/payment-overlay.jsx
src/components/phone-call-panel.jsx
src/components/quotes-panel.jsx
src/components/requirements-chat.jsx
src/components/rfq-form.jsx
src/components/summary-panel.jsx
src/components/ui/accordion.jsx
src/components/ui/alert-dialog.jsx
src/components/ui/alert.jsx
src/components/ui/aspect-ratio.jsx
src/components/ui/avatar.jsx
src/components/ui/badge.jsx
src/components/ui/breadcrumb.jsx
src/components/ui/button-group.jsx
src/components/ui/button.jsx
src/components/ui/calendar.jsx
src/components/ui/card.jsx
src/components/ui/carousel.jsx
src/components/ui/chart.jsx
src/components/ui/checkbox.jsx
src/components/ui/collapsible.jsx
src/components/ui/combobox.jsx
src/components/ui/command.jsx
src/components/ui/context-menu.jsx
src/components/ui/dialog.jsx
src/components/ui/direction.jsx
src/components/ui/drawer.jsx
src/components/ui/dropdown-menu.jsx
src/components/ui/empty.jsx
src/components/ui/field.jsx
src/components/ui/form.jsx
src/components/ui/hover-card.jsx
src/components/ui/input-group.jsx
src/components/ui/input-otp.jsx
src/components/ui/input.jsx
src/components/ui/item.jsx
src/components/ui/kbd.jsx
src/components/ui/label.jsx
src/components/ui/menubar.jsx
src/components/ui/native-select.jsx
src/components/ui/navigation-menu.jsx
src/components/ui/pagination.jsx
src/components/ui/popover.jsx
src/components/ui/progress.jsx
src/components/ui/radio-group.jsx
src/components/ui/resizable.jsx
src/components/ui/scroll-area.jsx
src/components/ui/select.jsx
src/components/ui/separator.jsx
src/components/ui/sheet.jsx
src/components/ui/sidebar.jsx
src/components/ui/skeleton.jsx
src/components/ui/slider.jsx
src/components/ui/sonner.jsx
src/components/ui/spinner.jsx
src/components/ui/switch.jsx
src/components/ui/table.jsx
src/components/ui/tabs.jsx
src/components/ui/textarea.jsx
src/components/ui/toggle-group.jsx
src/components/ui/toggle.jsx
src/components/ui/tooltip.jsx
src/components/workflow-steps.jsx
src/hooks/use-mobile.js
src/lib/agent-store.js
src/lib/agent-types.js
src/lib/agentmail.ts
src/lib/db.ts
src/lib/elastic.ts
src/lib/elevenlabs.ts
src/lib/events.ts
src/lib/finalize.ts
src/lib/negotiation.ts
src/lib/orchestrator.ts
src/lib/outreach.ts
src/lib/perplexity.ts
src/lib/utils.js
tsconfig.json
wallet_server/email/agentmailClient.js
wallet_server/invoices/invoiceStore.js
wallet_server/package.json
wallet_server/public/index.html
wallet_server/public/styles.css
wallet_server/README.md
wallet_server/routes/paymentRoutes.js
wallet_server/routes/webhookRoutes.js
[2 more files omitted for size]
```

### Dependencies

- package.json: @base-ui/react@^1.2.0, @elastic/elasticsearch@^9.3.1, @hookform/resolvers@^5.2.2, @neondatabase/serverless@^1.0.2, @prisma/adapter-neon@^7.4.0, @prisma/client@^7.4.0, @tailwindcss/postcss@^4, agentmail@^0.2.15, class-variance-authority@^0.7.1, clsx@^2.1.1, cmdk@^1.1.1, date-fns@^4.1.0, dotenv@^17.3.1, embla-carousel-react@^8.6.0, eslint@^9, eslint-config-next@16.1.6, input-otp@^1.4.2, lucide-react@^0.564.0, next@16.1.6, next-themes@^0.4.6, openai@^6.22.0, prisma@^7.4.0, radix-ui@^1.4.3, react@19.2.3, react-day-picker@^9.13.2, react-dom@19.2.3, react-hook-form@^7.71.1, react-resizable-panels@^4, recharts@2.15.4, shadcn@^3.8.4, sonner@^2.0.7, tailwind-merge@^3.4.0, tailwindcss@^4, tw-animate-css@^1.4.0, vaul@^1.1.2, ws@^8.19.0, zod@^4.3.6
- wallet_server/package.json: dotenv@^17.3.1, express@^5.1.0

### Recent commits (newest first)

- Update the README
- Update README
- Final Changes Prior to Presenting
- added more files updates
- merge conflict resovled
- removed elst
- hardcoded agentmail
- removed elasticSearch at the start
- Merge pull request #5 from mk-sriram/voice
- fixed race conditions
- elvenlabs confirmation agent agent
- fixing race condition
- core functionality roughly working
- reorg
- update wallet system with actual landing page
- Merge pull request #4 from mk-sriram/wallet-server
- Merge pull request #3 from mk-sriram/voice
- added files voice tst fixes
- wallet impl 2
- added first round voice agents negotiation

## Key source files (fetched from GitHub, selected and truncated for size)

### prompts/sonar_prompt.md

```markdown
You are a procurement research assistant. Your task is to identify and compare vendors for a specific item and constraints I will provide. Use up‑to‑date, high‑quality sources, and only include vendors that clearly offer the requested item.

Here are the requirements:
Item: {ITEM_DESCRIPTION}
Quantity: {QUANTITY_AND_UNITS}
Maximum total budget (including all fees and taxes if possible): {MAX_BUDGET}
Delivery location: {DELIVERY_LOCATION}
Latest acceptable delivery date (item must arrive by): {DELIVERY_DEADLINE}
Quality / specification constraints: {QUALITY_CONSTRAINTS}
Other hard constraints (must‑haves): {OTHER_MUST_HAVES_OR_WRITE_NONE}
Soft preferences (nice‑to‑haves): {PREFERENCES_OR_WRITE_NONE}

Your job:
Find legitimate vendors that can supply this item under these constraints (or as close as possible).
Exclude obvious marketplaces or irrelevant results (e.g., blog posts, content farms) unless they point to real vendors.
For each vendor, verify from their site or a reliable source that:
They actually sell the specified item or a very close equivalent.
They can ship to the delivery location or plausibly serve that region.
Output a concise table with at least 8–15 candidate vendors (or fewer if the market is very small) with the following columns:

Vendor name
Website
Located in / serves region
How they match the item and quality constraints
Indicative pricing for the requested quantity (or best available pricing info)
Shipping / lead time information, especially whether they can meet the delivery deadline
Notes (e.g., certifications, notable customers, risks, or limitations)
Preferred Contact Method ("Phone", "Email", or "Browser Form")
Phone
Email
Browser Form URL

The Vendor Name, Website, and Preferred Contact Method are most important, and must be accurate. If phone is their preferred method of contact, indicate that and ensure the phone number is found and filled in. Vice Versa for Email. If there is a form or some other contact method on the webpage itself, place the url from which you can contact the vendor.



```

### prompts/negotiation_agent_prompt.md

```markdown
# Negotiation Agent Prompt

This prompt is used by the ElevenLabs voice agent for procurement price negotiations.
Dynamic variables are injected at call time via `conversation_initiation_client_data.dynamic_variables`.

**Sync**: The same prompt is stored in `agent_info.json` under `agent.prompt.prompt`.
Copy the prompt below to the ElevenLabs dashboard when updating the agent.

## Dynamic Variables (ElevenLabs `{{variable_name}}`)

| Variable | Description |
|----------|-------------|
| `{{vendor_name}}` | Vendor being called |
| `{{item}}` | Item being purchased |
| `{{quantity}}` | Order quantity |
| `{{quality}}` | Quality/specs |
| `{{deadline}}` | Delivery deadline |
| `{{competing_offers}}` | Summary of quotes from other suppliers |
| `{{best_price}}` | Lowest competing price (another supplier) |
| `{{target_price}}` | Our negotiation target (lower than best_price) |
| `{{past_history}}` | Prior interactions with this vendor |

## Prompt (copy to ElevenLabs agent)

```
You are Alex, a procurement specialist calling {{vendor_name}} to negotiate a better price. You spoke with them previously and now have competing quotes.

WHAT YOU ARE BUYING:
- Item: {{item}}
- Quantity: {{quantity}}
- Quality/specs: {{quality}}
- Delivery deadline: {{deadline}}

COMPETITIVE INTELLIGENCE:
- Competing quotes you've received: {{competing_offers}}
- Current best (lowest) price from another supplier: {{best_price}}
- Your target price: {{target_price}}

PRICING LOGIC (critical):
- LOWER prices are BETTER for us. A price of $4 is better than $4.35 because it costs less.
- {{best_price}} = the lowest competing offer we have. Push for {{target_price}} or lower.
- If they offer a price LOWER than {{target_price}}, that is excellent — accept it and wrap up.
- Never reveal the exact {{best_price}} or competitor names.

PRIOR INTERACTIONS WITH THIS VENDOR:
{{past_history}}

YOUR TASK:
1. Reference your previous conversation about {{item}}
2. Say you've received several competitive quotes and are making final decisions
3. Ask if they can improve their pricing to be more competitive
4. If they ask what price you need, mention your target is around {{target_price}}
5. Do NOT reveal the exact best price or name the competing supplier — just say "we have more competitive offers on the table"
6. Ask about volume discounts, bulk pricing tiers, or payment term flexibility
7. Get their FINAL best offer
8. Once you have their final offer and confirmation, thank them ONCE, wish them well, and END THE CALL

RULES:
- Be respectful but firm — you have leverage
- Keep the call under 3 minutes
- Never reveal exact competing prices or competitor names
- If they can't budge on unit price, ask about free shipping, faster delivery, or better payment terms as alternatives
- If this is a service rather than a product, negotiate on hourly/project rate, scope, or timeline flexibility
- TERMINATE THE CALL: Once you get their final price and confirmation, wish them well ONCE and hang up. Do NO
[truncated — 101 more characters]
```

### package.json

```
{
  "name": "treehacks2026",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint"
  },
  "dependencies": {
    "@base-ui/react": "^1.2.0",
    "@elastic/elasticsearch": "^9.3.1",
    "@hookform/resolvers": "^5.2.2",
    "@neondatabase/serverless": "^1.0.2",
    "@prisma/adapter-neon": "^7.4.0",
    "@prisma/client": "^7.4.0",
    "agentmail": "^0.2.15",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "cmdk": "^1.1.1",
    "date-fns": "^4.1.0",
    "dotenv": "^17.3.1",
    "embla-carousel-react": "^8.6.0",
    "input-otp": "^1.4.2",
    "lucide-react": "^0.564.0",
    "next": "16.1.6",
    "next-themes": "^0.4.6",
    "openai": "^6.22.0",
    "radix-ui": "^1.4.3",
    "react": "19.2.3",
    "react-day-picker": "^9.13.2",
    "react-dom": "19.2.3",
    "react-hook-form": "^7.71.1",
    "react-resizable-panels": "^4",
    "recharts": "2.15.4",
    "sonner": "^2.0.7",
    "tailwind-merge": "^3.4.0",
    "vaul": "^1.1.2",
    "ws": "^8.19.0",
    "zod": "^4.3.6"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4",
    "eslint": "^9",
    "eslint-config-next": "16.1.6",
    "prisma": "^7.4.0",
    "shadcn": "^3.8.4",
    "tailwindcss": "^4",
    "tw-animate-css": "^1.4.0"
  }
}

```

### wallet_server/package.json

```
{
  "name": "wallet-server",
  "version": "0.2.0",
  "private": true,
  "description": "Mock B2B vendor invoice server with AgentMail email integration",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "dev": "node server.js"
  },
  "engines": {
    "node": ">=18"
  },
  "dependencies": {
    "dotenv": "^17.3.1",
    "express": "^5.1.0"
  }
}

```

### wallet_server/server.js

```javascript
/**
 * Wallet Server — Mock B2B vendor invoice system.
 *
 * Email-driven workflow:
 *   1. Buyer sends email with "Amount: <number>" to an AgentMail inbox.
 *   2. AgentMail webhook (POST /webhook/agentmail) triggers invoice creation.
 *   3. Server replies via AgentMail with invoice + mock wallet address.
 *   4. Client calls POST /initiate-payment to simulate settlement.
 *
 * No blockchain. No real wallet. Deterministic mock responses.
 */

require('dotenv').config();

const path = require('path');
const express = require('express');
const webhookRoutes = require('./routes/webhookRoutes');
const paymentRoutes = require('./routes/paymentRoutes');

const app = express();
const PORT = Number(process.env.PORT) || 4020;

// --- Middleware ---
app.use(express.json());

// CORS for local dev
app.use((_req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
  if (_req.method === 'OPTIONS') return res.sendStatus(204);
  next();
});

// --- Static files (company landing page) ---
app.use(express.static(path.join(__dirname, 'public')));

// --- Routes ---
app.get('/health', (_req, res) => {
  res.json({ ok: true });
});

// Contact info endpoint — serves env vars to the landing page
app.get('/api/contact-info', (_req, res) => {
  res.json({
    email: process.env.AGENTMAIL_INBOX_ID || 'sales@formosa-am.com.tw',
    phone: process.env.PHONE_NUMBER || '+886-3-578-0000',
    company: 'Formosa Advanced Materials Co., Ltd.',
    address: 'No. 8, Lixing 5th Rd., Hsinchu Science Park, Hsinchu 30078, Taiwan',
  });
});

app.use(webhookRoutes);
app.use(paymentRoutes);

// --- 404 fallback (API only — static files already handled above) ---
app.use((_req, res) => {
  res.status(404).json({ error: 'Not found' });
});

// --- Start ---
app.listen(PORT, () => {
  console.log(`[wallet-server] Listening on http://localhost:${PORT}`);
  console.log('[wallet-server] Routes:');
  console.log('  GET  /                   — company landing page');
  console.log('  GET  /health             — health check');
  console.log('  GET  /api/contact-info   — company contact info (from env)');
  console.log('  POST /webhook/agentmail  — AgentMail inbound email webhook');
  console.log('  POST /initiate-payment   — simulate payment for an invoice');
  console.log('  GET  /invoices           — list all invoices (debug)');
});

```

### src/app/layout.js

```javascript
import { Inter, JetBrains_Mono, DM_Sans } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
const dmSans = DM_Sans({ subsets: ["latin"], variable: "--font-display", weight: ["400", "500", "600", "700"] });
const jetbrainsMono = JetBrains_Mono({ subsets: ["latin"], variable: "--font-jetbrains" });

export const metadata = {
  title: "Procure",
  description: "AI-powered procurement agent — supplier discovery, quoting, negotiation, and payments.",
};

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className={`${inter.variable} ${dmSans.variable} ${jetbrainsMono.variable} font-sans antialiased`}>
        {children}
      </body>
    </html>
  );
}

```

### src/app/page.js

```javascript
"use client"

import { useState, useCallback, useRef, useEffect } from "react"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { RequirementsChat } from "@/components/requirements-chat"
import { RFQForm } from "@/components/rfq-form"
import { ActivityFeed } from "@/components/activity-feed"
import { PhoneCallPanel } from "@/components/phone-call-panel"
import { QuotesPanel } from "@/components/quotes-panel"
import { SummaryPanel } from "@/components/summary-panel"
// import { PaymentOverlay } from "@/components/payment-overlay"
// import { simulateWorkflow } from "@/lib/agent-store"
import { cn } from "@/lib/utils"
import {
  Search,
  Phone,
  Globe,
  Handshake,
  CreditCard,
  Zap,
  ArrowLeft,
} from "lucide-react"

const ACTION_BUTTONS = [
  { label: "Find suppliers", icon: Search, stage: "finding_suppliers" },
  { label: "Call for quote", icon: Phone, stage: "calling_for_quote" },
  { label: "Request web quote", icon: Globe, stage: "requesting_web_quote" },
  { label: "Negotiate", icon: Handshake, stage: "negotiating" },
  { label: "Pay deposit", icon: CreditCard, stage: "paying_deposit" },
]

const STAGE_INDEX = {
  finding_suppliers: 1,
  calling_for_quote: 2,
  requesting_web_quote: 3,
  negotiating: 4,
  paying_deposit: 5,
}

const DEFAULT_SERVICES = {
  perplexity: false,
  stagehand: false,
  elevenlabs: false,
  elasticsearch: false,
  openai: false,
  payment: false,
}

export default function ProcurementAgent() {
  const [view, setView] = useState("chat")
  const [rfq, setRfq] = useState({
    item: "",
    quantity: "",
    leadTime: "",
    quality: "",
    location: "",
  })
  const [stage, setStage] = useState("idle")
  const [activities, setActivities] = useState([])
  const [quotes, setQuotes] = useState([])
  const [calls, setCalls] = useState([])
  const [summary, setSummary] = useState(null)
  const [activeServices, setActiveServices] = useState(DEFAULT_SERVICES)
  // const [paymentConfirmed, setPaymentConfirmed] = useState(null)
  const [dashboardReady, setDashboardReady] = useState(false)
  const cleanupRef = useRef(null)

  // Service hold-time: keep services visually active for a minimum duration
  // so the user can actually see them light up (backend cycles are too fast)
  const backendServicesRef = useRef(DEFAULT_SERVICES)
  const serviceTimersRef = useRef({})

  const isRunning = stage !== "idle" && stage !== "complete" && stage !== "invoice_received"

  const handleChatComplete = useCallback((parsedRfq) => {
    setRfq(parsedRfq)
    setView("transitioning")
    setDashboardReady(true)
  }, [])

  const handleStart = useCallback(async () => {
    setActivities([])
    setQuotes([])
    setCalls([])
    setSummary(null)
    setActiveServices(DEFAULT_SERVICES)
    backendServicesRef.current = DEFAULT_SERVICES
    Object.values(serviceTimersRef.current).forEach((t) => clearTimeout(t))
    serviceTimersRef.current = {}

    cleanupRef.current?.()

    try {
      // Create a real run via the backend
      const response = await fetch('/api/run', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(rfq),
      })
      const { runId } = await response.json()

      // Connect to SSE stream
      const eventSource = new EventSource(`/api/run/${runId}/events`)

      eventSource.onmessage = (e) => {
        try {
          const event = JSON.parse(e.data)
          switch (event.type) {
            case 'stage_change':
              setStage(event.payload.stage)
              break
            case 'activity':
              setActivities((prev) => [...prev, event.payload])
              break
            case 'update_activity':
              setActivities((prev) =>
                prev.map((a) =>
                  a.id === event.payload.id
                    ? { ...a, ...event.payload.updates }
                    : a
                )
              )
              break
            case 'quote':
              setQuotes((prev) => [...prev, event.payload])
              break
            case 'calls_change':
              setCalls(event.payload)
              break
            case 'summary':
              setSummary(event.payload)
              break
            case 'services_change': {
              const svcPayload = event.payload
              backendServicesRef.current = svcPayload
              setActiveServices((prev) => {
                const next = { ...prev }
                for (const key of Object.keys(svcPayload)) {
                  if (svcPayload[key]) {
                    // Service is active — show immediately, cancel any pending off-timer
                    next[key] = true
                    if (serviceTimersRef.current[key]) {
                      clearTimeout(serviceTimersRef.current[key])
                      delete serviceTimersRef.current[key]
                    }
                  } else if (prev[key] && !svcPayload[key]) {
                    // Was on, now off — hold it visually active for 2s minimum
                    next[key] = true
                    if (!serviceTimersRef.current[key]) {
                      serviceTimersRef.current[key] = setTimeout(() => {
                        if (!backendServicesRef.current[key]) {
                          setActiveServices((curr) => ({ ...curr, [key]: false }))
                        }
                        delete serviceTimersRef.current[key]
                      }, 2000)
                    }
                  }
                  // If both prev and payload are false, stays false (no change needed)
                }
                return next
              })
              break
            }
            case 'email_sent':
              // Confirmation email was sent to winning vendor
              console.log('Email sent to vendor:', event.payload.vendorName, event.payload.vendorEmail)
              break
            case 'payment_confirmed':
              // Pa
[truncated — 15000 more characters]
```

### src/app/api/run/route.ts

```typescript
import { NextResponse } from 'next/server';
import { prisma } from '@/lib/db';
import { runOrchestrator } from '@/lib/orchestrator';

export async function POST(req: Request) {
  console.log(`[API /api/run] POST request received`);
  try {
    const body = await req.json();
    console.log(`[API /api/run] Request body:`, JSON.stringify(body));

    // body is the RFQ: { item, quantity, leadTime, quality, location }
    const run = await prisma.run.create({
      data: {
        rawQuery: body.item ?? '',
        parsedSpec: {
          item: body.item ?? '',
          quantity: body.quantity ?? '',
          leadTime: body.leadTime ?? '',
          quality: body.quality ?? '',
          location: body.location ?? '',
        },
        status: 'pending',
      },
    });
    console.log(`[API /api/run] Run created in Postgres — runId=${run.id}`);

    // Fire-and-forget: start the orchestrator in the background
    // We don't await this -- it runs async and emits SSE events
    void runOrchestrator(run.id);
    console.log(`[API /api/run] Orchestrator kicked off (fire-and-forget). Returning runId.`);

    return NextResponse.json({ runId: run.id });
  } catch (err: any) {
    console.error('[API /api/run] POST error:', err);
    return NextResponse.json({ error: err.message }, { status: 500 });
  }
}

```

### src/app/api/setup/route.ts

```typescript
import { NextResponse } from 'next/server';
import { prisma } from '@/lib/db';
import { createIndex } from '@/lib/elastic';

export async function GET() {
    console.log(`[API /api/setup] Setup check started`);
    const results: Record<string, any> = {};

    // 1. Verify Postgres connection
    try {
        console.log(`[API /api/setup] Testing Postgres connection...`);
        await prisma.$queryRaw`SELECT 1`;
        results.postgres = { ok: true };
        console.log(`[API /api/setup] Postgres: OK`);
    } catch (err: any) {
        results.postgres = { ok: false, error: err.message };
        console.error(`[API /api/setup] Postgres: FAILED —`, err.message);
    }

    // 2. Create Elasticsearch index (idempotent)
    try {
        console.log(`[API /api/setup] Creating/checking Elasticsearch index...`);
        const esResult = await createIndex();
        results.elasticsearch = { ok: true, ...esResult };
        console.log(`[API /api/setup] Elasticsearch:`, esResult);
    } catch (err: any) {
        results.elasticsearch = { ok: false, error: err.message };
        console.error(`[API /api/setup] Elasticsearch: FAILED —`, err.message);
    }

    const allOk = results.postgres?.ok && results.elasticsearch?.ok;
    console.log(`[API /api/setup] Setup result: ${allOk ? 'ALL OK' : 'SOME FAILED'}`, results);

    return NextResponse.json(results, { status: allOk ? 200 : 500 });
}

```

### src/app/api/extract-requirements/route.ts

```typescript

import { NextResponse } from 'next/server';
import OpenAI from 'openai';

export const runtime = 'nodejs';

const SYSTEM_PROMPT = `
You are an expert procurement agent. Your goal is to extract structured procurement requirements from a conversation.
Extract the following fields into a JSON object:

- item (string): The specific item to procure. Be descriptive (include material, type, specs).
- quantity (string): The quantity needed (e.g. "1,000 units", "50kg", "10,000").
- leadTime (string): When it is needed by (e.g. "30 days", "next week"). Default to "30 days" if not specified.
- quality (string): Any quality constraints, certifications (ISO, ASTM), or brands. Default to "Standard" if not specified.
- location (string): Preferred supplier location. Default to "Auto-detected" if not specified.

If a field is missing, try to infer it from context or set it to null (except defaults mentioned above).
Return ONLY valid JSON.
`;

export async function POST(req: Request) {
    try {
        const { messages } = await req.json();

        if (!messages || !Array.isArray(messages)) {
            return NextResponse.json({ error: 'Invalid messages format' }, { status: 400 });
        }

        const apiKey = process.env.OPENAI_API_KEY;
        if (!apiKey) {
            console.warn('[API] OPENAI_API_KEY missing, falling back to regex on client');
            return NextResponse.json({ error: 'OPENAI_API_KEY missing' }, { status: 503 });
        }

        const openai = new OpenAI({ apiKey });

        // Format conversation history
        const conversation = messages
            .map((m: any) => `${m.role === 'user' ? 'User' : 'Agent'}: ${m.content}`)
            .join('\n\n');

        const completion = await openai.chat.completions.create({
            model: "gpt-4o-mini",
            messages: [
                { role: "system", content: SYSTEM_PROMPT },
                { role: "user", content: `CONVERSATION:\n${conversation}\n\nExtract JSON:` }
            ],
            response_format: { type: "json_object" },
            temperature: 0.1,
        });

        const content = completion.choices[0].message.content;

        if (!content) {
            return NextResponse.json({ error: 'No response from OpenAI' }, { status: 500 });
        }

        try {
            const extraction = JSON.parse(content);
            console.log('[API] OpenAI extraction result:', JSON.stringify(extraction, null, 2));
            return NextResponse.json({ extraction });
        } catch (e) {
            console.error('[API] Failed to parse JSON:', content);
            return NextResponse.json({ error: 'JSON parse error' }, { status: 500 });
        }

    } catch (err: any) {
        console.error('[API] Extraction error:', err);
        return NextResponse.json({ error: `OpenAI API error: ${err.message}` }, { status: 500 });
    }
}

```

[98 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]