polyglot-alpha / ui /scripts /loop_a2.js
licaomeng
deploy: main@8970ffb → HF Spaces (2026-05-27T05:19Z)
88d2f2a
// A2 sub-agent: 5-cycle Playwright loop covering UI + backend triggers.
// All triggers use auction_mode='mock' to avoid auction LLM calls;
// translation/eval phases still call Haiku (cheapest tier).
//
// Outputs:
// outputs/playwright_loop_findings.md (running journal)
// outputs/loop_screenshots/cycle_N_step_M.png
/* eslint-disable no-console */
const fs = require("fs");
const path = require("path");
const http = require("http");
const { chromium } = require("playwright");
const BASE_UI = process.env.BASE_UI || "http://127.0.0.1:3001";
const BASE_API = process.env.BASE_API || "http://127.0.0.1:8000";
const OUT_DIR = path.resolve(__dirname, "..", "..", "outputs");
const SHOT_DIR = path.join(OUT_DIR, "loop_screenshots");
const FINDINGS = path.join(OUT_DIR, "playwright_loop_findings.md");
fs.mkdirSync(SHOT_DIR, { recursive: true });
function ts() {
return new Date().toISOString();
}
function append(line) {
fs.appendFileSync(FINDINGS, line + "\n");
}
function postJson(url, body) {
return new Promise((resolve, reject) => {
const data = JSON.stringify(body);
const u = new URL(url);
const req = http.request(
{
hostname: u.hostname,
port: u.port,
path: u.pathname,
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(data),
},
},
(res) => {
let chunks = "";
res.on("data", (c) => (chunks += c));
res.on("end", () => {
try {
resolve({ status: res.statusCode, body: JSON.parse(chunks) });
} catch (e) {
resolve({ status: res.statusCode, body: chunks });
}
});
},
);
req.on("error", reject);
req.write(data);
req.end();
});
}
function getJson(url) {
return new Promise((resolve, reject) => {
http
.get(url, (res) => {
let chunks = "";
res.on("data", (c) => (chunks += c));
res.on("end", () => {
try {
resolve({ status: res.statusCode, body: JSON.parse(chunks) });
} catch (e) {
resolve({ status: res.statusCode, body: chunks });
}
});
})
.on("error", reject);
});
}
async function waitForTerminal(eventId, timeoutMs = 150000) {
const t0 = Date.now();
const terminal = new Set([
"COMMITTED",
"SUBMITTED",
"FAILED",
"REJECTED",
]);
while (Date.now() - t0 < timeoutMs) {
const r = await getJson(`${BASE_API}/events/${eventId}`);
if (r.body && terminal.has(r.body.status)) {
return r.body;
}
await new Promise((r) => setTimeout(r, 2500));
}
return null;
}
const SESSION_TAG = `a2-${Date.now()}`;
const CYCLES = [
{
label: "3-mock-bids",
trigger: {
event_source: "user_payload",
title: `Will event A1 happen by 2026-12-31? [${SESSION_TAG}-c1]`,
sources: [{ name: "test-c1", url: `https://test/c1?s=${SESSION_TAG}` }],
language: "en",
auction_mode: "mock",
mock_bids: [
{
agent_address: "0xagent_a",
bid_amount: 0.5,
stake_amount: 5.0,
reputation: 0.9,
},
{
agent_address: "0xagent_b",
bid_amount: 0.7,
stake_amount: 5.0,
reputation: 0.8,
},
{
agent_address: "0xagent_c",
bid_amount: 0.45,
stake_amount: 5.0,
reputation: 0.85,
},
],
},
},
{
label: "1-mock-bid",
trigger: {
event_source: "user_payload",
title: `Will event B2 happen by 2026-12-31? [${SESSION_TAG}-c2]`,
sources: [{ name: "test-c2", url: `https://test/c2?s=${SESSION_TAG}` }],
language: "en",
auction_mode: "mock",
mock_bids: [
{
agent_address: "0xagent_solo",
bid_amount: 0.6,
stake_amount: 5.0,
reputation: 0.95,
},
],
},
},
{
label: "0-mock-bids-edge",
trigger: {
event_source: "user_payload",
title: `Will event C3 happen by 2026-12-31? [${SESSION_TAG}-c3]`,
sources: [{ name: "test-c3", url: `https://test/c3?s=${SESSION_TAG}` }],
language: "en",
auction_mode: "mock",
mock_bids: [],
},
},
{
label: "rep-gate-high-vs-low",
trigger: {
event_source: "user_payload",
title: `Will event D4 happen by 2026-12-31? [${SESSION_TAG}-c4]`,
sources: [{ name: "test-c4", url: `https://test/c4?s=${SESSION_TAG}` }],
language: "en",
auction_mode: "mock",
mock_bids: [
{
agent_address: "0xagent_high",
bid_amount: 0.4,
stake_amount: 5.0,
reputation: 0.99,
},
{
agent_address: "0xagent_low1",
bid_amount: 0.55,
stake_amount: 5.0,
reputation: 0.1,
},
{
agent_address: "0xagent_low2",
bid_amount: 0.6,
stake_amount: 5.0,
reputation: 0.15,
},
],
},
},
{
label: "explore-other-pages",
trigger: {
event_source: "user_payload",
title: `Will event E5 happen by 2026-12-31? [${SESSION_TAG}-c5]`,
sources: [{ name: "test-c5", url: `https://test/c5?s=${SESSION_TAG}` }],
language: "en",
auction_mode: "mock",
mock_bids: [
{
agent_address: "0xagent_a",
bid_amount: 0.5,
stake_amount: 5.0,
reputation: 0.9,
},
{
agent_address: "0xagent_b",
bid_amount: 0.7,
stake_amount: 5.0,
reputation: 0.8,
},
],
},
},
];
async function runCycle(browser, cycleN, spec) {
const cycleStart = ts();
const findings = [];
findings.push(`\n## Cycle ${cycleN}: ${spec.label} (${cycleStart})`);
console.log(`\n=== Cycle ${cycleN}: ${spec.label} ===`);
// 1. Trigger
let eventId = null;
let triggerResp = null;
try {
triggerResp = await postJson(`${BASE_API}/trigger/event`, spec.trigger);
findings.push(
`- Trigger HTTP ${triggerResp.status}: \`${JSON.stringify(triggerResp.body).slice(0, 200)}\``,
);
eventId = triggerResp.body && triggerResp.body.event_id;
} catch (e) {
findings.push(`- Trigger FAILED with exception: ${e.message}`);
}
if (cycleN === 3) {
// 0-bid edge: we expect this to either fail at validation OR succeed via fallback.
findings.push(`- Edge case: 0 mock bids — recording behavior.`);
}
let finalEvent = null;
if (eventId) {
finalEvent = await waitForTerminal(eventId);
if (finalEvent) {
findings.push(
`- Lifecycle terminal status: **${finalEvent.status}**, winner=${finalEvent.winner_address || "n/a"}, winning_bid=${finalEvent.winning_bid ?? "n/a"}`,
);
} else {
findings.push(`- Lifecycle did NOT reach terminal within 90s.`);
}
}
// 2. Playwright UI walk
const context = await browser.newContext({
viewport: { width: 1280, height: 800 },
});
const page = await context.newPage();
const errors = [];
page.on("pageerror", (e) => errors.push(`pageerror: ${e.message}`));
page.on("console", (msg) => {
if (msg.type() === "error") errors.push(`console.error: ${msg.text()}`);
});
let step = 0;
const shot = async (label) => {
step += 1;
const p = path.join(SHOT_DIR, `cycle_${cycleN}_step_${step}_${label}.png`);
try {
await page.screenshot({ path: p, fullPage: false });
} catch (e) {
findings.push(`- Screenshot failed for ${label}: ${e.message}`);
}
};
try {
// /events list page
await page.goto(`${BASE_UI}/events`, {
waitUntil: "domcontentloaded",
timeout: 20000,
});
await page.waitForTimeout(1500);
await shot("events_list");
if (eventId) {
await page.goto(`${BASE_UI}/events/${eventId}`, {
waitUntil: "domcontentloaded",
timeout: 20000,
});
await page.waitForTimeout(2500);
await shot("event_detail");
// Try click any DAG node (visible elements only)
const dagNodes = await page.$$('[data-testid^="dag-node"], .dag-node, svg g[data-id]');
findings.push(`- Found ${dagNodes.length} DAG-ish nodes on /events/${eventId}.`);
let dagClickedAny = false;
for (const n of dagNodes.slice(0, 5)) {
try {
const vis = await n.isVisible();
if (!vis) continue;
await n.click({ timeout: 2000 });
dagClickedAny = true;
await page.waitForTimeout(500);
await shot("dag_click");
break;
} catch (e) {
/* try next */
}
}
findings.push(`- DAG click reached visible node: ${dagClickedAny}`);
// Check Timeline test ID & sub-phase chips
const tl = await page.$('[data-testid*="timeline"], .timeline, [class*="Timeline"]');
const subPhaseChips = await page.$('[data-testid="sub-phase-chips"]');
const debatePanel = await page.$('[data-testid="agent-debate-panel"], [data-testid="agent-debate-panel-empty"]');
findings.push(`- Timeline element present: ${tl ? "yes" : "no"}`);
findings.push(`- sub-phase-chips present: ${subPhaseChips ? "yes" : "no"}`);
findings.push(`- agent-debate-panel present: ${debatePanel ? "yes" : "no"}`);
// Try clicking any tab/button to exercise interactivity
const buttons = await page.$$('button[role="tab"], [role="tab"]');
findings.push(`- Tabs found: ${buttons.length}`);
if (buttons.length > 1) {
try {
await buttons[1].click({ timeout: 2000 });
await page.waitForTimeout(500);
await shot("tab_click");
} catch (e) {
findings.push(`- Tab click failed: ${e.message}`);
}
}
// Probe DOM for status text matching our final event
if (finalEvent) {
const html = await page.content();
const seen = html.includes(finalEvent.status);
findings.push(`- Final status \`${finalEvent.status}\` visible in DOM: ${seen}`);
// Cross-check phases page
const phasesResp = await getJson(
`${BASE_API}/events/${eventId}/phases`,
);
if (phasesResp.body && Array.isArray(phasesResp.body)) {
const completed = phasesResp.body.filter((p) => p.status === "completed").length;
const failed = phasesResp.body.filter((p) => p.status === "failed").length;
findings.push(
`- /phases API: ${phasesResp.body.length} total, ${completed} completed, ${failed} failed`,
);
}
}
}
// Cycle 5: explore other pages
if (cycleN === 5) {
const otherRoutes = ["/", "/leaderboard", "/about", "/operators"];
for (const r of otherRoutes) {
try {
await page.goto(`${BASE_UI}${r}`, {
waitUntil: "domcontentloaded",
timeout: 15000,
});
await page.waitForTimeout(1200);
await shot(`route_${r.replace(/\//g, "_") || "root"}`);
findings.push(`- ${r}: loaded OK`);
} catch (e) {
findings.push(`- ${r}: FAILED ${e.message}`);
}
}
}
} catch (e) {
findings.push(`- Playwright walk error: ${e.message}`);
}
if (errors.length > 0) {
findings.push(`- JS errors observed (${errors.length}):`);
for (const er of errors.slice(0, 5)) findings.push(` - ${er}`);
} else {
findings.push(`- JS errors observed: 0`);
}
await context.close();
findings.push(`- Cycle finished at ${ts()}`);
for (const f of findings) append(f);
return { eventId, finalEvent, errors: errors.length };
}
(async () => {
if (!fs.existsSync(FINDINGS)) {
append(`# Playwright Loop Findings (A2 sub-agent)`);
append(`Started ${ts()}`);
} else {
append(`\n---`);
append(`# A2 Loop Session ${ts()}`);
}
const browser = await chromium.launch({ headless: true });
const results = [];
for (let i = 0; i < CYCLES.length; i++) {
try {
const r = await runCycle(browser, i + 1, CYCLES[i]);
results.push(r);
} catch (e) {
append(`- Cycle ${i + 1} threw: ${e.message}`);
results.push({ error: e.message });
}
}
await browser.close();
append(`\n## Session summary`);
append(`- Cycles attempted: ${CYCLES.length}`);
append(
`- Cycles completed: ${results.filter((r) => !r.error).length}`,
);
append(`- Session end: ${ts()}`);
console.log("DONE");
console.log(JSON.stringify(results, null, 2));
})();