Spaces:
Running
Running
| // A2 sub-agent: focused SSE + extra UI walk session. | |
| // One trigger, then probe SSE streams, /agents/{address}, /events/{id}/bids, | |
| // /events/{id}/translations, /builder_fees, /health endpoints. | |
| /* eslint-disable no-console */ | |
| const fs = require("fs"); | |
| const path = require("path"); | |
| const http = require("http"); | |
| const { chromium } = require("playwright"); | |
| const BASE_UI = "http://127.0.0.1:3001"; | |
| const 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"); | |
| const SESSION_TAG = `a2sse-${Date.now()}`; | |
| 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; | |
| } | |
| // SSE probe: collect first ~5 lines or 10s | |
| function sseProbe(pathPart) { | |
| return new Promise((resolve) => { | |
| const u = new URL(`${BASE_API}${pathPart}`); | |
| const req = http.get({ hostname: u.hostname, port: u.port, path: u.pathname, headers: { Accept: "text/event-stream" }}, | |
| (res) => { | |
| const lines = []; | |
| const status = res.statusCode; | |
| res.on("data", (c) => { | |
| lines.push(c.toString().slice(0, 200)); | |
| if (lines.length >= 8) { req.destroy(); resolve({ status, lines, ended: false }); } | |
| }); | |
| res.on("end", () => resolve({ status, lines, ended: true })); | |
| setTimeout(() => { req.destroy(); resolve({ status, lines, ended: false }); }, 10000); | |
| }); | |
| req.on("error", (e) => resolve({ error: e.message })); | |
| }); | |
| } | |
| (async () => { | |
| append(`\n---`); | |
| append(`# A2 SSE+Extras Loop ${ts()}`); | |
| // 1) Trigger one event with 2 mock bids that we expect to potentially succeed | |
| const trigger = { | |
| event_source: "user_payload", | |
| title: `Will event SSE-X happen by 2026-12-31? [${SESSION_TAG}]`, | |
| sources: [{ name: "test-sse", url: `https://test/sse?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 }, | |
| ], | |
| }; | |
| append(`\n## SSE Cycle (single): ${ts()}`); | |
| const tr = await postJson(`${BASE_API}/trigger/event`, trigger); | |
| append(`- Trigger HTTP ${tr.status}: \`${JSON.stringify(tr.body).slice(0,200)}\``); | |
| const eventId = tr.body && tr.body.event_id; | |
| // 2) While lifecycle runs, probe SSE in parallel (10s window) | |
| const ssePromise1 = sseProbe("/sse/events"); | |
| const ssePromise2 = sseProbe("/sse/auctions"); | |
| const [sseEvents, sseAuctions] = await Promise.all([ssePromise1, ssePromise2]); | |
| append(`- /sse/events: status=${sseEvents.status}, lines=${(sseEvents.lines||[]).length}, ended=${sseEvents.ended}`); | |
| if (sseEvents.lines && sseEvents.lines[0]) append(` - first: \`${sseEvents.lines[0].replace(/\n/g,"\\n").slice(0,140)}\``); | |
| append(`- /sse/auctions: status=${sseAuctions.status}, lines=${(sseAuctions.lines||[]).length}, ended=${sseAuctions.ended}`); | |
| if (sseAuctions.lines && sseAuctions.lines[0]) append(` - first: \`${sseAuctions.lines[0].replace(/\n/g,"\\n").slice(0,140)}\``); | |
| // 3) Wait terminal | |
| let finalEvent = null; | |
| if (eventId) { | |
| finalEvent = await waitForTerminal(eventId); | |
| append(`- Lifecycle: ${finalEvent ? finalEvent.status : "TIMEOUT"}${finalEvent ? `, winner=${finalEvent.winner_address}` : ""}`); | |
| } | |
| // 4) Probe extra REST endpoints | |
| const endpoints = [ | |
| "/health", | |
| "/builder_fees", | |
| `/events/${eventId}`, | |
| `/events/${eventId}/bids`, | |
| `/events/${eventId}/phases`, | |
| `/events/${eventId}/translations`, | |
| "/agents/0xagent_a", | |
| "/agents/0xagent_a/history", | |
| "/leaderboard", | |
| ]; | |
| append(`\n### REST endpoint probes`); | |
| for (const ep of endpoints) { | |
| try { | |
| const r = await getJson(`${BASE_API}${ep}`); | |
| const bodyLen = typeof r.body === "object" ? JSON.stringify(r.body).length : r.body.length; | |
| append(`- \`${ep}\` -> HTTP ${r.status}, body-len=${bodyLen}`); | |
| } catch (e) { | |
| append(`- \`${ep}\` -> exception: ${e.message}`); | |
| } | |
| } | |
| // 5) UI walk for that event + /history page | |
| const browser = await chromium.launch({ headless: true }); | |
| const ctx = await browser.newContext({ viewport: { width: 1280, height: 800 } }); | |
| const page = await ctx.newPage(); | |
| const errors = []; | |
| page.on("pageerror", e => errors.push(`pageerror: ${e.message}`)); | |
| page.on("console", m => { if (m.type() === "error") errors.push(`console.error: ${m.text()}`); }); | |
| let step = 0; | |
| const shot = async (label) => { | |
| step += 1; | |
| try { await page.screenshot({ path: path.join(SHOT_DIR, `sse_step_${step}_${label}.png`) }); } | |
| catch (e) {} | |
| }; | |
| append(`\n### UI walk`); | |
| try { | |
| if (eventId) { | |
| await page.goto(`${BASE_UI}/events/${eventId}`, { waitUntil: "domcontentloaded", timeout: 20000 }); | |
| await page.waitForTimeout(2500); | |
| await shot("event_detail"); | |
| const sub = await page.$('[data-testid="sub-phase-chips"]'); | |
| const debate = await page.$('[data-testid="agent-debate-panel"], [data-testid="agent-debate-panel-empty"]'); | |
| append(`- /events/${eventId}: sub-phase-chips=${!!sub}, debate-panel=${!!debate}`); | |
| } | |
| // Visit /history | |
| await page.goto(`${BASE_UI}/history`, { waitUntil: "domcontentloaded", timeout: 15000 }); | |
| await page.waitForTimeout(1500); | |
| await shot("history"); | |
| const histText = await page.textContent("body"); | |
| append(`- /history: loaded, body-text-len=${(histText || "").length}`); | |
| // Visit /leaderboard | |
| await page.goto(`${BASE_UI}/leaderboard`, { waitUntil: "domcontentloaded", timeout: 15000 }); | |
| await page.waitForTimeout(1500); | |
| await shot("leaderboard"); | |
| const lbText = await page.textContent("body"); | |
| append(`- /leaderboard: loaded, body-text-len=${(lbText || "").length}`); | |
| } catch (e) { | |
| append(`- UI walk error: ${e.message}`); | |
| } | |
| append(`- JS errors observed: ${errors.length}`); | |
| for (const er of errors.slice(0, 5)) append(` - ${er}`); | |
| await ctx.close(); | |
| await browser.close(); | |
| append(`- SSE+Extras cycle finished at ${ts()}`); | |
| console.log("DONE"); | |
| })(); | |