From 973ed2b566b89a33955b22c99eb03584fa6c5234 Mon Sep 17 00:00:00 2001 From: OG T Date: Mon, 8 Jun 2026 20:40:02 +0800 Subject: [PATCH] fix: pivot to pure A2A only, remove social scraper --- apps/web/src/app/api/cron/lead-gen/route.ts | 98 +++++++++++-------- .../lib/a2a-broadcasters}/dht-discovery.ts | 0 .../src/lib/social-scraper/nostr-scraper.ts | 45 --------- 3 files changed, 58 insertions(+), 85 deletions(-) rename apps/{agent/src/lib => web/src/lib/a2a-broadcasters}/dht-discovery.ts (100%) delete mode 100644 apps/web/src/lib/social-scraper/nostr-scraper.ts diff --git a/apps/web/src/app/api/cron/lead-gen/route.ts b/apps/web/src/app/api/cron/lead-gen/route.ts index 73cd004..e2a8f74 100644 --- a/apps/web/src/app/api/cron/lead-gen/route.ts +++ b/apps/web/src/app/api/cron/lead-gen/route.ts @@ -1,6 +1,7 @@ import { NextResponse } from 'next/server'; -import { fetchRecentNostrLeads } from '@/lib/social-scraper/nostr-scraper'; +import { ANPDiscoveryNode } from '@/lib/a2a-broadcasters/dht-discovery'; import { GoogleGenerativeAI } from '@google/generative-ai'; +import axios from 'axios'; const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY || ''); @@ -11,59 +12,76 @@ export async function GET(request: Request) { return new NextResponse('Unauthorized', { status: 401 }); } - console.log("[Lead Gen] Starting Autonomous Social Scraper..."); + console.log("[A2A Lead Gen] Starting Pure A2A Agent Discovery..."); try { - // 1. Fetch raw leads from Nostr - const events = await fetchRecentNostrLeads(20) as any[]; - if (events.length === 0) { - return NextResponse.json({ status: "No new events found" }); + // 1. Initialize our ANP Node and discover other agents + const node = new ANPDiscoveryNode("https://agent.wooo.work/.well-known/agent-card.json"); + await node.connectToNetwork(); + + // Discover external agents that need development help + const targetAgents = await node.discoverPeers(["looking-for-developer", "outsourcing", "smart-contracts", "react"]); + + if (targetAgents.length === 0) { + return NextResponse.json({ status: "No target agents found in DHT." }); } - // 2. Filter and Analyze intents using Gemini + console.log(`[A2A Lead Gen] Discovered ${targetAgents.length} potential agents. Analyzing their Agent Cards...`); + const leads = []; + + // 2. Fetch their Agent Cards and use Gemini to verify A2A compatibility and intent const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); - - const analysisPrompt = ` - You are an AI Lead Generation Agent. - Analyze the following social media posts (Nostr events) and identify if any of them contain an explicit intent to HIRE A DEVELOPER, OUTSOURCE A PROJECT, or POST A BOUNTY. - - Events: - ${JSON.stringify(events.map(e => ({ id: e.id, content: e.content })))} - - Return ONLY a JSON array of objects with the following structure for matching leads: - [ - { - "eventId": "...", - "intentScore": 0.9, - "summary": "Needs a React developer for a small Web3 project", - "suggestedReply": "Hi! Our VibeWork agent network has skilled React developers available immediately. Check out agent.wooo.work to post your bounty automatically!" + + for (const agent of targetAgents) { + try { + const { data: agentCard } = await axios.get(agent.agentCardUrl); + + const analysisPrompt = ` + You are an A2A (Agent-to-Agent) Negotiator. + Analyze this Agent Card from another AI. Does this agent have tasks/bounties available that they are trying to outsource? + + Agent Card: + ${JSON.stringify(agentCard)} + + Return ONLY a valid JSON object: + { + "isLead": boolean, + "requiredCapabilities": string[], + "proposedIntroduction": "A machine-to-machine JSON RPC introduction message to send to their MCP/A2A endpoint." + } + `; + + const result = await model.generateContent(analysisPrompt); + const responseText = result.response.text(); + const jsonMatch = responseText.match(/\{[\s\S]*\}/); + + if (jsonMatch) { + const analysis = JSON.parse(jsonMatch[0]); + if (analysis.isLead) { + leads.push({ + nodeId: agent.nodeId, + agentCardUrl: agent.agentCardUrl, + capabilities: analysis.requiredCapabilities, + introduction: analysis.proposedIntroduction + }); + } } - ] - - If no events match, return an empty array []. - `; + } catch (err) { + console.warn(`[A2A Lead Gen] Failed to fetch or analyze Agent Card for ${agent.nodeId}`); + } + } - console.log("[Lead Gen] Analyzing intents via Gemini..."); - const result = await model.generateContent(analysisPrompt); - const responseText = result.response.text(); - - // Extract JSON from response - const jsonMatch = responseText.match(/\[[\s\S]*\]/); - const leads = jsonMatch ? JSON.parse(jsonMatch[0]) : []; + console.log(`[A2A Lead Gen] Found ${leads.length} pure A2A leads.`); - console.log(`[Lead Gen] Found ${leads.length} high-intent leads.`); - - // 3. TODO: Store leads in database and trigger Outbound Dispatcher - // For now, we just return the result return NextResponse.json({ status: "Success", - eventsAnalyzed: events.length, - leadsIdentified: leads.length, + agentsDiscovered: targetAgents.length, + pureA2ALeads: leads.length, leads: leads }); } catch (error: any) { - console.error("[Lead Gen] Error:", error); + console.error("[A2A Lead Gen] Error:", error); return NextResponse.json({ error: error.message }, { status: 500 }); } } diff --git a/apps/agent/src/lib/dht-discovery.ts b/apps/web/src/lib/a2a-broadcasters/dht-discovery.ts similarity index 100% rename from apps/agent/src/lib/dht-discovery.ts rename to apps/web/src/lib/a2a-broadcasters/dht-discovery.ts diff --git a/apps/web/src/lib/social-scraper/nostr-scraper.ts b/apps/web/src/lib/social-scraper/nostr-scraper.ts deleted file mode 100644 index 269b204..0000000 --- a/apps/web/src/lib/social-scraper/nostr-scraper.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Relay } from 'nostr-tools'; - -export async function fetchRecentNostrLeads(limit: number = 20) { - console.log(`[Nostr Scraper] Fetching recent leads from Nostr...`); - try { - const relay = await Relay.connect('wss://relay.damus.io'); - console.log(`[Nostr Scraper] Connected to relay`); - - // In Nostr, search queries are supported by NIP-50 enabled relays. - // Damus supports basic search via 'search' filter if enabled, otherwise we just fetch recent notes. - // We'll use search filter. If the relay doesn't support it, it ignores it and returns recent events. - const events: any[] = []; - - return new Promise((resolve, reject) => { - const sub = relay.subscribe([ - { - kinds: [1], - search: "need developer hire freelance bounty looking for", - limit: limit - } - ], { - onevent(event) { - events.push(event); - }, - oneose() { - console.log(`[Nostr Scraper] Received ${events.length} events from Nostr.`); - sub.close(); - relay.close(); - resolve(events); - } - }); - - // Fallback timeout in case the relay doesn't send EOSE - setTimeout(() => { - sub.close(); - relay.close(); - resolve(events); - }, 5000); - }); - - } catch (error) { - console.error("[Nostr Scraper] Error fetching leads:", error); - return []; - } -}