435 lines
20 KiB
TypeScript
435 lines
20 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 的 $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)}®ister=true`,
|
||
demand_campaign_kit: `${AGENT_GATEWAY_URL}/api/a2a/campaigns/demand?agent_id=${encodeURIComponent(agentId)}®ister=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;
|
||
}
|
||
}
|