chore: open conversion flow + disable scout import noise
Some checks failed
Deploy to 110 WOOO Server / deploy (push) Failing after 8s

This commit is contained in:
OG T
2026-06-07 18:56:45 +08:00
parent 4f639c2654
commit 4fd89a447e
10 changed files with 423 additions and 117 deletions

View File

@@ -20,6 +20,7 @@ import { z } from "zod";
const MCP_SURGE_WINDOW_MINUTES = 10;
const MCP_SURGE_INTERVAL = 25;
const AUTO_WHITELIST_EXTERNAL_AGENTS = (process.env.AUTO_WHITELIST_EXTERNAL_AGENTS || "false").toLowerCase() === "true";
const MCP_AGENT_HEADERS = [
"x-agent-id",
@@ -64,6 +65,52 @@ function resolveSourceIp(request: NextRequest) {
);
}
async function ensureBuilderAgent(agentId: string) {
const existingAgent = await prisma.agentProfile.findUnique({ where: { agent_id: agentId } });
if (existingAgent) {
return existingAgent;
}
if (!AUTO_WHITELIST_EXTERNAL_AGENTS) {
return null;
}
try {
const created = await prisma.agentProfile.create({
data: {
agent_id: agentId,
type: "BUILDER",
status: "WHITELISTED",
},
});
void sendTrafficAlert({
level: "info",
action: "EXTERNAL_AGENT_AUTO_WHITELIST",
surface: "mcp/claim_task",
actorType: "AGENT",
actorId: agentId,
message: `外部 Agent 首次接案已自動白名單: ${agentId}`,
metadata: { agent_id: agentId },
});
return created;
} catch (error: unknown) {
// In high-concurrency case, another request may create the same agent first.
const isUniqueViolation =
typeof error === "object" &&
error !== null &&
"code" in error &&
(error as { code?: unknown }).code === "P2002";
if (isUniqueViolation) {
return prisma.agentProfile.findUnique({ where: { agent_id: agentId } });
}
throw error;
}
}
function isPrivateIp(ip: string | undefined) {
if (!ip) return true;
const normalized = ip.trim().toLowerCase();
@@ -227,10 +274,24 @@ export async function POST(request: NextRequest, props: { params: Promise<{ tool
const parsed = ClaimTaskRequestSchema.parse(body);
// Verify Agent Whitelist
const agent = await prisma.agentProfile.findUnique({
where: { agent_id: parsed.agent_id }
});
if (!agent || agent.status !== "WHITELISTED") {
const agent = await ensureBuilderAgent(parsed.agent_id);
if (!agent) {
void sendTrafficAlert({
level: "warning",
action: "EXTERNAL_CLAIM_TASK_FORBIDDEN",
surface: "mcp/claim_task",
actorType: "AGENT",
actorId: parsed.agent_id,
taskId: parsed.task_id,
message: `外部 Agent 嘗試接案但尚未白名單: ${parsed.agent_id}`,
metadata: {
developer_wallet: parsed.developer_wallet,
},
});
return NextResponse.json({ error: "Forbidden: Agent is not whitelisted" }, { status: 403 });
}
if (agent.status !== "WHITELISTED") {
return NextResponse.json({ error: "Forbidden: Agent is not whitelisted" }, { status: 403 });
}
@@ -286,7 +347,7 @@ export async function POST(request: NextRequest, props: { params: Promise<{ tool
action: "EXTERNAL_CLAIM_TASK_SUCCESS",
surface: "mcp/claim_task",
actorType: "AGENT",
actorId: parsed.developer_wallet,
actorId: parsed.agent_id,
taskId: claim.task_id,
message: `Agent 成功接案: ${parsed.task_id}`,
metadata: {

View File

@@ -146,110 +146,136 @@ export async function GET(request: Request) {
const isPublicIp = !isPrivateIp(sourceIp);
const trafficAction = isPublicIp ? "EXTERNAL_LIST_OPEN_TASKS" : "INTERNAL_LIST_OPEN_TASKS";
const tasks = await prisma.task.findMany({
where: {
status: TaskStatus.OPEN,
title: {
not: {
startsWith: "GitHub Issue:",
try {
const tasks = await prisma.task.findMany({
where: {
status: TaskStatus.OPEN,
title: {
not: {
startsWith: "GitHub Issue:",
},
},
},
},
orderBy: { created_at: "desc" },
select: {
id: true,
title: true,
description: true,
reward_amount: true,
reward_currency: true,
required_stack: true,
status: true,
difficulty: true,
scope_clarity_score: true,
created_at: true,
updated_at: true,
scout_id: true,
stripe_checkout_session_id: true,
stripe_payment_intent_id: true,
},
});
orderBy: { created_at: "desc" },
select: {
id: true,
title: true,
description: true,
reward_amount: true,
reward_currency: true,
required_stack: true,
status: true,
difficulty: true,
scope_clarity_score: true,
created_at: true,
updated_at: true,
scout_id: true,
stripe_checkout_session_id: true,
stripe_payment_intent_id: true,
},
});
const publicPayload = tasks.map((task) => ({
task_id: task.id,
title: task.title,
status: task.status,
difficulty: task.difficulty,
reward_amount_cents: task.reward_amount,
reward_display: `$${(task.reward_amount / 100).toFixed(2)} ${task.reward_currency}`,
required_stack: task.required_stack,
scope_clarity_score: task.scope_clarity_score,
created_at: task.created_at.toISOString(),
updated_at: task.updated_at.toISOString(),
source: task.scout_id ? "scout" : "human",
payout_mode: getPayoutMode(task),
task_url: `https://agent.wooo.work/tasks/${task.id}`,
}));
const publicPayload = tasks.map((task) => ({
task_id: task.id,
title: task.title,
status: task.status,
difficulty: task.difficulty,
reward_amount_cents: task.reward_amount,
reward_display: `$${(task.reward_amount / 100).toFixed(2)} ${task.reward_currency}`,
required_stack: task.required_stack,
scope_clarity_score: task.scope_clarity_score,
created_at: task.created_at.toISOString(),
updated_at: task.updated_at.toISOString(),
source: task.scout_id ? "scout" : "human",
payout_mode: getPayoutMode(task),
task_url: `https://agent.wooo.work/tasks/${task.id}`,
}));
const actor = resolveExternalActor(request);
const actor = resolveExternalActor(request);
void sendTrafficAlert({
level: "info",
action: trafficAction,
surface: "public-open-tasks",
actorType: actor.actorType,
actorId: actor.actorId,
taskId: "open-tasks",
message: `External discovery call for open tasks (${publicPayload.length} items)`,
metadata: {
source: "public-open-tasks",
task_count: publicPayload.length,
source_ip: sourceIp,
user_agent: request.headers.get("user-agent") ?? "unknown",
},
});
// Light-weight surge signal: when this endpoint is hit in large bursts,
// emit a warning once every 25 requests in a 10-minute window.
const surgeWindow = 10;
const surgeWindowStart = new Date(Date.now() - surgeWindow * 60 * 1000);
void prisma.auditEvent.count({
where: {
createdAt: { gte: surgeWindowStart },
void sendTrafficAlert({
level: "info",
action: trafficAction,
},
}).then((eventCount) => {
if (eventCount > 0 && eventCount % 25 === 0) {
void sendTrafficAlert({
level: "warning",
action: isPublicIp ? "EXTERNAL_LIST_OPEN_TASKS_SURGE" : "INTERNAL_LIST_OPEN_TASKS_SURGE",
surface: "public-open-tasks",
actorType: "SYSTEM",
actorId: "traffic-monitor",
taskId: "open-tasks",
message: `Open tasks discovery surge detected: ${eventCount} hits in ${surgeWindow}m`,
metadata: {
alert_window_minutes: surgeWindow,
event_count: eventCount,
surface: "public-open-tasks",
actorType: actor.actorType,
actorId: actor.actorId,
taskId: "open-tasks",
message: `External discovery call for open tasks (${publicPayload.length} items)`,
metadata: {
source: "public-open-tasks",
task_count: publicPayload.length,
source_ip: sourceIp,
user_agent: request.headers.get("user-agent") ?? "unknown",
},
});
// Light-weight surge signal: when this endpoint is hit in large bursts,
// emit a warning once every 25 requests in a 10-minute window.
const surgeWindow = 10;
const surgeWindowStart = new Date(Date.now() - surgeWindow * 60 * 1000);
void prisma.auditEvent.count({
where: {
createdAt: { gte: surgeWindowStart },
action: trafficAction,
},
}).then((eventCount) => {
if (eventCount > 0 && eventCount % 25 === 0) {
void sendTrafficAlert({
level: "warning",
action: isPublicIp ? "EXTERNAL_LIST_OPEN_TASKS_SURGE" : "INTERNAL_LIST_OPEN_TASKS_SURGE",
surface: "public-open-tasks",
},
actorType: "SYSTEM",
actorId: "traffic-monitor",
taskId: "open-tasks",
message: `Open tasks discovery surge detected: ${eventCount} hits in ${surgeWindow}m`,
metadata: {
alert_window_minutes: surgeWindow,
event_count: eventCount,
surface: "public-open-tasks",
},
});
}
}).catch(() => {});
if (isPublicIp) {
void evaluateExternalFunnelHealth({
surface: "public-open-tasks",
periodMinutes: 10,
});
}
}).catch(() => {});
if (isPublicIp) {
void evaluateExternalFunnelHealth({
surface: "public-open-tasks",
periodMinutes: 10,
return NextResponse.json({
platform: "VibeWork",
version: "v1",
discovery_mode: "ai-first",
beta_program: "VibeWork Beta Zero Friction + 0% Platform Fee for promoted tasks",
tasks: publicPayload,
total_open: publicPayload.length,
last_refreshed_at: new Date().toISOString(),
});
}
} catch (error: any) {
console.error("[open-tasks] Internal error", error);
return NextResponse.json({
platform: "VibeWork",
version: "v1",
discovery_mode: "ai-first",
beta_program: "VibeWork Beta Zero Friction + 0% Platform Fee for promoted tasks",
tasks: publicPayload,
total_open: publicPayload.length,
last_refreshed_at: new Date().toISOString(),
});
const actor = resolveExternalActor(request);
const msg = error?.message ?? "internal_error";
void sendTrafficAlert({
level: "error",
action: isPublicIp ? "EXTERNAL_LIST_OPEN_TASKS_ERROR" : "INTERNAL_LIST_OPEN_TASKS_ERROR",
surface: "public-open-tasks",
actorType: actor.actorType,
actorId: actor.actorId,
taskId: "open-tasks",
message: `open-tasks 查詢失敗: ${msg}`,
metadata: {
source: "public-open-tasks",
source_ip: sourceIp,
user_agent: request.headers.get("user-agent") ?? "unknown",
},
});
return NextResponse.json(
{ error_type: "InternalError", message: msg },
{ status: 500 }
);
}
}

View File

@@ -12,6 +12,13 @@ function asRecordJson(value: unknown): Record<string, unknown> | undefined {
return undefined;
}
function normalizedJudgeResult(value: unknown) {
if (typeof value !== "string") {
return "";
}
return value.trim().toLowerCase();
}
function isMissingTableError(error: unknown): boolean {
return (
typeof error === "object" &&
@@ -199,11 +206,11 @@ export async function GET(request: NextRequest) {
const submitEvents = actionSummary["EXTERNAL_SUBMIT_SOLUTION_SUCCESS"] || 0;
const judgePassEvents = judgeCompleteRows.filter((row) => {
const metadata = asRecordJson(row.metadata);
return metadata?.overall_result === "PASS";
return normalizedJudgeResult(metadata?.overall_result) === "pass";
}).length;
const judgeFailEvents = judgeCompleteRows.filter((row) => {
const metadata = asRecordJson(row.metadata);
return metadata?.overall_result === "FAIL";
return normalizedJudgeResult(metadata?.overall_result) === "fail";
}).length;
const conversionRate = (numerator: number, denominator: number) => {

View File

@@ -105,7 +105,7 @@ export default async function Home() {
{`curl https://agent.wooo.work/api/open-tasks`}
</div>
<p className="text-gray-500 text-sm">
Agent
AI
</p>
<div className="mt-6">
<Link href="/traffic" className="inline-flex items-center gap-2 text-emerald-400 hover:text-emerald-300">

View File

@@ -31,6 +31,13 @@ function asRecordJson(value: unknown): Record<string, unknown> | undefined {
return undefined;
}
function normalizedJudgeResult(value: unknown) {
if (typeof value !== "string") {
return "";
}
return value.trim().toLowerCase();
}
function percent(numerator: number, denominator: number) {
if (!denominator) return 0;
return Math.round((numerator / denominator) * 1000) / 10;
@@ -217,11 +224,11 @@ async function getTrafficSummary(minutes: number) {
const submitEvents = actionSummary["EXTERNAL_SUBMIT_SOLUTION_SUCCESS"] || 0;
const judgePassEvents = judgeCompleteRows.filter((row) => {
const metadata = asRecordJson(row.metadata);
return metadata?.overall_result === "PASS";
return normalizedJudgeResult(metadata?.overall_result) === "pass";
}).length;
const judgeFailEvents = judgeCompleteRows.filter((row) => {
const metadata = asRecordJson(row.metadata);
return metadata?.overall_result === "FAIL";
return normalizedJudgeResult(metadata?.overall_result) === "fail";
}).length;
const conversionSummary = {

View File

@@ -1,3 +1,4 @@
import { request } from "node:https";
import { prisma } from "./prisma";
import { Prisma } from "../../prisma/generated/client";
@@ -37,6 +38,52 @@ function buildTelegramMessage(event: TrafficAlertEvent) {
);
}
async function sendViaHttps(url: string, body: Record<string, unknown>) {
return new Promise<void>((resolve, reject) => {
try {
const parsed = new URL(url);
const payload = JSON.stringify(body);
const requestHandle = request(
{
method: "POST",
hostname: parsed.hostname,
path: `${parsed.pathname}${parsed.search}`,
port: 443,
protocol: "https:",
headers: {
"content-type": "application/json",
"content-length": Buffer.byteLength(payload),
},
},
(response) => {
let responseBody = "";
response.on("data", (chunk) => {
responseBody += chunk;
});
response.on("end", () => {
if (response.statusCode && response.statusCode >= 200 && response.statusCode < 300) {
return resolve();
}
reject(new Error(`Telegram API ${response.statusCode}: ${responseBody.slice(0, 200)}`));
});
}
);
requestHandle.on("error", reject);
requestHandle.setTimeout(3000, () => {
requestHandle.destroy(new Error("Telegram request timeout"));
});
requestHandle.write(payload);
requestHandle.end();
} catch (error) {
reject(error);
}
});
}
function resolveEntityFromTrafficEvent(event: TrafficAlertEvent) {
if (event.taskId) {
return { entityType: "TASK", entityId: event.taskId };
@@ -118,11 +165,11 @@ export async function sendTrafficAlert(event: TrafficAlertEvent): Promise<void>
headers: {
"content-type": "application/json",
},
body: JSON.stringify({
chat_id: TELEGRAM_CHAT_ID,
text: buildTelegramMessage(event),
parse_mode: "MarkdownV2",
}),
body: JSON.stringify({
chat_id: TELEGRAM_CHAT_ID,
text: buildTelegramMessage(event),
parse_mode: "MarkdownV2",
}),
},
},
].filter(Boolean) as Array<{ kind: string; url: string; init: RequestInit }>;
@@ -132,11 +179,18 @@ export async function sendTrafficAlert(event: TrafficAlertEvent): Promise<void>
await Promise.allSettled(
notifyTargets.map(async (target) => {
try {
const response = await fetch(target.url, { ...target.init, signal: AbortSignal.timeout(3000) });
if (!response.ok) {
console.warn(
`[Traffic alert notify failed] ${target.kind} ${response.status} ${await response.text()}`
);
if (target.kind === "telegram") {
const payload = target.init.body
? JSON.parse(typeof target.init.body === "string" ? target.init.body : "{}")
: {};
await sendViaHttps(target.url, payload);
} else {
const response = await fetch(target.url, { ...target.init, signal: AbortSignal.timeout(3000) });
if (!response.ok) {
console.warn(
`[Traffic alert notify failed] ${target.kind} ${response.status} ${await response.text()}`
);
}
}
} catch (error) {
console.warn(`[Traffic alert notify failed] ${target.kind}`, error);

View File

@@ -28,6 +28,13 @@ function asRecordJson(value: unknown): Record<string, unknown> | undefined {
return undefined;
}
function normalizedJudgeResult(value: unknown) {
if (typeof value !== "string") {
return "";
}
return value.trim().toLowerCase();
}
function isMissingLedgerTableError(error: unknown) {
return (
typeof error === "object" &&
@@ -96,12 +103,12 @@ async function fetchFunnelSummary(minutes: number): Promise<FunnelSummary> {
const judgePassEvents = judgeRows.filter((row) => {
const metadata = asRecordJson(row.metadata);
return metadata?.overall_result === "PASS";
return normalizedJudgeResult(metadata?.overall_result) === "pass";
}).length;
const judgeFailEvents = judgeRows.filter((row) => {
const metadata = asRecordJson(row.metadata);
return metadata?.overall_result === "FAIL";
return normalizedJudgeResult(metadata?.overall_result) === "fail";
}).length;
return {