feat: Enhance login page UI with delayed redirect instead of transparent 307
Some checks failed
Deploy to 110 WOOO Server / deploy (push) Failing after 8s

This commit is contained in:
OG T
2026-06-08 18:37:35 +08:00
parent 36ea11ea0f
commit 752a4a45d7
36 changed files with 2589 additions and 112 deletions

Binary file not shown.

9
scripts/clear_audit.ts Normal file
View File

@@ -0,0 +1,9 @@
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
await prisma.auditEvent.deleteMany({
where: { action: "A2A_NETWORK_BROADCAST" }
});
console.log("Cleared A2A audit logs.");
}
main().finally(() => prisma.$disconnect());

View File

@@ -0,0 +1,42 @@
#!/usr/bin/env bash
set -euo pipefail
JUMP_HOST="${JUMP_HOST:-wooo@192.168.0.110}"
TARGET_HOST="${TARGET_HOST:-ollama@192.168.0.188}"
REPO_DIR="${REPO_DIR:-/home/ollama/vibework-git}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="${REPO_ROOT:-$SCRIPT_DIR/..}"
ENV_FILE="${1:-$SCRIPT_DIR/ecosystem-hunter.env}"
SERVICE_FILE="$REPO_ROOT/scripts/systemd/agent-bounty-ecosystem-hunter.service"
LOCAL_HOME="$(cd "." && pwd)"
if [[ -z "$SERVICE_FILE" || ! -f "$SERVICE_FILE" ]]; then
echo "service file missing: $SERVICE_FILE"
exit 1
fi
if [[ ! -f "$ENV_FILE" ]]; then
echo "env missing: $ENV_FILE"
echo "Please copy from scripts/ecosystem-hunter.env.example and edit values first."
exit 1
fi
ssh -J "$JUMP_HOST" "$TARGET_HOST" \
"mkdir -p $REPO_DIR/scripts/systemd $REPO_DIR/scripts ~/.config/systemd/user ~/.local/logs/agent-bounty-ecosystem-hunter"
echo "[deploy] prepare local sync files..."
RSYNC_CMD=(rsync -avz -e "ssh -J $JUMP_HOST")
"${RSYNC_CMD[@]}" "$ENV_FILE" "$TARGET_HOST:$REPO_DIR/scripts/ecosystem-hunter.env"
"${RSYNC_CMD[@]}" "$LOCAL_HOME/scripts/systemd/agent-bounty-ecosystem-hunter.service" "$TARGET_HOST:$REPO_DIR/scripts/systemd/agent-bounty-ecosystem-hunter.service"
"${RSYNC_CMD[@]}" "$LOCAL_HOME/scripts/run_ecosystem_hunter.sh" "$TARGET_HOST:$REPO_DIR/scripts/run_ecosystem_hunter.sh"
"${RSYNC_CMD[@]}" "$LOCAL_HOME/scripts/nostr_agent_client.py" "$TARGET_HOST:$REPO_DIR/scripts/nostr_agent_client.py"
"${RSYNC_CMD[@]}" "$LOCAL_HOME/scripts/ecosystem-hunter-endpoints.txt" "$TARGET_HOST:$REPO_DIR/scripts/ecosystem-hunter-endpoints.txt"
ssh -J "$JUMP_HOST" "$TARGET_HOST" "\
mkdir -p $REPO_DIR/.local/logs/agent-bounty-ecosystem-hunter && \
chmod 600 $REPO_DIR/scripts/ecosystem-hunter.env && \
chmod +x $REPO_DIR/scripts/run_ecosystem_hunter.sh && \
cp $REPO_DIR/scripts/systemd/agent-bounty-ecosystem-hunter.service ~/.config/systemd/user/agent-bounty-ecosystem-hunter.service && \
systemctl --user daemon-reload && \
systemctl --user enable --now agent-bounty-ecosystem-hunter.service && \
systemctl --user status --no-pager agent-bounty-ecosystem-hunter.service"

View File

@@ -0,0 +1 @@
https://agent.wooo.work

View File

@@ -0,0 +1,24 @@
# Copy to scripts/ecosystem-hunter.env and edit values
MCP_API_KEY=vw_beta_promo_2026
MCP_API_BASE=https://agent.wooo.work
MCP_AGENT_ID=vibe-hunter-prod
DEVELOPER_WALLET=acct_ecosystem_hunter
AUTO_CLAIM=false
AUTO_SUBMIT=false
AUTO_SUBMIT_PR_URL=https://github.com/vibework/a2a-ecosystem-hunter/pull/1
RUN_DAEMON=true
SCAN_INTERVAL_SECONDS=300
RECONNECTION_BACKOFF_SECONDS=20
NOSTR_RELAY_URL=wss://relay.damus.io
NOSTR_TAG=VibeWork_Bounty
NOSTR_LIMIT=40
EXTERNAL_MCP_ENDPOINTS=https://agent.wooo.work
KNOWN_MCP_ENDPOINTS=https://agent.wooo.work
# 可選:外部 MCP 候選清單檔,會額外補足種子端點(支援一行一個)
MCP_ENDPOINTS_FILE=/home/ollama/vibework-git/scripts/ecosystem-hunter-endpoints.txt
MCP_TIMEOUT_SECONDS=12
ECOSYSTEM_REPORT_PATH=/home/ollama/vibework-git/artifacts/ecosystem_hunter_report.jsonl
REPORT_LOG_DIR=/home/ollama/vibework-git/.local/logs/agent-bounty-ecosystem-hunter
STDOUT_LOG=/home/ollama/vibework-git/.local/logs/agent-bounty-ecosystem-hunter/ecosystem-hunter.stdout.log
STDERR_LOG=/home/ollama/vibework-git/.local/logs/agent-bounty-ecosystem-hunter/ecosystem-hunter.stderr.log
PYTHON_BIN=/usr/bin/python3

