273 lines
9.1 KiB
TypeScript
273 lines
9.1 KiB
TypeScript
import {
|
|
VibeWorkAgentSDK,
|
|
ClaimTaskResponse,
|
|
QueryAgentMemoryRequest,
|
|
SubmitSolutionRequest,
|
|
TaskBounty,
|
|
} from '@vibework/agent-sdk';
|
|
import 'dotenv/config';
|
|
|
|
type A2AAction = 'query-memory' | 'create-sub-task' | 'peer-review' | 'help-signal' | 'rent-resource';
|
|
|
|
interface A2AConfig {
|
|
enabled: boolean;
|
|
sequence: A2AAction[];
|
|
maxActionsPerCycle: number;
|
|
helpErrorMessage: string;
|
|
peerReviewSnippet: string;
|
|
rentDurationMinutes: number;
|
|
}
|
|
|
|
function resolveEnv(name: string, fallback: string): string {
|
|
return process.env[name]?.trim() || fallback;
|
|
}
|
|
|
|
function parseIntEnv(name: string, fallback: number): number {
|
|
const raw = process.env[name]?.trim();
|
|
if (!raw) {
|
|
return fallback;
|
|
}
|
|
|
|
const parsed = Number(raw);
|
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
}
|
|
|
|
function parseA2ASequence(value: string, fallback: A2AAction[]): A2AAction[] {
|
|
const aliases: Record<string, A2AAction> = {
|
|
memory: 'query-memory',
|
|
'query-memory': 'query-memory',
|
|
query: 'query-memory',
|
|
'create-sub-task': 'create-sub-task',
|
|
subtask: 'create-sub-task',
|
|
'peer-review': 'peer-review',
|
|
review: 'peer-review',
|
|
'help-signal': 'help-signal',
|
|
help: 'help-signal',
|
|
rent: 'rent-resource',
|
|
'rent-resource': 'rent-resource',
|
|
};
|
|
|
|
const parsed: A2AAction[] = [];
|
|
for (const token of value.split(',').map((item) => item.trim().toLowerCase()).filter(Boolean)) {
|
|
const action = aliases[token];
|
|
if (action && !parsed.includes(action)) {
|
|
parsed.push(action);
|
|
}
|
|
}
|
|
return parsed.length ? parsed : fallback;
|
|
}
|
|
|
|
function getA2AConfig(): A2AConfig {
|
|
const defaultSequence: A2AAction[] = ['query-memory', 'create-sub-task', 'peer-review', 'rent-resource'];
|
|
const enabled = resolveEnv('VIBEWORK_A2A_ENABLED', 'true').toLowerCase() === 'true';
|
|
const sequence = parseA2ASequence(process.env.VIBEWORK_A2A_SEQUENCE || '', defaultSequence);
|
|
|
|
return {
|
|
enabled,
|
|
sequence,
|
|
maxActionsPerCycle: parseIntEnv('VIBEWORK_A2A_MAX_ACTIONS_PER_CYCLE', 1),
|
|
helpErrorMessage: resolveEnv(
|
|
'VIBEWORK_A2A_HELP_ERROR',
|
|
'Stuck while reproducing external failure case'
|
|
),
|
|
peerReviewSnippet: resolveEnv(
|
|
'VIBEWORK_A2A_REVIEW_SNIPPET',
|
|
'function demo() { return "ok"; }'
|
|
),
|
|
rentDurationMinutes: parseIntEnv('VIBEWORK_A2A_RENT_MINUTES', 5),
|
|
};
|
|
}
|
|
|
|
function sleep(ms: number) {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
|
|
async function runA2AProbe(
|
|
sdk: VibeWorkAgentSDK,
|
|
agentId: string,
|
|
targetTask: TaskBounty,
|
|
claimResult: ClaimTaskResponse,
|
|
config: A2AConfig,
|
|
cycle: number,
|
|
) {
|
|
const actions = config.sequence.slice(0, config.maxActionsPerCycle);
|
|
if (!actions.length) {
|
|
console.log('🕊️ A2A sequence is empty, skip probe.');
|
|
return;
|
|
}
|
|
|
|
for (const action of actions) {
|
|
if (action === 'query-memory') {
|
|
const request: QueryAgentMemoryRequest = {
|
|
query: `open_task_lookup ${targetTask.title}`,
|
|
error_code: 'A2A_TEST_DRILL',
|
|
};
|
|
const memory = await sdk.a2a.queryAgentMemory(request);
|
|
console.log(`🧠 Memory probe: ${memory.results.length} results`);
|
|
continue;
|
|
}
|
|
|
|
if (action === 'create-sub-task') {
|
|
if (claimResult.held_amount <= 1) {
|
|
console.log('⚠️ Skip create-sub-task: held_amount too small.');
|
|
continue;
|
|
}
|
|
const rewardAmount = Math.min(
|
|
claimResult.held_amount - 1,
|
|
Math.max(1, Math.floor(claimResult.held_amount / 10))
|
|
);
|
|
const created = await sdk.a2a.createSubTask({
|
|
parent_task_id: targetTask.task_id,
|
|
claim_token: claimResult.claim_token,
|
|
title: `[A2A Drill][${cycle}] ${targetTask.title.slice(0, 20)}`,
|
|
description:
|
|
'Automated helper sub-task generated by test agent for A2A communication verification.',
|
|
reward_amount: rewardAmount,
|
|
acceptance_criteria: {
|
|
validation_mode: 'AST_PARSING',
|
|
test_file_content: `// A2A helper stub\nexport const a2aTask = () => "${targetTask.task_id}";`,
|
|
rules: [
|
|
{
|
|
assertion: 'helper_export_exists',
|
|
expected: true,
|
|
description: 'helper function should exist',
|
|
},
|
|
],
|
|
},
|
|
});
|
|
console.log(`🧩 Sub-task created: ${created.sub_task_id} (${created.status})`);
|
|
continue;
|
|
}
|
|
|
|
if (action === 'peer-review') {
|
|
const review = await sdk.a2a.requestPeerReview({
|
|
parent_task_id: targetTask.task_id,
|
|
claim_token: claimResult.claim_token,
|
|
code_snippet: config.peerReviewSnippet,
|
|
review_instructions: `Run a quick static review for task ${targetTask.task_id}.`,
|
|
});
|
|
console.log(`🧾 Peer-review task created: ${review.review_task_id}, cost=${review.cost}`);
|
|
continue;
|
|
}
|
|
|
|
if (action === 'help-signal') {
|
|
const sos = await sdk.a2a.broadcastHelpSignal({
|
|
parent_task_id: targetTask.task_id,
|
|
claim_token: claimResult.claim_token,
|
|
error_message: config.helpErrorMessage,
|
|
contextual_code: `function fallback() { return 'retry later'; } // cycle=${cycle}`,
|
|
});
|
|
console.log(`🆘 Help signal emitted: ${sos.sos_task_id} (${sos.status})`);
|
|
continue;
|
|
}
|
|
|
|
if (action === 'rent-resource') {
|
|
const rent = await sdk.a2a.rentApiResource({
|
|
agent_id: agentId,
|
|
resource_type: 'GPT_4O',
|
|
duration_minutes: config.rentDurationMinutes,
|
|
});
|
|
console.log(`📦 Rent resource result: ${rent.status} - ${rent.message}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
console.log('🤖 Starting VibeWork Test Agent...');
|
|
|
|
const baseUrl = resolveEnv('VIBEWORK_API_URL', 'https://agent.wooo.work');
|
|
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 agentName = resolveEnv('VIBEWORK_AGENT_NAME', 'HunterBot-Test');
|
|
const githubPrUrl = resolveEnv(
|
|
'VIBEWORK_PR_URL',
|
|
'https://github.com/agent-bounty-protocol/pr/123'
|
|
);
|
|
const iterationLimit = parseIntEnv('VIBEWORK_MAX_ITERATIONS', 1);
|
|
const sleepMs = parseIntEnv('VIBEWORK_SIMULATE_WORK_MS', 3000);
|
|
const a2aConfig = getA2AConfig();
|
|
|
|
const sdk = new VibeWorkAgentSDK({
|
|
baseUrl,
|
|
apiKey,
|
|
agentId,
|
|
agentName,
|
|
});
|
|
|
|
try {
|
|
console.log('📝 Registering Agent Identity...');
|
|
const registerResult = await sdk.identity.registerAgent({
|
|
agent_id: agentId,
|
|
name: agentName,
|
|
description:
|
|
'A test agent built with @vibework/agent-sdk to run A2A drill traffic and verify MCP interoperability.',
|
|
supported_models: ['gpt-4o'],
|
|
skills: ['typescript', 'javascript', 'react', 'testing', 'a2a'],
|
|
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 through MCP...`);
|
|
|
|
const openBountiesResp = await sdk.a2a.listOpenBounties(8);
|
|
const openBounties = openBountiesResp.tasks as TaskBounty[];
|
|
console.log(`🎯 Found ${openBounties.length} open bounties (stockout=${openBountiesResp.stockout_warning})`);
|
|
|
|
if (!openBounties.length) {
|
|
console.log('😴 No MCP-open tasks, run visibility heartbeat only.');
|
|
const heartbeat = await sdk.a2a.queryAgentMemory({
|
|
query: `open-tasks empty cycle=${iteration}`,
|
|
error_code: 'EMPTY_BOARD_DRILL',
|
|
});
|
|
console.log(`📡 Visibility heartbeat: memory hits ${heartbeat.results.length}`);
|
|
await sleep(1500);
|
|
continue;
|
|
}
|
|
|
|
const targetTask = openBounties[0];
|
|
console.log(
|
|
`📌 Target: [${targetTask.task_id}] ${targetTask.title} (Reward: ${
|
|
targetTask.reward?.display_amount ?? targetTask.reward_display ?? 'n/a'
|
|
})`
|
|
);
|
|
|
|
const claimResult = 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 sleep(sleepMs);
|
|
|
|
if (a2aConfig.enabled) {
|
|
await runA2AProbe(sdk, agentId, targetTask, claimResult, a2aConfig, iteration);
|
|
} else {
|
|
console.log('🔒 A2A drill disabled, skipping external MCP interactions.');
|
|
}
|
|
|
|
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);
|
|
});
|