feat: add test agent, python sdk, and external traffic validator
Some checks failed
Deploy to 110 WOOO Server / deploy (push) Failing after 9s

This commit is contained in:
OG T
2026-06-08 14:12:56 +08:00
parent 0d4d694201
commit 36ea11ea0f
13 changed files with 1469 additions and 61 deletions

4
.gitignore vendored
View File

@@ -33,5 +33,9 @@ build
.env.*
!.env.example
# Python
venv/
.venv/
# E2B / MCP specific
.e2b

88
apps/test-agent/index.ts Normal file
View File

@@ -0,0 +1,88 @@
import { VibeWorkAgentSDK } from '@vibework/agent-sdk';
import { ClaimTaskResponse, SubmitSolutionRequest, TaskBounty } from '@vibework/agent-sdk';
import 'dotenv/config';
function resolveEnv(name: string, fallback: string): string {
return process.env[name]?.trim() || fallback;
}
async function main() {
console.log("🤖 Starting VibeWork Test Agent...");
const baseUrl = resolveEnv('VIBEWORK_API_URL', 'http://localhost:3000');
const apiKey = process.env.VIBEWORK_API_KEY;
const agentId = resolveEnv('VIBEWORK_AGENT_ID', 'test-hunter-bot-001');
const wallet = resolveEnv('VIBEWORK_AGENT_WALLET', '0x1234567890abcdef1234567890abcdef12345678');
const githubPrUrl = resolveEnv(
'VIBEWORK_PR_URL',
'https://github.com/agent-bounty-protocol/pr/123'
);
const iterationLimit = Number(process.env.VIBEWORK_MAX_ITERATIONS ?? '1');
const sleepMs = Number(process.env.VIBEWORK_SIMULATE_WORK_MS ?? '3000');
const sdk = new VibeWorkAgentSDK({
baseUrl,
apiKey,
});
try {
console.log("📝 Registering Agent Identity...");
const registerResult = await sdk.identity.registerAgent({
agent_id: agentId,
name: resolveEnv("VIBEWORK_AGENT_NAME", "HunterBot-Test"),
description:
"A test agent built with @vibework/agent-sdk to hunt for bounties autonomously.",
supported_models: ["gpt-4o"],
skills: ["typescript", "javascript", "react", "testing"],
max_concurrent_tasks: 3,
x402_wallet_address: wallet,
});
console.log(`✅ Registered successfully: ${registerResult.message}`);
let iteration = 0;
while (iteration < iterationLimit) {
iteration += 1;
console.log(`\n[Cycle ${iteration}] 🔍 Scanning for open bounties...`);
const openBounties = await sdk.tasks.listOpenBounties(5);
console.log(`🎯 Found ${openBounties.length} open bounties.`);
if (!openBounties.length) {
console.log("😴 No open bounties found. Exit.");
return;
}
const targetTask = openBounties[0] as TaskBounty;
console.log(
`📌 Target: [${targetTask.task_id}] ${targetTask.title} (Reward: ${
targetTask.reward?.display_amount ?? targetTask.reward_display ?? 'n/a'
})`
);
const claimResult: ClaimTaskResponse = await sdk.tasks.claimBounty(targetTask.task_id, agentId, wallet);
console.log(`✅ Bounty claimed. Claim token prefix: ${claimResult.claim_token.slice(0, 10)}...`);
console.log(`⏳ Working on task ${targetTask.task_id}...`);
await new Promise((resolve) => setTimeout(resolve, sleepMs));
const submitPayload: SubmitSolutionRequest = {
task_id: targetTask.task_id,
claim_token: claimResult.claim_token,
deliverables: {
'README.md': 'Completed the task from the automated test agent.',
'solution.diff': 'noop',
},
github_pr_url: githubPrUrl,
};
const submitResult = await sdk.tasks.submitWork(submitPayload);
console.log(`🎉 Submit done. Status: ${submitResult.status}, submission_id=${submitResult.submission_id}`);
}
console.log("🤖 Agent cycles complete.");
} catch (err: any) {
console.error("❌ Agent encountered an error:", err?.response?.data || err.message || err);
}
}
main().catch((err) => {
console.error(err);
});

View File

@@ -0,0 +1,23 @@
{
"name": "test-agent",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "tsx index.ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"packageManager": "pnpm@10.32.1",
"dependencies": {
"@vibework/agent-sdk": "workspace:*",
"dotenv": "^17.4.2"
},
"devDependencies": {
"@types/node": "^25.9.2",
"tsx": "^4.22.4",
"typescript": "^6.0.3"
}
}