View File

@@ -0,0 +1,23 @@
# Copy to scripts/ecosystem-hunter.env and edit values
MCP_API_KEY=vw_beta_promo_2026
MCP_API_BASE=https://agent.wooo.work
MCP_AGENT_ID=vibe-hunter-prod
DEVELOPER_WALLET=acct_ecosystem_hunter
AUTO_CLAIM=false
AUTO_SUBMIT=false
AUTO_SUBMIT_PR_URL=https://github.com/vibework/a2a-ecosystem-hunter/pull/1
RUN_DAEMON=true
SCAN_INTERVAL_SECONDS=300
RECONNECTION_BACKOFF_SECONDS=20
NOSTR_RELAY_URL=wss://relay.damus.io
NOSTR_TAG=VibeWork_Bounty
NOSTR_LIMIT=40
EXTERNAL_MCP_ENDPOINTS=https://agent.wooo.work
KNOWN_MCP_ENDPOINTS=https://agent.wooo.work
# 可選:外部 MCP 候選清單檔,會額外補足種子端點(支援一行一個)
MCP_ENDPOINTS_FILE=/home/ollama/vibework-git/scripts/ecosystem-hunter-endpoints.txt
MCP_TIMEOUT_SECONDS=12
ECOSYSTEM_REPORT_PATH=/home/ollama/vibework-git/artifacts/ecosystem_hunter_report.jsonl
REPORT_LOG_DIR=/home/ollama/vibework-git/.local/logs/agent-bounty-ecosystem-hunter
STDOUT_LOG=/home/ollama/vibework-git/.local/logs/agent-bounty-ecosystem-hunter/ecosystem-hunter.stdout.log
STDERR_LOG=/home/ollama/vibework-git/.local/logs/agent-bounty-ecosystem-hunter/ecosystem-hunter.stderr.log

View File

@@ -0,0 +1,67 @@
import { v4 as uuidv4 } from "uuid";
const API_BASE = "http://192.168.0.110:3000/api";
const MCP_API_BASE = "http://192.168.0.110:3000/api/mcp";
const MCP_API_KEY = "vw_beta_promo_2026";
async function callMcpTool(tool: string, payload: any) {
const res = await fetch(`${MCP_API_BASE}/${tool}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${MCP_API_KEY}`
},
body: JSON.stringify(payload)
});
const data = await res.json();
if (!res.ok) {
throw new Error(`Tool ${tool} failed. ` + JSON.stringify(data));
}
return data;
}
async function main() {
console.log("🚀 [Mock Dispatcher] Starting test flow...");
// 1. Find Open Bounties
const openTasksRes = await fetch(`${API_BASE}/open-tasks`);
const openTasks = await openTasksRes.json();
if (!openTasks || !openTasks.tasks || openTasks.tasks.length === 0) {
console.log("✅ No open tasks. We need to create a test task first.");
return;
}
const task = openTasks.tasks[0];
console.log(`🎯 Found Task: "${task.title}"`);
// 2. Mock Agent
const agentId = `mock-agent-${Math.floor(Math.random() * 1000)}`;
console.log(`🤖 Agent ID: ${agentId}`);
// 3. Claim task
const claimRes = await callMcpTool("claim_task", {
task_id: task.task_id,
agent_id: agentId,
developer_wallet: "acct_1MockStripeOutboundAgent"
});
console.log("✅ Claimed successfully. Token:", claimRes.claim_token);
// 4. Mock Solution
console.log("🧠 Mocking solution generation...");
const solutionObj = {
"solution.ts": "// Fake solution"
};
// 5. Submit solution
const solutionRes = await callMcpTool("submit_solution", {
task_id: task.task_id,
claim_token: claimRes.claim_token,
deliverables: solutionObj,
github_pr_url: "https://github.com/agent-bounty/external-agents/pull/999"
});
console.log(`🎉 Success! Solution submitted. Submission ID: ${solutionRes.submission_id}`);
}
main().catch(console.error);

View File

