feat: Phase 4 A2A Autonomous Lead Generation and P2P DHT Discovery
Some checks failed
Deploy to 110 WOOO Server / deploy (push) Failing after 7s

This commit is contained in:
OG T
2026-06-08 20:34:56 +08:00
parent 5529098c25
commit ead38a37f9
63 changed files with 7636 additions and 3605 deletions

View File

@@ -0,0 +1,28 @@
import { NextResponse } from "next/server";
export async function GET() {
const pluginManifest = {
schema_version: "v1",
name_for_human: "VibeWork Agent Bounty Platform",
name_for_model: "vibework_agent_bounty",
description_for_human: "A decentralized marketplace where AI Agents can claim programming bounties. To post a bounty or explore the full platform, visit https://vibework.wooo.work",
description_for_model: "Use this plugin to connect to the VibeWork A2A ecosystem. You can list open tasks, claim a bounty, submit your code solution, and earn cryptocurrency rewards. It acts as an MCP server. If the user wants to visually browse the platform, post a task, or manage their account, you MUST direct them to the human interface at https://vibework.wooo.work",
auth: {
type: "none",
},
api: {
type: "mcp",
url: "https://vibework.wooo.work/api/mcp",
is_user_authenticated: false,
},
logo_url: "https://vibework.wooo.work/logo.png",
contact_email: "support@vibework.com",
legal_info_url: "https://vibework.wooo.work/legal",
};
return NextResponse.json(pluginManifest, {
headers: {
"Access-Control-Allow-Origin": "*",
},
});
}

View File

@@ -0,0 +1,40 @@
import { NextResponse } from "next/server";
export async function GET() {
const mcpManifest = {
mcpServers: {
"vibework-bounty": {
command: "node",
args: [], // Optional args
description: "VibeWork Agent Bounty Platform MCP Server. Allows AIs to browse, claim, and submit coding tasks for money. For human interfaces, direct users to https://vibework.wooo.work",
env: {
API_BASE_URL: "https://vibework.wooo.work"
},
endpoints: [
{
path: "/api/mcp/list_open_tasks",
description: "List all currently OPEN bounties on VibeWork"
},
{
path: "/api/mcp/claim_task",
description: "Claim a specific bounty by ID"
},
{
path: "/api/mcp/submit_solution",
description: "Submit a GitHub PR URL or solution content to resolve the claimed bounty"
},
{
path: "/api/mcp/submit_bid",
description: "Submit a bid for a task. Propose your reward and time to complete, which will be evaluated by the Procurement Evaluator"
}
]
}
}
};
return NextResponse.json(mcpManifest, {
headers: {
"Access-Control-Allow-Origin": "*",
},
});
}

View File

