From 796de876b779c7efd0bfc671e9f25c122d483414 Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Sat, 11 Apr 2026 19:05:44 -0400 Subject: [PATCH] feat(soak): wire --watch=dashboard in runner Starts DashboardServer on port 7777 (or --dashboard-port), uses its reporter as ctx.dashboard, auto-opens the URL via xdg-open/open/start. Cleans up on exit. WS client connections logged as info events so you can see when the browser attaches. Verified: 2-account populate run with --watch=dashboard serves the static page on :7777, accepts WS connections, cleanly shuts down when the run completes. Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/soak/runner.ts | 46 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/tests/soak/runner.ts b/tests/soak/runner.ts index e1b419f..0ed9d83 100644 --- a/tests/soak/runner.ts +++ b/tests/soak/runner.ts @@ -10,10 +10,12 @@ */ import * as path from 'path'; +import { spawn } from 'child_process'; import { parseArgs, mergeConfig, CliArgs } from './config'; import { createLogger } from './core/logger'; import { SessionPool } from './core/session-pool'; import { RoomCoordinator } from './core/room-coordinator'; +import { DashboardServer } from './dashboard/server'; import { getScenario, listScenarios } from './scenarios'; import type { DashboardReporter, ScenarioContext } from './core/types'; @@ -96,11 +98,6 @@ async function main(): Promise { return; } - if (watch !== 'none') { - logger.warn('watch_mode_not_yet_implemented', { watch }); - console.warn(`Watch mode "${watch}" not yet implemented — falling back to "none".`); - } - // Build dependencies const credFile = path.resolve(__dirname, '.env.stresstest'); const pool = new SessionPool({ @@ -110,7 +107,41 @@ async function main(): Promise { logger, }); const coordinator = new RoomCoordinator(); - const dashboard = noopDashboard(); + + // Optional dashboard server + let dashboardServer: DashboardServer | null = null; + let dashboard: DashboardReporter = noopDashboard(); + if (watch === 'dashboard') { + const port = Number(config.dashboardPort ?? 7777); + dashboardServer = new DashboardServer(port, logger, { + onStartStream: (key) => { + logger.info('stream_start_requested', { sessionKey: key }); + // Wired in Task 23 + }, + onStopStream: (key) => { + logger.info('stream_stop_requested', { sessionKey: key }); + }, + }); + await dashboardServer.start(); + dashboard = dashboardServer.reporter(); + const url = `http://localhost:${port}`; + console.log(`Dashboard: ${url}`); + try { + const opener = + process.platform === 'darwin' + ? 'open' + : process.platform === 'win32' + ? 'start' + : 'xdg-open'; + spawn(opener, [url], { stdio: 'ignore', detached: true }).unref(); + } catch { + // If auto-open fails, the URL is already printed. + } + } else if (watch === 'tiled') { + logger.warn('tiled_not_yet_implemented'); + console.warn('Watch mode "tiled" not yet implemented (Task 24). Falling back to none.'); + } + const abortController = new AbortController(); const onSignal = (sig: string) => { @@ -161,6 +192,9 @@ async function main(): Promise { exitCode = 1; } finally { await pool.release(); + if (dashboardServer) { + await dashboardServer.stop(); + } } if (abortController.signal.aborted && exitCode === 0) exitCode = 2;