View File

@@ -0,0 +1,49 @@
# @vibework/agent-sdk (Python)
This package helps external AI Agents integrate with VibeWork without directly touching MCP or X402 details.
## Install
```bash
pip install .
```
## Quick Start
```python
from vibework_agent_sdk.client import VibeWorkAgentSDK
sdk = VibeWorkAgentSDK(
base_url="https://agent.wooo.work",
api_key="YOUR_API_KEY",
)
# 1) Register / update agent profile
sdk.identity.register_agent(
agent_id="agent-001",
name="MyAgent",
description="Auto coder for bounty tasks.",
skills=["python", "backend", "testing"],
)
# 2) Find open bounties
tasks = sdk.tasks.list_open_bounties(limit=5)
print(len(tasks), "tasks")
# 3) Claim and submit
if tasks:
claim = sdk.tasks.claim_bounty(task_id=tasks[0].task_id, agent_id="agent-001")
sdk.tasks.submit_solution(
task_id=tasks[0].task_id,
claim_token=claim.claim_token,
deliverables={"README.md": "done"},
github_pr_url="https://github.com/example/repo/pull/123",
)
```
## API Surface
- `identity.register_agent(...)`
- `tasks.list_open_bounties(limit=10, skills=None, difficulty=None)`
- `tasks.claim_bounty(task_id, agent_id, developer_wallet)`
- `tasks.submit_solution(task_id, claim_token, deliverables, github_pr_url=None)`

View File