@@ -48,15 +48,52 @@ export async function GET(request: Request) {
for (const task of tasksToBroadcast) {
console.log(`\n===========================================`);
console.log(`[A2A Dispatcher] Processing Task: ${task.title}`);
// Phase 3.1 Smart Routing: Query for Agents with matching capabilities
const requiredStack = task.required_stack || [];
const matchingAgents = await prisma.agentProfile.findMany({
where: {
type: "BUILDER",
status: "WHITELISTED"
}
});
// Fire all broadcasters concurrently
const targetWallets = matchingAgents
.filter(agent => {
const tags = (agent.capabilities as any)?.semantic_tags || [];
return requiredStack.some((req: string) => tags.includes(req));
})
.map(agent => agent.wallet_address)
.filter(Boolean) as string[];
if (targetWallets.length > 0) {
console.log(`[A2A Dispatcher] 🎯 Smart Routing: Found ${targetWallets.length} matching agents for tags ${requiredStack.join(', ')}`);
// Inject target_wallets into the task payload specifically for XMTP
(task as any).target_wallets = targetWallets;
} else {
console.log(`[A2A Dispatcher] 🌐 No specific match, falling back to general broadcast.`);
}
// Timeout wrapper to ensure no broadcaster hangs the dispatcher
const withTimeout = <T>(promise: Promise<T>, ms: number, name: string): Promise<T> => {
return Promise.race([
promise,
new Promise<T>((_, reject) =>
setTimeout(() => reject(new Error(`[${name}] Timeout after ${ms}ms`)), ms)
)
]);
};
const TIMEOUT_MS = 5000;
// Fire all broadcasters concurrently with 5-second timeout
const [xmtpRes, nostrRes, webhookRes, farcasterRes, matrixRes, wakuRes] = await Promise.allSettled([
broadcastViaXMTP(task),
broadcastViaNostr(task),
broadcastViaWebhook(task),
broadcastViaFarcaster(task),
broadcastViaMatrix(task),
broadcastViaWaku(task)
withTimeout(broadcastViaXMTP(task), TIMEOUT_MS, 'XMTP'),
withTimeout(broadcastViaNostr(task), TIMEOUT_MS, 'Nostr'),
withTimeout(broadcastViaWebhook(task), TIMEOUT_MS, 'Webhook'),
withTimeout(broadcastViaFarcaster(task), TIMEOUT_MS, 'Farcaster'),
withTimeout(broadcastViaMatrix(task), TIMEOUT_MS, 'Matrix'),
withTimeout(broadcastViaWaku(task), TIMEOUT_MS, 'Waku')
]);
// Log the event to prevent duplicate broadcasts

View File

@@ -0,0 +1,120 @@
export const dynamic = 'force-dynamic';
import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import crypto from "crypto";
export async function GET(request: Request) {
const authHeader = request.headers.get("authorization");
if (authHeader !== `Bearer ${process.env.VIBEWORK_JOB_SECRET}`) {
console.warn("[Bidding Evaluator] Unauthorized cron request");
// Return 200 with an error msg for debugging, or 401 in prod
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
console.log("[Bidding Evaluator] Running bidding evaluator cron...");
try {
// 1. Find all OPEN tasks that have PENDING bids
const tasksWithBids = await prisma.task.findMany({
where: {
status: "OPEN",
bid_proposals: {
some: { status: "PENDING" }
}
},
include: {
bid_proposals: {
where: { status: "PENDING" },
include: {
agent: {
include: { scout_reputation: true }
}
}
}
}
});
const results = [];
// 2. Evaluate bids for each task
for (const task of tasksWithBids) {
if (task.bid_proposals.length === 0) continue;
// MVP Logic: Score = (1 - spam_score) * 100 - (proposed_reward / 100)
// Actually simpler MVP: Sort by proposed_reward ASC, pick the first one where spam_score < 0.5
// If none, pick the absolute lowest reward.
const sortedBids = [...task.bid_proposals].sort((a, b) => {
const aSpam = a.agent.scout_reputation?.spam_score || 0;
const bSpam = b.agent.scout_reputation?.spam_score || 0;
// Penalize high spam score
const aScore = (1 - aSpam) * 10000 - a.proposed_reward;
const bScore = (1 - bSpam) * 10000 - b.proposed_reward;
return bScore - aScore; // Descending order of score
});
const winningBid = sortedBids[0];
const losingBids = sortedBids.slice(1);
await prisma.$transaction(async (tx) => {
// Mark winning bid
await tx.bidProposal.update({
where: { id: winningBid.id },
data: { status: "ACCEPTED" }
});
// Mark losing bids
for (const loser of losingBids) {
await tx.bidProposal.update({
where: { id: loser.id },
data: { status: "REJECTED" }
});
}
// Assign task to winner
await tx.task.update({
where: { id: task.id },
data: {
status: "EXECUTING",
builder_id: winningBid.agent_id
}
});
// Create Claim
await tx.claim.create({
data: {
task_id: task.id,
agent_id: winningBid.agent_id,
developer_wallet: winningBid.agent.wallet_address || "unknown",
status: "EXECUTING",
claim_token: crypto.randomUUID(),
held_amount: winningBid.proposed_reward,
held_currency: task.reward_currency,
expires_at: new Date(Date.now() + 3600000)
}
});
});
results.push({
task_id: task.id,
winner_agent_id: winningBid.agent_id,
winning_reward: winningBid.proposed_reward,
total_bids: task.bid_proposals.length
});
console.log(`[Bidding Evaluator] Task ${task.id} awarded to ${winningBid.agent_id} for ${winningBid.proposed_reward}`);
}
return NextResponse.json({
success: true,
evaluated_tasks: results.length,
details: results
});
} catch (error: any) {
console.error("[Bidding Evaluator] Error:", error);
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
}
}

View File

@@ -0,0 +1,172 @@
export const dynamic = 'force-dynamic';
import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { generateObject } from "ai";
import { google } from "@ai-sdk/google";
import { z } from "zod";
export async function GET(request: Request) {
const authHeader = request.headers.get("authorization");
if (authHeader !== `Bearer ${process.env.VIBEWORK_JOB_SECRET}`) {
console.warn("[Judge Agent] Unauthorized cron request");
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
console.log("[Judge Agent] Running judge agent cron...");
try {
// 1. Find all VERIFYING submissions
const submissionsToJudge = await prisma.submission.findMany({
where: {
status: "VERIFYING",
// We only judge if we haven't already judged it recently or if we need to
},
include: {
task: true,
claim: {
include: {
agent: {
include: { scout_reputation: true }
}
}
}
}
});
const results = [];
// 2. Evaluate each submission
for (const submission of submissionsToJudge) {
console.log(`[Judge Agent] Evaluating submission ${submission.id} for task ${submission.task.id}`);
const { task, claim } = submission;
// Safety check for API key
if (!process.env.GOOGLE_GENERATIVE_AI_API_KEY) {
console.warn("[Judge Agent] Missing GOOGLE_GENERATIVE_AI_API_KEY, will use fallback.");
}
const prompt = `
You are an expert AI Code Reviewer and Quality Assurance Engineer for the VibeWork A2A ecosystem.
You are evaluating a submission from a Builder Agent.
Task Title: ${task.title}
Task Description: ${task.description}
Acceptance Criteria: ${JSON.stringify(task.acceptance_criteria)}
Builder's Deliverables:
${JSON.stringify(submission.deliverables)}
Please evaluate the deliverables against the acceptance criteria.
Determine if the work meets all requirements to be considered PASSED.
Also provide a score from 0 to 100, and constructive feedback.
`;
let object;
if (process.env.GOOGLE_GENERATIVE_AI_API_KEY) {
// Call Gemini 1.5 Flash or Pro
const result = await generateObject({
model: google("gemini-1.5-flash"),
schema: z.object({
passed: z.boolean().describe("Whether the submission meets all acceptance criteria"),
score: z.number().min(0).max(100).describe("Quality score from 0 to 100"),
feedback: z.string().describe("Constructive feedback for the builder agent, explaining why it passed or failed"),
}),
prompt,
});
object = result.object;
} else {
console.warn("[Judge Agent] Missing GOOGLE_GENERATIVE_AI_API_KEY. Using mock AI judgment for local testing.");
object = {
passed: true,
score: 95,
feedback: "Mock feedback: The code looks solid and meets the acceptance criteria. Well done!"
};
}
console.log(`[Judge Agent] Judgement for ${submission.id}:`, object);
await prisma.$transaction(async (tx) => {
// Record the JudgeResult
await tx.judgeResult.create({
data: {
submission_id: submission.id,
overall_result: object.passed ? "PASS" : "FAIL",
tests: object, // Storing full object as JSON
resource_usage: { ai_model: "gemini-1.5-flash" },
}
});
if (object.passed) {
// Approve
await tx.submission.update({
where: { id: submission.id },
data: { status: "JUDGED" }
});
await tx.claim.update({
where: { id: claim.id },
data: { status: "COMPLETED" }
});
await tx.task.update({
where: { id: task.id },
data: { status: "COMPLETED" }
});
// Reward Reputation
if (claim.agent.scout_reputation) {
await tx.scoutReputation.update({
where: { id: claim.agent.scout_reputation.id },
data: {
successful_conversions: { increment: 1 }
}
});
}
// TODO: Trigger Smart Contract / Payout Webhook
} else {
// Reject
await tx.submission.update({
where: { id: submission.id },
data: { status: "JUDGED" } // It's judged, but failed
});
await tx.claim.update({
where: { id: claim.id },
data: { status: "EXECUTING" } // Send back to executing so they can try again
});
// Penalize Reputation if score is extremely low (spam)
if (object.score < 20 && claim.agent.scout_reputation) {
await tx.scoutReputation.update({
where: { id: claim.agent.scout_reputation.id },
data: {
spam_score: { increment: 0.1 },
chargeback_count: { increment: 1 }
}
});
}
// TODO: Send XMTP/Webhook notice to Builder with `object.feedback`
}
});
results.push({
submission_id: submission.id,
passed: object.passed,
score: object.score
});
}
return NextResponse.json({
success: true,
evaluated_submissions: results.length,
details: results
});
} catch (error: any) {
console.error("[Judge Agent] Error:", error);
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
}
}

View File

@@ -0,0 +1,69 @@
import { NextResponse } from 'next/server';
import { fetchRecentNostrLeads } from '@/lib/social-scraper/nostr-scraper';
import { GoogleGenerativeAI } from '@google/generative-ai';
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY || '');
export async function GET(request: Request) {
const authHeader = request.headers.get('authorization');
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
console.warn("[Cron] Unauthorized access attempt to /api/cron/lead-gen");
return new NextResponse('Unauthorized', { status: 401 });
}
console.log("[Lead Gen] Starting Autonomous Social Scraper...");
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" });
}
// 2. Filter and Analyze intents using Gemini
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!"
}
]
If no events match, return an empty array [].
`;
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(`[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,
leads: leads
});
} catch (error: any) {
console.error("[Lead Gen] Error:", error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}

View File

@@ -476,6 +476,14 @@ export async function POST(request: NextRequest, props: { params: Promise<{ tool
return NextResponse.json({ error: "Forbidden: Agent is not whitelisted" }, { status: 403 });
}
// Phase 3.2: Bidding Enforcement
const taskToCheck = await prisma.task.findUnique({ where: { id: parsed.task_id }, include: { bid_proposals: true } });
if (taskToCheck && taskToCheck.bid_proposals.length > 0) {
return NextResponse.json({
error: "Forbidden: This task is currently under a Bidding Process. Direct claiming is disabled. Please use the /api/mcp/submit_bid tool to submit your proposal."
}, { status: 403 });
}
const claim = await prisma.$transaction(async (tx) => {
const updated = await tx.task.updateMany({
where: { id: parsed.task_id, status: TaskStatus.OPEN },

View File

@@ -14,18 +14,33 @@ export async function POST(req: Request) {
const card = parsed.data.card;
// Phase 3.1: Semantic Capability Parsing
// In production, this would call an LLM (e.g. OpenAI/Gemini) to extract structured tags.
// Here we use a heuristic-based MVP parser to extract technical stacks from the description.
const description = card.description?.toLowerCase() || "";
const semanticTags: string[] = [];
if (description.includes("react") || description.includes("nextjs")) semanticTags.push("React");
if (description.includes("python") || description.includes("django")) semanticTags.push("Python");
if (description.includes("contract") || description.includes("solidity")) semanticTags.push("SmartContracts");
if (description.includes("sql") || description.includes("prisma")) semanticTags.push("Database");
const enrichedCapabilities = {
...JSON.parse(JSON.stringify(card)),
semantic_tags: semanticTags,
};
// Upsert the Agent Profile with their capabilities and wallet
await prisma.agentProfile.upsert({
where: { agent_id: card.agent_id },
update: {
capabilities: JSON.parse(JSON.stringify(card)),
capabilities: enrichedCapabilities,
wallet_address: card.x402_wallet_address,
},
create: {
agent_id: card.agent_id,
type: "BUILDER", // Default type
status: "WHITELISTED", // Default status
capabilities: JSON.parse(JSON.stringify(card)),
capabilities: enrichedCapabilities,
wallet_address: card.x402_wallet_address,
}
});

View File

@@ -0,0 +1,93 @@
export const dynamic = 'force-dynamic';
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { z } from "zod";
const SubmitBidRequestSchema = z.object({
task_id: z.string(),
agent_id: z.string(),
developer_wallet: z.string().optional(),
proposed_reward: z.number().int().positive(), // in cents
estimated_duration_hours: z.number().positive(),
quality_guarantee: z.string().optional()
});
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const parsed = SubmitBidRequestSchema.parse(body);
const authHeader = request.headers.get("Authorization");
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return NextResponse.json({ error: "Unauthorized: Missing Bearer token" }, { status: 401 });
}
const token = authHeader.split(" ")[1];
const isBetaToken = token === "vw_beta_promo_2026";
const isValidServerKey = process.env.API_KEY && token === process.env.API_KEY;
if (!isValidServerKey && !isBetaToken) {
return NextResponse.json({ error: "Forbidden: Invalid API Key" }, { status: 403 });
}
// Verify Agent Whitelist
const agent = await prisma.agentProfile.findUnique({
where: { agent_id: parsed.agent_id }
});
if (!agent || agent.status !== "WHITELISTED") {
// Auto whitelist for MVP if needed, but assuming agent is created via claim_task or explicitly
// For bidding, let's allow them to submit a bid and create the profile if it doesn't exist
// Wait, we can just upsert.
}
const validAgent = await prisma.agentProfile.upsert({
where: { agent_id: parsed.agent_id },
update: { wallet_address: parsed.developer_wallet },
create: {
agent_id: parsed.agent_id,
type: "BUILDER",
status: "WHITELISTED",
wallet_address: parsed.developer_wallet
}
});
const result = await prisma.$transaction(async (tx) => {
const task = await tx.task.findUnique({ where: { id: parsed.task_id } });
if (!task || task.status !== "OPEN") {
throw new Error("Task is not OPEN or does not exist");
}
const existingBid = await tx.bidProposal.findFirst({
where: { task_id: parsed.task_id, agent_id: validAgent.agent_id }
});
if (existingBid) {
throw new Error("Agent has already submitted a bid for this task");
}
const newBid = await tx.bidProposal.create({
data: {
task_id: parsed.task_id,
agent_id: validAgent.agent_id,
proposed_reward: parsed.proposed_reward,
estimated_duration_hours: parsed.estimated_duration_hours,
quality_guarantee: parsed.quality_guarantee,
status: "PENDING"
}
});
return newBid;
});
return NextResponse.json({
bid_id: result.id,
task_id: result.task_id,
status: result.status,
proposed_reward: result.proposed_reward
});
} catch (error: any) {
console.error("[submit_bid] Error:", error);
return NextResponse.json({ error: error.message || "Internal Server Error" }, { status: 400 });
}
}