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
Some checks failed
Deploy to 110 WOOO Server / deploy (push) Failing after 8s
This commit is contained in:
BIN
scripts/__pycache__/nostr_agent_client.cpython-311.pyc
Normal file
BIN
scripts/__pycache__/nostr_agent_client.cpython-311.pyc
Normal file
Binary file not shown.
9
scripts/clear_audit.ts
Normal file
9
scripts/clear_audit.ts
Normal 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());
|
||||
42
scripts/deploy_ecosystem_hunter.sh
Executable file
42
scripts/deploy_ecosystem_hunter.sh
Executable 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"
|
||||
1
scripts/ecosystem-hunter-endpoints.txt
Normal file
1
scripts/ecosystem-hunter-endpoints.txt
Normal file
@@ -0,0 +1 @@
|
||||
https://agent.wooo.work
|
||||
24
scripts/ecosystem-hunter.env
Normal file
24
scripts/ecosystem-hunter.env
Normal 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
|
||||
23
scripts/ecosystem-hunter.env.example
Normal file
23
scripts/ecosystem-hunter.env.example
Normal 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
|
||||
67
scripts/mock_dispatcher.ts
Normal file
67
scripts/mock_dispatcher.ts
Normal 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);
|
||||
40
scripts/monitor_external_traffic.sh
Executable file
40
scripts/monitor_external_traffic.sh
Executable 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
|
||||
610
scripts/nostr_agent_client.py
Normal file
610
scripts/nostr_agent_client.py
Normal 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())
|
||||
151
scripts/outbound_dispatcher.ts
Normal file
151
scripts/outbound_dispatcher.ts
Normal 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
54
scripts/run_ecosystem_hunter.sh
Executable 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"
|
||||
16
scripts/systemd/agent-bounty-ecosystem-hunter.service
Normal file
16
scripts/systemd/agent-bounty-ecosystem-hunter.service
Normal 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
|
||||
Reference in New Issue
Block a user