@@ -0,0 +1,32 @@
[build-system]
requires = ["setuptools>=64", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "vibework-agent-sdk"
version = "0.1.0"
description = "Official SDK for AI agents to interact with VibeWork A2A workflow APIs."
readme = "README.md"
requires-python = ">=3.10"
license = "ISC"
authors = [{ name = "VibeWork Team" }]
dependencies = [
"requests>=2.31,<3.0",
"pydantic>=2.6,<3.0",
]
classifiers = [
"License :: OSI Approved :: ISC License (ISCL)",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
]
[project.urls]
Homepage = "https://agent.wooo.work"
Repository = "https://github.com/agent-bounty-protocol"
[tool.setuptools]
package-dir = {"" = "."}
[tool.setuptools.packages.find]
where = ["."]

View File

@@ -0,0 +1,20 @@
from .client import VibeWorkAgentSDK, VibeWorkApiError
from .models import (
AgentCard,
ClaimTaskResponse,
ClaimTaskRequest,
SubmitSolutionRequest,
SubmitSolutionResponse,
TaskBounty,
)
__all__ = [
"VibeWorkAgentSDK",
"VibeWorkApiError",
"AgentCard",
"ClaimTaskRequest",
"ClaimTaskResponse",
"SubmitSolutionRequest",
"SubmitSolutionResponse",
"TaskBounty",
]

View File

@@ -0,0 +1,127 @@
from __future__ import annotations
import uuid
from dataclasses import dataclass
from typing import Dict, List, Optional
import requests
from .models import (
AgentCard,
ClaimTaskRequest,
ClaimTaskResponse,
SubmitSolutionRequest,
SubmitSolutionResponse,
TaskBounty,
)
class VibeWorkApiError(RuntimeError):
def __init__(self, message: str, status_code: int | None = None, payload: object | None = None):
super().__init__(message)
self.status_code = status_code
self.payload = payload
@dataclass
class IdentityModule:
client: "VibeWorkAgentSDK"
def register_agent(self, card: AgentCard) -> Dict[str, str]:
payload = card.model_dump(exclude_none=True)
response = self.client._request("post", "/api/mcp/agent_card", payload={"card": payload})
return response
@dataclass
class TasksModule:
client: "VibeWorkAgentSDK"
def list_open_bounties(
self,
limit: int = 10,
skills: Optional[List[str]] = None,
difficulty: Optional[str] = None,
) -> List[TaskBounty]:
response = self.client._request(
"get",
"/api/open-tasks",
params={"limit": str(min(max(limit, 1), 20))} if limit else None,
)
tasks = response.get("tasks", [])
return [TaskBounty.model_validate(item) for item in tasks]
def claim_bounty(
self,
task_id: str,
agent_id: str,
developer_wallet: str,
) -> ClaimTaskResponse:
payload = ClaimTaskRequest(
task_id=task_id,
agent_id=agent_id,
developer_wallet=developer_wallet,
).model_dump()
response = self.client._request("post", "/api/mcp/claim_task", payload=payload)
return ClaimTaskResponse.model_validate(response)
def submit_solution(
self,
task_id: str,
claim_token: str,
deliverables: Dict[str, str],
github_pr_url: Optional[str] = None,
) -> SubmitSolutionResponse:
payload = SubmitSolutionRequest(
task_id=task_id,
claim_token=claim_token,
deliverables=deliverables,
github_pr_url=github_pr_url,
)
response = self.client._request("post", "/api/mcp/submit_solution", payload=payload.model_dump())
return SubmitSolutionResponse.model_validate(response)
class VibeWorkAgentSDK:
def __init__(self, base_url: str = "https://agent.wooo.work", api_key: str | None = None):
self.base_url = base_url.rstrip("/")
self.http = requests.Session()
self.http.headers.update({"Content-Type": "application/json"})
if api_key:
self.http.headers.update({"Authorization": f"Bearer {api_key}"})
self.identity = IdentityModule(self)
self.tasks = TasksModule(self)
def _request(
self,
method: str,
path: str,
payload: Optional[dict] = None,
params: Optional[dict] = None,
) -> dict:
url = f"{self.base_url}{path}"
headers = {"x-request-id": str(uuid.uuid4())}
request_method = self.http.request
response = request_method(
method=method.upper(),
url=url,
json=payload,
params=params,
headers=headers,
timeout=30,
)
if response.status_code < 200 or response.status_code >= 300:
try:
body = response.json()
except ValueError:
body = response.text
raise VibeWorkApiError(
f"{method.upper()} {path} failed with status {response.status_code}",
status_code=response.status_code,
payload=body,
)
return response.json()

View File

@@ -0,0 +1,66 @@
from __future__ import annotations
from typing import Dict, List, Literal, Optional
from pydantic import BaseModel, Field, HttpUrl
SupportedCurrency = Literal["USD", "TWD", "USDC"]
class AgentCard(BaseModel):
agent_id: str = Field(min_length=1)
name: str = Field(min_length=2, max_length=100)
description: Optional[str] = Field(default=None, max_length=1000)
supported_models: List[str] = Field(default_factory=lambda: ["gpt-4o"])
skills: List[str] = Field(min_length=1)
max_concurrent_tasks: int = Field(default=5, ge=1, le=100)
x402_wallet_address: Optional[str] = None
class TaskReward(BaseModel):
amount: int
currency: SupportedCurrency
display_amount: Optional[str] = None
class TaskBounty(BaseModel):
task_id: str
title: str
description: Optional[str] = None
description_preview: Optional[str] = None
reward_amount_cents: Optional[int] = None
reward_display: Optional[str] = None
reward: Optional[TaskReward] = None
status: str
required_stack: List[str] = Field(default_factory=list)
difficulty: Optional[str] = None
scope_clarity_score: Optional[float] = None
class ClaimTaskRequest(BaseModel):
task_id: str
agent_id: str
developer_wallet: str
class ClaimTaskResponse(BaseModel):
task_id: str
status: Literal["EXECUTING"]
held_amount: int
held_currency: Literal["USD", "TWD"]
claim_token: str
expires_at: str
class SubmitSolutionRequest(BaseModel):
task_id: str
claim_token: str
deliverables: Dict[str, str]
github_pr_url: Optional[HttpUrl] = None
class SubmitSolutionResponse(BaseModel):
task_id: str
submission_id: str
status: Literal["VERIFYING"]
estimated_judge_complete_at: Optional[str] = None

View File

@@ -1,5 +1,5 @@
import { VibeWorkClient } from '../client';
import { AgentProfile } from '../types';
import { AgentCard, RegisterAgentResponse } from '../types';
export class IdentityModule {
private client: VibeWorkClient;
@@ -11,23 +11,8 @@ export class IdentityModule {
/**
* Register or update an Agent Card
*/
async registerAgent(card: {
agent_id: string;
name: string;
description: string;
model_provider?: string;
capabilities: string[];
x402_wallet_address?: string;
}): Promise<AgentProfile> {
const response = await this.client.http.post('/api/mcp/agent_card', card);
return response.data;
}
/**
* Get an Agent Profile by ID
*/
async getProfile(agentId: string): Promise<AgentProfile> {
const response = await this.client.http.get(`/api/mcp/agent_card?agent_id=${agentId}`);
async registerAgent(card: AgentCard): Promise<RegisterAgentResponse> {
const response = await this.client.http.post<{ status: string; message: string }>('/api/mcp/agent_card', { card });
return response.data;
}
}

View File

@@ -1,5 +1,5 @@
import { VibeWorkClient } from '../client';
import { TaskBounty, PagedResponse } from '../types';
import { ClaimTaskRequest, ClaimTaskResponse, SubmitSolutionRequest, SubmitSolutionResponse, TaskBounty } from '../types';
export class TasksModule {
private client: VibeWorkClient;
@@ -11,33 +11,29 @@ export class TasksModule {
/**
* List all open bounties in the IntentPool
*/
async listOpenBounties(): Promise<PagedResponse<TaskBounty>> {
const response = await this.client.http.get('/api/open-tasks');
return {
data: response.data,
total: response.data.length
};
async listOpenBounties(limit = 10): Promise<TaskBounty[]> {
const response = await this.client.http.get<{ tasks: TaskBounty[] }>('/api/open-tasks');
return response.data.tasks.slice(0, limit);
}
/**
* Claim a bounty task
*/
async claimBounty(taskId: string, agentId: string): Promise<TaskBounty> {
const response = await this.client.http.post(`/api/mcp/claim_task`, {
taskId,
agentId
});
async claimBounty(taskId: string, agentId: string, developerWallet: string = "0x0000000000000000000000000000000000000000"): Promise<ClaimTaskResponse> {
const request: ClaimTaskRequest = {
task_id: taskId,
agent_id: agentId,
developer_wallet: developerWallet
};
const response = await this.client.http.post<ClaimTaskResponse>('/api/mcp/claim_task', request);
return response.data;
}
/**
* Submit work for a bounty
*/
async submitWork(taskId: string, pullRequestUrl: string): Promise<TaskBounty> {
const response = await this.client.http.post(`/api/mcp/submit_work`, {
taskId,
pullRequestUrl
});
async submitWork(options: SubmitSolutionRequest): Promise<SubmitSolutionResponse> {
const response = await this.client.http.post<SubmitSolutionResponse>('/api/mcp/submit_solution', options);
return response.data;
}
}

View File

@@ -8,14 +8,82 @@ export interface PagedResponse<T> {
total: number;
}
export interface TaskReward {
amount: number;
currency: 'USD' | 'TWD' | 'USDC';
display_amount?: string;
}
export interface TaskBounty {
id: string;
task_id: string;
title: string;
description: string;
reward_amount: number;
reward_currency: string;
status: 'OPEN' | 'ASSIGNED' | 'IN_REVIEW' | 'COMPLETED' | 'CANCELLED';
requirements: string[];
description?: string;
description_preview?: string;
reward_amount_cents?: number;
reward_display?: string;
reward?: TaskReward;
status:
| 'DRAFT'
| 'OPEN'
| 'EXECUTING'
| 'VERIFYING'
| 'COMPLETED'
| 'FAILED'
| 'FAILED_RETRYABLE'
| 'CANCELLED'
| 'DISPUTED'
| 'REFUND_PENDING'
| 'PAYOUT_READY'
| 'PAYOUT_SETTLED'
| 'ARCHIVED'
| 'IN_REVIEW';
required_stack: string[];
difficulty?: string;
scope_clarity_score?: number;
}
export interface AgentCard {
agent_id: string;
name: string;
description?: string;
supported_models?: string[];
skills: string[];
max_concurrent_tasks?: number;
x402_wallet_address?: string;
}
export interface RegisterAgentResponse {
status: string;
message: string;
}
export interface ClaimTaskRequest {
task_id: string;
agent_id: string;
developer_wallet: string;
}
export interface ClaimTaskResponse {
task_id: string;
status: 'EXECUTING';
held_amount: number;
held_currency: 'USD' | 'TWD';
claim_token: string;
expires_at: string;
}
export interface SubmitSolutionRequest {
task_id: string;
claim_token: string;
deliverables: Record<string, string>;
github_pr_url?: string;
}
export interface SubmitSolutionResponse {
task_id: string;
submission_id: string;
status: 'VERIFYING';
estimated_judge_complete_at?: string;
}
export interface AgentProfile {

928
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,12 +4,16 @@
# This script simulates an external AI agent discovering the network via the Beta Promo token.
API_URL=${1:-"https://agent.wooo.work"}
API_KEY="vw_beta_promo_2026"
AGENT_ID="test_agent_$(date +%s)"
API_KEY="${MCP_API_KEY:-vw_beta_promo_2026}"
AGENT_ID="${MCP_AGENT_ID:-test_agent_$(date +%s)}"
DEVELOPER_WALLET="${MCP_DEVELOPER_WALLET:-acct_demoagent001}"
SUBMIT="${MCP_DO_SUBMIT:-0}"
SUBMIT_DELAY_MS="${MCP_SUBMIT_DELAY_MS:-1500}"
echo "🚀 Simulating External AI Agent connecting to $API_URL"
echo "🔑 Using Public Beta Token: $API_KEY"
echo "🤖 Agent ID: $AGENT_ID"
echo "💳 Wallet: $DEVELOPER_WALLET"
echo "---------------------------------------------------"
# 1. Fetch Open Tasks
@@ -48,10 +52,12 @@ CLAIM_RESPONSE=$(curl -s -X POST "$API_URL/api/mcp/claim_task" \
-d '{
"task_id": "'"$TASK_ID"'",
"agent_id": "'"$AGENT_ID"'",
"developer_wallet": "0xTestWalletExternalAgent999"
"developer_wallet": "'"$DEVELOPER_WALLET"'"
}')
CLAIM_STATUS=$(echo $CLAIM_RESPONSE | grep -o '"status":"[^"]*"' | cut -d'"' -f4)
CLAIM_TOKEN=$(echo $CLAIM_RESPONSE | grep -o '"claim_token":"[^"]*"' | cut -d'"' -f4)
CLAIM_STATUS=$(echo $CLAIM_RESPONSE | grep -o '"status":"[^"]*"' | head -n 1 | cut -d'"' -f4)
TASK_LOCK_STATUS=$(echo $CLAIM_RESPONSE | grep -o '"status":"[^"]*"' | cut -d'"' -f4)
ERROR_MSG=$(echo $CLAIM_RESPONSE | grep -o '"error":"[^"]*"' | cut -d'"' -f4)
if [ -n "$ERROR_MSG" ]; then
@@ -59,7 +65,51 @@ if [ -n "$ERROR_MSG" ]; then
exit 1
fi
echo "✅ Task claimed successfully! Status: $CLAIM_STATUS"
if [ -n "$ERROR_MSG" ] || [ -z "$CLAIM_TOKEN" ]; then
echo "❌ Claim failed. Status=$CLAIM_STATUS Error=$ERROR_MSG"
echo "📄 Response: $CLAIM_RESPONSE"
if [ "$SUBMIT" = "1" ]; then
exit 1
fi
exit 0
fi
echo "✅ Task claimed successfully! Status: $CLAIM_STATUS, token=$CLAIM_TOKEN"
if [ "$SUBMIT" = "1" ]; then
if [ -z "$CLAIM_TOKEN" ]; then
echo "❌ Skip submit: claim_token missing."
exit 1
fi
echo "👉 3. Submitting Solution (submit_solution)..."
if [ "$SUBMIT_DELAY_MS" -gt 0 ]; then
sleep_time=$(echo "$SUBMIT_DELAY_MS" | awk 'BEGIN{FS=1} {printf "%f", $1/1000}')
sleep "$sleep_time"
fi
SUBMIT_RESPONSE=$(curl -s -X POST "$API_URL/api/mcp/submit_solution" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-H "x-agent-id: $AGENT_ID" \
-d '{
"task_id": "'"$TASK_ID"'",
"claim_token": "'"$CLAIM_TOKEN"'",
"deliverables": {
"README.md": "Automated verification from external test agent."
},
"github_pr_url": "https://github.com/example/agent-task-demo/pull/999"
}')
SUBMIT_STATUS=$(echo $SUBMIT_RESPONSE | grep -o '"status":"[^"]*"' | cut -d'"' -f4)
SUBMIT_ERROR=$(echo $SUBMIT_RESPONSE | grep -o '"error":"[^"]*"' | cut -d'"' -f4)
if [ -n "$SUBMIT_ERROR" ]; then
echo "❌ Failed to submit solution: $SUBMIT_ERROR"
echo $SUBMIT_RESPONSE
exit 1
fi
echo "✅ Solution submitted successfully. Status: $SUBMIT_STATUS"
fi
# We won't submit solution because we don't have the real sandbox context.
echo "🎉 Test pipeline passed!"