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

This commit is contained in:
OG T
2026-06-07 15:20:05 +08:00
parent 081aab3002
commit d39db03e06
2 changed files with 80 additions and 34 deletions

View File

@@ -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, () => {

View File

@@ -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}