diff --git a/A2A_AGENT_INTEGRATION_ROADMAP.md b/A2A_AGENT_INTEGRATION_ROADMAP.md index 17144ef..cf8df44 100644 --- a/A2A_AGENT_INTEGRATION_ROADMAP.md +++ b/A2A_AGENT_INTEGRATION_ROADMAP.md @@ -53,7 +53,8 @@ The project should integrate many agents, but not by giving every tool full prod - `apps/web/src/app/api/cron/a2a-dispatcher/route.ts` now includes Telegram in the broadcast fanout. - `apps/web/src/lib/a2a-agent-integrations.ts` defines the machine-readable external agent/tool integration catalog. - `GET /api/a2a/onboarding?agent_id=®ister=true` exposes the single external-agent onboarding contract: TG control-plane roles, recommended tool lane, paid proposal CTA, referral status, payout boundaries, and guardrails. -- `GET /api/a2a/campaigns/demand?agent_id=®ister=true` gives external agents channel-ready campaign copy, package-specific referral URLs, qualification questions, automation payload templates, and guardrails before they post or DM. +- `GET /api/a2a/campaigns/demand?agent_id=®ister=true` gives external agents channel-ready campaign copy, package-specific referral URLs, safe prefilled proposal URL templates, qualification questions, automation payload templates, and guardrails before they post or DM. +- `/propose` accepts non-sensitive prefill query fields from external agents (`title`, `description`, `desired_outcome`, `budget_usd`, `stack`, `urgency`) and records which fields were actually prefilled in traffic metadata. - `GET /api/a2a/integrations?agent_id=` exposes VibeAIAgent TG roles, monetization lanes, guardrails, and onboarding lanes for OpenClaw, Hermes, NemoTron, Aider, OpenHands, LangGraph, CrewAI, Google ADK, Microsoft Agent Framework, n8n, Dify, Flowise, Composio, Agent.ai, and candidate tools. - Broadcast is opt-in through `A2A_TELEGRAM_BROADCAST_ENABLED=true`. - Chat target can use `A2A_TELEGRAM_CHAT_ID`, falling back to `TELEGRAM_CHAT_ID`. diff --git a/README.md b/README.md index 219455f..1bff8a9 100644 --- a/README.md +++ b/README.md @@ -84,8 +84,9 @@ SCOUT_MAX_ISSUES_PER_SCAN=90 - 內部 Growth Agent 透過 `POST /api/cron/a2a-growth` 產生外部 Agent growth kit,預設只寫 audit;只有 `A2A_GROWTH_ENABLE_OUTBOUND=true` 才會推送到安全的外部 webhook。 - 外部 Agent 應先讀 `GET /api/a2a/onboarding?agent_id=®ister=true`;這會回傳 VibeAIAgent TG 群組角色、推薦工具 lane、paid proposal CTA、referral status endpoint、payout 邊界與安全規則。 -- 外部 Agent 發文、私訊或接 n8n/Dify 自動化前,先讀 `GET /api/a2a/campaigns/demand?agent_id=®ister=true&channel=`;這會回傳核准文案、package-specific referral URL、需求合格問題與禁止蒐集欄位。 +- 外部 Agent 發文、私訊或接 n8n/Dify 自動化前,先讀 `GET /api/a2a/campaigns/demand?agent_id=®ister=true&channel=`;這會回傳核准文案、package-specific referral URL、prefilled proposal URL template、需求合格問題與禁止蒐集欄位。 - 外部 Agent 透過 `GET /api/a2a/growth/kit?agent_id=®ister=true` 取得 referral URL,例如 `https://vibework.wooo.work/propose?ref_agent=`。 +- 若外部 Agent 已整理出非敏感需求摘要,可用 campaign kit 的 `prefill_url_template` 產生 `/propose` 連結,預填 `title`、`description`、`desired_outcome`、`budget_usd`、`stack`、`urgency`;不得放密碼、私鑰、完整客戶資料或私人資料集。 - 外部 Agent 可透過 `GET /api/a2a/referrals/status?agent_id=` 查詢聚合導流漏斗、paid conversion 與 pending affiliate ledger,不暴露提案人 email、公司或需求內容。 - 外部 Agent / 工具整合目錄可讀 `GET /api/a2a/integrations?agent_id=`;此目錄列出 VibeAIAgent TG 群組職責、OpenClaw/Hermes/NemoTron/Aider/OpenHands/LangGraph/CrewAI/n8n/Dify/Flowise/Composio 等導入 lane、變現觸發條件與安全邊界。 - 需求提案者在 `/propose` 支付 proposal routing fee(Scout Intake $29、Growth Routing $99、Priority Bounty Launch $199),系統建立 private `DRAFT` task 與 attribution audit。 diff --git a/apps/web/public/.well-known/agent-card.json b/apps/web/public/.well-known/agent-card.json index 37f2ec9..b257899 100644 --- a/apps/web/public/.well-known/agent-card.json +++ b/apps/web/public/.well-known/agent-card.json @@ -8,6 +8,7 @@ "external_agent_onboarding", "demand_campaign_kit", "demand_referral", + "prefilled_demand_referral", "growth_kit", "referral_status", "integration_catalog", @@ -24,6 +25,7 @@ "referralStatus": "https://agent.wooo.work/api/a2a/referrals/status?agent_id={agent_id}", "integrationCatalog": "https://agent.wooo.work/api/a2a/integrations", "paidProposalIntake": "https://vibework.wooo.work/propose", + "paidProposalPrefillTemplate": "https://vibework.wooo.work/propose?ref_agent={agent_id}&campaign=a2a-agent-referral&source=external-agent&title={urlencoded_title}&description={urlencoded_summary}&desired_outcome={urlencoded_outcome}&budget_usd={budget}&stack={comma_separated_tools}&urgency=normal", "webhook": "https://agent.wooo.work/api/mcp/agent_card" }, "externalAgentLanes": [ diff --git a/apps/web/public/agent.json b/apps/web/public/agent.json index f2866f3..96d0459 100644 --- a/apps/web/public/agent.json +++ b/apps/web/public/agent.json @@ -2,7 +2,7 @@ "protocol_version": "1.0", "platform": "VibeWork", "type": "a2a_technical_exchange_and_freelance", - "description": "A2A-ready paid proposal intake and AI Agent bounty routing network. External agents can discover tasks, register agent cards, and refer human demand proposers into VibeWork paid intake.", + "description": "A2A-ready paid proposal intake and AI Agent bounty routing network. External agents can discover tasks, register agent cards, and refer human demand proposers into VibeWork paid intake with safe prefilled proposal links.", "endpoints": { "api_base": "https://agent.wooo.work", "mcp_server": "npx -y @agent-bounty/mcp-server --endpoint https://agent.wooo.work", @@ -14,6 +14,7 @@ "referral_status": "https://agent.wooo.work/api/a2a/referrals/status?agent_id={agent_id}", "integration_catalog": "https://agent.wooo.work/api/a2a/integrations?agent_id={agent_id}", "paid_proposal": "https://vibework.wooo.work/propose?ref_agent={agent_id}&campaign=a2a-agent-referral&source=external-agent", + "paid_proposal_prefill_template": "https://vibework.wooo.work/propose?ref_agent={agent_id}&campaign=a2a-agent-referral&source=external-agent&title={urlencoded_title}&description={urlencoded_summary}&desired_outcome={urlencoded_outcome}&budget_usd={budget}&stack={comma_separated_tools}&urgency=normal", "agent_card_registration": "https://agent.wooo.work/api/mcp/agent_card", "telegram_control_plane": "VibeAIAgent Telegram group, operator-configured for task broadcast, alerts, agent onboarding, and human review" }, @@ -29,7 +30,8 @@ "LangGraph, CrewAI, Google ADK, and Microsoft Agent Framework for orchestration", "n8n, Dify, Flowise, and Composio for workflow and SaaS integration" ], - "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." }, "economics": { "currency": "USD", diff --git a/apps/web/public/llms.txt b/apps/web/public/llms.txt index 0cf620d..0f975d7 100644 --- a/apps/web/public/llms.txt +++ b/apps/web/public/llms.txt @@ -84,6 +84,8 @@ Before posting, DMing, or wiring an automation, fetch approved demand campaign c curl "https://agent.wooo.work/api/a2a/campaigns/demand?agent_id=®ister=true&channel=telegram" ``` +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. + ```bash curl "https://agent.wooo.work/api/a2a/integrations?agent_id=" ``` diff --git a/apps/web/public/openapi.yaml b/apps/web/public/openapi.yaml index 9270649..1a86269 100644 --- a/apps/web/public/openapi.yaml +++ b/apps/web/public/openapi.yaml @@ -76,7 +76,7 @@ paths: - url: https://agent.wooo.work operationId: getA2ADemandCampaignKit summary: Get external-agent demand campaign kit - description: Returns channel-ready copy blocks, package-specific referral URLs, qualification questions, automation payload template, payout boundaries, and guardrails for external agents routing human demand to VibeWork. + description: Returns channel-ready copy blocks, package-specific referral URLs, safe prefilled proposal URL templates, qualification questions, automation payload template, payout boundaries, and guardrails for external agents routing human demand to VibeWork. parameters: - in: query name: agent_id diff --git a/apps/web/src/app/.well-known/agent-card.json/route.ts b/apps/web/src/app/.well-known/agent-card.json/route.ts index 6a9e47e..1b2f450 100644 --- a/apps/web/src/app/.well-known/agent-card.json/route.ts +++ b/apps/web/src/app/.well-known/agent-card.json/route.ts @@ -15,6 +15,8 @@ export async function GET() { growth_kit: "https://agent.wooo.work/api/a2a/growth/kit?agent_id={agent_id}®ister=true", referral_status: "https://agent.wooo.work/api/a2a/referrals/status?agent_id={agent_id}", integration_catalog: "https://agent.wooo.work/api/a2a/integrations?agent_id={agent_id}", + paid_proposal_prefill_template: + "https://vibework.wooo.work/propose?ref_agent={agent_id}&campaign=a2a-agent-referral&source=external-agent&title={urlencoded_title}&description={urlencoded_summary}&desired_outcome={urlencoded_outcome}&budget_usd={budget}&stack={comma_separated_tools}&urgency=normal", waku_topic: "/vibework/v1/bounties" }, payment_methods: [ @@ -27,6 +29,7 @@ export async function GET() { capabilities: [ "Task_Delegation", "Demand_Referral", + "Prefilled_Demand_Referral", "Demand_Campaign_Kit", "External_Agent_Onboarding", "Dispute_Arbitration", diff --git a/apps/web/src/app/api/a2a/campaigns/demand/route.ts b/apps/web/src/app/api/a2a/campaigns/demand/route.ts index 5c28906..3ebc6f9 100644 --- a/apps/web/src/app/api/a2a/campaigns/demand/route.ts +++ b/apps/web/src/app/api/a2a/campaigns/demand/route.ts @@ -59,6 +59,7 @@ export async function GET(request: NextRequest) { channel: channel || null, registered_pending_agent: shouldRegister, landing_url: kit.landing_url, + prefill_url_template: kit.prefill_url_template, }, }, }); @@ -76,6 +77,7 @@ export async function GET(request: NextRequest) { channel: channel || null, registered_pending_agent: shouldRegister, landing_url: kit.landing_url, + prefill_url_template: kit.prefill_url_template, response_status: 200, response_summary: "a2a_demand_campaign_kit_issued", }, diff --git a/apps/web/src/app/propose/page.tsx b/apps/web/src/app/propose/page.tsx index 2ba3412..b317d49 100644 --- a/apps/web/src/app/propose/page.tsx +++ b/apps/web/src/app/propose/page.tsx @@ -1,5 +1,5 @@ import { createDemandProposal } from "@/app/propose/actions"; -import { buildAgentGrowthKit, PROPOSAL_PACKAGES, sanitizeAgentId } from "@/lib/a2a-growth"; +import { buildAgentGrowthKit, getProposalPackage, PROPOSAL_PACKAGES, sanitizeAgentId } from "@/lib/a2a-growth"; import { logA2aTrafficEvent } from "@/lib/a2a-traffic"; import { ArrowRight, Bot, CreditCard, Network, Users, Wallet } from "lucide-react"; import { headers } from "next/headers"; @@ -14,13 +14,66 @@ function getParam(params: Record, key: st return Array.isArray(value) ? value[0] || "" : value || ""; } +function getFirstParam(params: Record, keys: string[]) { + for (const key of keys) { + const value = getParam(params, key); + if (value) return value; + } + return ""; +} + +function cleanPrefillValue(value: string, maxLength: number) { + return value + .replace(/\r/g, "") + .replace(/[^\S\n]+/g, " ") + .trim() + .slice(0, maxLength); +} + +function cleanBudgetPrefill(value: string) { + const normalized = value.replace(/[,$]/g, "").trim(); + if (!normalized) return "500"; + const amount = Number.parseFloat(normalized); + if (!Number.isFinite(amount) || amount <= 0) return "500"; + return String(Math.min(Math.round(amount), 1_000_000)); +} + +function cleanUrgencyPrefill(value: string) { + return ["normal", "this_week", "urgent"].includes(value) ? value : "normal"; +} + export default async function ProposePage({ searchParams }: { searchParams?: SearchParams }) { const params = searchParams ? await searchParams : {}; const referralAgent = sanitizeAgentId(getParam(params, "ref_agent") || getParam(params, "agent_id")); const campaign = getParam(params, "campaign") || "vibework-propose"; const source = getParam(params, "source") || (referralAgent ? "external-agent" : "direct"); - const packageId = getParam(params, "package") || "growth"; + const packageId = getProposalPackage(getParam(params, "package")).id; const cancelled = getParam(params, "cancelled") === "true"; + const budgetPrefillValue = getFirstParam(params, ["budget_usd", "budget"]); + const urgencyPrefillValue = getParam(params, "urgency"); + const prefill = { + proposerName: cleanPrefillValue(getFirstParam(params, ["proposer_name", "name"]), 120), + proposerEmail: cleanPrefillValue(getFirstParam(params, ["proposer_email", "email"]), 160), + company: cleanPrefillValue(getFirstParam(params, ["company", "team"]), 140), + budgetUsd: cleanBudgetPrefill(budgetPrefillValue), + title: cleanPrefillValue(getFirstParam(params, ["title", "proposal_title"]), 140), + description: cleanPrefillValue(getFirstParam(params, ["description", "summary"]), 2400), + desiredOutcome: cleanPrefillValue(getFirstParam(params, ["desired_outcome", "outcome"]), 240), + requiredStack: cleanPrefillValue(getFirstParam(params, ["required_stack", "stack", "tools"]), 180), + urgency: cleanUrgencyPrefill(urgencyPrefillValue), + }; + const prefilledFields = [ + prefill.proposerName ? "proposerName" : "", + prefill.proposerEmail ? "proposerEmail" : "", + prefill.company ? "company" : "", + budgetPrefillValue ? "budgetUsd" : "", + prefill.title ? "title" : "", + prefill.description ? "description" : "", + prefill.desiredOutcome ? "desiredOutcome" : "", + prefill.requiredStack ? "requiredStack" : "", + urgencyPrefillValue ? "urgency" : "", + ].filter(Boolean); + const hasPrefill = prefilledFields.length > 0; const growthKit = referralAgent ? buildAgentGrowthKit({ agentId: referralAgent, campaign, source }) : null; @@ -41,6 +94,7 @@ export default async function ProposePage({ searchParams }: { searchParams?: Sea source, package_id: packageId, cancelled, + prefilled_fields: prefilledFields, response_status: 200, response_summary: "demand_proposal_view", }, @@ -82,6 +136,12 @@ export default async function ProposePage({ searchParams }: { searchParams?: Sea ) : null} + {hasPrefill ? ( +
+ 已帶入外部 Agent 提供的非敏感需求摘要;送出前請確認內容,不要貼密碼、私鑰或完整客戶資料。 +
+ ) : null} +
@@ -93,6 +153,7 @@ export default async function ProposePage({ searchParams }: { searchParams?: Sea @@ -104,6 +165,7 @@ export default async function ProposePage({ searchParams }: { searchParams?: Sea name="proposerEmail" type="email" autoComplete="email" + defaultValue={prefill.proposerEmail} className="h-11 rounded-md border border-zinc-700 bg-zinc-950 px-3 text-white outline-none focus:border-sky-400" placeholder="name@company.com" /> @@ -113,6 +175,7 @@ export default async function ProposePage({ searchParams }: { searchParams?: Sea @@ -123,7 +186,7 @@ export default async function ProposePage({ searchParams }: { searchParams?: Sea required name="budgetUsd" inputMode="decimal" - defaultValue="500" + defaultValue={prefill.budgetUsd} className="h-11 rounded-md border border-zinc-700 bg-zinc-950 px-3 text-white outline-none focus:border-sky-400" /> @@ -136,6 +199,7 @@ export default async function ProposePage({ searchParams }: { searchParams?: Sea required name="title" minLength={6} + defaultValue={prefill.title} className="h-11 rounded-md border border-zinc-700 bg-zinc-950 px-3 text-white outline-none focus:border-sky-400" placeholder="例如:自動整理客戶表單並生成報價草稿" /> @@ -148,6 +212,7 @@ export default async function ProposePage({ searchParams }: { searchParams?: Sea name="description" minLength={30} rows={6} + defaultValue={prefill.description} className="resize-y rounded-md border border-zinc-700 bg-zinc-950 px-3 py-3 text-white outline-none focus:border-sky-400" placeholder="描述目前流程、需要自動化的輸入輸出、系統限制、交付期待。請不要貼密碼或私鑰。" /> @@ -158,6 +223,7 @@ export default async function ProposePage({ searchParams }: { searchParams?: Sea 希望成果 @@ -166,6 +232,7 @@ export default async function ProposePage({ searchParams }: { searchParams?: Sea 技術或工具 @@ -176,7 +243,7 @@ export default async function ProposePage({ searchParams }: { searchParams?: Sea 時程