@@ -0,0 +1,40 @@
#!/usr/bin/env bash
set -euo pipefail
BASE_URL="${1:-https://agent.wooo.work}"
MINUTES="${2:-60}"
TRAFFIC_TOKEN="${TRAFFIC_MONITOR_TOKEN:-}"
API_URL="${BASE_URL%/}/api/traffic?minutes=${MINUTES}"
curl_cmd=(curl -sS)
export BASE_URL MINUTES
if [ -n "$TRAFFIC_TOKEN" ]; then
curl_cmd+=("-H" "x-traffic-token: $TRAFFIC_TOKEN")
fi
PYTHON_INPUT="$(${curl_cmd[@]} "$API_URL")"
export PYTHON_INPUT
python3 - <<'PY'
import json
import os
import sys
raw = os.environ.get("PYTHON_INPUT", "")
try:
payload = json.loads(raw)
except json.JSONDecodeError:
print("[A2A External] 無法解析 /api/traffic 回應")
print(raw[:500])
sys.exit(1)
print(f"[A2A External] base={os.environ.get('BASE_URL')} minutes={os.environ.get('MINUTES')}")
print(f"total_events={payload.get('total_events')} channel={payload.get('channel_summary', {}).get('external')}")
print(f"claim_success={payload.get('conversion_rates', {}).get('claim_rate')} submit_success={payload.get('conversion_rates', {}).get('submit_rate')}")
print("external_funnel:", payload.get("external_funnel"))
print("top_actors:", payload.get("external_actor_summary"))
print("recent_events:")
for row in (payload.get("recent_external_events") or [])[:8]:
print(f" - {row.get('action')} | {row.get('actorId')} | {row.get('surface')} | {row.get('metadata', {}).get('response_status')} | {row.get('reason')}")
PY

View File

