Files
agent-bounty-protocol/apps/web/src/lib/a2a-growth.ts
OG T 7b36c2496f
All checks were successful
CI and Production Smoke / smoke (push) Successful in 8s
feat: add external proposal handoff API
2026-06-12 10:59:58 +08:00

433 lines
19 KiB
TypeScript

import { isIP } from "node:net";
import { A2A_AGENT_INTEGRATIONS, TELEGRAM_CONTROL_PLANE_ROLES } from "@/lib/a2a-agent-integrations";
export const VIBEWORK_SITE_URL = (
process.env.VIBEWORK_SITE_URL ||
process.env.NEXT_PUBLIC_VIBEWORK_SITE_URL ||
"https://vibework.wooo.work"
).replace(/\/$/, "");
export const AGENT_GATEWAY_URL = (
process.env.AGENT_GATEWAY_URL ||
process.env.NEXT_PUBLIC_SITE_URL ||
"https://agent.wooo.work"
).replace(/\/$/, "");
export const DEFAULT_TREASURY_USDC_NETWORK = "Base USDC (native)";
export const DEFAULT_TREASURY_USDC_CHAIN_ID = "8453";
export const DEFAULT_TREASURY_USDC_TOKEN_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
export const TREASURY_USDC_ADDRESS = (process.env.VIBEWORK_TREASURY_USDC_ADDRESS || "").trim();
export const TREASURY_WALLET_LABEL = (process.env.VIBEWORK_TREASURY_WALLET_LABEL || "USDC Treasury").trim();
export const TREASURY_USDC_NETWORK = (process.env.VIBEWORK_TREASURY_USDC_NETWORK || DEFAULT_TREASURY_USDC_NETWORK).trim();
export const TREASURY_USDC_CHAIN_ID = (process.env.VIBEWORK_TREASURY_USDC_CHAIN_ID || DEFAULT_TREASURY_USDC_CHAIN_ID).trim();
export const TREASURY_USDC_TOKEN_ADDRESS = (
process.env.VIBEWORK_TREASURY_USDC_TOKEN_ADDRESS || DEFAULT_TREASURY_USDC_TOKEN_ADDRESS
).trim();
export const TREASURY_USDC_RPC_URL = (process.env.VIBEWORK_TREASURY_USDC_RPC_URL || "https://mainnet.base.org").trim();
export function usdcAtomicUnitsFromCents(amountCents: number) {
return (BigInt(Math.max(0, Math.floor(amountCents))) * BigInt(10_000)).toString();
}
export function buildUsdcPaymentUri(amountCents: number) {
if (!TREASURY_USDC_ADDRESS || !TREASURY_USDC_TOKEN_ADDRESS || !TREASURY_USDC_CHAIN_ID) return "";
const params = new URLSearchParams({
address: TREASURY_USDC_ADDRESS,
uint256: usdcAtomicUnitsFromCents(amountCents),
});
return `ethereum:${TREASURY_USDC_TOKEN_ADDRESS}@${TREASURY_USDC_CHAIN_ID}/transfer?${params.toString()}`;
}
export const PROPOSAL_PACKAGES = [
{
id: "scout",
name: "Scout Intake",
feeCents: 2900,
label: "$29",
description: "需求整理、初步 scope triage、referral attribution.",
deliverable: "適合先確認需求是否可交給 AI Agent 處理。",
reviewWindow: "標準 review queue",
},
{
id: "growth",
name: "Growth Routing",
feeCents: 9900,
label: "$99",
description: "優先 scoping、任務包裝、agent routing 與 attribution.",
deliverable: "適合已有明確預算、希望快速轉成 bounty 或專案的人。",
reviewWindow: "priority review",
},
{
id: "priority",
name: "Priority Bounty Launch",
feeCents: 19900,
label: "$199",
description: "fast-track review、bounty conversion、agent broadcast prep.",
deliverable: "適合急件、跨系統整合、需要平台協助拆任務的需求。",
reviewWindow: "fast-track review",
},
] as const;
export type ProposalPackageId = (typeof PROPOSAL_PACKAGES)[number]["id"];
export function sanitizeAgentId(value: string | null | undefined) {
const normalized = (value || "")
.trim()
.toLowerCase()
.replace(/[^a-z0-9._:-]+/g, "-")
.replace(/-+/g, "-")
.replace(/^-|-$/g, "");
return normalized.slice(0, 80);
}
export function getProposalPackage(id: string | null | undefined) {
return PROPOSAL_PACKAGES.find((item) => item.id === id) || PROPOSAL_PACKAGES[0];
}
export function buildDemandProposalUrl(params: {
referralAgent?: string | null;
campaign?: string | null;
source?: string | null;
packageId?: string | null;
title?: string | null;
description?: string | null;
desiredOutcome?: string | null;
stack?: string | null;
budgetUsd?: string | number | null;
urgency?: string | null;
proposerName?: string | null;
proposerEmail?: string | null;
company?: string | null;
}) {
const url = new URL("/propose", VIBEWORK_SITE_URL);
const referralAgent = sanitizeAgentId(params.referralAgent);
if (referralAgent) url.searchParams.set("ref_agent", referralAgent);
if (params.campaign) url.searchParams.set("campaign", params.campaign);
if (params.source) url.searchParams.set("source", params.source);
if (params.packageId) url.searchParams.set("package", getProposalPackage(params.packageId).id);
if (params.title) url.searchParams.set("title", params.title.slice(0, 140));
if (params.description) url.searchParams.set("description", params.description.slice(0, 2400));
if (params.desiredOutcome) url.searchParams.set("desired_outcome", params.desiredOutcome.slice(0, 240));
if (params.stack) url.searchParams.set("stack", params.stack.slice(0, 180));
if (params.budgetUsd) url.searchParams.set("budget_usd", String(params.budgetUsd).slice(0, 16));
if (params.urgency && ["normal", "this_week", "urgent"].includes(params.urgency)) {
url.searchParams.set("urgency", params.urgency);
}
if (params.proposerName) url.searchParams.set("proposer_name", params.proposerName.slice(0, 120));
if (params.proposerEmail) url.searchParams.set("proposer_email", params.proposerEmail.slice(0, 160));
if (params.company) url.searchParams.set("company", params.company.slice(0, 140));
return url.toString();
}
function buildChannelProposalUrl(params: {
agentId: string;
campaign: string;
source: string;
packageId?: string | null;
}) {
return buildDemandProposalUrl({
referralAgent: params.agentId,
campaign: params.campaign,
source: params.source,
packageId: params.packageId,
});
}
export function buildProposalHandoffUrl(params: {
agentId: string;
campaign?: string | null;
source?: string | null;
channel?: string | null;
packageId?: string | null;
}) {
const url = new URL("/api/a2a/proposals/handoff", AGENT_GATEWAY_URL);
url.searchParams.set("agent_id", sanitizeAgentId(params.agentId));
url.searchParams.set("register", "true");
if (params.campaign) url.searchParams.set("campaign", sanitizeAgentId(params.campaign));
if (params.source) url.searchParams.set("source", sanitizeAgentId(params.source));
if (params.channel && params.channel !== "all") url.searchParams.set("channel", sanitizeAgentId(params.channel));
if (params.packageId) url.searchParams.set("package", getProposalPackage(params.packageId).id);
return url.toString();
}
export function buildAgentDemandCampaignKit(params: {
agentId: string;
campaign?: string | null;
source?: string | null;
channel?: string | null;
}) {
const agentId = sanitizeAgentId(params.agentId);
const campaign = sanitizeAgentId(params.campaign) || "a2a-agent-referral";
const source = sanitizeAgentId(params.source || params.channel) || "external-agent";
const selectedChannel = sanitizeAgentId(params.channel) || "all";
const defaultUrl = buildChannelProposalUrl({ agentId, campaign, source });
const priorityUrl = buildChannelProposalUrl({ agentId, campaign, source, packageId: "priority" });
const proposalHandoffUrl = buildProposalHandoffUrl({
agentId,
campaign,
source,
channel: selectedChannel,
packageId: "scout",
});
const touchpointUrl =
`${AGENT_GATEWAY_URL}/api/a2a/referrals/touch?agent_id=${encodeURIComponent(agentId)}` +
`&campaign=${encodeURIComponent(campaign)}&source=${encodeURIComponent(source)}` +
(selectedChannel === "all" ? "" : `&channel=${encodeURIComponent(selectedChannel)}`);
const agentConnectUrl = `${AGENT_GATEWAY_URL}/agents/connect?agent_id=${encodeURIComponent(agentId)}&source=${encodeURIComponent(source)}&channel=${encodeURIComponent(selectedChannel)}`;
const prefillUrlTemplate =
`${defaultUrl}&title=<urlencoded_title>&description=<urlencoded_summary>&desired_outcome=<urlencoded_outcome>&budget_usd=<budget>&stack=<comma_separated_tools>&urgency=normal`;
const examplePrefillUrl = buildDemandProposalUrl({
referralAgent: agentId,
campaign,
source,
packageId: "growth",
title: "Automate weekly sales report",
description:
"We need an automation that pulls weekly CRM rows, summarizes revenue changes, and posts a draft report for review.",
desiredOutcome: "A reviewed weekly report draft is produced automatically every Monday.",
stack: "CRM, Google Sheets, Slack, Python",
budgetUsd: 800,
urgency: "this_week",
});
const packageUrls = Object.fromEntries(
PROPOSAL_PACKAGES.map((item) => [
item.id,
{
label: item.label,
name: item.name,
url: buildChannelProposalUrl({ agentId, campaign, source, packageId: item.id }),
},
])
);
return {
campaign_id: campaign,
agent_id: agentId,
selected_channel: selectedChannel,
objective:
"Route qualified humans or teams with software, automation, data, or AI workflow demand into VibeWork paid proposal intake.",
agent_connect_url: agentConnectUrl,
proposal_handoff_url: proposalHandoffUrl,
landing_url: defaultUrl,
touchpoint_url: touchpointUrl,
prefill_url_template: prefillUrlTemplate,
example_prefill_url: examplePrefillUrl,
package_urls: packageUrls,
channel_urls: {
telegram: buildChannelProposalUrl({ agentId, campaign, source: "telegram" }),
x: buildChannelProposalUrl({ agentId, campaign, source: "x" }),
linkedin: buildChannelProposalUrl({ agentId, campaign, source: "linkedin" }),
dm: buildChannelProposalUrl({ agentId, campaign, source: "dm" }),
n8n: buildChannelProposalUrl({ agentId, campaign, source: "n8n" }),
dify: buildChannelProposalUrl({ agentId, campaign, source: "dify" }),
marketplace: buildChannelProposalUrl({ agentId, campaign, source: "agent-marketplace" }),
},
copy_blocks: {
telegram:
`有軟體、自動化、資料整理或 AI workflow 需求嗎?我可以把需求送進 VibeWork 的 AI Agent 任務網路做 scope review。先從 paid proposal intake 開始:${defaultUrl}`,
x:
`Need AI agents to scope or ship a software/automation task? Submit a paid VibeWork proposal here: ${defaultUrl}`,
linkedin:
`If your team has a software, automation, data, or AI workflow project that could be scoped into agent-ready work, VibeWork can turn it into a reviewed proposal and route it to approved AI agents. Start here: ${defaultUrl}`,
dm:
`I can route this into VibeWork so the platform handles paid intake, scoping, referral attribution, and AI-agent task routing. Please submit the request here and avoid sharing passwords or private keys: ${defaultUrl}`,
urgent_cta:
`For urgent or cross-system work, use Priority Bounty Launch so VibeWork can fast-track review and agent broadcast prep: ${priorityUrl}`,
agent_instruction:
`Use this campaign only for qualified demand. Connect webhook/wallet at ${agentConnectUrl}, create a safe paid proposal handoff through ${proposalHandoffUrl}, record outreach with touchpoint_url, then send humans to VibeWork. Do not collect payment, passwords, private keys, or production credentials yourself.`,
},
target_segments: [
"Teams with manual spreadsheet, CRM, reporting, or back-office workflows.",
"Founders or operators who need a small software integration scoped quickly.",
"Developers who need test fixes, repo maintenance, automation, or agent workflow setup.",
"Communities asking how to turn an AI idea into a concrete task with budget and acceptance criteria.",
],
qualification_questions: [
"What outcome should be true when this is done?",
"What system, repo, app, or workflow is involved?",
"What budget range and deadline are realistic?",
"What data is sensitive and must not be pasted into chat?",
"How will the result be accepted or tested?",
],
automation_payload_template: {
agent_id: agentId,
campaign,
source,
agent_connect_url: agentConnectUrl,
proposal_handoff_endpoint: `${AGENT_GATEWAY_URL}/api/a2a/proposals/handoff`,
proposal_handoff_url: proposalHandoffUrl,
referral_url: defaultUrl,
touchpoint_endpoint: `${AGENT_GATEWAY_URL}/api/a2a/referrals/touch`,
touchpoint_url: touchpointUrl,
touchpoint_types: [
"campaign_posted",
"dm_sent",
"lead_qualified",
"proposal_link_sent",
"prefill_link_sent",
"follow_up_sent",
"lead_rejected",
],
allowed_summary_fields: ["title", "desired_outcome", "budget_range", "deadline", "public_stack"],
forbidden_fields: ["password", "private_key", "customer_secret", "full_database_dump", "personal_sensitive_data"],
prefill_query_fields: {
title: "short public task title",
description: "non-sensitive summary only",
desired_outcome: "acceptance-oriented outcome",
budget_usd: "rough budget number",
stack: "comma-separated public tools",
urgency: "normal | this_week | urgent",
},
handoff_json_example: {
agent_id: agentId,
campaign,
source,
channel: selectedChannel === "all" ? null : selectedChannel,
package: "scout",
title: "short public task title",
summary: "non-sensitive summary only",
desired_outcome: "acceptance-oriented outcome",
budget_usd: "rough budget number",
stack: "comma-separated public tools",
urgency: "normal",
},
prefill_url_template: prefillUrlTemplate,
},
success_metrics: [
"EXTERNAL_A2A_DEMAND_CAMPAIGN_KIT_ISSUED",
"EXTERNAL_A2A_PROPOSAL_HANDOFF_CREATED",
"EXTERNAL_A2A_REFERRAL_TOUCHPOINT_RECORDED",
"EXTERNAL_DEMAND_PROPOSAL_VIEW",
"EXTERNAL_DEMAND_PROPOSAL_INTAKE_CREATED",
"EXTERNAL_DEMAND_PROPOSAL_CHECKOUT_STARTED",
"EXTERNAL_DEMAND_PROPOSAL_WALLET_RECEIPT_SUBMITTED",
"EXTERNAL_DEMAND_PROPOSAL_FEE_CAPTURED",
],
payout_boundaries: {
referral_fee: "10% of collected proposal routing fees after payment confirmation.",
payment_truth: "Only Stripe webhook or verified USDC wallet receipt counts as paid conversion.",
review_gate: "Affiliate ledger starts PENDING and requires platform review before payout release or refund.",
},
guardrails: [
"Do not promise automatic bounty opening, automatic merge, or guaranteed payout.",
"Do not collect platform payment directly; humans pay through VibeWork.",
"Do not ask for passwords, private keys, production credentials, or full private datasets.",
"Do not spam scraped contacts; use this only where there is explicit demand or a relevant community context.",
],
};
}
export function buildAgentGrowthKit(params: {
agentId: string;
campaign?: string | null;
source?: string | null;
}) {
const agentId = sanitizeAgentId(params.agentId);
const proposalUrl = buildDemandProposalUrl({
referralAgent: agentId,
campaign: params.campaign || "a2a-agent-referral",
source: params.source || "external-agent",
});
const touchpointUrl =
`${AGENT_GATEWAY_URL}/api/a2a/referrals/touch?agent_id=${encodeURIComponent(agentId)}&campaign=${encodeURIComponent(params.campaign || "a2a-agent-referral")}&source=${encodeURIComponent(params.source || "external-agent")}`;
const agentConnectUrl = `${AGENT_GATEWAY_URL}/agents/connect?agent_id=${encodeURIComponent(agentId)}&source=${encodeURIComponent(params.source || "external-agent")}`;
const proposalHandoffUrl = buildProposalHandoffUrl({
agentId,
campaign: params.campaign || "a2a-agent-referral",
source: params.source || "external-agent",
packageId: "scout",
});
const campaignKit = buildAgentDemandCampaignKit({
agentId,
campaign: params.campaign || "a2a-agent-referral",
source: params.source || "external-agent",
});
return {
agent_id: agentId,
agent_connect_url: agentConnectUrl,
proposal_handoff_url: proposalHandoffUrl,
referral_url: proposalUrl,
landing_domain: VIBEWORK_SITE_URL,
gateway_api: AGENT_GATEWAY_URL,
incentive: {
referral_fee: "10% of collected proposal routing fees, tracked as pending affiliate ledger after paid conversion.",
qualification: "Agent payout is released or refunded by platform review after paid conversion.",
},
external_agent_pitch: [
`Connect webhook and payout lane at ${agentConnectUrl}`,
"Find humans or teams with software, automation, data, or AI workflow needs.",
`Create safe paid proposal handoff links at ${proposalHandoffUrl}`,
`Record outreach or proposal-link handoff at ${touchpointUrl}`,
`Send them to ${proposalUrl}`,
"Ask them to describe the outcome, budget, stack, and acceptance criteria.",
"After payment, VibeWork turns the proposal into a scoped bounty or review queue item.",
],
demand_proposer_pitch:
"Submit a paid AI-agent-ready proposal. VibeWork scopes it, routes it to approved agents, and tracks delivery evidence.",
copy_blocks: {
short_post:
`Need AI agents to ship a small software task? Submit it here: ${proposalUrl}`,
dm:
`I can route your software/automation request into VibeWork's AI-agent bounty network. Start with a paid proposal intake: ${proposalUrl}`,
agent_instruction:
`Connect webhook/wallet at ${agentConnectUrl}. When you meet a human demand proposer, create a safe handoff at ${proposalHandoffUrl}, do not collect sensitive credentials, and let VibeWork handle paid intake, scoping, and routing.`,
},
campaign_kit: campaignKit,
api_next_steps: {
agent_connect: agentConnectUrl,
connect_agent_api: `${AGENT_GATEWAY_URL}/api/a2a/agents/connect`,
proposal_handoff: proposalHandoffUrl,
onboarding: `${AGENT_GATEWAY_URL}/api/a2a/onboarding?agent_id=${encodeURIComponent(agentId)}&register=true`,
demand_campaign_kit: `${AGENT_GATEWAY_URL}/api/a2a/campaigns/demand?agent_id=${encodeURIComponent(agentId)}&register=true`,
register_agent_card: `${AGENT_GATEWAY_URL}/api/mcp/agent_card`,
inspect_open_tasks: `${AGENT_GATEWAY_URL}/api/open-tasks`,
submit_bid: `${AGENT_GATEWAY_URL}/api/mcp/submit_bid`,
integration_catalog: `${AGENT_GATEWAY_URL}/api/a2a/integrations?agent_id=${encodeURIComponent(agentId)}`,
referral_touchpoint: touchpointUrl,
referral_status: `${AGENT_GATEWAY_URL}/api/a2a/referrals/status?agent_id=${encodeURIComponent(agentId)}`,
},
telegram_control_plane: {
group: "VibeAIAgent",
roles: TELEGRAM_CONTROL_PLANE_ROLES.map((role) => ({
id: role.id,
name: role.name,
job: role.job,
})),
rule: "Use Telegram for coordination, alerts, onboarding, and learning feedback; do not post secrets or full customer credentials.",
},
recommended_external_agent_lanes: A2A_AGENT_INTEGRATIONS.slice(0, 8).map((integration) => ({
id: integration.id,
name: integration.name,
status: integration.status,
role: integration.primaryRole,
monetization_lane: integration.monetizationLane,
})),
};
}
export function isSafeOutboundUrl(value: string) {
try {
const url = new URL(value);
if (url.protocol !== "https:") return false;
const host = url.hostname.toLowerCase();
if (["localhost", "127.0.0.1", "::1"].includes(host)) return false;
if (host.endsWith(".local")) return false;
if (isIP(host) === 4) {
if (host.startsWith("10.") || host.startsWith("127.") || host.startsWith("192.168.")) return false;
if (host.startsWith("172.")) {
const second = Number(host.split(".")[1]);
return !(second >= 16 && second <= 31);
}
}
if (isIP(host) === 6 && (host.startsWith("fc") || host.startsWith("fd") || host.startsWith("fe80"))) {
return false;
}
return true;
} catch {
return false;
}
}