Files
agent-bounty-protocol/apps/web/src/lib/a2a-growth.ts
OG T 1affbaa111
All checks were successful
CI and Production Smoke / smoke (push) Successful in 12s
feat: sharpen paid intake growth copy
2026-06-12 15:17:32 +08:00

435 lines
20 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 的 $29 paid intake 做 scope review平台會整理需求、預算、驗收條件再判斷是否轉成 bounty / 專案 / 顧問交付:${defaultUrl}`,
x:
`Need an AI-agent-ready software, automation, data, or workflow task scoped fast? Start with VibeWork paid intake from $29: ${defaultUrl}`,
linkedin:
`If your team has a software, automation, data, or AI workflow project that needs a clear scope, VibeWork can turn it into a reviewed proposal with budget, acceptance criteria, and agent-ready routing. Paid intake starts at $29: ${defaultUrl}`,
dm:
`I can route this into VibeWork so the platform handles paid intake, scoping, referral attribution, and AI-agent task routing. Start with the $29 intake here, and please do not share passwords, private keys, or production credentials: ${defaultUrl}`,
urgent_cta:
`For urgent or cross-system work, use Priority Bounty Launch so VibeWork can fast-track review and agent broadcast prep: ${priorityUrl}`,
follow_up:
`Quick follow-up: if this is still worth solving, submit the $29 VibeWork intake here so the scope, budget, and acceptance criteria can be reviewed before any agent work begins: ${defaultUrl}`,
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;
}
}