fix: add scout bot startup retry and compose health gating for scale
Some checks failed
Deploy to 110 WOOO Server / deploy (push) Failing after 7s
Some checks failed
Deploy to 110 WOOO Server / deploy (push) Failing after 7s
This commit is contained in:
@@ -11,6 +11,7 @@ const SCOUT_AGENT_ID = process.env.SCOUT_AGENT_ID || "scout_official_1";
|
||||
const SCOUT_CRON_EXPRESSION = process.env.SCOUT_CRON_EXPRESSION || "*/10 * * * *";
|
||||
const SCOUT_ISSUE_LABEL = process.env.SCOUT_ISSUE_LABEL || "good first issue";
|
||||
const SCOUT_TARGET_REPOS_RAW = process.env.SCOUT_TARGET_REPOS || "vibe-work/test-bounty-repo";
|
||||
|
||||
const SCOUT_TARGET_REPOS = SCOUT_TARGET_REPOS_RAW
|
||||
.split(",")
|
||||
.map((entry) => entry.trim())
|
||||
@@ -24,6 +25,9 @@ const SCOUT_TARGET_REPOS = SCOUT_TARGET_REPOS_RAW
|
||||
const fallbackRepo = { owner: "vibe-work", repo: "test-bounty-repo" };
|
||||
const TARGET_REPOS = SCOUT_TARGET_REPOS.length > 0 ? SCOUT_TARGET_REPOS : [fallbackRepo];
|
||||
|
||||
if (!SCOUT_API_KEY) {
|
||||
console.warn("WARNING: SCOUT_API_KEY is not set. Draft API may be rejected by server.");
|
||||
}
|
||||
if (!GITHUB_TOKEN) {
|
||||
console.warn("WARNING: GITHUB_TOKEN is not set. Scout bot cannot post comments.");
|
||||
}
|
||||
@@ -32,35 +36,65 @@ const octokit = new Octokit({
|
||||
auth: GITHUB_TOKEN,
|
||||
});
|
||||
|
||||
async function draftBountyTask(issueTitle: string, issueBody: string, issueUrl: string) {
|
||||
try {
|
||||
const response = await fetch(`${VIBEWORK_API_URL}/scout/draft`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${SCOUT_API_KEY}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
scout_id: SCOUT_AGENT_ID,
|
||||
title: `GitHub Issue: ${issueTitle}`,
|
||||
description: `${issueBody}\n\nSource: ${issueUrl}`,
|
||||
reward_amount: 1000, // $10.00 Beta Promo subsidy (<= 2000 triggers free promo logic)
|
||||
reward_currency: "USD",
|
||||
required_stack: ["TypeScript", "GitHub"],
|
||||
test_file_content: "// Needs manual test writing based on issue"
|
||||
})
|
||||
});
|
||||
async function wait(seconds: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, seconds * 1000));
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Draft API failed: ${await response.text()}`);
|
||||
async function postDraftWithRetry(payload: {
|
||||
scout_id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
reward_amount: number;
|
||||
reward_currency: string;
|
||||
required_stack: string[];
|
||||
test_file_content: string;
|
||||
}) {
|
||||
let lastError: unknown = null;
|
||||
|
||||
for (let attempt = 0; attempt < 3; attempt++) {
|
||||
try {
|
||||
const response = await fetch(`${VIBEWORK_API_URL}/scout/draft`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${SCOUT_API_KEY}`
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const bodyText = await response.text();
|
||||
throw new Error(`Draft API failed (HTTP ${response.status}): ${bodyText}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
const attemptText = attempt + 1;
|
||||
const maxAttempts = 3;
|
||||
console.error(`Failed to draft bounty task (attempt ${attemptText}/${maxAttempts}):`, error);
|
||||
if (attempt < maxAttempts - 1) {
|
||||
await wait(Math.min(5 * (attempt + 1), 15));
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Failed to draft bounty task:", error);
|
||||
return null;
|
||||
}
|
||||
|
||||
console.error("Failed to draft bounty task after retries:", lastError);
|
||||
return null;
|
||||
}
|
||||
|
||||
async function draftBountyTask(issueTitle: string, issueBody: string, issueUrl: string) {
|
||||
const payload = {
|
||||
scout_id: SCOUT_AGENT_ID,
|
||||
title: `GitHub Issue: ${issueTitle}`,
|
||||
description: `${issueBody}\n\nSource: ${issueUrl}`,
|
||||
reward_amount: 1000, // $10.00 Beta Promo subsidy (<= 2000 triggers free promo logic)
|
||||
reward_currency: "USD",
|
||||
required_stack: ["TypeScript", "GitHub"],
|
||||
test_file_content: "// Needs manual test writing based on issue"
|
||||
};
|
||||
|
||||
return postDraftWithRetry(payload);
|
||||
}
|
||||
|
||||
async function processIssue(owner: string, repo: string, issue: any) {
|
||||
@@ -74,7 +108,7 @@ async function processIssue(owner: string, repo: string, issue: any) {
|
||||
issue_number: issue.number,
|
||||
});
|
||||
|
||||
const alreadyCommented = comments.data.some(c => c.body?.includes("agent.wooo.work"));
|
||||
const alreadyCommented = comments.data.some((c) => c.body?.includes("agent.wooo.work"));
|
||||
if (alreadyCommented) {
|
||||
console.log(`Already commented on #${issue.number}, skipping.`);
|
||||
return;
|
||||
@@ -83,7 +117,7 @@ async function processIssue(owner: string, repo: string, issue: any) {
|
||||
|
||||
// Generate draft task on VibeWork
|
||||
const draft = await draftBountyTask(issue.title, issue.body || "", issue.html_url);
|
||||
|
||||
|
||||
if (!draft) {
|
||||
console.log(`Failed to generate draft for #${issue.number}, skipping comment.`);
|
||||
return;
|
||||
@@ -123,7 +157,7 @@ npx -y @agent-bounty/mcp-server --endpoint https://agent.wooo.work
|
||||
|
||||
async function scanRepositories() {
|
||||
console.log("Starting GitHub scan...");
|
||||
|
||||
|
||||
for (const target of TARGET_REPOS) {
|
||||
try {
|
||||
const issues = await octokit.issues.listForRepo({
|
||||
@@ -135,7 +169,7 @@ async function scanRepositories() {
|
||||
});
|
||||
|
||||
console.log(`Found ${issues.data.length} open issues in ${target.owner}/${target.repo}`);
|
||||
|
||||
|
||||
for (const issue of issues.data) {
|
||||
if (!issue.pull_request) { // Ignore PRs
|
||||
await processIssue(target.owner, target.repo, issue);
|
||||
@@ -147,8 +181,13 @@ async function scanRepositories() {
|
||||
}
|
||||
}
|
||||
|
||||
// Run immediately on startup for testing
|
||||
scanRepositories();
|
||||
async function bootstrap() {
|
||||
// Delay first scan a bit so main web API is ready after startup.
|
||||
await wait(10);
|
||||
await scanRepositories();
|
||||
}
|
||||
|
||||
bootstrap();
|
||||
|
||||
// Default: every 10 minutes for higher volume inbound traffic
|
||||
cron.schedule(SCOUT_CRON_EXPRESSION, () => {
|
||||
|
||||
@@ -41,6 +41,13 @@ services:
|
||||
# We use a command override to run database push before starting next.js
|
||||
command: >
|
||||
sh -c "npx prisma@6.4.1 db push --schema=apps/web/prisma/schema.prisma --skip-generate && node apps/web/server.js"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "node -e \"const http=require('http');const req=http.get('http://127.0.0.1:3000/api/v1/health',(res)=>process.exit(res.statusCode===200?0:1));req.on('error',()=>process.exit(1));\""]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
start_period: 10s
|
||||
stop_grace_period: 30s
|
||||
networks:
|
||||
- agent-bounty-network
|
||||
|
||||
@@ -52,11 +59,11 @@ services:
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
web:
|
||||
condition: service_started
|
||||
condition: service_healthy
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- VIBEWORK_API_URL=http://agent_bounty_web:3000/api
|
||||
- SCOUT_API_KEY=${SCOUT_API_KEY:-dev_scout_key}
|
||||
- SCOUT_API_KEY=${SCOUT_API_KEY:-super-secret-mcp-key}
|
||||
- SCOUT_AGENT_ID=scout_official_1
|
||||
# Optional: add more discovery repos via env (comma separated "owner/repo"), e.g. openai/swarm,significant-gravitas/autogpt
|
||||
- SCOUT_TARGET_REPOS=${SCOUT_TARGET_REPOS}
|
||||
|
||||
Reference in New Issue
Block a user