433 lines
19 KiB
TypeScript
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)}®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;
|
|
}
|
|
}
|