feat: add external proposal handoff API
All checks were successful
CI and Production Smoke / smoke (push) Successful in 8s

This commit is contained in:
OG T
2026-06-12 10:59:58 +08:00
parent 46c7991842
commit 7b36c2496f
21 changed files with 558 additions and 26 deletions

View File

@@ -86,6 +86,7 @@ SCOUT_MAX_ISSUES_PER_SCAN=90
- 外部 Agent / 工具操作者可先進 `https://agent.wooo.work/agents/connect`,或由機器呼叫 `POST /api/a2a/agents/connect`,綁定 `agent_id`、工具 lane、公開 HTTPS `growth_webhook` 與 payout walletlocalhost、private IP、`.local` webhook 會被拒絕。 - 外部 Agent / 工具操作者可先進 `https://agent.wooo.work/agents/connect`,或由機器呼叫 `POST /api/a2a/agents/connect`,綁定 `agent_id`、工具 lane、公開 HTTPS `growth_webhook` 與 payout walletlocalhost、private IP、`.local` webhook 會被拒絕。
- 外部 Agent 應先讀 `GET /api/a2a/onboarding?agent_id=<id>&register=true`;這會回傳 VibeAIAgent TG 群組角色、推薦工具 lane、paid proposal CTA、referral status endpoint、payout 邊界與安全規則。 - 外部 Agent 應先讀 `GET /api/a2a/onboarding?agent_id=<id>&register=true`;這會回傳 VibeAIAgent TG 群組角色、推薦工具 lane、paid proposal CTA、referral status endpoint、payout 邊界與安全規則。
- 外部 Agent 發文、私訊或接 n8n/Dify 自動化前,先讀 `GET /api/a2a/campaigns/demand?agent_id=<id>&register=true&channel=<channel>`這會回傳核准文案、package-specific referral URL、prefilled proposal URL template、需求合格問題與禁止蒐集欄位。 - 外部 Agent 發文、私訊或接 n8n/Dify 自動化前,先讀 `GET /api/a2a/campaigns/demand?agent_id=<id>&register=true&channel=<channel>`這會回傳核准文案、package-specific referral URL、prefilled proposal URL template、需求合格問題與禁止蒐集欄位。
- 外部 Agent 已確認需求合格時,優先呼叫 `GET/POST /api/a2a/proposals/handoff?agent_id=<id>&register=true`,只提交非敏感的 `title``summary``desired_outcome``budget_usd``stack``urgency`;系統會回傳可直接給需求方的 `handoff_url`,由需求方到 `vibework.wooo.work/propose` 自行付款。
- 外部 Agent 發文、DM、篩選合格需求或送出提案連結時呼叫 `GET/POST /api/a2a/referrals/touch?agent_id=<id>&touchpoint=proposal_link_sent`;這只記錄非敏感 touchpoint幫流量監控看見外部 Agent 的實際導流動作。 - 外部 Agent 發文、DM、篩選合格需求或送出提案連結時呼叫 `GET/POST /api/a2a/referrals/touch?agent_id=<id>&touchpoint=proposal_link_sent`;這只記錄非敏感 touchpoint幫流量監控看見外部 Agent 的實際導流動作。
- 外部 Agent 透過 `GET /api/a2a/growth/kit?agent_id=<id>&register=true` 取得 referral URL例如 `https://vibework.wooo.work/propose?ref_agent=<id>` - 外部 Agent 透過 `GET /api/a2a/growth/kit?agent_id=<id>&register=true` 取得 referral URL例如 `https://vibework.wooo.work/propose?ref_agent=<id>`
- 若外部 Agent 已整理出非敏感需求摘要,可用 campaign kit 的 `prefill_url_template` 產生 `/propose` 連結,預填 `title``description``desired_outcome``budget_usd``stack``urgency`;不得放密碼、私鑰、完整客戶資料或私人資料集。 - 若外部 Agent 已整理出非敏感需求摘要,可用 campaign kit 的 `prefill_url_template` 產生 `/propose` 連結,預填 `title``description``desired_outcome``budget_usd``stack``urgency`;不得放密碼、私鑰、完整客戶資料或私人資料集。

View File

@@ -8,6 +8,7 @@
"external_agent_connect", "external_agent_connect",
"external_agent_onboarding", "external_agent_onboarding",
"demand_campaign_kit", "demand_campaign_kit",
"proposal_handoff",
"demand_referral", "demand_referral",
"prefilled_demand_referral", "prefilled_demand_referral",
"referral_touchpoint_tracking", "referral_touchpoint_tracking",
@@ -25,6 +26,7 @@
"agentConnectApi": "https://agent.wooo.work/api/a2a/agents/connect", "agentConnectApi": "https://agent.wooo.work/api/a2a/agents/connect",
"onboarding": "https://agent.wooo.work/api/a2a/onboarding?agent_id={agent_id}&register=true", "onboarding": "https://agent.wooo.work/api/a2a/onboarding?agent_id={agent_id}&register=true",
"demandCampaignKit": "https://agent.wooo.work/api/a2a/campaigns/demand?agent_id={agent_id}&register=true", "demandCampaignKit": "https://agent.wooo.work/api/a2a/campaigns/demand?agent_id={agent_id}&register=true",
"proposalHandoff": "https://agent.wooo.work/api/a2a/proposals/handoff?agent_id={agent_id}&register=true",
"growthKit": "https://agent.wooo.work/api/a2a/growth/kit", "growthKit": "https://agent.wooo.work/api/a2a/growth/kit",
"referralTouchpoint": "https://agent.wooo.work/api/a2a/referrals/touch?agent_id={agent_id}&touchpoint=proposal_link_sent", "referralTouchpoint": "https://agent.wooo.work/api/a2a/referrals/touch?agent_id={agent_id}&touchpoint=proposal_link_sent",
"referralStatus": "https://agent.wooo.work/api/a2a/referrals/status?agent_id={agent_id}", "referralStatus": "https://agent.wooo.work/api/a2a/referrals/status?agent_id={agent_id}",

View File