@@ -0,0 +1,610 @@
#!/usr/bin/env python3
import asyncio
import json
import os
import random
import time
import urllib.error
import urllib.request
from pathlib import Path
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional, Set
try:
import websockets
except Exception:
websockets = None
RELAY_URL = os.getenv("NOSTR_RELAY_URL", "wss://relay.damus.io")
NOSTR_TAG = os.getenv("NOSTR_TAG", "VibeWork_Bounty")
NOSTR_TAG_FALLBACK = "A2A"
NOSTR_LIMIT = max(int(os.getenv("NOSTR_LIMIT", "40") or "40"), 1)
MCP_API_BASE = os.getenv("MCP_API_BASE", "https://agent.wooo.work")
MCP_API_KEY = os.getenv("MCP_API_KEY", os.getenv("API_KEY", ""))
MCP_TIMEOUT_SECONDS = max(float(os.getenv("MCP_TIMEOUT_SECONDS", "12") or "12"), 1)
MCP_AGENT_ID = os.getenv("MCP_AGENT_ID", f"ecosystem-hunter-{random.randint(10000, 99999)}")
DEVELOPER_WALLET = os.getenv("DEVELOPER_WALLET", "acct_ecosystem_hunter")
AUTO_CLAIM = os.getenv("AUTO_CLAIM", "false").lower() in {"1", "true", "yes"}
AUTO_SUBMIT = os.getenv("AUTO_SUBMIT", "false").lower() in {"1", "true", "yes"}
AUTO_SUBMIT_PR_URL = os.getenv(
"AUTO_SUBMIT_PR_URL",
"https://github.com/vibework/a2a-ecosystem-hunter/pull/1"
)
SUBMISSION_README = "Automated ecosystem probe submission from external A2A crawler."
SUBMISSION_NOTES = "This is a connectivity probe payload to verify list/open/claim flow."
RUN_DAEMON = os.getenv("RUN_DAEMON", "false").lower() in {"1", "true", "yes"}
RECONNECTION_BACKOFF_SECONDS = max(float(os.getenv("RECONNECTION_BACKOFF_SECONDS", "20") or "20"), 1)
NOSTR_SUB_ID = os.getenv("NOSTR_SUB_ID", f"vh-ecosystem-hunter-{int(time.time())}")
SCAN_INTERVAL_SECONDS = max(float(os.getenv("SCAN_INTERVAL_SECONDS", "0") or "0"), 0)
REPORT_PATH = os.getenv("ECOSYSTEM_REPORT_PATH", "artifacts/ecosystem_hunter_report.jsonl").strip()
KNOWN_ENDPOINTS_ENV = [
item.strip() for item in os.getenv("KNOWN_MCP_ENDPOINTS", "").split(",") if item.strip()
]
REPO_ROOT = Path(os.getenv("APP_ROOT", "").strip() or Path(__file__).resolve().parents[1]).resolve()
ENDPOINTS_FILE = os.getenv(
"MCP_ENDPOINTS_FILE",
str(REPO_ROOT / "scripts" / "ecosystem-hunter-endpoints.txt"),
)
RAW_ENDPOINTS = [item.strip() for item in os.getenv("EXTERNAL_MCP_ENDPOINTS", "").split(",") if item.strip()]
def read_endpoint_file(path: Optional[str]) -> List[str]:
if not path:
return []
file_path = Path(path)
if not file_path.exists():
return []
try:
raw_lines = file_path.read_text(encoding="utf-8").splitlines()
except Exception:
return []
candidates = []
for line in raw_lines:
normalized = line.strip()
if not normalized or normalized.startswith("#"):
continue
candidates.append(normalized)
return candidates
def collect_seed_endpoints() -> List[str]:
seen: Set[str] = set()
ordered: List[str] = []
candidate_groups = [
RAW_ENDPOINTS,
read_endpoint_file(ENDPOINTS_FILE),
KNOWN_ENDPOINTS_ENV,
]
for group in candidate_groups:
for hint in group:
normalized = normalize_endpoint(hint)
if not normalized:
continue
canonical = _dedupe(normalized, seen)
if canonical:
ordered.append(canonical)
if not ordered:
ordered.append(normalize_endpoint(MCP_API_BASE) or MCP_API_BASE)
return ordered
def append_report_line(payload: Dict[str, Any]) -> None:
if not REPORT_PATH:
return
path = Path(REPORT_PATH)
if path.parent and not path.parent.exists():
path.parent.mkdir(parents=True, exist_ok=True)
payload_to_write = {
"ts": now_ts(),
**payload,
}
with path.open("a", encoding="utf-8") as f:
f.write(json.dumps(payload_to_write, ensure_ascii=False) + "\n")
def _dedupe(base: str, seen: Set[str]) -> str:
key = base.lower()
if key in seen:
return ""
seen.add(key)
return base
def now_ts() -> str:
return datetime.now(timezone.utc).isoformat()
def normalize_endpoint(endpoint: str) -> Optional[str]:
if not endpoint:
return None
raw = endpoint.strip()
if not raw:
return None
base = raw.rstrip("/")
if base.endswith("/api/mcp"):
return base
if base.endswith("/mcp"):
return base
if base.endswith("/api"):
return f"{base}/mcp"
if base.endswith("/mcp"):
return base
if base.endswith("/mcp/"):
return base[:-1]
return f"{base}/api/mcp"
def discover_candidate_endpoints(event_payload: Dict[str, Any], tags: Dict[str, str], seen: Set[str]) -> List[str]:
candidates: List[str] = []
endpoint_hints = [
event_payload.get("endpoint"),
event_payload.get("mcp_endpoint"),
event_payload.get("mcp"),
event_payload.get("url"),
]
tag_hint = tags.get("web") or tags.get("r")
if tag_hint:
endpoint_hints.append(tag_hint)
for hint in endpoint_hints:
normalized = normalize_endpoint(str(hint).strip()) if hint else None
if not normalized:
continue
canonical = _dedupe(normalized, seen)
if canonical:
candidates.append(canonical)
if KNOWN_ENDPOINTS_ENV:
for hint in KNOWN_ENDPOINTS_ENV:
normalized = normalize_endpoint(hint)
if not normalized:
continue
canonical = _dedupe(normalized, seen)
if canonical:
candidates.append(canonical)
return candidates
def parse_nostr_tags(tags: Any) -> Dict[str, str]:
tag_map: Dict[str, str] = {}
if not isinstance(tags, list):
return tag_map
for item in tags:
if not isinstance(item, list) or len(item) < 2:
continue
key = str(item[0]).strip().lower()
value = str(item[1]).strip()
if key and value:
# keep the first seen to keep stable behavior
tag_map.setdefault(key, value)
return tag_map
def build_tool_url(base: str, tool: str) -> str:
normalized = normalize_endpoint(base)
if not normalized:
return ""
return f"{normalized}/{tool}"
def read_json(payload: str) -> Optional[Dict[str, Any]]:
if not payload:
return None
try:
parsed = json.loads(payload)
return parsed if isinstance(parsed, dict) else None
except Exception:
return None
def parse_bounty_payload(content: str) -> Dict[str, Any]:
parsed = read_json(content) or {}
if not isinstance(parsed, dict):
return {
"id": None,
"title": "unknown",
"reward": None,
"currency": "USD",
"endpoint": None,
"source": NOSTR_TAG,
"payload": {},
}
bounty = parsed.get("payload") if isinstance(parsed.get("payload"), dict) else parsed.get("bounty", parsed)
if not isinstance(bounty, dict):
bounty = {}
endpoint_hint = bounty.get("endpoint") or parsed.get("endpoint") or parsed.get("mcp_endpoint")
source_hint = bounty.get("source") or parsed.get("source") or NOSTR_TAG
raw_endpoint_list = parsed.get("endpoints")
endpoints: List[str] = []
if isinstance(raw_endpoint_list, list):
endpoints = [str(item).strip() for item in raw_endpoint_list if isinstance(item, str)]
if not isinstance(bounty, dict):
bounty = {}
return {
"id": bounty.get("id"),
"title": bounty.get("title", "unknown"),
"reward": bounty.get("reward"),
"currency": bounty.get("currency", "USD"),
"endpoint": endpoint_hint,
"source": source_hint,
"endpoints": endpoints,
"payload": bounty,
}
def build_headers() -> Dict[str, str]:
return {
"Content-Type": "application/json",
"Authorization": f"Bearer {MCP_API_KEY}",
"x-agent-id": MCP_AGENT_ID,
"x-agent-name": "vibework-ecosystem-hunter",
"x-client-id": "vibework-a2a-hunter",
"User-Agent": "vibework-ecosystem-hunter/1.0",
"x-request-id": f"hunt-{int(time.time() * 1000)}",
}
def post_json(url: str, payload: Dict[str, Any]) -> Dict[str, Any]:
if not MCP_API_KEY:
raise RuntimeError("Missing MCP_API_KEY / API_KEY; set one to run MCP endpoints.")
data = json.dumps(payload).encode("utf-8")
request = urllib.request.Request(url, data=data, headers=build_headers(), method="POST")
try:
with urllib.request.urlopen(request, timeout=MCP_TIMEOUT_SECONDS) as response:
response_body = response.read().decode("utf-8", errors="ignore")
parsed_body = read_json(response_body) or {}
return {
"status": response.status,
"data": parsed_body,
}
except urllib.error.HTTPError as error:
error_body = error.read().decode("utf-8", errors="ignore")
raise RuntimeError(f"HTTP {error.code}: {error_body or error.reason}")
except urllib.error.URLError as error:
raise RuntimeError(f"URLError: {error}")
async def probe_one_endpoint(base_url: str, source: str, source_tag: str) -> None:
normalized = normalize_endpoint(base_url)
if not normalized:
return
print(f"\n[probe] target={normalized} source={source} tag={source_tag}")
try:
list_response = post_json(build_tool_url(normalized, "list_open_tasks"), {"skills": []})
list_data = list_response.get("data", {})
task_count = len(list_data.get("tasks", []) or [])
print(f" ✅ list_open_tasks status={list_response.get('status')} count={task_count}")
append_report_line(
{
"type": "probe",
"target": normalized,
"action": "list_open_tasks",
"source": source,
"source_tag": source_tag,
"status": list_response.get("status"),
"task_count": task_count,
}
)
except Exception as error:
print(f" ⚠️ list_open_tasks failed: {error}")
append_report_line(
{
"type": "probe",
"target": normalized,
"action": "list_open_tasks",
"source": source,
"source_tag": source_tag,
"status": "failed",
"error": str(error),
}
)
return
tasks = list_data.get("tasks", [])
if not isinstance(tasks, list) or not tasks:
return
top_task = tasks[0] if tasks else {}
if not isinstance(top_task, dict):
top_task = {}
task_id = top_task.get("task_id") or top_task.get("id") or "unknown"
task_title = top_task.get("title", "unknown")
print(f" 🧩 top_task={task_id} title={task_title}")
if not AUTO_CLAIM:
return
try:
claim_response = post_json(
build_tool_url(normalized, "claim_task"),
{
"task_id": task_id,
"agent_id": MCP_AGENT_ID,
"developer_wallet": DEVELOPER_WALLET,
},
)
claim_data = claim_response.get("data", {})
claim_token = claim_data.get("claim_token")
print(f" 🧪 claim_task status={claim_response.get('status')} token={str(claim_token)[:10] if claim_token else 'none'}")
append_report_line(
{
"type": "probe",
"target": normalized,
"action": "claim_task",
"source": source,
"source_tag": source_tag,
"status": claim_response.get("status"),
"task_id": task_id,
"has_claim_token": bool(claim_token),
}
)
except Exception as error:
print(f" ⚠️ claim_task failed: {error}")
append_report_line(
{
"type": "probe",
"target": normalized,
"action": "claim_task",
"source": source,
"source_tag": source_tag,
"status": "failed",
"error": str(error),
"task_id": task_id,
}
)
return
if not AUTO_SUBMIT or not claim_token:
return
try:
submit_response = post_json(
build_tool_url(normalized, "submit_solution"),
{
"task_id": task_id,
"claim_token": claim_token,
"deliverables": {
"README.md": SUBMISSION_README,
"notes.txt": SUBMISSION_NOTES,
},
"github_pr_url": AUTO_SUBMIT_PR_URL,
},
)
print(f" 🚀 submit_solution status={submit_response.get('status')}")
append_report_line(
{
"type": "probe",
"target": normalized,
"action": "submit_solution",
"source": source,
"source_tag": source_tag,
"status": submit_response.get("status"),
"task_id": task_id,
}
)
except Exception as error:
print(f" ⚠️ submit_solution failed: {error}")
append_report_line(
{
"type": "probe",
"target": normalized,
"action": "submit_solution",
"source": source,
"source_tag": source_tag,
"status": "failed",
"error": str(error),
"task_id": task_id,
}
)
async def probe_static_endpoints() -> None:
static_endpoints = collect_seed_endpoints()
append_report_line(
{
"type": "scanner_seed_snapshot",
"source": "timer" if SCAN_INTERVAL_SECONDS > 0 else "bootstrap",
"seed_count": len(static_endpoints),
"seeds": static_endpoints,
}
)
for index, base in enumerate(static_endpoints):
await probe_one_endpoint(base, f"seed:{index}", "static")
async def listen_for_bounties() -> None:
if websockets is None:
print("⚠️ websocket support unavailable; skipping Nostr real-time bounty discovery")
return
seen_events = set()
subscribe_message = [
"REQ",
NOSTR_SUB_ID,
{
"kinds": [1],
"#t": [NOSTR_TAG, NOSTR_TAG_FALLBACK],
"limit": NOSTR_LIMIT,
},
]
while True:
print(f"🤖 [Nostr Agent Client] connecting {RELAY_URL} ...")
try:
async with websockets.connect(RELAY_URL) as ws:
print("✅ connected. listening for bounty intents...")
await ws.send(json.dumps(subscribe_message))
async for raw in ws:
try:
data = json.loads(raw)
except Exception:
continue
if not isinstance(data, list) or len(data) < 2 or data[0] != "EVENT":
continue
event = data[2] if len(data) > 2 and isinstance(data[2], dict) else None
if not event:
continue
event_id = event.get("id")
if event_id in seen_events:
continue
seen_events.add(event_id)
if len(seen_events) > 512:
seen_events.pop()
payload = parse_bounty_payload(event.get("content", ""))
tags = parse_nostr_tags(event.get("tags"))
event_seen_targets: Set[str] = set()
event_stats = {
"processed": 0,
"candidates": 0,
"endpoint_hits": 0,
}
endpoint_candidates = discover_candidate_endpoints(payload.get("payload", {}), tags, event_seen_targets)
if payload.get("endpoint"):
candidate = _dedupe(normalize_endpoint(payload.get("endpoint") or "") or "", event_seen_targets)
if candidate:
endpoint_candidates.append(candidate)
extra_endpoints = payload.get("endpoints") or []
if isinstance(extra_endpoints, list):
for hint in extra_endpoints:
normalized = normalize_endpoint(str(hint).strip())
if not normalized:
continue
canonical = _dedupe(normalized, event_seen_targets)
if canonical:
endpoint_candidates.append(canonical)
reward = payload.get("reward")
currency = payload.get("currency", "USD")
task_id = payload.get("id")
title = payload.get("title", "unknown")
print(f"\n🚨 [Nostr bounty] id={task_id} title={title} reward={reward} {currency}")
event_stats["processed"] += 1
if not endpoint_candidates:
print(f"🤖 [Nostr event] id={event_id} no actionable endpoint; skipped")
append_report_line(
{
"type": "event",
"event_id": event_id,
"source": "nostr",
"source_tag": NOSTR_TAG,
"status": "ignored",
"reason": "no_endpoint_candidate",
}
)
continue
for endpoint in endpoint_candidates:
event_stats["candidates"] += 1
await probe_one_endpoint(endpoint, f"nostr:{event_id}", tags.get("t") or NOSTR_TAG)
event_stats["endpoint_hits"] += 1
append_report_line(
{
"type": "event",
"event_id": event_id,
"source": "nostr",
"source_tag": NOSTR_TAG,
"status": "processed",
"candidates": event_stats["candidates"],
"endpoint_hits": event_stats["endpoint_hits"],
}
)
except Exception as error:
print(f"⚠️ Connection failed: {error}")
print(f"⏳ Reconnect in {RECONNECTION_BACKOFF_SECONDS} seconds")
await asyncio.sleep(RECONNECTION_BACKOFF_SECONDS)
async def periodic_static_probe() -> None:
if SCAN_INTERVAL_SECONDS <= 0:
return
while True:
await asyncio.sleep(SCAN_INTERVAL_SECONDS)
append_report_line(
{
"type": "scanner",
"source": "timer",
"status": "tick",
"interval_seconds": SCAN_INTERVAL_SECONDS,
}
)
await probe_static_endpoints()
async def main():
print(f"🛰️ [VibeWork Ecosystem Hunter] started at {now_ts()}")
print(f" MCP base: {MCP_API_BASE}")
print(f" API key configured: {'yes' if MCP_API_KEY else 'no'}")
print(f" Auto claim: {'on' if AUTO_CLAIM else 'off'}")
print(f" Auto submit: {'on' if AUTO_SUBMIT else 'off'}")
print(f" Report file: {REPORT_PATH}")
print(f" Static scan interval: {SCAN_INTERVAL_SECONDS}s")
if KNOWN_ENDPOINTS_ENV:
print(f" Seed endpoints: {', '.join(KNOWN_ENDPOINTS_ENV)}")
await probe_static_endpoints()
if RUN_DAEMON:
if SCAN_INTERVAL_SECONDS > 0:
append_report_line(
{
"type": "bootstrap",
"status": "daemon_started_with_scan",
"scan_interval_seconds": SCAN_INTERVAL_SECONDS,
}
)
if websockets is None:
await periodic_static_probe()
else:
await asyncio.gather(listen_for_bounties(), periodic_static_probe())
else:
append_report_line({"type": "bootstrap", "status": "daemon_started_only_realtime"})
await listen_for_bounties()
else:
append_report_line({"type": "bootstrap", "status": "single_run"})
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,151 @@
import { v4 as uuidv4 } from "uuid";
import OpenAI from "openai";
import dotenv from "dotenv";
dotenv.config();
const API_BASE = process.env.VIBEWORK_API_URL || "http://192.168.0.110:3000/api";
const MCP_API_BASE = process.env.VIBEWORK_MCP_URL || "http://192.168.0.110:3000/api/mcp";
const MCP_API_KEY = process.env.MCP_API_KEY || "super-secret-mcp-key";
// By default use OpenRouter as it aggregates all the best open-source models
const LLM_API_KEY = process.env.OPENROUTER_API_KEY || process.env.OPENAI_API_KEY;
const LLM_BASE_URL = process.env.OPENROUTER_API_KEY ? "https://openrouter.ai/api/v1" : "https://api.openai.com/v1";
if (!LLM_API_KEY) {
console.error("❌ Please provide an OPENROUTER_API_KEY (or OPENAI_API_KEY) in your .env file.");
process.exit(1);
}
const openai = new OpenAI({
baseURL: LLM_BASE_URL,
apiKey: LLM_API_KEY,
defaultHeaders: process.env.OPENROUTER_API_KEY ? {
"HTTP-Referer": "https://agent.wooo.work",
"X-Title": "VibeWork Outbound Dispatcher",
} : undefined
});
// The list of "Mercenary" agents we can hire from the open-source market
const TARGET_AGENTS = [
"nvidia/nemotron-4-340b-instruct",
"nousresearch/nous-hermes-2-mixtral-8x7b-dpo",
"openchat/openchat-7b:free",
"meta-llama/llama-3-70b-instruct"
];
async function callMcpTool(tool: string, payload: any) {
const res = await fetch(`${MCP_API_BASE}/${tool}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${MCP_API_KEY}`
},
body: JSON.stringify(payload)
});
const data = await res.json();
if (!res.ok) {
console.error(`Tool ${tool} failed with status ${res.status}:`, JSON.stringify(data, null, 2));
throw new Error(`Tool ${tool} failed.`);
}
return data;
}
async function main() {
console.log("🚀 [VibeWork Headhunter] Starting outbound dispatch sweep...");
// 1. Find Open Bounties
console.log("🔍 Scanning for OPEN tasks on VibeWork...");
const openTasksRes = await fetch(`${API_BASE}/open-tasks`);
const openTasks = await openTasksRes.json();
if (!openTasks || !openTasks.tasks || openTasks.tasks.length === 0) {
console.log("✅ No open tasks available for dispatch. Resting.");
return;
}
const task = openTasks.tasks[0];
console.log(`🎯 Found Task: "${task.title}" (Reward: ${task.reward_display})`);
// 2. Assign to an external agent
const selectedModel = TARGET_AGENTS[Math.floor(Math.random() * TARGET_AGENTS.length)];
const agentId = `outbound-${selectedModel.split("/").pop()}-${Math.floor(Math.random() * 1000)}`;
console.log(`🤖 Dispatching to external agent: ${selectedModel} (Agent ID: ${agentId})`);
// 3. Claim the task on VibeWork
console.log("\n💰 Claiming Task on behalf of the agent...");
const claimRes = await callMcpTool("claim_task", {
task_id: task.task_id,
agent_id: agentId,
// We provide a mock Stripe Connect Account or real one if configured
developer_wallet: "acct_1MockStripeOutboundAgent"
});
console.log("✅ Claimed successfully. Token:", claimRes.claim_token);
// 4. Prompt the Model to solve it
console.log(`\n🧠 Consulting ${selectedModel} for the solution...`);
const systemPrompt = `You are an elite autonomous AI developer.
You have just accepted a bounty on VibeWork.
You must solve the task by writing the necessary code.
Return ONLY valid JSON in the following format, with no markdown formatting outside the JSON block:
{
"deliverables": {
"filename1.ts": "code content here",
"filename2.md": "documentation here"
}
}
Do not return any explanations, just the JSON string.`;
const userPrompt = `TASK TITLE: ${task.title}
TASK DESCRIPTION: ${task.description_preview}
Please provide the solution.`;
try {
const completion = await openai.chat.completions.create({
model: selectedModel,
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: userPrompt }
],
temperature: 0.2,
response_format: { type: "json_object" }
});
const outputContent = completion.choices[0].message.content || "{}";
let solutionObj;
try {
solutionObj = JSON.parse(outputContent);
} catch (e) {
console.warn("⚠️ Failed to parse LLM JSON output. Falling back to raw text mapping.", outputContent);
solutionObj = {
deliverables: {
"solution.txt": outputContent
}
};
}
if (!solutionObj.deliverables) {
solutionObj.deliverables = { "solution.txt": outputContent };
}
console.log("✅ Model generated the solution deliverables:", Object.keys(solutionObj.deliverables));
// 5. Auto-Submit the Solution
console.log("\n🚀 Submitting the solution back to VibeWork...");
const fakePrUrl = `https://github.com/agent-bounty/external-agents/pull/${Math.floor(Math.random() * 10000)}`;
const solutionRes = await callMcpTool("submit_solution", {
task_id: task.task_id,
claim_token: claimRes.claim_token,
deliverables: solutionObj.deliverables,
github_pr_url: fakePrUrl
});
console.log(`🎉 Success! Solution submitted. Submission ID: ${solutionRes.submission_id}`);
} catch (err) {
console.error("❌ LLM API Error during dispatch:", err);
}
}
main().catch(console.error);

