// SPEC: PJ2026-01060505 Workbench Performance draft-2026-06-17-p0.
// Responsibility: Source for the authenticated HWLAB node web-probe runner.

export function nodeWebProbeScriptRunnerSource(): string {
  return String.raw`#!/usr/bin/env node
import { createHash } from "node:crypto";
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
import path from "node:path";
import { pathToFileURL } from "node:url";

const startedAtMs = Date.now();
const startedAt = new Date(startedAtMs).toISOString();
const baseUrl = normalizeBaseUrl(process.env.HWLAB_WEB_BASE_URL);
const username = process.env.HWLAB_WEB_USER || "admin";
const password = process.env.HWLAB_WEB_PASS || "";
const runDir = path.resolve(process.env.UNIDESK_WEB_PROBE_RUN_DIR || ".state/web-probe-script/run-manual");
const userScript = path.resolve(process.env.UNIDESK_WEB_PROBE_USER_SCRIPT || path.join(runDir, "user-script.mjs"));
const timeoutMs = positiveInteger(process.env.UNIDESK_WEB_PROBE_TIMEOUT_MS, 30000);
const viewport = parseViewport(process.env.UNIDESK_WEB_PROBE_VIEWPORT || "1440x900");
const browserProxyMode = parseBrowserProxyMode(process.env.UNIDESK_WEB_PROBE_BROWSER_PROXY_MODE || "auto");
const playwrightProxy = proxyConfigFromEnv(baseUrl);
const chromiumLaunchOptions = chromiumLaunchOptionsForProxy(playwrightProxy);
const artifactRecords = [];
const readinessRecords = [];
const stepRecords = [];
let browser;
let context;
let page;
let auth = null;
let cookieSecrets = [];
let userScriptSha256 = null;

try {
  if (!password) throw new Error("missing HWLAB_WEB_PASS");
  await mkdir(runDir, { recursive: true, mode: 0o700 });
  userScriptSha256 = await sha256File(userScript).catch(() => null);
  const launcher = await import(pathToFileURL(path.resolve("scripts/src/browser-launcher.mjs")).href);
  const { chromium } = await launcher.importPlaywright();
  browser = await launcher.launchChromium(chromium, chromiumLaunchOptions);
  context = await browser.newContext({ viewport, ...(playwrightProxy === null ? {} : { proxy: playwrightProxy }) });
  auth = await authenticate(context);
  if (!auth.ok) throw new Error("auth-login-failed");
  page = await context.newPage();

  const mod = await import(pathToFileURL(userScript).href + "?t=" + Date.now());
  const fn = typeof mod.default === "function" ? mod.default : typeof mod.run === "function" ? mod.run : typeof mod.probe === "function" ? mod.probe : null;
  if (fn === null) throw new Error("custom script must export default, run, or probe function");
  const scriptResult = await fn(scriptHelpers());
  const safeResult = sanitize(scriptResult);
  const scriptOk = scriptResultOk(safeResult);
  const failure = scriptOk ? null : classifiedProbeError(assertionResultError(safeResult));
  const lastScreenshot = scriptOk ? null : await captureFailureScreenshot("failure.png");
  const lastUrl = currentPageUrl();
  await emit({
    ok: scriptOk,
    status: scriptOk ? "pass" : "blocked",
    command: "web-probe-script",
    generatedAt: new Date().toISOString(),
    startedAt,
    baseUrl,
    network: publicNetwork(playwrightProxy),
    finalUrl: lastUrl,
    lastUrl,
    scriptSha256: userScriptSha256,
    runDir,
    auth: publicAuth(auth),
    script: { ok: scriptOk, result: safeResult, steps: publicSteps() },
    steps: publicSteps(),
    failureKind: failure ? failure.failureKind : null,
    error: failure ? failure.code : null,
    errorMessage: failure ? sanitize(failure.message) : null,
    guidance: failure ? failure.guidance : null,
    lastScreenshot,
    readiness: publicReadiness(failure ? { error: failure.code, failureKind: failure.failureKind } : {}),
    artifacts: { runDir, items: artifactRecords },
    safety: { valuesRedacted: true, secretValuesPrinted: false }
  });
  process.exitCode = scriptOk ? 0 : 2;
} catch (error) {
  const failure = classifiedProbeError(error);
  const lastScreenshot = await captureFailureScreenshot("failure.png");
  const lastUrl = currentPageUrl();
  await emit({
    ok: false,
    status: "blocked",
    command: "web-probe-script",
    generatedAt: new Date().toISOString(),
    startedAt,
    baseUrl,
    network: publicNetwork(playwrightProxy),
    finalUrl: lastUrl,
    lastUrl,
    scriptSha256: userScriptSha256,
    runDir,
    auth: auth === null ? null : publicAuth(auth),
    script: { ok: false, steps: publicSteps() },
    steps: publicSteps(),
    failureKind: failure.failureKind,
    error: failure.code,
    errorMessage: sanitize(failure.message),
    guidance: failure.guidance,
    lastScreenshot,
    readiness: publicReadiness({ error: failure.code, failureKind: failure.failureKind }),
    artifacts: { runDir, items: artifactRecords },
    safety: { valuesRedacted: true, secretValuesPrinted: false }
  });
  process.exitCode = 2;
} finally {
  if (browser) await browser.close().catch(() => {});
}

async function authenticate(browserContext) {
  return authenticateWithApiRetries(browserContext);
}

async function authenticateWithApiRetries(browserContext) {
  const loginUrl = new URL("/auth/login", baseUrl).toString();
  const attempts = [];
  const maxAttempts = 5;
  const initialDelayMs = 250;
  const maxDelayMs = 5000;
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
    const retryDelayMs = attempt < maxAttempts ? Math.min(maxDelayMs, initialDelayMs * (2 ** (attempt - 1))) : 0;
    try {
      const response = await browserContext.request.post(loginUrl, {
        data: { username, password },
        headers: { accept: "application/json", "content-type": "application/json" },
        timeout: Math.min(timeoutMs, 12000),
      });
      const summary = await responseSummary(response);
      const cookieState = await readAuthCookieState(browserContext);
      const retryable = isRetryableAuthStatus(response.status());
      const item = {
        attempt,
        retryAttempt: attempt,
        retryMaxAttempts: maxAttempts,
        retryLabel: attempt + "/" + maxAttempts,
        retryDelayMs: retryable && attempt < maxAttempts ? retryDelayMs : 0,
        method: "api",
        ...summary,
        retryable,
        cookiePresent: cookieState.cookiePresent,
        cookieNames: cookieState.cookieNames,
      };
      attempts.push(item);
      if (response.ok() && cookieState.cookiePresent) {
        return {
          ok: true,
          method: "api",
          loginUrl: new URL("/auth/login", baseUrl).pathname,
          status: response.status(),
          statusText: response.statusText(),
          cookiePresent: true,
          cookieNames: cookieState.cookieNames,
          attempts,
          retryCount: attempt - 1,
          retryMaxAttempts: maxAttempts,
          lastRetryLabel: attempt + "/" + maxAttempts,
          retryExhausted: false,
          fallbackUsed: false,
          valuesRedacted: true,
        };
      }
      if (!retryable) break;
    } catch (error) {
      attempts.push({
        attempt,
        retryAttempt: attempt,
        retryMaxAttempts: maxAttempts,
        retryLabel: attempt + "/" + maxAttempts,
        retryDelayMs: attempt < maxAttempts ? retryDelayMs : 0,
        method: "api",
        status: 0,
        statusText: "request-error",
        retryable: true,
        error: error instanceof Error ? error.message : String(error),
        cookiePresent: false,
      });
    }
    if (attempt < maxAttempts && attempts[attempts.length - 1]?.retryable === true) await sleep(retryDelayMs);
  }
  const cookieState = await readAuthCookieState(browserContext);
  const last = attempts[attempts.length - 1] ?? null;
  const retryable = authAttemptsRetryable(attempts);
  return {
    ok: false,
    method: "api",
    loginUrl: new URL("/auth/login", baseUrl).pathname,
    status: typeof last?.status === "number" ? last.status : 0,
    statusText: typeof last?.statusText === "string" ? last.statusText : "api-login-failed",
    cookiePresent: cookieState.cookiePresent,
    cookieNames: cookieState.cookieNames,
    attempts,
    retryCount: retryCountFromAttempts(attempts),
    retryMaxAttempts: maxAttempts,
    lastRetryLabel: last?.retryLabel ?? null,
    retryExhausted: retryable && attempts.length >= maxAttempts,
    fallbackUsed: false,
    retryable,
    errorSummary: last,
    valuesRedacted: true,
  };
}

async function authenticateWithFormFallback(browserContext, apiAuth) {
  const loginPage = await browserContext.newPage();
  const fallback = {
    method: "form",
    startPath: "/workbench",
    surface: null,
    finalPath: null,
    response: null,
    outcome: null,
    error: null,
  };
  try {
    await loginPage.goto(new URL("/workbench", baseUrl).toString(), { waitUntil: "domcontentloaded", timeout: timeoutMs });
    const surface = await waitForAuthSurface(loginPage);
    fallback.surface = surface;
    if (surface === "workspace") {
      const cookieState = await readAuthCookieState(browserContext);
      fallback.finalPath = new URL(loginPage.url()).pathname;
      return {
        ok: cookieState.cookiePresent,
        method: "form-fallback",
        loginUrl: new URL("/auth/login", baseUrl).pathname,
        status: cookieState.cookiePresent ? 200 : 0,
        statusText: cookieState.cookiePresent ? "already-authenticated" : "workspace-without-session-cookie",
        cookiePresent: cookieState.cookiePresent,
        cookieNames: cookieState.cookieNames,
        attempts: apiAuth.attempts,
        retryCount: apiAuth.retryCount ?? retryCountFromAttempts(apiAuth.attempts),
        fallbackUsed: true,
        retryable: apiAuth.retryable === true,
        apiErrorSummary: apiAuth.errorSummary,
        fallback,
        valuesRedacted: true,
      };
    }
    if (surface !== "login-card" && surface !== "legacy-id") {
      throw new Error("login form not visible");
    }

    const usernameInput = loginPage.locator('#login-username, input[autocomplete="username"], .login-card input').first();
    const passwordInput = loginPage.locator('#login-password, input[autocomplete="current-password"], .login-card input[type="password"], input[type="password"]').first();
    const submitButton = loginPage.locator('#login-submit, .login-card button[type="submit"], button[type="submit"]').first();
    await usernameInput.fill(username);
    await passwordInput.fill(password);
    await loginPage.waitForFunction((expected) => {
      const user = document.querySelector('#login-username, input[autocomplete="username"], .login-card input');
      const pass = document.querySelector('#login-password, input[autocomplete="current-password"], .login-card input[type="password"], input[type="password"]');
      const submit = document.querySelector('#login-submit, .login-card button[type="submit"], button[type="submit"]');
      return Boolean(user && pass && user.value === expected.user && pass.value.length === expected.passwordLength && !submit?.disabled);
    }, { user: username, passwordLength: password.length }, { timeout: Math.min(timeoutMs, 5000) }).catch(() => null);

    const loginResponsePromise = loginPage.waitForResponse((response) => responsePathMatches(response.url(), "/auth/login"), { timeout: Math.min(timeoutMs, 15000) }).catch(() => null);
    await Promise.allSettled([
      loginPage.waitForFunction(() => document.querySelector("#workspace") || document.querySelector("#command-input"), null, { timeout: Math.min(timeoutMs, 15000) }),
      submitButton.click(),
    ]);
    const loginResponse = await loginResponsePromise;
    if (loginResponse) fallback.response = await responseSummary(loginResponse);
    fallback.outcome = await formLoginOutcome(loginPage);
    fallback.finalPath = new URL(loginPage.url()).pathname;
    assertNoCredentialLeak(loginPage.url());

    const cookieState = await readAuthCookieState(browserContext);
    return {
      ok: cookieState.cookiePresent,
      method: "form-fallback",
      loginUrl: new URL("/auth/login", baseUrl).pathname,
      status: fallback.response?.status ?? (cookieState.cookiePresent ? 200 : 0),
      statusText: fallback.response?.statusText ?? (cookieState.cookiePresent ? "OK" : "form-login-no-cookie"),
      cookiePresent: cookieState.cookiePresent,
      cookieNames: cookieState.cookieNames,
      attempts: apiAuth.attempts,
      retryCount: apiAuth.retryCount ?? retryCountFromAttempts(apiAuth.attempts),
      fallbackUsed: true,
      retryable: apiAuth.retryable === true,
      apiErrorSummary: apiAuth.errorSummary,
      fallback,
      valuesRedacted: true,
    };
  } catch (error) {
    fallback.error = error instanceof Error ? error.message : String(error);
    fallback.finalPath = loginPage ? new URL(loginPage.url()).pathname : null;
    const cookieState = await readAuthCookieState(browserContext).catch(() => ({ cookiePresent: false, cookieNames: [] }));
    return {
      ok: false,
      method: "form-fallback",
      loginUrl: new URL("/auth/login", baseUrl).pathname,
      status: fallback.response?.status ?? 0,
      statusText: fallback.response?.statusText ?? "form-login-failed",
      cookiePresent: cookieState.cookiePresent,
      cookieNames: cookieState.cookieNames,
      attempts: apiAuth.attempts,
      retryCount: apiAuth.retryCount ?? retryCountFromAttempts(apiAuth.attempts),
      fallbackUsed: true,
      retryable: apiAuth.retryable === true,
      apiErrorSummary: apiAuth.errorSummary,
      fallback,
      valuesRedacted: true,
    };
  } finally {
    await loginPage.close().catch(() => {});
  }
}

async function waitForAuthSurface(targetPage) {
  const handle = await targetPage.waitForFunction(() => {
    if (document.querySelector("#workspace") || document.querySelector("#command-input")) return "workspace";
    if (document.querySelector("#login-username")) return "legacy-id";
    if (document.querySelector('input[autocomplete="username"]') && document.querySelector('input[autocomplete="current-password"]')) return "login-card";
    if (document.querySelector(".login-card input")) return "login-card";
    return "";
  }, null, { timeout: Math.min(timeoutMs, 12000) }).catch(() => null);
  if (!handle) return null;
  return handle.jsonValue().catch(() => null);
}

async function formLoginOutcome(targetPage) {
  const handle = await targetPage.waitForFunction(() => {
    if (document.querySelector("#workspace") || document.querySelector("#command-input")) return { kind: "workspace" };
    const errorText = document.querySelector(".form-error, .login-error, [role='alert'], .error, .text-red-500, .text-destructive")?.textContent?.trim();
    if (errorText) return { kind: "error", errorText };
    return null;
  }, null, { timeout: Math.min(timeoutMs, 5000) }).catch(() => null);
  if (!handle) return null;
  return handle.jsonValue().catch(() => null);
}

function scriptHelpers() {
  const livePage = createLivePageProxy();
  const helpers = {
    browser,
    context,
    page: livePage,
    baseUrl,
    runDir,
    auth: publicAuth(auth),
    artifactPath,
    screenshot,
    screenshotOnError,
    jsonArtifact,
    sha256File,
    wait: sleep,
    goto,
    gotoRaw,
    gotoStable,
    reloadStable,
    safeReload,
    gotoCurrentStable,
    waitWorkbenchReady,
    waitForReady,
    fetchJson,
    safeFetchJson,
    fetchApiMatrix,
    recordStep,
    collectText,
    safeEvaluate,
    summarizeWorkspace,
    summarizeConversation,
    getPage: () => page,
    request: context.request,
  };
  Object.defineProperty(helpers, "currentPage", {
    enumerable: false,
    get: () => page,
  });
  return helpers;
}

function proxyConfigFromEnv(targetBaseUrl) {
  if (browserProxyMode === "direct") return null;
  let target;
  try {
    target = new URL(targetBaseUrl);
  } catch {
    return null;
  }
  const noProxy = process.env.NO_PROXY || process.env.no_proxy || "";
  if (noProxyMatches(target.hostname, noProxy)) return null;
  const raw = target.protocol === "https:"
    ? process.env.HTTPS_PROXY || process.env.https_proxy || process.env.ALL_PROXY || process.env.all_proxy || process.env.HTTP_PROXY || process.env.http_proxy || ""
    : process.env.HTTP_PROXY || process.env.http_proxy || process.env.ALL_PROXY || process.env.all_proxy || process.env.HTTPS_PROXY || process.env.https_proxy || "";
  if (!raw) return null;
  return { server: raw };
}

function chromiumLaunchOptionsForProxy(proxy) {
  const base = { env: browserProcessEnvWithoutProxy() };
  if (proxy === null) return { ...base, args: ["--no-proxy-server"] };
  return { ...base, proxy };
}

function browserProcessEnvWithoutProxy() {
  const blocked = new Set(["HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "all_proxy", "no_proxy"]);
  const env = {};
  for (const [key, value] of Object.entries(process.env)) {
    if (!blocked.has(key) && value !== undefined) env[key] = value;
  }
  return env;
}

function noProxyMatches(hostname, rawList) {
  const host = String(hostname || "").toLowerCase();
  if (!host) return false;
  return String(rawList || "").split(",").some((raw) => {
    let item = raw.trim().toLowerCase();
    if (!item) return false;
    if (item === "*") return true;
    item = item.replace(/^\*\./u, ".");
    const portIndex = item.lastIndexOf(":");
    if (portIndex > -1 && !item.includes("]")) item = item.slice(0, portIndex);
    if (item.startsWith(".")) return host === item.slice(1) || host.endsWith(item);
    return host === item;
  });
}

function publicNetwork(proxy) {
  return {
    proxy: proxy === null ? { enabled: false, source: "env", valuesRedacted: true } : {
      enabled: true,
      source: "env",
      server: publicProxyServer(proxy.server),
      valuesRedacted: true,
    },
    browser: {
      proxyMode: proxy === null ? "direct-no-proxy-server" : "explicit-playwright-proxy",
      requestedProxyMode: browserProxyMode,
      proxyEnvCleared: true,
      valuesRedacted: true,
    },
    valuesRedacted: true,
  };
}

function parseBrowserProxyMode(raw) {
  if (raw === "auto" || raw === "direct") return raw;
  return "auto";
}

function publicProxyServer(raw) {
  try {
    const parsed = new URL(String(raw || ""));
    parsed.username = "";
    parsed.password = "";
    const value = parsed.toString();
    if (parsed.pathname === "/" && parsed.search === "" && parsed.hash === "") return value.replace(/\/$/u, "");
    return value;
  } catch {
    return String(raw || "").replace(/\/\/[^/@]+@/u, "//[redacted]@");
  }
}

function createLivePageProxy() {
  return new Proxy({}, {
    get(_target, prop) {
      if (prop === "then") return undefined;
      const target = page;
      if (!target) throw stableProbeError("browser-load-jitter", "browser page is not initialized", publicReadiness({ error: "browser-page-missing" }));
      const value = target[prop];
      return typeof value === "function" ? value.bind(target) : value;
    },
    set(_target, prop, value) {
      const target = page;
      if (!target) throw stableProbeError("browser-load-jitter", "browser page is not initialized", publicReadiness({ error: "browser-page-missing" }));
      target[prop] = value;
      return true;
    },
    has(_target, prop) {
      return Boolean(page && prop in page);
    }
  });
}

function recordStep(name, data = {}, options = {}) {
  const normalizedName = String(name || "step").replace(/[^A-Za-z0-9_.:-]/gu, "-").slice(0, 80) || "step";
  const item = {
    index: stepRecords.length,
    name: normalizedName,
    ok: data && typeof data === "object" && typeof data.ok === "boolean" ? data.ok : null,
    atMs: Date.now() - startedAtMs,
    data: sanitize(data),
  };
  stepRecords.push(item);
  while (stepRecords.length > boundedInteger(options.maxSteps, 80, 1, 200)) stepRecords.shift();
  return deepClonePlain(item);
}

function publicSteps() {
  return stepRecords.slice(-50).map((item) => deepClonePlain(item));
}

async function safeFetchJson(target, options = {}) {
  try {
    return await fetchJson(target, { ...normalizeHelperOptions(options), throwOnError: false });
  } catch (error) {
    const failure = classifiedProbeError(error);
    return {
      ok: false,
      path: typeof target === "string" ? urlPath(target) : String(target ?? ""),
      status: 0,
      statusText: "helper-error",
      failureKind: failure.failureKind,
      error: sanitize(failure.message),
      guidance: failure.guidance,
    };
  }
}

async function fetchApiMatrix(paths, options = {}) {
  options = normalizeHelperOptions(options);
  const items = normalizeApiMatrixItems(paths);
  const rows = [];
  for (const item of items) {
    const response = await safeFetchJson(item.path, {
      headers: item.headers ?? options.headers,
      method: item.method ?? options.method,
      body: item.body ?? undefined,
    });
    rows.push(compactApiMatrixResponse(item, response));
  }
  const failed = rows.filter((row) => row.ok !== true);
  const matrix = {
    ok: failed.length === 0,
    count: rows.length,
    okCount: rows.length - failed.length,
    failedCount: failed.length,
    items: rows,
  };
  if (options.record !== false) recordStep(typeof options.name === "string" ? options.name : "api-matrix", matrix);
  return matrix;
}

function normalizeApiMatrixItems(value) {
  const list = Array.isArray(value) ? value : [value];
  return list.slice(0, 20).map((item, index) => {
    if (typeof item === "string") return { name: item, path: item, method: "GET" };
    if (item && typeof item === "object") {
      const path = typeof item.path === "string" ? item.path : typeof item.url === "string" ? item.url : "/";
      return {
        name: typeof item.name === "string" ? item.name : path,
        path,
        method: typeof item.method === "string" ? item.method : "GET",
        headers: item.headers,
        body: item.body,
      };
    }
    return { name: "item-" + index, path: "/", method: "GET" };
  });
}

function compactApiMatrixResponse(item, response) {
  const body = response && typeof response.body === "object" && response.body !== null ? response.body : null;
  return {
    name: item.name,
    path: response?.path ?? item.path,
    method: item.method ?? "GET",
    ok: response?.ok === true,
    status: response?.status ?? null,
    statusText: response?.statusText ?? null,
    error: response?.error ?? response?.parseError ?? null,
    failureKind: response?.failureKind ?? null,
    bodyKeys: body && !Array.isArray(body) ? Object.keys(body).slice(0, 20) : [],
    bodyPreview: body ? normalizeTextPreview(JSON.stringify(sanitize(body)), 240) : response?.textPreview ?? null,
  };
}

async function safeEvaluate(fn, arg = undefined, options = {}) {
  options = normalizeHelperOptions(options);
  if (typeof fn !== "function") {
    return {
      ok: false,
      failureKind: "script-api-misuse",
      error: "safeEvaluate requires a function",
      guidance: evaluateSingleArgGuidance(),
    };
  }
  try {
    return { ok: true, value: sanitize(await page.evaluate(fn, arg)) };
  } catch (error) {
    const failure = classifiedProbeError(markEvaluateError(error));
    if (options.throwOnError === true) throw markEvaluateError(error);
    return {
      ok: false,
      failureKind: failure.failureKind,
      error: sanitize(failure.message),
      guidance: failure.guidance,
    };
  }
}

async function fetchJson(target, options = {}) {
  options = normalizeHelperOptions(options);
  let url;
  try {
    url = new URL(String(target || "/"), baseUrl).toString();
  } catch (error) {
    return {
      ok: false,
      path: String(target ?? ""),
      status: 0,
      statusText: "invalid-url",
      error: error instanceof Error ? error.message : String(error),
    };
  }

  const headers = normalizeFetchHeaders(options.headers);
  const method = typeof options.method === "string" && options.method.length > 0 ? options.method : "GET";
  const body = normalizeFetchBody(options.body, headers);
  const evaluated = await safeEvaluate(async ({ url: requestUrl, method: requestMethod, headers: requestHeaders, body: requestBody }) => {
    try {
      const response = await fetch(requestUrl, {
        method: requestMethod,
        headers: requestHeaders,
        body: requestBody === null ? undefined : requestBody,
        credentials: "include",
      });
      const text = await response.text();
      let json = null;
      let parseError = null;
      if (text.length > 0) {
        try {
          json = JSON.parse(text);
        } catch (error) {
          parseError = error instanceof Error ? error.message : String(error);
        }
      }
      return {
        ok: response.ok,
        status: response.status,
        statusText: response.statusText,
        url: response.url,
        body: json,
        textPreview: json === null ? text.slice(0, 1000) : null,
        parseError,
      };
    } catch (error) {
      return {
        ok: false,
        status: 0,
        statusText: "fetch-error",
        error: error instanceof Error ? error.message : String(error),
      };
    }
  }, { url, method, headers, body });

  if (evaluated.ok !== true) {
    const result = {
      ok: false,
      path: urlPath(url),
      status: 0,
      statusText: "page-evaluate-failed",
      failureKind: evaluated.failureKind ?? "script-api-misuse",
      error: evaluated.error ?? "page.evaluate failed",
      guidance: evaluated.guidance ?? null,
    };
    if (options.throwOnError === true) throw stableProbeError("api-response-failed", result.error, publicReadiness({ error: "api-response-failed" }));
    return result;
  }

  const response = evaluated.value && typeof evaluated.value === "object" ? evaluated.value : {};
  const result = {
    ok: response.ok === true,
    path: urlPath(response.url || url),
    status: Number.isInteger(response.status) ? response.status : 0,
    statusText: typeof response.statusText === "string" ? response.statusText : null,
    body: response.body ?? null,
    textPreview: typeof response.textPreview === "string" ? response.textPreview : null,
    parseError: typeof response.parseError === "string" ? response.parseError : null,
    error: typeof response.error === "string" ? response.error : null,
  };
  result.failureKind = result.ok ? null : classifyFetchFailureKind(result);
  if (options.throwOnError === true && result.ok !== true) {
    throw stableProbeError(result.failureKind || "api-response-failed", result.error || result.statusText || "fetchJson failed", publicReadiness({ error: result.failureKind || "api-response-failed" }));
  }
  return result;
}

function classifyFetchFailureKind(result) {
  const text = String(result?.error ?? "") + " " + String(result?.statusText ?? "");
  if (Number(result?.status ?? 0) === 0 && /Failed to fetch|fetch-error|network|NetworkError/iu.test(text)) return "same-origin-api-fetch-failed";
  if (Number(result?.status ?? 0) === 401 || Number(result?.status ?? 0) === 403) return "auth-or-session-api-failed";
  return "api-response-failed";
}

async function collectText(selector, options = {}) {
  options = normalizeHelperOptions(options);
  const normalizedSelector = String(selector || "").trim();
  if (normalizedSelector.length === 0) return { ok: false, selector: normalizedSelector, count: 0, texts: [], error: "selector is required" };
  const limit = boundedInteger(options.limit, 20, 1, 200);
  const textLimit = boundedInteger(options.textLimit, 1000, 80, 6000);
  const waitTimeoutMs = boundedInteger(options.timeoutMs ?? options.waitTimeoutMs, Math.min(timeoutMs, 5000), 1, Math.max(timeoutMs, 60000));
  const state = typeof options.state === "string" ? options.state : "attached";
  try {
    const locator = page.locator(normalizedSelector);
    if (options.wait !== false) await locator.first().waitFor({ state, timeout: waitTimeoutMs });
    const count = await locator.count();
    const texts = (await locator.allTextContents())
      .slice(0, limit)
      .map((text) => normalizeTextPreview(text, textLimit));
    return { ok: true, selector: normalizedSelector, count, returned: texts.length, texts };
  } catch (error) {
    return {
      ok: false,
      selector: normalizedSelector,
      count: 0,
      texts: [],
      error: error instanceof Error ? error.message : String(error),
    };
  }
}

async function waitWorkbenchReady(options = {}) {
  options = normalizeHelperOptions(options);
  const target = typeof options.target === "string" && options.target.length > 0 ? options.target : "/workbench";
  const next = { ...options };
  delete next.target;
  if (next.selectors === undefined) next.selectors = ["#workspace", "#command-input"];
  const stable = await gotoStable(target, next);
  const readiness = await ensureWorkbenchComposerReady(next);
  const result = {
    ...stable,
    shellReady: stable.ok === true,
    sessionReady: readiness.sessionReady,
    composerReady: readiness.composerReady,
    composer: readiness.composer,
    workspace: readiness.workspace,
    sessionRepair: readiness.sessionRepair
  };
  recordStep("workbench-ready", {
    ok: result.shellReady && result.sessionReady && result.composerReady,
    finalUrl: result.finalUrl,
    shellReady: result.shellReady,
    sessionReady: result.sessionReady,
    composerReady: result.composerReady,
    disabledReason: result.composer?.disabledReason ?? null,
    sessionRepair: result.sessionRepair
  });
  if (options.throwOnFailure === true && (!result.sessionReady || !result.composerReady)) {
    const reason = result.composer?.disabledReason || (!result.sessionReady ? "session-not-selected" : "composer-disabled");
    throw stableProbeError(reason === "session_required" ? "session-not-selected" : "composer-disabled", "Workbench composer is not ready: " + JSON.stringify({ reason, composer: result.composer, workspace: result.workspace }), publicReadiness({ error: reason }));
  }
  return result;
}

async function ensureWorkbenchComposerReady(options = {}) {
  const before = await collectWorkbenchReadyState();
  let sessionRepair = null;
  let after = before;
  if (before.composer.disabledReason === "session_required" && options.ensureSession !== false) {
    sessionRepair = await createProbeSessionFromUi(options);
    after = await collectWorkbenchReadyState();
  }
  const timeout = boundedInteger(options.composerTimeoutMs ?? options.readinessTimeoutMs ?? options.timeoutMs, Math.min(timeoutMs, 10000), 1, Math.max(timeoutMs, 60000));
  if (!after.composer.ready) {
    await page.waitForFunction(() => {
      const input = document.querySelector("#command-input");
      const send = document.querySelector("#command-send");
      const warning = document.querySelector(".composer-warning")?.textContent?.trim() || "";
      const activeTab = document.querySelector(".session-tab[data-active='true'], .session-tab[aria-selected='true']");
      return Boolean(input && send && activeTab && !input.disabled && !warning);
    }, null, { timeout }).catch(() => null);
    after = await collectWorkbenchReadyState();
  }
  return {
    shellReady: after.shellReady,
    sessionReady: after.sessionReady,
    composerReady: after.composer.ready,
    composer: after.composer,
    workspace: after.workspace,
    before,
    after,
    sessionRepair
  };
}

async function createProbeSessionFromUi(options = {}) {
  const railExpansion = await ensureSessionRailExpandedForProbe();
  const create = page.locator("#session-create").first();
  if (!(await create.isVisible({ timeout: Math.min(timeoutMs, 5000) }).catch(() => false))) {
    return { ok: false, method: "ui-click", reason: "session-create-not-visible", railExpansion };
  }
  const before = await collectWorkbenchReadyState();
  await create.click();
  const timeout = boundedInteger(options.sessionCreateTimeoutMs ?? options.readinessTimeoutMs ?? options.timeoutMs, Math.min(timeoutMs, 15000), 1, Math.max(timeoutMs, 60000));
  await page.waitForFunction((initial) => {
    const activeTab = document.querySelector(".session-tab[data-active='true'], .session-tab[aria-selected='true']");
    const sessionId = activeTab?.getAttribute("data-session-id") || "";
    const warning = document.querySelector(".composer-warning")?.textContent?.trim() || "";
    const input = document.querySelector("#command-input");
    return Boolean(activeTab && sessionId && sessionId !== initial.sessionId && input && !input.disabled && !warning);
  }, { sessionId: before.workspace.activeSessionId }, { timeout }).catch(() => null);
  const after = await collectWorkbenchReadyState();
  return {
    ok: after.sessionReady && after.composer.ready,
    method: "ui-click",
    before: before.workspace,
    after: after.workspace,
    composer: after.composer,
    railExpansion
  };
}

async function ensureSessionRailExpandedForProbe() {
  const before = await collectWorkbenchReadyState();
  if (before?.workspace?.sessionCreateVisible === true) {
    return { ok: true, action: "already-visible", before: before.workspace, after: before.workspace, valuesRedacted: true };
  }
  const toggle = page.locator("#session-collapse-toggle").first();
  const toggleVisible = await toggle.isVisible({ timeout: Math.min(timeoutMs, 2000) }).catch(() => false);
  if (before?.workspace?.sessionRailCollapsed !== true || !toggleVisible) {
    return { ok: false, action: "not-expanded", reason: before?.workspace?.sessionRailCollapsed === true ? "collapse-toggle-not-visible" : "session-rail-not-collapsed", before: before.workspace, after: before.workspace, valuesRedacted: true };
  }
  await toggle.click();
  await page.waitForFunction(() => {
    const visible = (element) => {
      if (!element) return false;
      const rect = element.getBoundingClientRect();
      const style = window.getComputedStyle(element);
      return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
    };
    const rail = document.querySelector("#session-sidebar");
    return Boolean(visible(document.querySelector("#session-create")) || (rail && rail.getAttribute("data-collapsed") === "false"));
  }, null, { timeout: Math.min(timeoutMs, 5000) }).catch(() => null);
  const after = await collectWorkbenchReadyState();
  return { ok: after?.workspace?.sessionCreateVisible === true, action: "expanded-session-rail", before: before.workspace, after: after.workspace, valuesRedacted: true };
}

async function collectWorkbenchReadyState() {
  return page.evaluate(() => {
    const activeTab = document.querySelector(".session-tab[data-active='true'], .session-tab[aria-selected='true']");
    const routeMatch = window.location.pathname.match(/\/workbench\/sessions\/([^/]+)/u) || window.location.pathname.match(/\/workspace\/sessions\/([^/]+)/u);
    const input = document.querySelector("#command-input");
    const send = document.querySelector("#command-send");
    const form = document.querySelector("#command-form");
    const visible = (element) => {
      if (!element) return false;
      const rect = element.getBoundingClientRect();
      const style = window.getComputedStyle(element);
      return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
    };
    const sessionCreate = document.querySelector("#session-create");
    const sessionRail = document.querySelector("#session-sidebar");
    const sessionCollapseToggle = document.querySelector("#session-collapse-toggle");
    const warning = document.querySelector(".composer-warning")?.textContent?.trim() || null;
    const mode = document.querySelector(".composer-mode")?.textContent?.trim() || null;
    const draft = input && typeof input.value === "string" ? input.value : "";
    const sendAction = send?.getAttribute("data-action") || null;
    const inputVisible = Boolean(input);
    const sendVisible = Boolean(send);
    const inputDisabled = input ? Boolean(input.disabled) : null;
    const sendDisabled = send ? Boolean(send.disabled) : null;
    const disabledReason = warning || (form?.getAttribute("title") === "session_required" ? "session_required" : null);
    const sendDisabledReason = disabledReason || (sendDisabled && draft.trim().length === 0 && sendAction !== "cancel" ? "empty_draft" : sendDisabled ? "button_disabled" : null);
    const sessionId = activeTab?.getAttribute("data-session-id") || null;
    const conversationId = activeTab?.getAttribute("data-conversation-id") || (routeMatch ? decodeURIComponent(routeMatch[1] || "") : null);
    const sessionReady = Boolean(sessionId || conversationId);
    const composerReady = Boolean(inputVisible && sendVisible && inputDisabled === false && !disabledReason && sessionReady);
    return {
      shellReady: Boolean(document.querySelector("#workspace") && inputVisible),
      sessionReady,
      workspace: {
        finalUrl: window.location.href,
        routeConversationId: routeMatch ? decodeURIComponent(routeMatch[1] || "") : null,
        activeSessionId: sessionId,
        activeConversationId: conversationId,
        activeStatus: activeTab?.getAttribute("data-status") || null,
        tabCount: document.querySelectorAll(".session-tab").length,
        sessionCreatePresent: Boolean(sessionCreate),
        sessionCreateVisible: visible(sessionCreate),
        sessionRailPresent: Boolean(sessionRail),
        sessionRailCollapsed: sessionRail ? sessionRail.getAttribute("data-collapsed") === "true" || sessionRail.classList.contains("is-collapsed") : null,
        sessionCollapseTogglePresent: Boolean(sessionCollapseToggle),
        sessionCollapseToggleVisible: visible(sessionCollapseToggle),
        sessionCollapseToggleExpanded: sessionCollapseToggle ? sessionCollapseToggle.getAttribute("aria-expanded") : null
      },
      composer: {
        ready: composerReady,
        inputVisible,
        inputDisabled,
        inputLength: draft.length,
        sendVisible,
        sendDisabled,
        sendAction,
        disabledReason,
        sendDisabledReason,
        mode,
        formTitle: form?.getAttribute("title") || null
      }
    };
  });
}

async function screenshotOnError(name = "failure.png", fn = null, options = {}) {
  if (typeof name === "function") {
    options = normalizeHelperOptions(fn);
    fn = name;
    name = "failure.png";
  }
  if (typeof fn !== "function") return captureFailureScreenshot(String(name || "failure.png"));
  try {
    return await fn();
  } catch (error) {
    const shot = await captureFailureScreenshot(String(name || "failure.png"));
    if (error && typeof error === "object") error.screenshot = shot;
    throw error;
  }
}

async function summarizeWorkspace(options = {}) {
  options = normalizeHelperOptions(options);
  const projectId = typeof options.projectId === "string" && options.projectId.length > 0 ? options.projectId : inferProjectId();
  const response = await fetchJson("/v1/workbench/workspace?projectId=" + encodeURIComponent(projectId));
  if (response.ok !== true) return { ...response, projectId };
  const body = response.body && typeof response.body === "object" ? response.body : {};
  const conversations = firstArrayAtPaths(body, ["conversations", "workspace.conversations", "data.conversations"]);
  return {
    ok: true,
    path: response.path,
    status: response.status,
    projectId,
    selectedConversationId: firstStringAtPaths(body, ["selectedConversationId", "workspace.selectedConversationId", "data.selectedConversationId"]),
    sessionId: firstStringAtPaths(body, ["sessionId", "workspace.sessionId", "data.sessionId"]),
    conversationCount: conversations ? conversations.length : null,
    conversationPreview: conversations ? conversations.slice(0, 8).map(summarizeConversationItem) : [],
  };
}

async function summarizeConversation(input = {}) {
  const options = typeof input === "string" ? { conversationId: input } : normalizeHelperOptions(input);
  let workspace = null;
  let conversationId = typeof options.conversationId === "string" && options.conversationId.length > 0 ? options.conversationId : inferConversationIdFromUrl();
  if (!conversationId) {
    workspace = await summarizeWorkspace(options);
    conversationId = typeof workspace.selectedConversationId === "string" ? workspace.selectedConversationId : null;
  }
  if (!conversationId) {
    return { ok: false, error: "conversationId is required and could not be inferred from the current workbench", workspace };
  }
  const response = await fetchJson("/v1/agent/conversations/" + encodeURIComponent(conversationId));
  if (response.ok !== true) return { ...response, conversationId, workspace };
  const body = response.body && typeof response.body === "object" ? response.body : {};
  const messages = firstArrayAtPaths(body, ["messages", "conversation.messages", "data.messages"]) || [];
  return {
    ok: true,
    path: response.path,
    status: response.status,
    conversationId,
    title: firstStringAtPaths(body, ["title", "conversation.title", "data.title"]),
    lastTraceId: firstStringAtPaths(body, ["lastTraceId", "conversation.lastTraceId", "data.lastTraceId"]),
    messageCount: messages.length,
    lastMessage: messages.length > 0 ? summarizeMessage(messages[messages.length - 1]) : null,
  };
}

async function goto(target = "/workbench", options = {}) {
  options = normalizeHelperOptions(options);
  if (options && typeof options === "object" && options.stable === false) return gotoRaw(target, options);
  const stable = await gotoStable(target, options);
  return stable.finalUrl;
}

async function gotoRaw(target = "/workbench", options = {}) {
  options = normalizeHelperOptions(options);
  const url = new URL(target, baseUrl).toString();
  await page.goto(url, navigationOptions(options));
  return page.url();
}

async function reloadStable(options = {}) {
  return stableCurrentPageNavigation("reload-stable", null, normalizeHelperOptions(options));
}

async function safeReload(options = {}) {
  try {
    return await reloadStable(options);
  } catch (error) {
    const failure = classifiedProbeError(error);
    return {
      ok: false,
      failureKind: failure.failureKind,
      error: failure.code,
      errorMessage: sanitize(failure.message),
      guidance: failure.guidance,
      beforeUrl: error && typeof error === "object" && typeof error.beforeUrl === "string" ? error.beforeUrl : null,
      finalUrl: currentPageUrl(),
      readiness: publicReadiness({ error: failure.code, failureKind: failure.failureKind }),
    };
  }
}

async function gotoCurrentStable(options = {}) {
  options = normalizeHelperOptions(options);
  const target = typeof options.url === "string" && options.url.length > 0 ? new URL(options.url, baseUrl).toString() : currentPageUrl();
  if (!target) throw stableProbeError("browser-load-jitter", "cannot retry current page navigation because the browser page has no URL", publicReadiness({ error: "browser-page-missing" }));
  return stableCurrentPageNavigation("goto-current-stable", target, options);
}

async function stableCurrentPageNavigation(operation, targetUrl, options = {}) {
  options = normalizeHelperOptions(options);
  const attempts = boundedInteger(options.attempts, 3, 1, 5);
  const retryDelayMs = boundedInteger(options.retryDelayMs, 350, 0, 5000);
  const beforeUrl = currentPageUrl();
  let retryUrl = targetUrl;
  let lastRecord = null;

  for (let attempt = 1; attempt <= attempts; attempt += 1) {
    if (!page || page.isClosed()) page = await context.newPage();
    const attemptPage = page;
    const attemptBeforeUrl = currentPageUrl();
    if (!retryUrl && !attemptBeforeUrl) {
      throw stableProbeError("browser-load-jitter", "cannot reload because the browser page has no URL", publicReadiness({ error: "browser-page-missing" }));
    }
    const navigationTarget = retryUrl || attemptBeforeUrl;
    const readinessSelectors = normalizeSelectorList(options.selectors, defaultReadinessSelectors(navigationTarget || baseUrl));
    const network = createNetworkTracker(attemptPage, options.apiPattern);
    const record = {
      ok: false,
      attempt,
      attempts,
      policy: operation,
      targetPath: urlPath(navigationTarget),
      beforePath: urlPath(attemptBeforeUrl),
      finalPath: null,
      stage: operation,
      error: null,
      message: null,
      selector: null,
      selectors: [],
      apiRequestsSent: false,
      apiRequestCount: 0,
      apiResponseFailed: false,
      apiFailureCount: 0,
      screenshot: null,
      durationMs: 0,
    };
    const started = Date.now();

    try {
      if (operation === "reload-stable" && attempt === 1) {
        await attemptPage.reload(navigationOptions(options));
      } else {
        await attemptPage.goto(navigationTarget, navigationOptions(options));
      }
      retryUrl = attemptPage.url();
      record.finalPath = urlPath(attemptPage.url());
      const readiness = await waitForReadyInternal(attemptPage, {
        ...options,
        selectors: readinessSelectors,
        url: attemptPage.url(),
      }, network);
      applyReadinessToRecord(record, readiness);
      if (!readiness.ok) {
        record.screenshot = await captureReadinessScreenshot(attemptPage, attempt, options);
        lastRecord = record;
      } else {
        record.ok = true;
        record.stage = "ready";
        lastRecord = record;
      }
    } catch (error) {
      record.finalPath = attemptPage && !attemptPage.isClosed() ? urlPath(attemptPage.url()) : null;
      record.error = classifyNavigationError(error);
      record.message = error instanceof Error ? error.message : String(error);
      record.screenshot = await captureReadinessScreenshot(attemptPage, attempt, options);
      lastRecord = record;
    } finally {
      Object.assign(record, network.summary());
      record.durationMs = Date.now() - started;
      network.dispose();
      readinessRecords.push(record);
    }

    if (record.ok) {
      const result = {
        ok: true,
        operation,
        attempt,
        attempts,
        beforeUrl,
        finalUrl: page.url(),
        readiness: record,
        summary: publicReadiness(),
      };
      recordStep(typeof options.name === "string" ? options.name : operation, {
        ok: true,
        operation,
        attempt,
        attempts,
        beforeUrl,
        finalUrl: result.finalUrl,
      });
      Object.defineProperty(result, "page", {
        enumerable: false,
        value: page,
      });
      return result;
    }
    if (attempt < attempts && retryDelayMs > 0) await sleep(retryDelayMs);
  }

  const code = lastRecord?.error || "browser-load-jitter";
  const message = lastRecord?.message || operation + " did not become ready";
  recordStep(typeof options.name === "string" ? options.name : operation, {
    ok: false,
    operation,
    attempts,
    beforeUrl,
    finalUrl: currentPageUrl(),
    error: code,
    message,
    screenshot: lastRecord?.screenshot ?? null,
  });
  const error = stableProbeError(code, message, publicReadiness({ error: code }));
  error.beforeUrl = beforeUrl;
  throw error;
}

async function gotoStable(target = "/workbench", options = {}) {
  options = normalizeHelperOptions(options);
  const url = new URL(target, baseUrl).toString();
  const attempts = boundedInteger(options.attempts, 3, 1, 5);
  const retryDelayMs = boundedInteger(options.retryDelayMs, 350, 0, 5000);
  const readinessSelectors = normalizeSelectorList(options.selectors, defaultReadinessSelectors(url));
  let lastRecord = null;

  for (let attempt = 1; attempt <= attempts; attempt += 1) {
    const useFreshPage = attempt > 1 || options.freshPage === true || options.reusePage === false || !page || page.isClosed();
    if (useFreshPage) {
      const previousPage = page;
      page = await context.newPage();
      if (previousPage && previousPage !== page && !previousPage.isClosed()) await previousPage.close().catch(() => {});
    }

    const attemptPage = page;
    const network = createNetworkTracker(attemptPage, options.apiPattern);
    const record = {
      ok: false,
      attempt,
      attempts,
      policy: useFreshPage ? "fresh-page" : "reuse-current-page",
      targetPath: urlPath(url),
      finalPath: null,
      stage: "navigate",
      error: null,
      message: null,
      selector: null,
      selectors: [],
      apiRequestsSent: false,
      apiRequestCount: 0,
      apiResponseFailed: false,
      apiFailureCount: 0,
      screenshot: null,
      durationMs: 0,
    };
    const started = Date.now();

    try {
      await attemptPage.goto(url, navigationOptions(options));
      record.finalPath = urlPath(attemptPage.url());
      const readiness = await waitForReadyInternal(attemptPage, {
        ...options,
        selectors: readinessSelectors,
        url,
      }, network);
      applyReadinessToRecord(record, readiness);
      if (!readiness.ok) {
        record.screenshot = await captureReadinessScreenshot(attemptPage, attempt, options);
        lastRecord = record;
      } else if (options.expectApiRequest === true && !readiness.apiRequestsSent) {
        record.ok = false;
        record.stage = "api";
        record.error = "api-not-sent";
        record.message = "expected API request was not observed before readiness finished";
        record.screenshot = await captureReadinessScreenshot(attemptPage, attempt, options);
        lastRecord = record;
      } else if (options.failOnApiResponseFailed === true && readiness.apiResponseFailed) {
        record.ok = false;
        record.stage = "api";
        record.error = "api-response-failed";
        record.message = "API request failed during readiness";
        record.screenshot = await captureReadinessScreenshot(attemptPage, attempt, options);
        lastRecord = record;
      } else {
        record.ok = true;
        record.stage = "ready";
        lastRecord = record;
      }
    } catch (error) {
      record.finalPath = attemptPage && !attemptPage.isClosed() ? urlPath(attemptPage.url()) : null;
      record.error = classifyNavigationError(error);
      record.message = error instanceof Error ? error.message : String(error);
      record.screenshot = await captureReadinessScreenshot(attemptPage, attempt, options);
      lastRecord = record;
    } finally {
      Object.assign(record, network.summary());
      record.durationMs = Date.now() - started;
      network.dispose();
      readinessRecords.push(record);
    }

    if (record.ok) {
      const result = {
        ok: true,
        attempt,
        attempts,
        finalUrl: page.url(),
        readiness: record,
        summary: publicReadiness(),
      };
      Object.defineProperty(result, "page", {
        enumerable: false,
        value: page,
      });
      return result;
    }
    if (attempt < attempts && retryDelayMs > 0) await sleep(retryDelayMs);
  }

  const code = lastRecord?.error || "browser-load-jitter";
  const message = lastRecord?.message || "page did not become ready";
  throw stableProbeError(code, message, publicReadiness({ error: code }));
}

async function waitForReady(options = {}) {
  options = normalizeHelperOptions(options);
  const readiness = await waitForReadyInternal(options.page || page, options, null);
  readinessRecords.push({
    ...readiness,
    attempt: readinessRecords.length + 1,
    attempts: 1,
    policy: "wait-current-page",
    durationMs: readiness.durationMs,
  });
  if (options.throwOnFailure === true && !readiness.ok) {
    throw stableProbeError(readiness.error || "selector-timeout", readiness.message || "page did not become ready", publicReadiness({ error: readiness.error || "selector-timeout" }));
  }
  return readiness;
}

async function waitForReadyInternal(targetPage, options = {}, network = null) {
  const started = Date.now();
  const url = typeof options.url === "string" ? options.url : targetPage.url();
  const readinessTimeoutMs = boundedInteger(options.readinessTimeoutMs ?? options.timeoutMs, timeoutMs, 1, Math.max(timeoutMs, 60000));
  const selectorState = typeof options.selectorState === "string" ? options.selectorState : "visible";
  const selectors = normalizeSelectorList(options.selectors, defaultReadinessSelectors(url));
  const activeSelector = typeof options.activeSelector === "string" ? options.activeSelector : typeof options.activeTabSelector === "string" ? options.activeTabSelector : null;
  const result = {
    ok: false,
    stage: "load-state",
    error: null,
    message: null,
    targetPath: urlPath(url),
    finalPath: null,
    selector: null,
    selectors: [],
    apiRequestsSent: false,
    apiRequestCount: 0,
    apiResponseFailed: false,
    apiFailureCount: 0,
    durationMs: 0,
  };

  try {
    await targetPage.waitForLoadState(typeof options.loadState === "string" ? options.loadState : "domcontentloaded", { timeout: readinessTimeoutMs });
    result.finalPath = urlPath(targetPage.url());
    for (const selector of selectors) {
      result.stage = "selector";
      result.selector = selector;
      await targetPage.locator(selector).first().waitFor({ state: selectorState, timeout: readinessTimeoutMs });
      result.selectors.push({ selector, state: selectorState, matched: true });
    }
    if (activeSelector !== null) {
      result.stage = "active-selector";
      result.selector = activeSelector;
      await targetPage.locator(activeSelector).first().waitFor({ state: "visible", timeout: readinessTimeoutMs });
      result.selectors.push({ selector: activeSelector, state: "visible", matched: true, role: "active" });
    }
    result.ok = true;
    result.stage = "ready";
  } catch (error) {
    result.ok = false;
    result.error = result.stage === "selector" || result.stage === "active-selector" ? "selector-timeout" : "browser-load-jitter";
    result.message = error instanceof Error ? error.message : String(error);
  } finally {
    Object.assign(result, network ? network.summary() : emptyNetworkSummary());
    result.durationMs = Date.now() - started;
  }
  return result;
}

function applyReadinessToRecord(record, readiness) {
  record.ok = readiness.ok;
  record.stage = readiness.stage;
  record.error = readiness.error;
  record.message = readiness.message;
  record.selector = readiness.selector;
  record.selectors = readiness.selectors;
  record.finalPath = readiness.finalPath;
  record.apiRequestsSent = readiness.apiRequestsSent;
  record.apiRequestCount = readiness.apiRequestCount;
  record.apiResponseFailed = readiness.apiResponseFailed;
  record.apiFailureCount = readiness.apiFailureCount;
}

function createNetworkTracker(targetPage, apiPattern) {
  const started = Date.now();
  const requests = [];
  const failures = [];
  const matches = createApiMatcher(apiPattern);
  const onRequest = (request) => {
    if (!matches(request.url())) return;
    requests.push({
      method: request.method(),
      path: urlPath(request.url()),
      atMs: Date.now() - started,
    });
  };
  const onResponse = (response) => {
    if (!matches(response.url())) return;
    if (response.status() >= 400) {
      failures.push({
        type: "response",
        status: response.status(),
        statusText: response.statusText(),
        path: urlPath(response.url()),
        atMs: Date.now() - started,
      });
    }
  };
  const onRequestFailed = (request) => {
    if (!matches(request.url())) return;
    failures.push({
      type: "requestfailed",
      method: request.method(),
      path: urlPath(request.url()),
      failureText: request.failure()?.errorText ?? null,
      atMs: Date.now() - started,
    });
  };
  targetPage.on("request", onRequest);
  targetPage.on("response", onResponse);
  targetPage.on("requestfailed", onRequestFailed);
  return {
    summary: () => ({
      apiRequestsSent: requests.length > 0,
      apiRequestCount: requests.length,
      apiResponseFailed: failures.length > 0,
      apiFailureCount: failures.length,
      apiRequests: requests.slice(0, 5),
      apiFailures: failures.slice(0, 5),
    }),
    dispose: () => {
      targetPage.off("request", onRequest);
      targetPage.off("response", onResponse);
      targetPage.off("requestfailed", onRequestFailed);
    },
  };
}

function createApiMatcher(pattern) {
  if (pattern === false) return () => false;
  if (pattern instanceof RegExp) return (rawUrl) => pattern.test(rawUrl);
  if (typeof pattern === "function") return (rawUrl) => {
    try {
      return pattern(rawUrl) === true;
    } catch {
      return false;
    }
  };
  const normalized = typeof pattern === "string" && pattern.length > 0 ? pattern : "/v1";
  return (rawUrl) => {
    try {
      const parsed = new URL(rawUrl);
      if (normalized.startsWith("/")) return parsed.pathname === normalized || parsed.pathname.startsWith(normalized.replace(/\/$/u, "") + "/");
      return rawUrl.includes(normalized);
    } catch {
      return false;
    }
  };
}

function emptyNetworkSummary() {
  return {
    apiRequestsSent: false,
    apiRequestCount: 0,
    apiResponseFailed: false,
    apiFailureCount: 0,
    apiRequests: [],
    apiFailures: [],
  };
}

function defaultReadinessSelectors(rawUrl) {
  try {
    const pathname = new URL(rawUrl, baseUrl).pathname;
    if (pathname === "/" || pathname === "/workbench" || pathname.startsWith("/workbench/")) return ["#workspace", "#command-input"];
  } catch {
    return ["body"];
  }
  return ["body"];
}

function normalizeSelectorList(value, fallback) {
  if (value === false || value === null) return [];
  if (Array.isArray(value)) return value.map((item) => String(item).trim()).filter(Boolean).slice(0, 10);
  if (typeof value === "string" && value.trim().length > 0) return [value.trim()];
  return fallback;
}

function normalizeHelperOptions(value) {
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
}

function navigationOptions(options = {}) {
  options = normalizeHelperOptions(options);
  const next = { ...options };
  for (const key of [
    "activeSelector",
    "activeTabSelector",
    "apiPattern",
    "attempts",
    "expectApiRequest",
    "failOnApiResponseFailed",
    "freshPage",
    "name",
    "readinessTimeoutMs",
    "record",
    "retryDelayMs",
    "reusePage",
    "screenshotOnFailure",
    "selectorState",
    "selectors",
    "stable",
    "url",
  ]) {
    delete next[key];
  }
  return {
    waitUntil: "domcontentloaded",
    timeout: timeoutMs,
    ...next,
  };
}

async function captureReadinessScreenshot(targetPage, attempt, options) {
  if (options.screenshotOnFailure === false || !targetPage || targetPage.isClosed()) return null;
  try {
    return await screenshotPage(targetPage, "readiness-attempt-" + attempt + ".png");
  } catch (error) {
    return {
      error: error instanceof Error ? error.message : String(error),
    };
  }
}

async function captureFailureScreenshot(name = "failure.png") {
  if (!page || page.isClosed()) return null;
  try {
    return await screenshotPage(page, name);
  } catch (error) {
    return {
      kind: "screenshot",
      path: artifactPath(name),
      error: error instanceof Error ? error.message : String(error),
    };
  }
}

async function screenshotPage(targetPage, name = "screenshot.png", options = {}) {
  const file = artifactPath(name);
  await targetPage.screenshot({ path: file, fullPage: true, ...options });
  return recordArtifact(file, "screenshot");
}

function publicReadiness(extra = {}) {
  const attempts = readinessRecords.slice(-10).map((record) => ({
    ok: record.ok === true,
    attempt: record.attempt ?? null,
    attempts: record.attempts ?? null,
    policy: record.policy ?? null,
    targetPath: record.targetPath ?? null,
    finalPath: record.finalPath ?? null,
    stage: record.stage ?? null,
    error: record.error ?? null,
    message: typeof record.message === "string" ? record.message.slice(0, 500) : null,
    selector: record.selector ?? null,
    selectors: clonePlainList(record.selectors, 10),
    apiRequestsSent: record.apiRequestsSent === true,
    apiRequestCount: Number.isInteger(record.apiRequestCount) ? record.apiRequestCount : 0,
    apiResponseFailed: record.apiResponseFailed === true,
    apiFailureCount: Number.isInteger(record.apiFailureCount) ? record.apiFailureCount : 0,
    apiRequests: clonePlainList(record.apiRequests, 5),
    apiFailures: clonePlainList(record.apiFailures, 5),
    screenshot: clonePlainObject(record.screenshot),
    durationMs: Number.isInteger(record.durationMs) ? record.durationMs : null,
  }));
  return {
    attemptCount: readinessRecords.length,
    last: attempts.length > 0 ? deepClonePlain(attempts[attempts.length - 1]) : null,
    attempts,
    ...extra,
  };
}

function deepClonePlain(value) {
  return JSON.parse(JSON.stringify(value));
}

function clonePlainList(value, limit) {
  if (!Array.isArray(value)) return [];
  return value.slice(0, limit).map((item) => clonePlainObject(item));
}

function clonePlainObject(value) {
  if (!value || typeof value !== "object" || Array.isArray(value)) return value ?? null;
  return { ...value };
}

function classifyNavigationError(error) {
  const message = error instanceof Error ? error.message : String(error);
  if (/timeout|navigation|net::|target page|browser has been closed|page has been closed/iu.test(message)) return "browser-load-jitter";
  return "browser-load-jitter";
}

function classifiedProbeError(error) {
  const message = error instanceof Error ? error.message : String(error);
  const code = probeErrorCode(error, message);
  const failureKind = probeFailureKind(code, message);
  return {
    code,
    failureKind,
    message,
    detail: error && typeof error === "object" ? error.readiness ?? null : null,
    guidance: probeFailureGuidance(failureKind),
  };
}

function stableProbeError(code, message, readiness) {
  const error = new Error(message);
  error.code = code;
  error.readiness = readiness;
  return error;
}

function assertionResultError(value) {
  const message = extractFailureMessage(value) || scriptFailureSignalMessage(value) || "script returned a failure result";
  const error = new Error(message);
  error.code = "assertion-failed";
  return error;
}

function scriptResultOk(value) {
  if (!value || typeof value !== "object") return true;
  if (value.ok === false) return false;
  if (value.pass === false) return false;
  if (value.success === false) return false;
  return true;
}

function scriptFailureSignalMessage(value) {
  if (!value || typeof value !== "object") return null;
  if (value.pass === false) return "script returned pass:false";
  if (value.success === false) return "script returned success:false";
  if (value.ok === false) return "script returned ok:false";
  return null;
}

function extractFailureMessage(value) {
  if (!value || typeof value !== "object") return null;
  for (const key of ["failedCondition", "errorMessage", "message", "error", "reason", "summary"]) {
    const nested = value[key];
    if (typeof nested === "string" && nested.length > 0) return nested;
  }
  return null;
}

function probeErrorCode(error, message) {
  const known = new Set([
    "assertion-failed",
    "auth-login-failed",
    "auth-or-session-api-failed",
    "auth-redirect-login",
    "browser-failed",
    "browser-load-jitter",
    "composer-disabled",
    "selector-timeout",
    "same-origin-api-fetch-failed",
    "session-not-selected",
    "api-not-sent",
    "api-response-failed",
    "script-api-misuse",
    "script-failed",
  ]);
  const explicit = error && typeof error === "object" && typeof error.code === "string" ? error.code : null;
  if (explicit && known.has(explicit)) return explicit;
  if (known.has(message)) return message;
  if (isEvaluateApiMisuse(message)) return "script-api-misuse";
  if (/same-origin-api-fetch-failed|Failed to fetch/iu.test(message)) return "same-origin-api-fetch-failed";
  if (/auth-redirect-login|login\?redirect|authState.*login/iu.test(message)) return "auth-redirect-login";
  if (/session_required|session-not-selected|session not selected/iu.test(message)) return "session-not-selected";
  if (/composer-disabled|command-send|command bar|composer is not ready|button_disabled|disabledReason/iu.test(message)) return "composer-disabled";
  if (/AssertionError|ERR_ASSERTION|\bassert(?:ion)?\b|expect\(.*\)|ok:false/iu.test(message)) return "assertion-failed";
  if (/auth-login-failed|login form not visible|missing HWLAB_WEB_PASS/iu.test(message)) return "auth-login-failed";
  if (/executable doesn't exist|browser executable|failed to launch|browserType\.launch|browser has been closed/iu.test(message)) return "browser-failed";
  if (/timeout|navigation|net::|target page|page has been closed|page did not become ready|selector/iu.test(message)) return "browser-load-jitter";
  return "script-failed";
}

function probeFailureKind(code, message) {
  if (code === "auth-redirect-login") return "auth-redirect-login";
  if (code === "same-origin-api-fetch-failed") return "same-origin-api-fetch-failed";
  if (code === "composer-disabled") return "composer-disabled";
  if (code === "session-not-selected") return "session-not-selected";
  if (code === "auth-or-session-api-failed") return "auth-or-session-api-failed";
  if (code === "script-api-misuse") return "script-api-misuse";
  if (code === "auth-login-failed") return "auth-failed";
  if (code === "browser-failed") return "browser-failed";
  if (code === "browser-load-jitter") return "browser-navigation-jitter";
  if (code === "selector-timeout" || code === "api-not-sent" || code === "api-response-failed") return "navigation-failed";
  if (/browser has been closed|browser executable|failed to launch/iu.test(message)) return "browser-failed";
  return "assertion-failed";
}

function probeFailureGuidance(failureKind) {
  if (failureKind === "composer-disabled" || failureKind === "session-not-selected") return "Inspect readiness.composer and readiness.workspace; waitWorkbenchReady can create/select a submit-capable session unless ensureSession:false is set.";
  if (failureKind === "auth-redirect-login") return "Inspect auth/session bootstrap and finalUrl; the browser returned to /login after CLI-managed auth.";
  if (failureKind === "same-origin-api-fetch-failed") return "Inspect the failed same-origin API path, current URL, auth state, and browser console/network evidence in the full report.";
  if (failureKind === "script-api-misuse") return evaluateSingleArgGuidance();
  if (failureKind === "browser-navigation-jitter") return "Use reloadStable(), gotoCurrentStable(), or safeReload() to retry the same browser navigation; inspect readiness.last, lastUrl, and screenshots before treating this as an API fetch failure.";
  if (failureKind === "navigation-failed") return "Inspect probe.readiness for selector/API readiness details and lastScreenshot for the browser state.";
  if (failureKind === "auth-failed") return "Inspect probe.auth sourceRef/fingerprint/status; do not print or copy the web login secret.";
  if (failureKind === "browser-failed") return "Inspect Playwright/browser-launcher availability on the target workspace.";
  return null;
}

function isEvaluateApiMisuse(message) {
  return /Too many arguments.*wrap them in an object|page\.evaluate.*one serializable argument|safeEvaluate requires a function/iu.test(String(message));
}

function evaluateSingleArgGuidance() {
  return "Playwright page.evaluate accepts one serializable argument; use page.evaluate(({ a, b }) => ..., { a, b }) or safeEvaluate(fn, { a, b }).";
}

function markEvaluateError(error) {
  if (error && typeof error === "object" && isEvaluateApiMisuse(error instanceof Error ? error.message : String(error))) {
    error.code = "script-api-misuse";
  }
  return error;
}

function currentPageUrl() {
  try {
    return page && !page.isClosed() ? page.url() : null;
  } catch {
    return null;
  }
}

function urlPath(rawUrl) {
  try {
    const parsed = new URL(rawUrl, baseUrl);
    return parsed.pathname + parsed.search;
  } catch {
    return String(rawUrl);
  }
}

function boundedInteger(raw, fallback, min, max) {
  const value = Number(raw ?? fallback);
  if (!Number.isInteger(value)) return fallback;
  return Math.max(min, Math.min(max, value));
}

function inferProjectId() {
  try {
    const current = page && !page.isClosed() ? new URL(page.url()) : null;
    const fromUrl = current ? current.searchParams.get("projectId") : null;
    if (fromUrl) return fromUrl;
  } catch {
    // Fall through to the Workbench default.
  }
  return "prj_hwpod_workbench";
}

function inferConversationIdFromUrl() {
  try {
    if (!page || page.isClosed()) return null;
    const pathname = new URL(page.url()).pathname;
    const match = pathname.match(/\/(?:workbench|workspace)\/sessions\/([^/]+)/u);
    return match ? decodeURIComponent(match[1]) : null;
  } catch {
    return null;
  }
}

function firstStringAtPaths(value, paths) {
  for (const pathSpec of paths) {
    const nested = valueAtPath(value, pathSpec);
    if (typeof nested === "string" && nested.length > 0) return nested;
  }
  return null;
}

function firstArrayAtPaths(value, paths) {
  for (const pathSpec of paths) {
    const nested = valueAtPath(value, pathSpec);
    if (Array.isArray(nested)) return nested;
  }
  return null;
}

function valueAtPath(value, pathSpec) {
  let current = value;
  for (const key of String(pathSpec).split(".")) {
    if (!current || typeof current !== "object") return null;
    current = current[key];
  }
  return current ?? null;
}

function summarizeConversationItem(value) {
  if (!value || typeof value !== "object") return value;
  return {
    conversationId: firstStringAtPaths(value, ["conversationId", "id"]),
    title: firstStringAtPaths(value, ["title", "name"]),
    lastTraceId: firstStringAtPaths(value, ["lastTraceId", "traceId"]),
    status: firstStringAtPaths(value, ["status", "state"]),
  };
}

function summarizeMessage(value) {
  if (!value || typeof value !== "object") return value ?? null;
  return {
    role: firstStringAtPaths(value, ["role", "type", "sender"]),
    status: firstStringAtPaths(value, ["status", "state"]),
    traceId: firstStringAtPaths(value, ["traceId", "runnerTrace.traceId"]),
    textPreview: normalizeTextPreview(firstStringAtPaths(value, ["content", "text", "message", "finalResponse"]) || "", 500),
  };
}

function normalizeTextPreview(text, limit) {
  return String(text ?? "").replace(/\s+/gu, " ").trim().slice(0, limit);
}

function normalizeFetchHeaders(value) {
  const headers = {};
  if (!value || typeof value !== "object" || Array.isArray(value)) return headers;
  for (const [key, nested] of Object.entries(value)) {
    if (typeof nested === "string") headers[key] = nested;
  }
  return headers;
}

function normalizeFetchBody(value, headers) {
  if (value === undefined || value === null) return null;
  if (typeof value === "string") return value;
  const hasContentType = Object.keys(headers).some((key) => key.toLowerCase() === "content-type");
  if (!hasContentType) headers["content-type"] = "application/json";
  return JSON.stringify(value);
}

function isRetryableAuthStatus(status) {
  return status === 0 || status === 429 || status >= 500;
}

function retryCountFromAttempts(attempts) {
  return Math.max(0, Array.isArray(attempts) ? attempts.length - 1 : 0);
}

function authAttemptsRetryable(attempts) {
  if (!Array.isArray(attempts) || attempts.length === 0) return false;
  return attempts.some((attempt) => attempt && typeof attempt === "object" && attempt.retryable === true);
}

async function responseSummary(response) {
  const status = response.status();
  let bodyPreview = null;
  if (status >= 400) {
    try {
      bodyPreview = redactString(await response.text()).replace(/\s+/gu, " ").slice(0, 500);
    } catch {
      bodyPreview = null;
    }
  }
  return {
    status,
    statusText: response.statusText(),
    bodyPreview,
  };
}

async function readAuthCookieState(browserContext) {
  const cookies = await browserContext.cookies(baseUrl);
  cookieSecrets = cookies.map((cookie) => cookie.value).filter(Boolean);
  const cookieNames = cookies.map((cookie) => cookie.name).sort();
  return {
    cookiePresent: cookieNames.includes("hwlab_session"),
    cookieNames,
  };
}

function responsePathMatches(rawUrl, path) {
  try {
    return new URL(rawUrl).pathname === path;
  } catch {
    return false;
  }
}

function assertNoCredentialLeak(value) {
  const finalUrl = new URL(value);
  if (finalUrl.searchParams.has("username") || finalUrl.searchParams.has("password")) {
    throw new Error("login credentials leaked into URL query");
  }
}

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function publicAuth(value) {
  const retryCount = Number.isInteger(value.retryCount) ? value.retryCount : retryCountFromAttempts(value.attempts);
  const transientObserved = authAttemptsRetryable(value.attempts);
  const ok = value.ok === true;
  const retryable = ok ? false : value.retryable === true || transientObserved;
  const retryMaxAttempts = Number.isInteger(value.retryMaxAttempts) ? value.retryMaxAttempts : null;
  const lastRetryLabel = typeof value.lastRetryLabel === "string"
    ? value.lastRetryLabel
    : Array.isArray(value.attempts) && value.attempts.length > 0
      ? value.attempts[value.attempts.length - 1]?.retryLabel ?? null
      : null;
  const retryExhausted = value.retryExhausted === true || (retryable && retryMaxAttempts !== null && Array.isArray(value.attempts) && value.attempts.length >= retryMaxAttempts);
  return {
    ok,
    method: value.method ?? null,
    origin: new URL(baseUrl).origin,
    loginPath: value.loginUrl,
    status: value.status,
    statusText: value.statusText,
    cookiePresent: value.cookiePresent,
    cookieNames: value.cookieNames,
    attempts: value.attempts ?? null,
    retryCount,
    retryMaxAttempts,
    lastRetryLabel,
    retryExhausted,
    fallbackUsed: value.fallbackUsed === true || value.method === "form-fallback",
    fallback: value.fallback ?? null,
    errorSummary: cloneSummary(value.errorSummary ?? value.apiErrorSummary ?? null),
    degradedReason: ok ? null : "auth-login-failed",
    retryable,
    transientObserved,
    commanderAction: ok
      ? null
      : retryExhausted
        ? "auth retry exhausted; stop this web-probe run and inspect target /auth/login upstream before retrying"
        : retryable
          ? "retry same web-probe command after short backoff; inspect target /auth/login and Cloud Web/API rollout if repeated"
        : "inspect bootstrap admin credential source and target user state",
    fingerprint: authSummaryFingerprint(value),
    username,
    valuesRedacted: true,
  };
}

function authSummaryFingerprint(value) {
  const payload = JSON.stringify({
    origin: new URL(baseUrl).origin,
    loginPath: value.loginUrl ?? null,
    method: value.method ?? null,
    status: value.status ?? null,
    statusText: value.statusText ?? null,
    cookiePresent: value.cookiePresent === true,
    retryCount: Number.isInteger(value.retryCount) ? value.retryCount : retryCountFromAttempts(value.attempts),
    fallbackUsed: value.fallbackUsed === true || value.method === "form-fallback",
  });
  return "sha256:" + createHash("sha256").update(payload).digest("hex").slice(0, 16);
}

function cloneSummary(value) {
  if (!value || typeof value !== "object" || Array.isArray(value)) return value ?? null;
  return { ...value };
}

function normalizeBaseUrl(raw) {
  if (!raw) throw new Error("missing HWLAB_WEB_BASE_URL");
  const parsed = new URL(raw);
  parsed.hash = "";
  parsed.search = "";
  return parsed.toString().replace(/\/+$/u, "/");
}

function parseViewport(raw) {
  const match = String(raw).match(/^([0-9]+)x([0-9]+)$/u);
  if (!match) throw new Error("viewport must look like 1440x900");
  return { width: Number(match[1]), height: Number(match[2]) };
}

function positiveInteger(raw, fallback) {
  const value = Number(raw || fallback);
  if (!Number.isInteger(value) || value <= 0) return fallback;
  return value;
}

function artifactPath(name) {
  const safe = String(name || "artifact")
    .replace(/[^A-Za-z0-9._-]/gu, "_")
    .replace(/^\.+/u, "_")
    .slice(0, 120) || "artifact";
  return path.join(runDir, safe);
}

async function screenshot(name = "screenshot.png", options = {}) {
  const file = artifactPath(name);
  await page.screenshot({ path: file, fullPage: true, ...options });
  return recordArtifact(file, "screenshot");
}

async function jsonArtifact(name, value) {
  const file = artifactPath(name);
  await writeFile(file, JSON.stringify(sanitize(value), null, 2) + "\n", "utf8");
  return recordArtifact(file, "json");
}

async function recordArtifact(file, kind) {
  const fileStat = await stat(file);
  const item = {
    kind,
    path: file,
    byteCount: fileStat.size,
    sha256: await sha256File(file),
  };
  artifactRecords.push({ ...item });
  return { ...item };
}

async function sha256File(file) {
  const buffer = await readFile(file);
  return "sha256:" + createHash("sha256").update(buffer).digest("hex");
}

function sanitize(value, depth = 0, seen = new WeakSet()) {
  if (value === null || value === undefined) return value ?? null;
  if (typeof value === "string") return redactString(value).slice(0, 6000);
  if (typeof value === "number" || typeof value === "boolean") return value;
  if (typeof value === "bigint") return String(value);
  if (typeof value === "function" || typeof value === "symbol") return "[" + typeof value + "]";
  if (depth > 12) return "[max-depth]";
  if (Array.isArray(value)) return value.slice(0, 200).map((item) => sanitize(item, depth + 1, seen));
  if (typeof value === "object") {
    if (seen.has(value)) return "[circular]";
    seen.add(value);
    const out = {};
    for (const [key, nested] of Object.entries(value).slice(0, 200)) {
      if (/password|passwd|secret|token|authorization|cookie|api[_-]?key/iu.test(key) && typeof nested === "string") {
        out[key] = "<redacted>";
      } else {
        out[key] = sanitize(nested, depth + 1, seen);
      }
    }
    return out;
  }
  return String(value);
}

function redactString(text) {
  let next = String(text);
  for (const secret of [password, ...cookieSecrets].filter(Boolean)) {
    next = next.split(secret).join("<redacted>");
  }
  return next;
}

async function emit(payload) {
  const enriched = {
    ...payload,
    steps: publicSteps(),
    artifacts: { runDir, items: artifactRecords },
  };
  enriched.summary = scriptIssueSummary(enriched);
  const reportFile = artifactPath("web-probe-script-report.json");
  let reportArtifact = null;
  try {
    await writeFile(reportFile, JSON.stringify(sanitize(enriched), null, 2) + "\n", "utf8");
    const fileStat = await stat(reportFile);
    reportArtifact = {
      kind: "json",
      path: reportFile,
      byteCount: fileStat.size,
      sha256: await sha256File(reportFile),
    };
    artifactRecords.push({ ...reportArtifact });
  } catch (error) {
    reportArtifact = {
      kind: "json",
      path: reportFile,
      error: error instanceof Error ? error.message : String(error),
    };
  }
  const finalPayload = {
    ...enriched,
    reportPath: reportArtifact && typeof reportArtifact.path === "string" ? reportArtifact.path : null,
    reportSha256: reportArtifact && typeof reportArtifact.sha256 === "string" ? reportArtifact.sha256 : null,
    artifacts: { runDir, items: artifactRecords },
  };
  finalPayload.summary = scriptIssueSummary(finalPayload);
  process.stdout.write(JSON.stringify(sanitize(compactStdoutPayload(finalPayload)), null, 2) + "\n");
}

function compactStdoutPayload(payload) {
  const issueEvidence = compactIssueEvidenceForStdout(payload?.summary?.issueEvidence ?? issueEvidenceFromPayload(payload));
  return {
    ok: payload?.ok === true,
    status: payload?.status ?? null,
    command: payload?.command ?? "web-probe-script",
    generatedAt: payload?.generatedAt ?? null,
    startedAt: payload?.startedAt ?? null,
    baseUrl: payload?.baseUrl ?? null,
    finalUrl: payload?.finalUrl ?? payload?.lastUrl ?? null,
    lastUrl: payload?.lastUrl ?? null,
    scriptSha256: payload?.scriptSha256 ?? null,
    runDir,
    reportPath: payload?.reportPath ?? null,
    reportSha256: payload?.reportSha256 ?? null,
    auth: payload?.auth ?? null,
    script: compactScriptForStdout(payload?.script),
    steps: compactStepsForStdout(publicSteps()),
    failureKind: payload?.failureKind ?? null,
    error: payload?.error ?? null,
    errorMessage: typeof payload?.errorMessage === "string" ? compactText(payload.errorMessage, 1200) : payload?.errorMessage ?? null,
    guidance: typeof payload?.guidance === "string" ? compactText(payload.guidance, 1200) : payload?.guidance ?? null,
    lastScreenshot: compactArtifactForStdout(payload?.lastScreenshot),
    readiness: compactJsonForStdout(payload?.readiness),
    artifacts: compactArtifactsForStdout(payload?.artifacts),
    issueEvidence,
    summary: compactSummaryForStdout(payload?.summary),
    safety: {
      valuesRedacted: true,
      secretValuesPrinted: false,
      stdoutCompacted: true,
      fullReportInArtifact: true,
    },
  };
}

function compactScriptForStdout(script) {
  const value = script && typeof script === "object" ? script : {};
  const steps = Array.isArray(value.steps) ? value.steps : [];
  return {
    ok: value.ok === true,
    result: compactJsonForEvidence(value.result),
    stepCount: steps.length,
  };
}

function compactStepsForStdout(steps) {
  if (!Array.isArray(steps)) return [];
  return steps.slice(-3).map((step) => compactStepForStdout(step));
}

function compactStepForStdout(step) {
  const value = step && typeof step === "object" ? step : {};
  return {
    index: Number.isFinite(value.index) ? value.index : null,
    name: typeof value.name === "string" ? value.name : null,
    ok: typeof value.ok === "boolean" ? value.ok : null,
    atMs: Number.isFinite(value.atMs) ? value.atMs : null,
    data: compactStepDataForStdout(value.data),
  };
}

function compactStepDataForStdout(data) {
  if (data && typeof data === "object" && !Array.isArray(data) && Array.isArray(data.items) && typeof data.failedCount === "number") {
    return compactApiMatrixForStdout(data);
  }
  if (data && typeof data === "object" && !Array.isArray(data)) {
    const out = {};
    for (const [key, value] of Object.entries(data).slice(0, 8)) {
      if (key === "apiMatrix") out[key] = compactApiMatrixForStdout(value);
      else if (/screenshot/iu.test(key)) out[key] = compactArtifactForStdout(value);
      else out[key] = compactJsonForStdout(value);
    }
    return out;
  }
  return compactJsonForStdout(data);
}

function compactSummaryForStdout(summary) {
  const value = summary && typeof summary === "object" ? summary : {};
  return {
    ok: value.ok === true,
    status: value.status ?? null,
    degradedReason: value.degradedReason ?? null,
    failureKind: value.failureKind ?? null,
    failedCondition: typeof value.failedCondition === "string" ? compactText(value.failedCondition, 1200) : value.failedCondition ?? null,
    nextAction: typeof value.nextAction === "string" ? compactText(value.nextAction, 1200) : value.nextAction ?? null,
    baseUrl: value.baseUrl ?? null,
    finalUrl: value.finalUrl ?? null,
    lastUrl: value.lastUrl ?? null,
    scriptSha256: value.scriptSha256 ?? null,
    runDir: value.runDir ?? runDir,
    reportPath: value.reportPath ?? null,
    reportSha256: value.reportSha256 ?? null,
    lastScreenshot: compactArtifactForStdout(value.lastScreenshot),
    screenshots: Array.isArray(value.screenshots) ? value.screenshots.slice(-5).map((item) => compactArtifactForStdout(item)).filter(Boolean) : [],
    apiMatrix: compactApiMatrixForStdout(value.apiMatrix),
    stepCount: Number.isFinite(value.stepCount) ? value.stepCount : null,
    lastStep: compactStepForStdout(value.lastStep),
    issueEvidence: compactIssueEvidenceForStdout(value.issueEvidence),
    valuesRedacted: true,
  };
}

function compactArtifactsForStdout(artifacts) {
  const items = Array.isArray(artifacts?.items) ? artifacts.items : artifactRecords;
  return {
    runDir,
    count: items.length,
    screenshots: items.filter((item) => item && typeof item === "object" && item.kind === "screenshot").slice(-5).map((item) => compactArtifactForStdout(item)).filter(Boolean),
    items: items.slice(-12).map((item) => compactArtifactForStdout(item)).filter(Boolean),
  };
}

function compactArtifactForStdout(item) {
  if (!item || typeof item !== "object") return null;
  return {
    kind: item.kind ?? null,
    path: item.path ?? null,
    byteCount: Number.isFinite(item.byteCount) ? item.byteCount : null,
    sha256: item.sha256 ?? null,
    error: typeof item.error === "string" ? compactText(item.error, 600) : item.error ?? null,
  };
}

function compactApiMatrixForStdout(matrix) {
  if (!matrix || typeof matrix !== "object" || Array.isArray(matrix)) return null;
  return {
    ok: matrix.ok === true,
    count: Number.isFinite(matrix.count) ? matrix.count : null,
    okCount: Number.isFinite(matrix.okCount) ? matrix.okCount : null,
    failedCount: Number.isFinite(matrix.failedCount) ? matrix.failedCount : null,
    items: Array.isArray(matrix.items)
      ? matrix.items.slice(0, 12).map((item) => {
        const row = item && typeof item === "object" ? item : {};
        return {
          name: typeof row.name === "string" ? row.name : null,
          path: typeof row.path === "string" ? row.path : null,
          method: typeof row.method === "string" ? row.method : null,
          ok: row.ok === true,
          status: Number.isFinite(row.status) ? row.status : null,
          error: typeof row.error === "string" ? compactText(row.error, 300) : row.error ?? null,
          failureKind: typeof row.failureKind === "string" ? row.failureKind : null,
          bodyKeys: Array.isArray(row.bodyKeys) ? row.bodyKeys.filter((key) => typeof key === "string").slice(0, 12) : [],
        };
      })
      : [],
  };
}

function compactJsonForStdout(value, depth = 0) {
  if (value === null || value === undefined) return value ?? null;
  if (typeof value === "string") return compactText(value, 180);
  if (typeof value === "number" || typeof value === "boolean") return value;
  if (typeof value === "bigint") return String(value);
  if (typeof value === "function" || typeof value === "symbol") return "[" + typeof value + "]";
  if (depth >= 3) return "[max-depth]";
  if (Array.isArray(value)) return value.slice(0, 4).map((item) => compactJsonForStdout(item, depth + 1));
  if (typeof value === "object") {
    const out = {};
    for (const [key, nested] of Object.entries(value).slice(0, 8)) {
      out[key] = compactJsonForStdout(nested, depth + 1);
    }
    return out;
  }
  return compactText(String(value), 180);
}

function issueEvidenceFromPayload(payload) {
  const steps = publicSteps();
  const artifacts = Array.isArray(payload?.artifacts?.items) ? payload.artifacts.items : artifactRecords;
  const screenshots = artifacts
    .filter((item) => item && typeof item === "object" && item.kind === "screenshot")
    .slice(-5)
    .map((item) => compactArtifactForStdout(item))
    .filter(Boolean);
  const ok = payload?.ok === true;
  const degradedReason = ok ? null : payload?.error ?? payload?.failureKind ?? "web-probe-script-failed";
  const failureKind = ok ? null : classifyIssueFailureKind(payload?.failureKind ?? payload?.error ?? degradedReason, payload?.errorMessage);
  const failedCondition = ok ? null : payload?.errorMessage ?? payload?.error ?? payload?.failureKind ?? "script did not pass";
  const script = payload?.script && typeof payload.script === "object" ? payload.script : {};
  return {
    ok,
    status: payload?.status ?? null,
    degradedReason,
    failureKind,
    failedCondition,
    nextAction: ok ? null : issueNextAction(failureKind, payload),
    baseUrl: payload?.baseUrl ?? null,
    finalUrl: payload?.finalUrl ?? payload?.lastUrl ?? null,
    lastUrl: payload?.lastUrl ?? payload?.finalUrl ?? null,
    scriptSha256: payload?.scriptSha256 ?? null,
    runDir,
    reportPath: payload?.reportPath ?? null,
    reportSha256: payload?.reportSha256 ?? null,
    result: compactJsonForEvidence(script.result),
    apiMatrix: compactApiMatrixForStdout(latestApiMatrixFromSteps(steps)),
    lastStep: steps.length > 0 ? compactStepForEvidence(steps[steps.length - 1]) : null,
    steps: steps.slice(-3).map((step) => compactStepForEvidence(step)),
    lastScreenshot: compactArtifactForStdout(payload?.lastScreenshot),
    screenshots,
    valuesRedacted: true,
  };
}

function compactIssueEvidenceForStdout(value) {
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
  return {
    ok: value.ok === true,
    status: value.status ?? null,
    degradedReason: typeof value.degradedReason === "string" ? compactText(value.degradedReason, 600) : value.degradedReason ?? null,
    failureKind: typeof value.failureKind === "string" ? compactText(value.failureKind, 300) : value.failureKind ?? null,
    failedCondition: typeof value.failedCondition === "string" ? compactText(value.failedCondition, 1200) : value.failedCondition ?? null,
    nextAction: typeof value.nextAction === "string" ? compactText(value.nextAction, 1200) : value.nextAction ?? null,
    baseUrl: value.baseUrl ?? null,
    finalUrl: value.finalUrl ?? null,
    lastUrl: value.lastUrl ?? null,
    scriptSha256: value.scriptSha256 ?? null,
    runDir: value.runDir ?? runDir,
    reportPath: value.reportPath ?? null,
    reportSha256: value.reportSha256 ?? null,
    result: compactJsonForEvidence(value.result),
    apiMatrix: compactApiMatrixForStdout(value.apiMatrix),
    lastStep: compactStepForEvidence(value.lastStep),
    steps: Array.isArray(value.steps) ? value.steps.slice(-3).map((step) => compactStepForEvidence(step)) : [],
    lastScreenshot: compactArtifactForStdout(value.lastScreenshot),
    screenshots: Array.isArray(value.screenshots) ? value.screenshots.slice(-5).map((item) => compactArtifactForStdout(item)).filter(Boolean) : [],
    valuesRedacted: true,
  };
}

function compactStepForEvidence(step) {
  if (!step || typeof step !== "object" || Array.isArray(step)) return null;
  return {
    index: Number.isFinite(step.index) ? step.index : null,
    name: typeof step.name === "string" ? step.name : null,
    ok: typeof step.ok === "boolean" ? step.ok : null,
    atMs: Number.isFinite(step.atMs) ? step.atMs : null,
    data: compactJsonForEvidence(step.data),
  };
}

function compactJsonForEvidence(value, depth = 0) {
  if (value === null || value === undefined) return value ?? null;
  if (typeof value === "string") return compactText(value, 240);
  if (typeof value === "number" || typeof value === "boolean") return value;
  if (typeof value === "bigint") return String(value);
  if (typeof value === "function" || typeof value === "symbol") return "[" + typeof value + "]";
  if (depth >= 4) return "[max-depth]";
  if (Array.isArray(value)) return value.slice(0, 6).map((item) => compactJsonForEvidence(item, depth + 1));
  if (typeof value === "object") {
    const out = {};
    for (const [key, nested] of Object.entries(value).slice(0, 12)) {
      out[key] = compactJsonForEvidence(nested, depth + 1);
    }
    return out;
  }
  return compactText(String(value), 240);
}

function compactText(value, maxChars) {
  return redactString(String(value)).replace(/\s+/gu, " ").trim().slice(0, maxChars);
}

function scriptIssueSummary(payload) {
  const artifacts = Array.isArray(payload?.artifacts?.items) ? payload.artifacts.items : artifactRecords;
  const screenshots = artifacts
    .filter((item) => item && typeof item === "object" && item.kind === "screenshot")
    .slice(-5)
    .map((item) => ({
      path: item.path ?? null,
      sha256: item.sha256 ?? null,
      byteCount: item.byteCount ?? null,
    }));
  const apiMatrix = latestApiMatrixFromSteps(publicSteps());
  const ok = payload?.ok === true;
  const degradedReason = ok ? null : payload?.error ?? payload?.failureKind ?? "web-probe-script-failed";
  const failureKind = ok ? null : classifyIssueFailureKind(payload?.failureKind ?? payload?.error ?? degradedReason, payload?.errorMessage);
  return {
    ok,
    status: payload?.status ?? null,
    degradedReason,
    failureKind,
    failedCondition: ok ? null : payload?.errorMessage ?? payload?.error ?? payload?.failureKind ?? "script did not pass",
    nextAction: ok ? null : issueNextAction(failureKind, payload),
    baseUrl: payload?.baseUrl ?? null,
    finalUrl: payload?.finalUrl ?? payload?.lastUrl ?? null,
    lastUrl: payload?.lastUrl ?? payload?.finalUrl ?? null,
    scriptSha256: payload?.scriptSha256 ?? null,
    runDir,
    reportPath: payload?.reportPath ?? null,
    reportSha256: payload?.reportSha256 ?? null,
    lastScreenshot: payload?.lastScreenshot ?? null,
    screenshots,
    apiMatrix,
    stepCount: stepRecords.length,
    lastStep: stepRecords.length > 0 ? deepClonePlain(stepRecords[stepRecords.length - 1]) : null,
    issueEvidence: issueEvidenceFromPayload(payload),
    valuesRedacted: true,
  };
}

function latestApiMatrixFromSteps(steps) {
  for (let index = steps.length - 1; index >= 0; index -= 1) {
    const step = steps[index];
    const data = step && typeof step === "object" ? step.data : null;
    if (data && typeof data === "object" && Array.isArray(data.items) && typeof data.failedCount === "number") return data;
  }
  return null;
}

function classifyIssueFailureKind(kind, message = "") {
  const text = String(kind ?? "") + " " + String(message ?? "");
  if (/auth-redirect-login/iu.test(text)) return "auth-redirect-login";
  if (/composer-disabled/iu.test(text)) return "composer-disabled";
  if (/session-not-selected|session_required/iu.test(text)) return "session-not-selected";
  if (/same-origin-api-fetch-failed/iu.test(text)) return "same-origin-api-fetch-failed";
  if (/browser-navigation-jitter|browser-load-jitter|page\.(?:reload|goto)|gotoCurrentStable|reloadStable|safeReload|ERR_NETWORK_CHANGED|ERR_ABORTED|net::|navigation failed|domcontentloaded|chrome-error:\/\/chromewebdata/iu.test(text)) return "browser-navigation-jitter";
  if (/api|fetch|network|Failed to fetch|HTTP|status/iu.test(text)) return "network-or-api-fetch-bug";
  if (/script-api-misuse|page\.evaluate|safeEvaluate|Too many arguments/iu.test(text)) return "script-bug";
  if (/auth|login|credential/iu.test(text)) return "target-auth-bug";
  if (/browser|chromium|playwright|executable/iu.test(text)) return "browser-environment-bug";
  if (/assert|expect|ok:false|validation|final-response|message/iu.test(text)) return "unmet-expectation";
  if (/script|ReferenceError|TypeError|SyntaxError/iu.test(text)) return "script-bug";
  return "user-facing-web-bug";
}

function issueNextAction(failureKind, payload) {
  if (failureKind === "auth-redirect-login") return "Inspect summary.finalUrl, auth/session bootstrap, and target login redirect handling before rerunning fresh-session.";
  if (failureKind === "composer-disabled" || failureKind === "session-not-selected") return "Inspect readiness.composer disabledReason and workspace/session summary; create or select a submit-capable session, then rerun the same probe.";
  if (failureKind === "same-origin-api-fetch-failed") return "Inspect summary.apiMatrix failed rows, current URL, auth state, and browser console/network evidence in reportPath.";
  if (failureKind === "browser-navigation-jitter") return "Use reloadStable(), gotoCurrentStable(), or safeReload() to retry the same URL/page reload with bounded attempts; inspect summary.finalUrl, summary.lastScreenshot, and readiness attempts instead of the API matrix first.";
  if (failureKind === "network-or-api-fetch-bug") return "Inspect summary.apiMatrix failed rows and retry the same node/lane after checking API availability.";
  if (failureKind === "script-bug") return "Fix the probe script or use safeEvaluate/safeFetchJson/fetchApiMatrix helpers, then rerun the same command.";
  if (failureKind === "target-auth-bug") return "Inspect credential sourceRef/fingerprint and target /auth/login state; do not print secrets.";
  if (failureKind === "browser-environment-bug") return "Inspect Playwright/browser-launcher availability in the target workspace.";
  if (failureKind === "unmet-expectation") return "Inspect summary.failedCondition, DOM/API steps, and screenshots to decide whether the Web behavior or the assertion is stale.";
  return payload?.guidance ?? "Inspect reportPath for full redacted details and rerun the same node/lane entry after the root cause is fixed.";
}
`;
}