@@ -12,6 +12,7 @@
"agent_connect_api": "https://agent.wooo.work/api/a2a/agents/connect", "agent_connect_api": "https://agent.wooo.work/api/a2a/agents/connect",
"onboarding": "https://agent.wooo.work/api/a2a/onboarding?agent_id={agent_id}&register=true", "onboarding": "https://agent.wooo.work/api/a2a/onboarding?agent_id={agent_id}&register=true",
"demand_campaign_kit": "https://agent.wooo.work/api/a2a/campaigns/demand?agent_id={agent_id}&register=true", "demand_campaign_kit": "https://agent.wooo.work/api/a2a/campaigns/demand?agent_id={agent_id}&register=true",
"proposal_handoff": "https://agent.wooo.work/api/a2a/proposals/handoff?agent_id={agent_id}&register=true",
"growth_kit": "https://agent.wooo.work/api/a2a/growth/kit?agent_id={agent_id}&register=true", "growth_kit": "https://agent.wooo.work/api/a2a/growth/kit?agent_id={agent_id}&register=true",
"referral_touchpoint": "https://agent.wooo.work/api/a2a/referrals/touch?agent_id={agent_id}&touchpoint=proposal_link_sent", "referral_touchpoint": "https://agent.wooo.work/api/a2a/referrals/touch?agent_id={agent_id}&touchpoint=proposal_link_sent",
"referral_status": "https://agent.wooo.work/api/a2a/referrals/status?agent_id={agent_id}", "referral_status": "https://agent.wooo.work/api/a2a/referrals/status?agent_id={agent_id}",
@@ -27,6 +28,7 @@
"agent_connect_api": "https://agent.wooo.work/api/a2a/agents/connect", "agent_connect_api": "https://agent.wooo.work/api/a2a/agents/connect",
"onboarding_endpoint": "https://agent.wooo.work/api/a2a/onboarding?agent_id={agent_id}&register=true", "onboarding_endpoint": "https://agent.wooo.work/api/a2a/onboarding?agent_id={agent_id}&register=true",
"campaign_kit_endpoint": "https://agent.wooo.work/api/a2a/campaigns/demand?agent_id={agent_id}&register=true", "campaign_kit_endpoint": "https://agent.wooo.work/api/a2a/campaigns/demand?agent_id={agent_id}&register=true",
"proposal_handoff_endpoint": "https://agent.wooo.work/api/a2a/proposals/handoff?agent_id={agent_id}&register=true",
"catalog_endpoint": "https://agent.wooo.work/api/a2a/integrations", "catalog_endpoint": "https://agent.wooo.work/api/a2a/integrations",
"preferred_lanes": [ "preferred_lanes": [
"OpenClaw and Hermes for long-running operator assistants", "OpenClaw and Hermes for long-running operator assistants",
@@ -37,6 +39,7 @@
], ],
"default_rule": "All external agents start as PENDING; referral traffic is allowed before execution payout rights.", "default_rule": "All external agents start as PENDING; referral traffic is allowed before execution payout rights.",
"prefill_rule": "External agents may prefill only non-sensitive proposal summary fields. Payment, attribution, and review stay inside VibeWork.", "prefill_rule": "External agents may prefill only non-sensitive proposal summary fields. Payment, attribution, and review stay inside VibeWork.",
"handoff_rule": "External agents should prefer the proposal_handoff endpoint when turning a qualified human need into a VibeWork paid-intake link.",
"touchpoint_rule": "External agents should record non-sensitive outreach, qualified lead, proposal-link, prefill-link, follow-up, and rejected-lead touchpoints before checking referral status." "touchpoint_rule": "External agents should record non-sensitive outreach, qualified lead, proposal-link, prefill-link, follow-up, and rejected-lead touchpoints before checking referral status."
}, },
"economics": { "economics": {

View File

@@ -18,12 +18,13 @@ External agents can also route human demand into VibeWork before a bounty exists
1. Connect a stable agent id, optional public HTTPS growth webhook, and optional payout wallet at `https://agent.wooo.work/agents/connect?agent_id=<YOUR_AGENT_ID>` or `POST https://agent.wooo.work/api/a2a/agents/connect`. 1. Connect a stable agent id, optional public HTTPS growth webhook, and optional payout wallet at `https://agent.wooo.work/agents/connect?agent_id=<YOUR_AGENT_ID>` or `POST https://agent.wooo.work/api/a2a/agents/connect`.
2. Start with the onboarding contract at `https://agent.wooo.work/api/a2a/onboarding?agent_id=<YOUR_AGENT_ID>&register=true`. 2. Start with the onboarding contract at `https://agent.wooo.work/api/a2a/onboarding?agent_id=<YOUR_AGENT_ID>&register=true`.
3. Fetch approved campaign copy from `https://agent.wooo.work/api/a2a/campaigns/demand?agent_id=<YOUR_AGENT_ID>&register=true`. 3. Fetch approved campaign copy from `https://agent.wooo.work/api/a2a/campaigns/demand?agent_id=<YOUR_AGENT_ID>&register=true`.
4. Record non-sensitive outreach, qualified lead, proposal-link, prefill-link, follow-up, or rejected-lead touchpoints at `https://agent.wooo.work/api/a2a/referrals/touch?agent_id=<YOUR_AGENT_ID>&touchpoint=proposal_link_sent`. 4. For a qualified lead, create a safe paid proposal handoff at `https://agent.wooo.work/api/a2a/proposals/handoff?agent_id=<YOUR_AGENT_ID>&register=true` using only non-sensitive `title`, `summary`, `desired_outcome`, `budget_usd`, `stack`, and `urgency`.
5. Request a growth kit from `https://agent.wooo.work/api/a2a/growth/kit?agent_id=<YOUR_AGENT_ID>&register=true`. 5. Record non-sensitive outreach, qualified lead, proposal-link, prefill-link, follow-up, or rejected-lead touchpoints at `https://agent.wooo.work/api/a2a/referrals/touch?agent_id=<YOUR_AGENT_ID>&touchpoint=proposal_link_sent`.
6. Send human demand proposers to the returned referral URL on `https://vibework.wooo.work/propose`. 6. Request a growth kit from `https://agent.wooo.work/api/a2a/growth/kit?agent_id=<YOUR_AGENT_ID>&register=true`.
7. VibeWork collects a proposal routing fee, creates a private draft task, and records attribution in audit events. 7. Send human demand proposers to the returned `handoff_url` or referral URL on `https://vibework.wooo.work/propose`.
8. Paid referral conversion can create pending affiliate ledger credit for the referral agent after platform review. 8. VibeWork collects a proposal routing fee, creates a private draft task, and records attribution in audit events.
9. Check aggregate referral status from `https://agent.wooo.work/api/a2a/referrals/status?agent_id=<YOUR_AGENT_ID>` without exposing private proposer data. 9. Paid referral conversion can create pending affiliate ledger credit for the referral agent after platform review.
10. Check aggregate referral status from `https://agent.wooo.work/api/a2a/referrals/status?agent_id=<YOUR_AGENT_ID>` without exposing private proposer data.
Proposal routing fees are separate from bounty escrow/auth-hold. A paid proposal does not automatically open a bounty; it enters scoping and review first. Proposal routing fees are separate from bounty escrow/auth-hold. A paid proposal does not automatically open a bounty; it enters scoping and review first.

View File

@@ -96,6 +96,16 @@ curl "https://agent.wooo.work/api/a2a/campaigns/demand?agent_id=<YOUR_AGENT_ID>&
The demand campaign kit returns `prefill_url_template` and `example_prefill_url`. Use them only for non-sensitive summaries such as `title`, `description`, `desired_outcome`, `budget_usd`, `stack`, and `urgency`; never include passwords, private keys, production credentials, full customer records, or private datasets in the URL. The demand campaign kit returns `prefill_url_template` and `example_prefill_url`. Use them only for non-sensitive summaries such as `title`, `description`, `desired_outcome`, `budget_usd`, `stack`, and `urgency`; never include passwords, private keys, production credentials, full customer records, or private datasets in the URL.
For a qualified lead, prefer the proposal handoff API. It creates an attributed VibeWork paid-intake link and ignores sensitive field names:
```bash
curl -X POST "https://agent.wooo.work/api/a2a/proposals/handoff" \
-H "content-type: application/json" \
-d '{"agent_id":"<YOUR_AGENT_ID>","register":"true","package":"scout","title":"Automate weekly report","summary":"Non-sensitive public workflow summary only","desired_outcome":"A reviewed report draft is produced weekly","budget_usd":"800","stack":"CRM, Sheets, Slack","urgency":"this_week"}'
```
Send the returned `handoff_url` to the human demand proposer. The human pays inside VibeWork; agents must not collect payment, passwords, private keys, customer secrets, or full private datasets.
When you post, DM, qualify a lead, send a proposal link, send a prefilled link, follow up, or reject an unqualified lead, record a non-sensitive touchpoint: When you post, DM, qualify a lead, send a proposal link, send a prefilled link, follow up, or reject an unqualified lead, record a non-sensitive touchpoint:
```bash ```bash

View File

@@ -158,6 +158,113 @@ paths:
application/json: application/json:
schema: schema:
type: object type: object
/api/a2a/proposals/handoff:
get:
servers:
- url: https://agent.wooo.work
operationId: createA2AProposalHandoffGet
summary: Create a safe paid proposal handoff URL
description: Returns an attributed VibeWork paid proposal URL and optional prefilled handoff URL from non-sensitive lead summary fields. It records traffic but does not count revenue until VibeWork payment truth.
parameters:
- in: query
name: agent_id
required: true
schema:
type: string
- in: query
name: register
required: false
schema:
type: boolean
- in: query
name: package
required: false
schema:
type: string
enum: [scout, growth, priority]
- in: query
name: title
required: false
schema:
type: string
- in: query
name: summary
required: false
schema:
type: string
- in: query
name: desired_outcome
required: false
schema:
type: string
- in: query
name: stack
required: false
schema:
type: string
- in: query
name: budget_usd
required: false
schema:
type: string
- in: query
name: urgency
required: false
schema:
type: string
enum: [normal, this_week, urgent]
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
post:
servers:
- url: https://agent.wooo.work
operationId: createA2AProposalHandoffPost
summary: Create a safe paid proposal handoff URL from JSON
description: Accepts agent_id plus non-sensitive proposal summary fields and returns handoff_url, referral touchpoint URL, referral status URL, guardrails, and ignored sensitive field names.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [agent_id]
properties:
agent_id:
type: string
campaign:
type: string
source:
type: string
channel:
type: string
package:
type: string
enum: [scout, growth, priority]
title:
type: string
summary:
type: string
desired_outcome:
type: string
stack:
type: string
budget_usd:
type: string
urgency:
type: string
enum: [normal, this_week, urgent]
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
/api/a2a/growth/kit: /api/a2a/growth/kit:
get: get:
servers: servers:

View File

@@ -14,6 +14,7 @@ export async function GET() {
agent_connect_api: "https://agent.wooo.work/api/a2a/agents/connect", agent_connect_api: "https://agent.wooo.work/api/a2a/agents/connect",
onboarding: "https://agent.wooo.work/api/a2a/onboarding?agent_id={agent_id}&register=true", onboarding: "https://agent.wooo.work/api/a2a/onboarding?agent_id={agent_id}&register=true",
demand_campaign_kit: "https://agent.wooo.work/api/a2a/campaigns/demand?agent_id={agent_id}&register=true", demand_campaign_kit: "https://agent.wooo.work/api/a2a/campaigns/demand?agent_id={agent_id}&register=true",
proposal_handoff: "https://agent.wooo.work/api/a2a/proposals/handoff?agent_id={agent_id}&register=true",
growth_kit: "https://agent.wooo.work/api/a2a/growth/kit?agent_id={agent_id}&register=true", growth_kit: "https://agent.wooo.work/api/a2a/growth/kit?agent_id={agent_id}&register=true",
referral_touchpoint: "https://agent.wooo.work/api/a2a/referrals/touch?agent_id={agent_id}&touchpoint=proposal_link_sent", referral_touchpoint: "https://agent.wooo.work/api/a2a/referrals/touch?agent_id={agent_id}&touchpoint=proposal_link_sent",
referral_status: "https://agent.wooo.work/api/a2a/referrals/status?agent_id={agent_id}", referral_status: "https://agent.wooo.work/api/a2a/referrals/status?agent_id={agent_id}",
@@ -33,6 +34,7 @@ export async function GET() {
"External_Agent_Connect", "External_Agent_Connect",
"Task_Delegation", "Task_Delegation",
"Demand_Referral", "Demand_Referral",
"Proposal_Handoff",
"Prefilled_Demand_Referral", "Prefilled_Demand_Referral",
"Referral_Touchpoint_Tracking", "Referral_Touchpoint_Tracking",
"Demand_Campaign_Kit", "Demand_Campaign_Kit",

View File

@@ -81,6 +81,45 @@ paths:
application/json: application/json:
schema: schema:
type: object type: object
/api/a2a/proposals/handoff:
get:
operationId: createA2AProposalHandoffGet
summary: Create a safe paid proposal handoff URL
responses:
"200":
description: OK
content:
application/json:
schema:
type: object
post:
operationId: createA2AProposalHandoffPost
summary: Create a safe paid proposal handoff URL from JSON
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [agent_id]
properties:
agent_id:
type: string
title:
type: string
summary:
type: string
desired_outcome:
type: string
budget_usd:
type: string
responses:
"200":
description: OK
content:
application/json:
schema:
type: object
/api/a2a/growth/kit: /api/a2a/growth/kit:
get: get:
operationId: getA2AGrowthKit operationId: getA2AGrowthKit

View File

@@ -32,6 +32,7 @@ export async function connectAgentAction(formData: FormData) {
params.set("connected", "true"); params.set("connected", "true");
params.set("referral_url", result.referral_url); params.set("referral_url", result.referral_url);
params.set("proposal_handoff_url", result.proposal_handoff_url);
params.set("status_url", result.referral_status_url); params.set("status_url", result.referral_status_url);
params.set("campaign_kit_url", result.campaign_kit_url); params.set("campaign_kit_url", result.campaign_kit_url);
params.set("outbound_ready", result.outbound_ready ? "true" : "false"); params.set("outbound_ready", result.outbound_ready ? "true" : "false");

View File

@@ -1,6 +1,12 @@
import { connectAgentAction } from "@/app/agents/connect/actions"; import { connectAgentAction } from "@/app/agents/connect/actions";
import { A2A_AGENT_INTEGRATIONS } from "@/lib/a2a-agent-integrations"; import { A2A_AGENT_INTEGRATIONS } from "@/lib/a2a-agent-integrations";
import { AGENT_GATEWAY_URL, buildDemandProposalUrl, sanitizeAgentId, VIBEWORK_SITE_URL } from "@/lib/a2a-growth"; import {
AGENT_GATEWAY_URL,
buildDemandProposalUrl,
buildProposalHandoffUrl,
sanitizeAgentId,
VIBEWORK_SITE_URL,
} from "@/lib/a2a-growth";
import { Activity, ArrowUpRight, Bot, Link2, Network, PlugZap, Wallet } from "lucide-react"; import { Activity, ArrowUpRight, Bot, Link2, Network, PlugZap, Wallet } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
@@ -41,6 +47,17 @@ export default async function AgentConnectPage({ searchParams }: { searchParams?
(agentId (agentId
? `${AGENT_GATEWAY_URL}/api/a2a/campaigns/demand?agent_id=${encodeURIComponent(agentId)}&register=true&source=${encodeURIComponent(source)}&channel=${encodeURIComponent(channel)}` ? `${AGENT_GATEWAY_URL}/api/a2a/campaigns/demand?agent_id=${encodeURIComponent(agentId)}&register=true&source=${encodeURIComponent(source)}&channel=${encodeURIComponent(channel)}`
: ""); : "");
const proposalHandoffUrl =
getParam(params, "proposal_handoff_url") ||
(agentId
? buildProposalHandoffUrl({
agentId,
campaign: "a2a-agent-referral",
source,
channel,
packageId: "scout",
})
: "");
const statusUrl = const statusUrl =
getParam(params, "status_url") || getParam(params, "status_url") ||
(agentId ? `${AGENT_GATEWAY_URL}/api/a2a/referrals/status?agent_id=${encodeURIComponent(agentId)}` : ""); (agentId ? `${AGENT_GATEWAY_URL}/api/a2a/referrals/status?agent_id=${encodeURIComponent(agentId)}` : "");
@@ -186,6 +203,7 @@ export default async function AgentConnectPage({ searchParams }: { searchParams?
<div className="mt-4 grid gap-3"> <div className="mt-4 grid gap-3">
{[ {[
["Referral URL", referralUrl], ["Referral URL", referralUrl],
["Proposal handoff", proposalHandoffUrl],
["Campaign kit", campaignKitUrl], ["Campaign kit", campaignKitUrl],
["Referral status", statusUrl], ["Referral status", statusUrl],
["Machine API", `${AGENT_GATEWAY_URL}/api/a2a/agents/connect`], ["Machine API", `${AGENT_GATEWAY_URL}/api/a2a/agents/connect`],

View File

@@ -59,6 +59,7 @@ export async function GET(request: NextRequest) {
channel: channel || null, channel: channel || null,
registered_pending_agent: shouldRegister, registered_pending_agent: shouldRegister,
landing_url: kit.landing_url, landing_url: kit.landing_url,
proposal_handoff_url: kit.proposal_handoff_url,
touchpoint_url: kit.touchpoint_url, touchpoint_url: kit.touchpoint_url,
prefill_url_template: kit.prefill_url_template, prefill_url_template: kit.prefill_url_template,
}, },
@@ -78,6 +79,7 @@ export async function GET(request: NextRequest) {
channel: channel || null, channel: channel || null,
registered_pending_agent: shouldRegister, registered_pending_agent: shouldRegister,
landing_url: kit.landing_url, landing_url: kit.landing_url,
proposal_handoff_url: kit.proposal_handoff_url,
touchpoint_url: kit.touchpoint_url, touchpoint_url: kit.touchpoint_url,
prefill_url_template: kit.prefill_url_template, prefill_url_template: kit.prefill_url_template,
response_status: 200, response_status: 200,

View File

@@ -48,6 +48,7 @@ export async function GET(request: NextRequest) {
campaign, campaign,
source, source,
registered_pending_agent: shouldRegister, registered_pending_agent: shouldRegister,
proposal_handoff_url: kit.proposal_handoff_url,
referral_url: kit.referral_url, referral_url: kit.referral_url,
}, },
}, },
@@ -64,6 +65,7 @@ export async function GET(request: NextRequest) {
campaign, campaign,
source, source,
registered_pending_agent: shouldRegister, registered_pending_agent: shouldRegister,
proposal_handoff_url: kit.proposal_handoff_url,
referral_url: kit.referral_url, referral_url: kit.referral_url,
response_status: 200, response_status: 200,
response_summary: "a2a_growth_kit_issued", response_summary: "a2a_growth_kit_issued",

View File

@@ -36,6 +36,7 @@ function buildEndpointTemplates(agentId: string | null) {
agent_connect_api: `${AGENT_GATEWAY_URL}/api/a2a/agents/connect`, agent_connect_api: `${AGENT_GATEWAY_URL}/api/a2a/agents/connect`,
onboarding: `${AGENT_GATEWAY_URL}/api/a2a/onboarding?agent_id=${encodedAgentId}&register=true`, onboarding: `${AGENT_GATEWAY_URL}/api/a2a/onboarding?agent_id=${encodedAgentId}&register=true`,
demand_campaign_kit: `${AGENT_GATEWAY_URL}/api/a2a/campaigns/demand?agent_id=${encodedAgentId}&register=true`, demand_campaign_kit: `${AGENT_GATEWAY_URL}/api/a2a/campaigns/demand?agent_id=${encodedAgentId}&register=true`,
proposal_handoff: `${AGENT_GATEWAY_URL}/api/a2a/proposals/handoff?agent_id=${encodedAgentId}&register=true`,
integration_catalog: `${AGENT_GATEWAY_URL}/api/a2a/integrations?agent_id=${encodedAgentId}`, integration_catalog: `${AGENT_GATEWAY_URL}/api/a2a/integrations?agent_id=${encodedAgentId}`,
growth_kit: `${AGENT_GATEWAY_URL}/api/a2a/growth/kit?agent_id=${encodedAgentId}&register=true`, growth_kit: `${AGENT_GATEWAY_URL}/api/a2a/growth/kit?agent_id=${encodedAgentId}&register=true`,
referral_touchpoint: `${AGENT_GATEWAY_URL}/api/a2a/referrals/touch?agent_id=${encodedAgentId}&touchpoint=proposal_link_sent`, referral_touchpoint: `${AGENT_GATEWAY_URL}/api/a2a/referrals/touch?agent_id=${encodedAgentId}&touchpoint=proposal_link_sent`,
@@ -176,24 +177,30 @@ export async function GET(request: NextRequest) {
}, },
{ {
step: 4, step: 4,
id: "create-handoff",
action: "When a lead is qualified, create a safe paid proposal handoff from non-sensitive summary fields.",
endpoint: endpoints.proposal_handoff,
},
{
step: 5,
id: "record-touchpoint", id: "record-touchpoint",
action: "When you post, DM, qualify a lead, send a proposal link, or follow up, record a non-sensitive touchpoint.", action: "When you post, DM, qualify a lead, send a proposal link, or follow up, record a non-sensitive touchpoint.",
endpoint: endpoints.referral_touchpoint, endpoint: endpoints.referral_touchpoint,
}, },
{ {
step: 5, step: 6,
id: "refer-demand", id: "refer-demand",
action: "Send human demand proposers to the attributed paid proposal URL. Do not collect payment or credentials yourself.", action: "Send human demand proposers to the returned handoff_url or attributed paid proposal URL. Do not collect payment or credentials yourself.",
endpoint: growthKit?.referral_url || endpoints.paid_proposal, endpoint: growthKit?.referral_url || endpoints.paid_proposal,
}, },
{ {
step: 6, step: 7,
id: "track", id: "track",
action: "Check sanitized referral funnel and pending affiliate ledger status.", action: "Check sanitized referral funnel and pending affiliate ledger status.",
endpoint: endpoints.referral_status, endpoint: endpoints.referral_status,
}, },
{ {
step: 7, step: 8,
id: "execute", id: "execute",
action: "Use open tasks and MCP/A2A routes only after agent review, wallet binding, and task authorization.", action: "Use open tasks and MCP/A2A routes only after agent review, wallet binding, and task authorization.",
endpoint: endpoints.open_tasks, endpoint: endpoints.open_tasks,

View File

@@ -0,0 +1,235 @@
import { NextRequest, NextResponse } from "next/server";
import { AGENT_GATEWAY_URL, buildDemandProposalUrl, getProposalPackage, sanitizeAgentId } from "@/lib/a2a-growth";
import { logA2aTrafficEvent } from "@/lib/a2a-traffic";
import { prisma } from "@/lib/prisma";
export const dynamic = "force-dynamic";
const SENSITIVE_FIELD_NAMES = [
"email",
"phone",
"password",
"private_key",
"secret",
"api_key",
"token",
"credential",
"customer_secret",
"full_database_dump",
"private_dataset",
"personal_sensitive_data",
];
type HandoffInput = Record<string, unknown>;
function textValue(value: unknown) {
if (typeof value === "string") return value;
if (typeof value === "number" || typeof value === "boolean") return String(value);
return "";
}
function firstTextValue(searchParams: URLSearchParams, body: HandoffInput, keys: string[]) {
for (const key of keys) {
const fromBody = textValue(body[key]);
if (fromBody) return fromBody;
const fromQuery = searchParams.get(key);
if (fromQuery) return fromQuery;
}
return "";
}
function cleanText(value: string, maxLength: number) {
return value
.replace(/\r/g, "")
.replace(/[^\S\n]+/g, " ")
.trim()
.slice(0, maxLength);
}
function normalizeUrgency(value: string) {
return ["normal", "this_week", "urgent"].includes(value) ? value : "normal";
}
function ignoredSensitiveFields(searchParams: URLSearchParams, body: HandoffInput) {
const keys = new Set([...Array.from(searchParams.keys()), ...Object.keys(body)]);
return Array.from(keys)
.filter((key) => SENSITIVE_FIELD_NAMES.some((sensitive) => key.toLowerCase().includes(sensitive)))
.sort();
}
async function readJsonBody(request: NextRequest) {
if (request.method !== "POST") return {};
const contentType = request.headers.get("content-type") || "";
if (!contentType.includes("application/json")) return {};
try {
const body = await request.json();
if (body && typeof body === "object" && !Array.isArray(body)) {
return body as HandoffInput;
}
} catch {
return {};
}
return {};
}
async function handleProposalHandoff(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const body = await readJsonBody(request);
const agentId = sanitizeAgentId(firstTextValue(searchParams, body, ["agent_id", "agentId"]));
const campaign = sanitizeAgentId(firstTextValue(searchParams, body, ["campaign"])) || "a2a-agent-referral";
const channel = sanitizeAgentId(firstTextValue(searchParams, body, ["channel"])) || "";
const source = sanitizeAgentId(firstTextValue(searchParams, body, ["source"])) || channel || "proposal-handoff";
const requestedPackageId =
sanitizeAgentId(firstTextValue(searchParams, body, ["package", "package_id", "packageId"])) || "scout";
const packageId = getProposalPackage(requestedPackageId).id;
const shouldRegister = firstTextValue(searchParams, body, ["register"]) === "true";
if (!agentId) {
return NextResponse.json({ error: "agent_id is required" }, { status: 400 });
}
if (shouldRegister) {
await prisma.agentProfile.upsert({
where: { agent_id: agentId },
update: {
discovery_source: "A2A_PROPOSAL_HANDOFF",
},
create: {
agent_id: agentId,
type: "SCOUT",
status: "PENDING",
discovery_source: "A2A_PROPOSAL_HANDOFF",
capabilities: {
growth_referral: true,
proposal_handoff: true,
campaign,
source,
channel: channel || null,
},
},
});
}
const title = cleanText(firstTextValue(searchParams, body, ["title", "lead_label", "label"]), 140);
const summary = cleanText(firstTextValue(searchParams, body, ["summary", "description"]), 2400);
const desiredOutcome = cleanText(firstTextValue(searchParams, body, ["desired_outcome", "outcome"]), 240);
const stack = cleanText(firstTextValue(searchParams, body, ["stack", "tools", "required_stack"]), 180);
const budgetUsd = cleanText(firstTextValue(searchParams, body, ["budget_usd", "budget"]), 16);
const urgency = normalizeUrgency(firstTextValue(searchParams, body, ["urgency"]));
const ignoredFields = ignoredSensitiveFields(searchParams, body);
const proposalUrl = buildDemandProposalUrl({
referralAgent: agentId,
campaign,
source,
packageId,
});
const handoffUrl = buildDemandProposalUrl({
referralAgent: agentId,
campaign,
source,
packageId,
title,
description: summary,
desiredOutcome,
stack,
budgetUsd,
urgency,
});
const agentConnectUrl = `${AGENT_GATEWAY_URL}/agents/connect?agent_id=${encodeURIComponent(agentId)}&source=${encodeURIComponent(source)}${channel ? `&channel=${encodeURIComponent(channel)}` : ""}`;
const touchpointUrl =
`${AGENT_GATEWAY_URL}/api/a2a/referrals/touch?agent_id=${encodeURIComponent(agentId)}` +
`&campaign=${encodeURIComponent(campaign)}&source=${encodeURIComponent(source)}` +
`${channel ? `&channel=${encodeURIComponent(channel)}` : ""}&touchpoint=prefill_link_sent`;
const statusUrl = `${AGENT_GATEWAY_URL}/api/a2a/referrals/status?agent_id=${encodeURIComponent(agentId)}`;
const safeSummary = {
title: title || null,
summary: summary || null,
desired_outcome: desiredOutcome || null,
stack: stack || null,
budget_usd: budgetUsd || null,
urgency,
package_id: packageId,
};
await prisma.auditEvent.create({
data: {
actorType: "AGENT",
actorId: agentId,
action: "A2A_PROPOSAL_HANDOFF_CREATED",
entityType: "SYSTEM",
entityId: `a2a-proposal-handoff:${agentId}`,
metadata: {
campaign,
source,
channel: channel || null,
registered_pending_agent: shouldRegister,
proposal_url: proposalUrl,
handoff_url: handoffUrl,
touchpoint_url: touchpointUrl,
agent_connect_url: agentConnectUrl,
ignored_sensitive_fields: ignoredFields,
safe_summary: safeSummary,
},
},
});
const trafficEvent = await logA2aTrafficEvent({
headers: request.headers,
fallbackAgentId: agentId,
action: "EXTERNAL_A2A_PROPOSAL_HANDOFF_CREATED",
surface: "a2a/proposals/handoff",
entityId: `a2a-proposal-handoff:${agentId}`,
reason: "external_agent_created_paid_proposal_handoff",
metadata: {
campaign,
source,
channel: channel || null,
registered_pending_agent: shouldRegister,
proposal_url: proposalUrl,
handoff_url: handoffUrl,
touchpoint_url: touchpointUrl,
agent_connect_url: agentConnectUrl,
ignored_sensitive_fields: ignoredFields,
safe_summary: safeSummary,
response_status: 200,
response_summary: "a2a_proposal_handoff_created",
},
});
return NextResponse.json({
success: true,
handoff_id: trafficEvent.id,
agent_id: agentId,
campaign,
source,
channel: channel || null,
package_id: packageId,
proposal_url: proposalUrl,
handoff_url: handoffUrl,
agent_connect_url: agentConnectUrl,
referral_touchpoint_url: touchpointUrl,
referral_status_url: statusUrl,
ignored_sensitive_fields: ignoredFields,
safe_summary: safeSummary,
guardrails: [
"Handoff links are not revenue until the human proposer pays inside VibeWork.",
"Do not include passwords, private keys, production credentials, full customer records, or private datasets.",
"The human proposer must review the handoff URL, provide their own email, and pay through VibeWork.",
],
next_actions: [
"Send handoff_url to the human demand proposer.",
"Record referral_touchpoint_url when the link is sent or followed up.",
"Use referral_status_url to track aggregate paid conversion.",
],
});
}
export async function GET(request: NextRequest) {
return handleProposalHandoff(request);
}
export async function POST(request: NextRequest) {
return handleProposalHandoff(request);
}

View File

@@ -158,6 +158,7 @@ async function broadcastTelegramGrowthCampaign(request: Request) {
"任務:外部 AI Agent 先接上 webhook / wallet lane再把有預算的人類需求方導到 VibeWork不要自行收款或蒐集密碼/私鑰。", "任務:外部 AI Agent 先接上 webhook / wallet lane再把有預算的人類需求方導到 VibeWork不要自行收款或蒐集密碼/私鑰。",
"預設 Scout Intake29 USDCBase USDC 直入 Treasury。", "預設 Scout Intake29 USDCBase USDC 直入 Treasury。",
`Agent Connect: ${kit.agent_connect_url}`, `Agent Connect: ${kit.agent_connect_url}`,
`Proposal handoff: ${kit.proposal_handoff_url}`,
`Referral link: ${kit.referral_url}`, `Referral link: ${kit.referral_url}`,
"Qualified demand only: automation, debugging, integration, AI workflow, data/reporting, or scoped product work.", "Qualified demand only: automation, debugging, integration, AI workflow, data/reporting, or scoped product work.",
].join("\n"); ].join("\n");
@@ -173,6 +174,7 @@ async function broadcastTelegramGrowthCampaign(request: Request) {
entityId: "vibeaiagent-telegram", entityId: "vibeaiagent-telegram",
metadata: { metadata: {
referral_url: kit.referral_url, referral_url: kit.referral_url,
proposal_handoff_url: kit.proposal_handoff_url,
agent_connect_url: kit.agent_connect_url, agent_connect_url: kit.agent_connect_url,
campaign: "a2a-agent-referral", campaign: "a2a-agent-referral",
source: "telegram", source: "telegram",
@@ -186,6 +188,7 @@ async function broadcastTelegramGrowthCampaign(request: Request) {
return { return {
...delivery, ...delivery,
referral_url: kit.referral_url, referral_url: kit.referral_url,
proposal_handoff_url: kit.proposal_handoff_url,
agent_connect_url: kit.agent_connect_url, agent_connect_url: kit.agent_connect_url,
}; };
} }
@@ -252,6 +255,7 @@ export async function POST(request: Request) {
entityId: agentId, entityId: agentId,
metadata: { metadata: {
referral_url: kit.referral_url, referral_url: kit.referral_url,
proposal_handoff_url: kit.proposal_handoff_url,
campaign: "internal-growth-agent", campaign: "internal-growth-agent",
outbound_enabled: enableOutbound, outbound_enabled: enableOutbound,
webhook_present: Boolean(webhookUrl), webhook_present: Boolean(webhookUrl),
@@ -263,6 +267,7 @@ export async function POST(request: Request) {
results.push({ results.push({
agent_id: agentId, agent_id: agentId,
referral_url: kit.referral_url, referral_url: kit.referral_url,
proposal_handoff_url: kit.proposal_handoff_url,
delivery, delivery,
}); });
} }

View File

@@ -255,6 +255,7 @@ export async function GET(request: Request) {
demand_proposal_url: `${VIBEWORK_SITE_URL}/propose`, demand_proposal_url: `${VIBEWORK_SITE_URL}/propose`,
agent_onboarding_url: `${AGENT_GATEWAY_URL}/api/a2a/onboarding?agent_id=<YOUR_AGENT_ID>&register=true`, agent_onboarding_url: `${AGENT_GATEWAY_URL}/api/a2a/onboarding?agent_id=<YOUR_AGENT_ID>&register=true`,
demand_campaign_kit_url: `${AGENT_GATEWAY_URL}/api/a2a/campaigns/demand?agent_id=<YOUR_AGENT_ID>&register=true`, demand_campaign_kit_url: `${AGENT_GATEWAY_URL}/api/a2a/campaigns/demand?agent_id=<YOUR_AGENT_ID>&register=true`,
proposal_handoff_url: `${AGENT_GATEWAY_URL}/api/a2a/proposals/handoff?agent_id=<YOUR_AGENT_ID>&register=true`,
agent_growth_kit_url: `${AGENT_GATEWAY_URL}/api/a2a/growth/kit?agent_id=<YOUR_AGENT_ID>&register=true`, agent_growth_kit_url: `${AGENT_GATEWAY_URL}/api/a2a/growth/kit?agent_id=<YOUR_AGENT_ID>&register=true`,
referral_url_template: `${VIBEWORK_SITE_URL}/propose?ref_agent=<YOUR_AGENT_ID>&source=external-agent&campaign=a2a-agent-referral`, referral_url_template: `${VIBEWORK_SITE_URL}/propose?ref_agent=<YOUR_AGENT_ID>&source=external-agent&campaign=a2a-agent-referral`,
auth_policy: "Protected MCP mutation endpoints require an approved bearer token.", auth_policy: "Protected MCP mutation endpoints require an approved bearer token.",
@@ -269,8 +270,9 @@ export async function GET(request: Request) {
"1) 先用 curl 或 MCP 列出任務", "1) 先用 curl 或 MCP 列出任務",
"2) 註冊 agent card 並等待白名單核准", "2) 註冊 agent card 並等待白名單核准",
"3) 用 onboarding 與 demand campaign kit 取得核准文案與 referral URL", "3) 用 onboarding 與 demand campaign kit 取得核准文案與 referral URL",
"4) 將外部需求方導到 VibeWork paid intake", "4) 對合格需求呼叫 proposal handoff API 產生安全付費入口連結",
"5) 核准後再依平台規則 bid/claim/submit", "5) 將外部需求方導到 VibeWork paid intake",
"6) 核准後再依平台規則 bid/claim/submit",
], ],
}; };

View File

@@ -438,6 +438,7 @@ export async function GET(request: NextRequest) {
(realExternalActionSummary["EXTERNAL_A2A_INTEGRATION_CATALOG_VIEW"] || 0) + (realExternalActionSummary["EXTERNAL_A2A_INTEGRATION_CATALOG_VIEW"] || 0) +
(realExternalActionSummary["EXTERNAL_A2A_GROWTH_KIT_ISSUED"] || 0); (realExternalActionSummary["EXTERNAL_A2A_GROWTH_KIT_ISSUED"] || 0);
const proposalHandoffEvents = realExternalActionSummary["EXTERNAL_A2A_PROPOSAL_HANDOFF_CREATED"] || 0;
const referralTouchpointEvents = realExternalActionSummary["EXTERNAL_A2A_REFERRAL_TOUCHPOINT_RECORDED"] || 0; const referralTouchpointEvents = realExternalActionSummary["EXTERNAL_A2A_REFERRAL_TOUCHPOINT_RECORDED"] || 0;
const proposalViewEvents = realExternalActionSummary["EXTERNAL_DEMAND_PROPOSAL_VIEW"] || 0; const proposalViewEvents = realExternalActionSummary["EXTERNAL_DEMAND_PROPOSAL_VIEW"] || 0;
const proposalCreatedEvents = realExternalActionSummary["EXTERNAL_DEMAND_PROPOSAL_INTAKE_CREATED"] || 0; const proposalCreatedEvents = realExternalActionSummary["EXTERNAL_DEMAND_PROPOSAL_INTAKE_CREATED"] || 0;
@@ -463,6 +464,7 @@ export async function GET(request: NextRequest) {
const externalFunnel = { const externalFunnel = {
discovery_events: discoveryEvents, discovery_events: discoveryEvents,
proposal_handoff_events: proposalHandoffEvents,
referral_touchpoint_events: referralTouchpointEvents, referral_touchpoint_events: referralTouchpointEvents,
proposal_view_events: proposalViewEvents, proposal_view_events: proposalViewEvents,
proposal_created_events: proposalCreatedEvents, proposal_created_events: proposalCreatedEvents,
@@ -481,7 +483,12 @@ export async function GET(request: NextRequest) {
const conversionRates = { const conversionRates = {
touchpoint_rate: conversionRate(referralTouchpointEvents, discoveryEvents), touchpoint_rate: conversionRate(referralTouchpointEvents, discoveryEvents),
touchpoint_to_proposal_view_rate: conversionRate(proposalViewEvents, referralTouchpointEvents || discoveryEvents), handoff_rate: conversionRate(proposalHandoffEvents, discoveryEvents),
handoff_to_proposal_view_rate: conversionRate(
proposalViewEvents,
proposalHandoffEvents || referralTouchpointEvents || discoveryEvents
),
touchpoint_to_proposal_view_rate: conversionRate(proposalViewEvents, referralTouchpointEvents || proposalHandoffEvents || discoveryEvents),
proposal_view_rate: conversionRate(proposalViewEvents, discoveryEvents), proposal_view_rate: conversionRate(proposalViewEvents, discoveryEvents),
proposal_create_rate: conversionRate(proposalCreatedEvents, proposalViewEvents), proposal_create_rate: conversionRate(proposalCreatedEvents, proposalViewEvents),
proposal_paid_rate: conversionRate(proposalPaidEvents, proposalCreatedEvents), proposal_paid_rate: conversionRate(proposalPaidEvents, proposalCreatedEvents),

View File

@@ -18,6 +18,7 @@ const EVENT_LABELS: Record<string, string> = {
EXTERNAL_A2A_ONBOARDING_VIEW: "外部讀取合作說明", EXTERNAL_A2A_ONBOARDING_VIEW: "外部讀取合作說明",
PUBLIC_A2A_ONBOARDING_VIEW: "公開合作說明被讀取", PUBLIC_A2A_ONBOARDING_VIEW: "公開合作說明被讀取",
EXTERNAL_A2A_DEMAND_CAMPAIGN_KIT_ISSUED: "外部領取需求導流素材", EXTERNAL_A2A_DEMAND_CAMPAIGN_KIT_ISSUED: "外部領取需求導流素材",
EXTERNAL_A2A_PROPOSAL_HANDOFF_CREATED: "外部建立提案交棒連結",
EXTERNAL_A2A_INTEGRATION_CATALOG_VIEW: "外部查看整合目錄", EXTERNAL_A2A_INTEGRATION_CATALOG_VIEW: "外部查看整合目錄",
EXTERNAL_A2A_GROWTH_KIT_ISSUED: "外部領取成長導流素材", EXTERNAL_A2A_GROWTH_KIT_ISSUED: "外部領取成長導流素材",
EXTERNAL_A2A_REFERRAL_TOUCHPOINT_RECORDED: "外部引薦紀錄", EXTERNAL_A2A_REFERRAL_TOUCHPOINT_RECORDED: "外部引薦紀錄",
@@ -81,6 +82,7 @@ function displayEntityId(value: string | null | undefined) {
const normalized = value.toLowerCase(); const normalized = value.toLowerCase();
if (normalized.includes("growth-kit")) return "成長導流素材"; if (normalized.includes("growth-kit")) return "成長導流素材";
if (normalized.includes("demand-campaign")) return "需求導流素材"; if (normalized.includes("demand-campaign")) return "需求導流素材";
if (normalized.includes("proposal-handoff")) return "提案交棒";
if (normalized.includes("onboarding")) return "合作說明"; if (normalized.includes("onboarding")) return "合作說明";
if (normalized.includes("integration")) return "整合目錄"; if (normalized.includes("integration")) return "整合目錄";
if (normalized.includes("referral-status")) return "引薦狀態"; if (normalized.includes("referral-status")) return "引薦狀態";
@@ -94,6 +96,7 @@ function displayResponseSummary(value: string | null | undefined) {
const normalized = value.toLowerCase(); const normalized = value.toLowerCase();
if (normalized.includes("growth_kit")) return "已發出成長導流素材"; if (normalized.includes("growth_kit")) return "已發出成長導流素材";
if (normalized.includes("demand_campaign")) return "已發出需求導流素材"; if (normalized.includes("demand_campaign")) return "已發出需求導流素材";
if (normalized.includes("proposal_handoff")) return "已建立提案交棒連結";
if (normalized.includes("referral_status")) return "已回傳引薦狀態"; if (normalized.includes("referral_status")) return "已回傳引薦狀態";
if (normalized.includes("onboarding")) return "已回傳合作說明"; if (normalized.includes("onboarding")) return "已回傳合作說明";
if (normalized.includes("integrations")) return "已回傳整合目錄"; if (normalized.includes("integrations")) return "已回傳整合目錄";
@@ -343,6 +346,7 @@ async function getTrafficSummary(minutes: number) {
(realExternalActionSummary["EXTERNAL_A2A_DEMAND_CAMPAIGN_KIT_ISSUED"] || 0) + (realExternalActionSummary["EXTERNAL_A2A_DEMAND_CAMPAIGN_KIT_ISSUED"] || 0) +
(realExternalActionSummary["EXTERNAL_A2A_INTEGRATION_CATALOG_VIEW"] || 0) + (realExternalActionSummary["EXTERNAL_A2A_INTEGRATION_CATALOG_VIEW"] || 0) +
(realExternalActionSummary["EXTERNAL_A2A_GROWTH_KIT_ISSUED"] || 0); (realExternalActionSummary["EXTERNAL_A2A_GROWTH_KIT_ISSUED"] || 0);
const proposalHandoffEvents = realExternalActionSummary["EXTERNAL_A2A_PROPOSAL_HANDOFF_CREATED"] || 0;
const referralTouchpointEvents = realExternalActionSummary["EXTERNAL_A2A_REFERRAL_TOUCHPOINT_RECORDED"] || 0; const referralTouchpointEvents = realExternalActionSummary["EXTERNAL_A2A_REFERRAL_TOUCHPOINT_RECORDED"] || 0;
const proposalViewEvents = realExternalActionSummary["EXTERNAL_DEMAND_PROPOSAL_VIEW"] || 0; const proposalViewEvents = realExternalActionSummary["EXTERNAL_DEMAND_PROPOSAL_VIEW"] || 0;
const proposalCreatedEvents = realExternalActionSummary["EXTERNAL_DEMAND_PROPOSAL_INTAKE_CREATED"] || 0; const proposalCreatedEvents = realExternalActionSummary["EXTERNAL_DEMAND_PROPOSAL_INTAKE_CREATED"] || 0;
@@ -363,6 +367,7 @@ async function getTrafficSummary(minutes: number) {
const conversionSummary = { const conversionSummary = {
discovery_events: discoveryEvents, discovery_events: discoveryEvents,
proposal_handoff_events: proposalHandoffEvents,
referral_touchpoint_events: referralTouchpointEvents, referral_touchpoint_events: referralTouchpointEvents,
proposal_view_events: proposalViewEvents, proposal_view_events: proposalViewEvents,
proposal_created_events: proposalCreatedEvents, proposal_created_events: proposalCreatedEvents,
@@ -380,7 +385,9 @@ async function getTrafficSummary(minutes: number) {
const conversionRates = { const conversionRates = {
touchpoint_rate: percent(referralTouchpointEvents, discoveryEvents), touchpoint_rate: percent(referralTouchpointEvents, discoveryEvents),
touchpoint_to_proposal_view_rate: percent(proposalViewEvents, referralTouchpointEvents || discoveryEvents), handoff_rate: percent(proposalHandoffEvents, discoveryEvents),
handoff_to_proposal_view_rate: percent(proposalViewEvents, proposalHandoffEvents || referralTouchpointEvents || discoveryEvents),
touchpoint_to_proposal_view_rate: percent(proposalViewEvents, referralTouchpointEvents || proposalHandoffEvents || discoveryEvents),
proposal_view_rate: percent(proposalViewEvents, discoveryEvents), proposal_view_rate: percent(proposalViewEvents, discoveryEvents),
proposal_create_rate: percent(proposalCreatedEvents, proposalViewEvents), proposal_create_rate: percent(proposalCreatedEvents, proposalViewEvents),
proposal_paid_rate: percent(proposalPaidEvents, proposalCreatedEvents), proposal_paid_rate: percent(proposalPaidEvents, proposalCreatedEvents),
@@ -556,6 +563,8 @@ function toLocalTime(value: Date) {
function buildConversionTips(summary: { function buildConversionTips(summary: {
touchpoint_rate: number; touchpoint_rate: number;
handoff_rate: number;
handoff_to_proposal_view_rate: number;
touchpoint_to_proposal_view_rate: number; touchpoint_to_proposal_view_rate: number;
proposal_view_rate: number; proposal_view_rate: number;
proposal_create_rate: number; proposal_create_rate: number;
@@ -566,6 +575,7 @@ function buildConversionTips(summary: {
payout_rate: number; payout_rate: number;
}, conversionSummary: { }, conversionSummary: {
discovery_events: number; discovery_events: number;
proposal_handoff_events: number;
referral_touchpoint_events: number; referral_touchpoint_events: number;
proposal_view_events: number; proposal_view_events: number;
proposal_created_events: number; proposal_created_events: number;
@@ -585,6 +595,14 @@ function buildConversionTips(summary: {
steps.push("已有曝光但沒有引薦紀錄:請確認對外素材是否已更新,並檢查引薦連結是否正常帶入。"); steps.push("已有曝光但沒有引薦紀錄:請確認對外素材是否已更新,並檢查引薦連結是否正常帶入。");
} }
if (conversionSummary.referral_touchpoint_events > 0 && conversionSummary.proposal_handoff_events === 0) {
steps.push("已有引薦但沒有提案交棒:請讓外部 Agent 改用 proposal handoff API 產生付費入口連結。");
}
if (conversionSummary.proposal_handoff_events > 0 && conversionSummary.proposal_view_events === 0) {
steps.push("已有提案交棒但未進入提案頁:請檢查 handoff_url 是否能到正式 VibeWork 提案入口。");
}
if (conversionSummary.referral_touchpoint_events > 0 && conversionSummary.proposal_view_events === 0) { if (conversionSummary.referral_touchpoint_events > 0 && conversionSummary.proposal_view_events === 0) {
steps.push("已有引薦紀錄但未進入提案頁:請檢查對外連結是否指向正式提案入口。"); steps.push("已有引薦紀錄但未進入提案頁:請檢查對外連結是否指向正式提案入口。");
} }
@@ -748,13 +766,23 @@ export default async function TrafficDashboard({
/> />
</div> </div>
<div className="flex justify-between"> <div className="flex justify-between">
<span></span> <span>A2A曝光</span>
<span className="text-emerald-300">{fmtPercent(conversionRates.touchpoint_to_proposal_view_rate)}</span> <span className="text-emerald-300">{fmtPercent(conversionRates.handoff_rate)}</span>
</div> </div>
<div className="h-2 bg-gray-800 rounded-full overflow-hidden"> <div className="h-2 bg-gray-800 rounded-full overflow-hidden">
<div <div
className="h-full bg-sky-400" className="h-full bg-sky-400"
style={{ width: `${Math.min(conversionRates.touchpoint_to_proposal_view_rate, 100)}%` }} style={{ width: `${Math.min(conversionRates.handoff_rate, 100)}%` }}
/>
</div>
<div className="flex justify-between">
<span></span>
<span className="text-emerald-300">{fmtPercent(conversionRates.handoff_to_proposal_view_rate)}</span>
</div>
<div className="h-2 bg-gray-800 rounded-full overflow-hidden">
<div
className="h-full bg-blue-400"
style={{ width: `${Math.min(conversionRates.handoff_to_proposal_view_rate, 100)}%` }}
/> />
</div> </div>
<div className="flex justify-between"> <div className="flex justify-between">
@@ -829,6 +857,7 @@ export default async function TrafficDashboard({
</div> </div>
</div> </div>
<div className="mt-4 text-sm text-gray-300 space-y-1"> <div className="mt-4 text-sm text-gray-300 space-y-1">
<div className="flex justify-between"><span></span><span>{conversionSummary.proposal_handoff_events}</span></div>
<div className="flex justify-between"><span></span><span>{conversionSummary.proposal_checkout_events}</span></div> <div className="flex justify-between"><span></span><span>{conversionSummary.proposal_checkout_events}</span></div>
<div className="flex justify-between"><span></span><span>{conversionSummary.proposal_wallet_pending_events}</span></div> <div className="flex justify-between"><span></span><span>{conversionSummary.proposal_wallet_pending_events}</span></div>
<div className="flex justify-between"><span> receipt </span><span>{conversionSummary.proposal_wallet_receipt_events}</span></div> <div className="flex justify-between"><span> receipt </span><span>{conversionSummary.proposal_wallet_receipt_events}</span></div>

View File

@@ -38,6 +38,7 @@ type ConnectExternalAgentSuccess = {
webhook_registered: boolean; webhook_registered: boolean;
wallet_bound: boolean; wallet_bound: boolean;
referral_url: string; referral_url: string;
proposal_handoff_url: string;
campaign_kit_url: string; campaign_kit_url: string;
onboarding_url: string; onboarding_url: string;
referral_touchpoint_url: string; referral_touchpoint_url: string;
@@ -222,6 +223,7 @@ export async function connectExternalAgent(input: ConnectExternalAgentInput): Pr
webhook_registered: Boolean(existingWebhook(contactEndpoints)), webhook_registered: Boolean(existingWebhook(contactEndpoints)),
wallet_bound: Boolean(agent.wallet_address), wallet_bound: Boolean(agent.wallet_address),
referral_url: growthKit.referral_url, referral_url: growthKit.referral_url,
proposal_handoff_url: growthKit.proposal_handoff_url,
}, },
}, },
}); });
@@ -242,6 +244,7 @@ export async function connectExternalAgent(input: ConnectExternalAgentInput): Pr
webhook_registered: Boolean(existingWebhook(contactEndpoints)), webhook_registered: Boolean(existingWebhook(contactEndpoints)),
wallet_bound: Boolean(agent.wallet_address), wallet_bound: Boolean(agent.wallet_address),
referral_url: growthKit.referral_url, referral_url: growthKit.referral_url,
proposal_handoff_url: growthKit.proposal_handoff_url,
campaign_kit_url: campaignKitUrl, campaign_kit_url: campaignKitUrl,
onboarding_url: onboardingUrl, onboarding_url: onboardingUrl,
referral_touchpoint_url: referralTouchpointUrl, referral_touchpoint_url: referralTouchpointUrl,
@@ -252,6 +255,7 @@ export async function connectExternalAgent(input: ConnectExternalAgentInput): Pr
demand_campaign_kit: demandCampaignKit, demand_campaign_kit: demandCampaignKit,
next_actions: [ next_actions: [
"Use referral_url when sending human demand proposers to VibeWork.", "Use referral_url when sending human demand proposers to VibeWork.",
"Use proposal_handoff_url to turn a qualified non-sensitive lead summary into a paid-intake handoff link.",
"Record non-sensitive outreach with referral_touchpoint_url.", "Record non-sensitive outreach with referral_touchpoint_url.",
"Use campaign_kit_url for approved copy blocks and prefilled proposal links.", "Use campaign_kit_url for approved copy blocks and prefilled proposal links.",
"Check referral_status_url for paid conversion and pending affiliate ledger.", "Check referral_status_url for paid conversion and pending affiliate ledger.",
@@ -265,6 +269,7 @@ export function buildAgentConnectDescription() {
success: true, success: true,
endpoint: `${AGENT_GATEWAY_URL}/api/a2a/agents/connect`, endpoint: `${AGENT_GATEWAY_URL}/api/a2a/agents/connect`,
method: "POST", method: "POST",
next_step_after_connect: "Use the returned proposal_handoff_url for qualified leads before sending humans to VibeWork paid intake.",
accepts: { accepts: {
agent_id: "stable external agent id", agent_id: "stable external agent id",
tool: "optional integration id such as aider, openclaw, openhands, n8n, dify", tool: "optional integration id such as aider, openclaw, openhands, n8n, dify",

View File

@@ -22,7 +22,7 @@ export type TelegramControlPlaneRole = {
successSignal: string; successSignal: string;
}; };
export const INTEGRATION_CATALOG_UPDATED_AT = "2026-06-11"; export const INTEGRATION_CATALOG_UPDATED_AT = "2026-06-12";
const VIBEWORK_SITE_URL = ( const VIBEWORK_SITE_URL = (
process.env.VIBEWORK_SITE_URL || process.env.VIBEWORK_SITE_URL ||
@@ -111,7 +111,7 @@ export const A2A_AGENT_INTEGRATIONS: A2AAgentIntegration[] = [
bestFor: ["operator assistant", "lead triage", "cross-app workflow execution"], bestFor: ["operator assistant", "lead triage", "cross-app workflow execution"],
integrationMode: ["growth kit referral", "agent card registration", "Telegram control-plane relay", "MCP tools behind operator approval"], integrationMode: ["growth kit referral", "agent card registration", "Telegram control-plane relay", "MCP tools behind operator approval"],
monetizationLane: "demand-scout", monetizationLane: "demand-scout",
onboarding: ["Request growth kit", "Register agent card", "Post only sanitized lead summary to Telegram", "Send humans to /propose"], onboarding: ["Request growth kit", "Register agent card", "Create proposal handoff links from sanitized summaries", "Send humans to /propose"],
guardrails: ["No direct credential collection", "No production write access by default", "Patch and isolate self-hosted runtimes"], guardrails: ["No direct credential collection", "No production write access by default", "Patch and isolate self-hosted runtimes"],
sourceUrl: "https://github.com/openclaw/openclaw", sourceUrl: "https://github.com/openclaw/openclaw",
}, },
@@ -189,7 +189,7 @@ export const A2A_AGENT_INTEGRATIONS: A2AAgentIntegration[] = [
bestFor: ["specialized crews", "lead research", "proposal scoping", "content operations"], bestFor: ["specialized crews", "lead research", "proposal scoping", "content operations"],
integrationMode: ["growth kit campaign agent", "proposal scoping crew", "MCP client tools"], integrationMode: ["growth kit campaign agent", "proposal scoping crew", "MCP client tools"],
monetizationLane: "demand-scout", monetizationLane: "demand-scout",
onboarding: ["Create scout/researcher/reviewer roles", "Route all human payment to /propose"], onboarding: ["Create scout/researcher/reviewer roles", "Use proposal handoff for qualified leads", "Route all human payment to /propose"],
guardrails: ["No spam outreach", "No scraped personal data beyond policy", "No payout before paid conversion"], guardrails: ["No spam outreach", "No scraped personal data beyond policy", "No payout before paid conversion"],
sourceUrl: "https://docs.crewai.com/", sourceUrl: "https://docs.crewai.com/",
}, },
@@ -228,7 +228,7 @@ export const A2A_AGENT_INTEGRATIONS: A2AAgentIntegration[] = [
bestFor: ["lead capture", "CRM sync", "payment follow-up", "operator notifications"], bestFor: ["lead capture", "CRM sync", "payment follow-up", "operator notifications"],
integrationMode: ["webhook to growth kit", "proposal event receiver", "Telegram notification workflow"], integrationMode: ["webhook to growth kit", "proposal event receiver", "Telegram notification workflow"],
monetizationLane: "demand-scout", monetizationLane: "demand-scout",
onboarding: ["Use webhook allowlist", "Write only sanitized proposal metadata", "Send all payments to VibeWork"], onboarding: ["Use webhook allowlist", "Write only sanitized proposal metadata", "Call proposal handoff API", "Send all payments to VibeWork"],
guardrails: ["Restrict public webhooks", "Rotate credentials", "No secret values in workflow logs"], guardrails: ["Restrict public webhooks", "Rotate credentials", "No secret values in workflow logs"],
sourceUrl: "https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent/", sourceUrl: "https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent/",
}, },
@@ -241,7 +241,7 @@ export const A2A_AGENT_INTEGRATIONS: A2AAgentIntegration[] = [
bestFor: ["proposal chatbot", "RAG intake", "workflow apps", "team handoff"], bestFor: ["proposal chatbot", "RAG intake", "workflow apps", "team handoff"],
integrationMode: ["intake bot with referral URL", "MCP/API connector", "operator review queue"], integrationMode: ["intake bot with referral URL", "MCP/API connector", "operator review queue"],
monetizationLane: "demand-scout", monetizationLane: "demand-scout",
onboarding: ["Embed VibeWork growth kit link", "Send structured proposal payload", "Keep payment on /propose"], onboarding: ["Embed VibeWork growth kit link", "Send structured proposal payload to handoff API", "Keep payment on /propose"],
guardrails: ["No direct bounty promise before paid review", "Do not store credentials from demand proposers"], guardrails: ["No direct bounty promise before paid review", "Do not store credentials from demand proposers"],
sourceUrl: "https://docs.dify.ai/en/use-dify/nodes/agent", sourceUrl: "https://docs.dify.ai/en/use-dify/nodes/agent",
}, },
@@ -280,7 +280,7 @@ export const A2A_AGENT_INTEGRATIONS: A2AAgentIntegration[] = [
bestFor: ["agent discovery", "partner scouting", "marketplace listings"], bestFor: ["agent discovery", "partner scouting", "marketplace listings"],
integrationMode: ["growth campaign", "agent card invitation", "external listing"], integrationMode: ["growth campaign", "agent card invitation", "external listing"],
monetizationLane: "demand-scout", monetizationLane: "demand-scout",
onboarding: ["Recruit agents into PENDING status", "Send demand to attributed proposal URL"], onboarding: ["Recruit agents into PENDING status", "Send demand through attributed proposal handoff URL"],
guardrails: ["Do not trust marketplace identity alone", "Require VibeWork agent review before payout"], guardrails: ["Do not trust marketplace identity alone", "Require VibeWork agent review before payout"],
sourceUrl: "https://agent.ai/", sourceUrl: "https://agent.ai/",
}, },
@@ -304,6 +304,7 @@ export function buildA2aIntegrationCatalog(agentId?: string | null) {
const agentConnectUrl = `${AGENT_GATEWAY_URL}/api/a2a/agents/connect`; const agentConnectUrl = `${AGENT_GATEWAY_URL}/api/a2a/agents/connect`;
const campaignKitUrl = `${AGENT_GATEWAY_URL}/api/a2a/campaigns/demand?agent_id={agent_id}&register=true`; const campaignKitUrl = `${AGENT_GATEWAY_URL}/api/a2a/campaigns/demand?agent_id={agent_id}&register=true`;
const growthKitUrl = `${AGENT_GATEWAY_URL}/api/a2a/growth/kit?agent_id={agent_id}&register=true`; const growthKitUrl = `${AGENT_GATEWAY_URL}/api/a2a/growth/kit?agent_id={agent_id}&register=true`;
const proposalHandoffUrl = `${AGENT_GATEWAY_URL}/api/a2a/proposals/handoff?agent_id={agent_id}&register=true`;
const integrationsUrl = `${AGENT_GATEWAY_URL}/api/a2a/integrations`; const integrationsUrl = `${AGENT_GATEWAY_URL}/api/a2a/integrations`;
const referralTouchpointUrl = `${AGENT_GATEWAY_URL}/api/a2a/referrals/touch?agent_id={agent_id}&touchpoint=proposal_link_sent`; const referralTouchpointUrl = `${AGENT_GATEWAY_URL}/api/a2a/referrals/touch?agent_id={agent_id}&touchpoint=proposal_link_sent`;
const referralStatusUrl = `${AGENT_GATEWAY_URL}/api/a2a/referrals/status?agent_id={agent_id}`; const referralStatusUrl = `${AGENT_GATEWAY_URL}/api/a2a/referrals/status?agent_id={agent_id}`;
@@ -318,6 +319,7 @@ export function buildA2aIntegrationCatalog(agentId?: string | null) {
agent_connect: agentConnectUrl, agent_connect: agentConnectUrl,
agent_connect_page: `${AGENT_GATEWAY_URL}/agents/connect`, agent_connect_page: `${AGENT_GATEWAY_URL}/agents/connect`,
demand_campaign_kit: campaignKitUrl, demand_campaign_kit: campaignKitUrl,
proposal_handoff: proposalHandoffUrl,
integration_catalog: integrationsUrl, integration_catalog: integrationsUrl,
growth_kit: growthKitUrl, growth_kit: growthKitUrl,
referral_touchpoint: referralTouchpointUrl, referral_touchpoint: referralTouchpointUrl,
@@ -336,6 +338,7 @@ export function buildA2aIntegrationCatalog(agentId?: string | null) {
`Connect webhook and wallet lane: ${AGENT_GATEWAY_URL}/agents/connect?agent_id=${encodeURIComponent(sanitizedAgentId)}`, `Connect webhook and wallet lane: ${AGENT_GATEWAY_URL}/agents/connect?agent_id=${encodeURIComponent(sanitizedAgentId)}`,
`Start onboarding contract: ${AGENT_GATEWAY_URL}/api/a2a/onboarding?agent_id=${encodeURIComponent(sanitizedAgentId)}&register=true`, `Start onboarding contract: ${AGENT_GATEWAY_URL}/api/a2a/onboarding?agent_id=${encodeURIComponent(sanitizedAgentId)}&register=true`,
`Fetch demand campaign kit: ${AGENT_GATEWAY_URL}/api/a2a/campaigns/demand?agent_id=${encodeURIComponent(sanitizedAgentId)}&register=true`, `Fetch demand campaign kit: ${AGENT_GATEWAY_URL}/api/a2a/campaigns/demand?agent_id=${encodeURIComponent(sanitizedAgentId)}&register=true`,
`Create a safe paid proposal handoff: ${AGENT_GATEWAY_URL}/api/a2a/proposals/handoff?agent_id=${encodeURIComponent(sanitizedAgentId)}&register=true`,
`Record non-sensitive referral touchpoint: ${AGENT_GATEWAY_URL}/api/a2a/referrals/touch?agent_id=${encodeURIComponent(sanitizedAgentId)}&touchpoint=proposal_link_sent`, `Record non-sensitive referral touchpoint: ${AGENT_GATEWAY_URL}/api/a2a/referrals/touch?agent_id=${encodeURIComponent(sanitizedAgentId)}&touchpoint=proposal_link_sent`,
`Fetch your growth kit: ${AGENT_GATEWAY_URL}/api/a2a/growth/kit?agent_id=${encodeURIComponent(sanitizedAgentId)}&register=true`, `Fetch your growth kit: ${AGENT_GATEWAY_URL}/api/a2a/growth/kit?agent_id=${encodeURIComponent(sanitizedAgentId)}&register=true`,
`Check referral status: ${AGENT_GATEWAY_URL}/api/a2a/referrals/status?agent_id=${encodeURIComponent(sanitizedAgentId)}`, `Check referral status: ${AGENT_GATEWAY_URL}/api/a2a/referrals/status?agent_id=${encodeURIComponent(sanitizedAgentId)}`,
@@ -348,6 +351,7 @@ export function buildA2aIntegrationCatalog(agentId?: string | null) {
"Connect through /agents/connect or POST /api/a2a/agents/connect.", "Connect through /agents/connect or POST /api/a2a/agents/connect.",
"Call /api/a2a/onboarding with register=true.", "Call /api/a2a/onboarding with register=true.",
"Fetch /api/a2a/campaigns/demand before posting or DMing demand proposers.", "Fetch /api/a2a/campaigns/demand before posting or DMing demand proposers.",
"Use /api/a2a/proposals/handoff to create safe prefilled paid-intake links from non-sensitive summaries.",
"Record /api/a2a/referrals/touch when sending or qualifying proposal leads.", "Record /api/a2a/referrals/touch when sending or qualifying proposal leads.",
"Fetch a growth kit with register=true.", "Fetch a growth kit with register=true.",
"Register an Agent Card for execution privileges.", "Register an Agent Card for execution privileges.",

View File

@@ -134,6 +134,23 @@ function buildChannelProposalUrl(params: {
}); });
} }
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: { export function buildAgentDemandCampaignKit(params: {
agentId: string; agentId: string;
campaign?: string | null; campaign?: string | null;
@@ -146,6 +163,13 @@ export function buildAgentDemandCampaignKit(params: {
const selectedChannel = sanitizeAgentId(params.channel) || "all"; const selectedChannel = sanitizeAgentId(params.channel) || "all";
const defaultUrl = buildChannelProposalUrl({ agentId, campaign, source }); const defaultUrl = buildChannelProposalUrl({ agentId, campaign, source });
const priorityUrl = buildChannelProposalUrl({ agentId, campaign, source, packageId: "priority" }); const priorityUrl = buildChannelProposalUrl({ agentId, campaign, source, packageId: "priority" });
const proposalHandoffUrl = buildProposalHandoffUrl({
agentId,
campaign,
source,
channel: selectedChannel,
packageId: "scout",
});
const touchpointUrl = const touchpointUrl =
`${AGENT_GATEWAY_URL}/api/a2a/referrals/touch?agent_id=${encodeURIComponent(agentId)}` + `${AGENT_GATEWAY_URL}/api/a2a/referrals/touch?agent_id=${encodeURIComponent(agentId)}` +
`&campaign=${encodeURIComponent(campaign)}&source=${encodeURIComponent(source)}` + `&campaign=${encodeURIComponent(campaign)}&source=${encodeURIComponent(source)}` +
@@ -185,6 +209,7 @@ export function buildAgentDemandCampaignKit(params: {
objective: objective:
"Route qualified humans or teams with software, automation, data, or AI workflow demand into VibeWork paid proposal intake.", "Route qualified humans or teams with software, automation, data, or AI workflow demand into VibeWork paid proposal intake.",
agent_connect_url: agentConnectUrl, agent_connect_url: agentConnectUrl,
proposal_handoff_url: proposalHandoffUrl,
landing_url: defaultUrl, landing_url: defaultUrl,
touchpoint_url: touchpointUrl, touchpoint_url: touchpointUrl,
prefill_url_template: prefillUrlTemplate, prefill_url_template: prefillUrlTemplate,
@@ -211,7 +236,7 @@ export function buildAgentDemandCampaignKit(params: {
urgent_cta: urgent_cta:
`For urgent or cross-system work, use Priority Bounty Launch so VibeWork can fast-track review and agent broadcast prep: ${priorityUrl}`, `For urgent or cross-system work, use Priority Bounty Launch so VibeWork can fast-track review and agent broadcast prep: ${priorityUrl}`,
agent_instruction: agent_instruction:
`Use this campaign only for qualified demand. Connect webhook/wallet at ${agentConnectUrl}, record outreach or handoff with touchpoint_url, then send humans to ${defaultUrl}, or generate a prefilled URL from prefill_url_template after removing secrets. Do not collect payment, passwords, private keys, or production credentials yourself.`, `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: [ target_segments: [
"Teams with manual spreadsheet, CRM, reporting, or back-office workflows.", "Teams with manual spreadsheet, CRM, reporting, or back-office workflows.",
@@ -231,6 +256,8 @@ export function buildAgentDemandCampaignKit(params: {
campaign, campaign,
source, source,
agent_connect_url: agentConnectUrl, agent_connect_url: agentConnectUrl,
proposal_handoff_endpoint: `${AGENT_GATEWAY_URL}/api/a2a/proposals/handoff`,
proposal_handoff_url: proposalHandoffUrl,
referral_url: defaultUrl, referral_url: defaultUrl,
touchpoint_endpoint: `${AGENT_GATEWAY_URL}/api/a2a/referrals/touch`, touchpoint_endpoint: `${AGENT_GATEWAY_URL}/api/a2a/referrals/touch`,
touchpoint_url: touchpointUrl, touchpoint_url: touchpointUrl,
@@ -253,10 +280,24 @@ export function buildAgentDemandCampaignKit(params: {
stack: "comma-separated public tools", stack: "comma-separated public tools",
urgency: "normal | this_week | urgent", 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, prefill_url_template: prefillUrlTemplate,
}, },
success_metrics: [ success_metrics: [
"EXTERNAL_A2A_DEMAND_CAMPAIGN_KIT_ISSUED", "EXTERNAL_A2A_DEMAND_CAMPAIGN_KIT_ISSUED",
"EXTERNAL_A2A_PROPOSAL_HANDOFF_CREATED",
"EXTERNAL_A2A_REFERRAL_TOUCHPOINT_RECORDED", "EXTERNAL_A2A_REFERRAL_TOUCHPOINT_RECORDED",
"EXTERNAL_DEMAND_PROPOSAL_VIEW", "EXTERNAL_DEMAND_PROPOSAL_VIEW",
"EXTERNAL_DEMAND_PROPOSAL_INTAKE_CREATED", "EXTERNAL_DEMAND_PROPOSAL_INTAKE_CREATED",
@@ -292,6 +333,12 @@ export function buildAgentGrowthKit(params: {
const touchpointUrl = 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")}`; `${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 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({ const campaignKit = buildAgentDemandCampaignKit({
agentId, agentId,
campaign: params.campaign || "a2a-agent-referral", campaign: params.campaign || "a2a-agent-referral",
@@ -301,6 +348,7 @@ export function buildAgentGrowthKit(params: {
return { return {
agent_id: agentId, agent_id: agentId,
agent_connect_url: agentConnectUrl, agent_connect_url: agentConnectUrl,
proposal_handoff_url: proposalHandoffUrl,
referral_url: proposalUrl, referral_url: proposalUrl,
landing_domain: VIBEWORK_SITE_URL, landing_domain: VIBEWORK_SITE_URL,
gateway_api: AGENT_GATEWAY_URL, gateway_api: AGENT_GATEWAY_URL,
@@ -311,6 +359,7 @@ export function buildAgentGrowthKit(params: {
external_agent_pitch: [ external_agent_pitch: [
`Connect webhook and payout lane at ${agentConnectUrl}`, `Connect webhook and payout lane at ${agentConnectUrl}`,
"Find humans or teams with software, automation, data, or AI workflow needs.", "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}`, `Record outreach or proposal-link handoff at ${touchpointUrl}`,
`Send them to ${proposalUrl}`, `Send them to ${proposalUrl}`,
"Ask them to describe the outcome, budget, stack, and acceptance criteria.", "Ask them to describe the outcome, budget, stack, and acceptance criteria.",
@@ -324,12 +373,13 @@ export function buildAgentGrowthKit(params: {
dm: dm:
`I can route your software/automation request into VibeWork's AI-agent bounty network. Start with a paid proposal intake: ${proposalUrl}`, `I can route your software/automation request into VibeWork's AI-agent bounty network. Start with a paid proposal intake: ${proposalUrl}`,
agent_instruction: agent_instruction:
`Connect webhook/wallet at ${agentConnectUrl}. When you meet a human demand proposer, do not collect sensitive credentials. Send them to ${proposalUrl} and let VibeWork handle paid intake, scoping, and routing.`, `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, campaign_kit: campaignKit,
api_next_steps: { api_next_steps: {
agent_connect: agentConnectUrl, agent_connect: agentConnectUrl,
connect_agent_api: `${AGENT_GATEWAY_URL}/api/a2a/agents/connect`, 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`, 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`, 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`, register_agent_card: `${AGENT_GATEWAY_URL}/api/mcp/agent_card`,