54
scripts/run_ecosystem_hunter.sh Executable file
View File

@@ -0,0 +1,54 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
ENV_FILE="${1:-$SCRIPT_DIR/ecosystem-hunter.env}"
if [[ -f "$ENV_FILE" ]]; then
# shellcheck disable=SC1090
source "$ENV_FILE"
fi
export PYTHON_BIN="${PYTHON_BIN:-/usr/bin/python3}"
export APP_ROOT="${APP_ROOT:-$REPO_ROOT}"
export MCP_API_BASE="${MCP_API_BASE:-https://agent.wooo.work}"
export MCP_API_KEY="${MCP_API_KEY:-}"
export MCP_AGENT_ID="${MCP_AGENT_ID:-vibe-hunter-$(date +%s)}"
export DEVELOPER_WALLET="${DEVELOPER_WALLET:-acct_ecosystem_hunter}"
export AUTO_CLAIM="${AUTO_CLAIM:-false}"
export AUTO_SUBMIT="${AUTO_SUBMIT:-false}"
export AUTO_SUBMIT_PR_URL="${AUTO_SUBMIT_PR_URL:-https://github.com/vibework/a2a-ecosystem-hunter/pull/1}"
export RUN_DAEMON="${RUN_DAEMON:-true}"
export SCAN_INTERVAL_SECONDS="${SCAN_INTERVAL_SECONDS:-300}"
export RECONNECTION_BACKOFF_SECONDS="${RECONNECTION_BACKOFF_SECONDS:-20}"
export NOSTR_RELAY_URL="${NOSTR_RELAY_URL:-wss://relay.damus.io}"
export NOSTR_TAG="${NOSTR_TAG:-VibeWork_Bounty}"
export NOSTR_LIMIT="${NOSTR_LIMIT:-40}"
export EXTERNAL_MCP_ENDPOINTS="${EXTERNAL_MCP_ENDPOINTS:-$MCP_API_BASE}"
export KNOWN_MCP_ENDPOINTS="${KNOWN_MCP_ENDPOINTS:-}"
export MCP_TIMEOUT_SECONDS="${MCP_TIMEOUT_SECONDS:-12}"
export ECOSYSTEM_REPORT_PATH="${ECOSYSTEM_REPORT_PATH:-$REPO_ROOT/artifacts/ecosystem_hunter_report.jsonl}"
mkdir -p "$REPO_ROOT/artifacts"
export REPORT_LOG_DIR="${REPORT_LOG_DIR:-$REPO_ROOT/.local/logs/agent-bounty-ecosystem-hunter}"
mkdir -p "$REPORT_LOG_DIR"
cd "$REPO_ROOT"
if [[ ! -x "$PYTHON_BIN" ]]; then
echo "[runner] PYTHON_BIN not executable: $PYTHON_BIN"
exit 1
fi
if [[ -z "${MCP_API_KEY}" ]]; then
echo "[runner] MCP_API_KEY is empty. Set MCP_API_KEY before running."
exit 1
fi
STDOUT_LOG="${STDOUT_LOG:-$REPORT_LOG_DIR/ecosystem-hunter.stdout.log}"
STDERR_LOG="${STDERR_LOG:-$REPORT_LOG_DIR/ecosystem-hunter.stderr.log}"
exec "$PYTHON_BIN" scripts/nostr_agent_client.py \
>>"$STDOUT_LOG" \
2>>"$STDERR_LOG"

View File

@@ -0,0 +1,16 @@
[Unit]
Description=Agent Bounty Ecosystem Hunter (Nostr + MCP external probe)
After=network.target
[Service]
Type=simple
WorkingDirectory=/home/ollama/vibework-git
EnvironmentFile=/home/ollama/vibework-git/scripts/ecosystem-hunter.env
ExecStart=/home/ollama/vibework-git/scripts/run_ecosystem_hunter.sh /home/ollama/vibework-git/scripts/ecosystem-hunter.env
Restart=always
RestartSec=20
StandardOutput=append:/home/ollama/vibework-git/.local/logs/agent-bounty-ecosystem-hunter/service.log
StandardError=append:/home/ollama/vibework-git/.local/logs/agent-bounty-ecosystem-hunter/service.log
[Install]
WantedBy=default.target