// SPEC: PJ2026-01040111 长程观测 draft-2026-06-20-p0-passive-web-probe-observer.
// Responsibility: Source strings for the pure-client HWLAB web-probe observer and offline analyzer.

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

const specRef = "PJ2026-01040111 长程观测 draft-2026-06-20-p0-passive-web-probe-observer";
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 stateDir = path.resolve(process.env.UNIDESK_WEB_OBSERVE_STATE_DIR || ".state/web-observe/manual");
const jobId = safeId(process.env.UNIDESK_WEB_OBSERVE_JOB_ID || "webobs-" + Date.now().toString(36) + "-" + randomBytes(3).toString("hex"));
const targetPath = process.env.UNIDESK_WEB_OBSERVE_TARGET_PATH || "/workbench";
const sampleIntervalMs = positiveInteger(process.env.UNIDESK_WEB_OBSERVE_SAMPLE_INTERVAL_MS, 5000);
const screenshotIntervalMs = positiveInteger(process.env.UNIDESK_WEB_OBSERVE_SCREENSHOT_INTERVAL_MS, 300000);
const maxSamples = positiveInteger(process.env.UNIDESK_WEB_OBSERVE_MAX_SAMPLES, 0);
const viewport = parseViewport(process.env.UNIDESK_WEB_OBSERVE_VIEWPORT || "1440x900");
const playwrightProxy = proxyConfigFromEnv(baseUrl);
const chromiumLaunchOptions = chromiumLaunchOptionsForProxy(playwrightProxy);
const pageId = "page-" + randomBytes(4).toString("hex");
const dirs = {
  commandsPending: path.join(stateDir, "commands", "pending"),
  commandsProcessing: path.join(stateDir, "commands", "processing"),
  commandsDone: path.join(stateDir, "commands", "done"),
  commandsFailed: path.join(stateDir, "commands", "failed"),
  screenshots: path.join(stateDir, "screenshots"),
  analysis: path.join(stateDir, "analysis"),
};
const files = {
  manifest: path.join(stateDir, "manifest.json"),
  heartbeat: path.join(stateDir, "heartbeat.json"),
  control: path.join(stateDir, "control.jsonl"),
  samples: path.join(stateDir, "samples.jsonl"),
  network: path.join(stateDir, "network.jsonl"),
  console: path.join(stateDir, "console.jsonl"),
  errors: path.join(stateDir, "errors.jsonl"),
  artifacts: path.join(stateDir, "artifacts.jsonl"),
};

let browser;
let context;
let page;
let sampleSeq = 0;
let commandSeq = 0;
let artifactSeq = 0;
let activeCommandId = null;
let stopping = false;
let terminalStatus = "starting";
let lastScreenshotAtMs = 0;
let auth = null;
let pageLoadSeq = 0;
let currentPageProvenance = null;

try {
  if (!password) throw new Error("missing HWLAB_WEB_PASS");
  await prepareDirs();
  await writeManifest({ status: "starting" });
  await writeHeartbeat({ status: "starting" });
  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 runControlCommand({ id: "startup-login", type: "login", createdAt: startedAt, source: "startup" }, async () => authenticate(context));
  page = await context.newPage();
  attachPassiveListeners(page);
  await runControlCommand({ id: "startup-goto", type: "goto", path: targetPath, createdAt: new Date().toISOString(), source: "startup" }, async () => gotoTarget(targetPath));
  terminalStatus = "running";
  await writeManifest({ status: "running", auth: publicAuth(auth) });
  await writeHeartbeat({ status: "running" });
  while (!stopping) {
    await drainOneCommand();
    await samplePage("interval");
    if (maxSamples > 0 && sampleSeq >= maxSamples) {
      await appendJsonl(files.control, controlRecord({ id: "max-samples", type: "stop", source: "sampler" }, "completed", { reason: "max-samples", maxSamples }));
      break;
    }
    await sleep(sampleIntervalMs);
  }
  terminalStatus = "completed";
  await writeHeartbeat({ status: "completed" });
  await writeManifest({ status: "completed", completedAt: new Date().toISOString() });
  process.exitCode = 0;
} catch (error) {
  terminalStatus = "failed";
  await appendJsonl(files.errors, eventRecord("runner-error", { error: errorSummary(error) })).catch(() => {});
  await writeHeartbeat({ status: "failed", error: errorSummary(error) }).catch(() => {});
  await writeManifest({ status: "failed", error: errorSummary(error) }).catch(() => {});
  process.exitCode = 2;
} finally {
  if (browser) await browser.close().catch(() => {});
}

async function prepareDirs() {
  await mkdir(stateDir, { recursive: true, mode: 0o700 });
  await Promise.all(Object.values(dirs).map((dir) => mkdir(dir, { recursive: true, mode: 0o700 })));
}

async function writeManifest(extra = {}) {
  const manifest = {
    ok: extra.status !== "failed",
    command: "web-probe-observe",
    specRef,
    jobId,
    pid: process.pid,
    stateDir,
    baseUrl,
    targetPath,
    network: publicNetwork(playwrightProxy),
    pageAuthority: { browser: "chromium", context: "single", pageId, continuityBreaksRecorded: true },
    pageProvenance: compactPageProvenance(currentPageProvenance),
    sampling: { mode: "passive", sampleIntervalMs, screenshotIntervalMs, maxSamples, observerInitiatedDefault: false, responseBodyReadDefault: false },
    commandDirs: dirs,
    artifacts: files,
    safety: { pureClient: true, inboundApi: false, database: false, queueConsumer: false, k8sWorkload: false, valuesRedacted: true, secretValuesPrinted: false },
    startedAt,
    ...extra,
  };
  await writeFile(files.manifest, JSON.stringify(manifest, null, 2) + "\n", { mode: 0o600 });
}

async function writeHeartbeat(extra = {}) {
  const heartbeat = {
    ok: terminalStatus !== "failed",
    jobId,
    pid: process.pid,
    stateDir,
    status: terminalStatus,
    pageId,
    baseUrl,
    currentUrl: currentPageUrl(),
    pageProvenance: compactPageProvenance(currentPageProvenance),
    sampleSeq,
    commandSeq,
    activeCommandId,
    updatedAt: new Date().toISOString(),
    uptimeMs: Date.now() - startedAtMs,
    ...extra,
  };
  await writeFile(files.heartbeat, JSON.stringify(heartbeat, null, 2) + "\n", { mode: 0o600 });
}

function attachPassiveListeners(targetPage) {
  targetPage.on("request", (request) => {
    void appendJsonl(files.network, eventRecord("request", {
      observerInitiated: false,
      commandId: activeCommandId,
      method: request.method(),
      url: safeUrl(request.url()),
      resourceType: request.resourceType(),
      frameUrl: safeFrameUrl(request.frame()),
    }));
  });
  targetPage.on("response", (response) => {
    const request = response.request();
    void appendJsonl(files.network, eventRecord("response", {
      observerInitiated: false,
      commandId: activeCommandId,
      method: request.method(),
      url: safeUrl(response.url()),
      resourceType: request.resourceType(),
      status: response.status(),
      statusText: response.statusText(),
      fromServiceWorker: response.fromServiceWorker(),
      bodyRead: false,
    }));
  });
  targetPage.on("requestfailed", (request) => {
    void appendJsonl(files.network, eventRecord("requestfailed", {
      observerInitiated: false,
      commandId: activeCommandId,
      method: request.method(),
      url: safeUrl(request.url()),
      resourceType: request.resourceType(),
      failure: request.failure()?.errorText ?? null,
    }));
  });
  targetPage.on("console", (message) => {
    void appendJsonl(files.console, eventRecord("console", { type: message.type(), text: truncate(message.text(), 1000), location: message.location() }));
  });
  targetPage.on("pageerror", (error) => {
    void appendJsonl(files.errors, eventRecord("pageerror", { error: errorSummary(error) }));
  });
  targetPage.on("crash", () => {
    void appendJsonl(files.errors, eventRecord("page-crash", { pageId }));
  });
  targetPage.on("close", () => {
    void appendJsonl(files.control, eventRecord("continuity-break", { pageId, reason: "page-closed" }));
  });
}

async function drainOneCommand() {
  const entries = (await readdir(dirs.commandsPending).catch(() => [])).filter((name) => name.endsWith(".json")).sort();
  const name = entries[0];
  if (!name) return;
  const pending = path.join(dirs.commandsPending, name);
  const processing = path.join(dirs.commandsProcessing, name);
  await rename(pending, processing).catch(() => null);
  const raw = await readFile(processing, "utf8");
  const command = JSON.parse(raw);
  const id = safeId(command.id || name.replace(/[.]json$/u, ""));
  command.id = id;
  try {
    const result = await processCommand(command);
    const done = { ok: true, commandId: id, type: command.type, completedAt: new Date().toISOString(), result: sanitize(result) };
    await writeFile(path.join(dirs.commandsDone, id + ".json"), JSON.stringify(done, null, 2) + "\n", { mode: 0o600 });
    await appendJsonl(files.control, controlRecord(command, "completed", done.result));
    await unlink(processing).catch(() => {});
  } catch (error) {
    const failed = { ok: false, commandId: id, type: command.type, failedAt: new Date().toISOString(), error: errorSummary(error) };
    await writeFile(path.join(dirs.commandsFailed, id + ".json"), JSON.stringify(failed, null, 2) + "\n", { mode: 0o600 });
    await appendJsonl(files.control, controlRecord(command, "failed", failed.error));
    await unlink(processing).catch(() => {});
  } finally {
    activeCommandId = null;
    await writeHeartbeat({ status: terminalStatus });
  }
}

async function processCommand(command) {
  commandSeq += 1;
  activeCommandId = command.id;
  await writeHeartbeat({ status: "running", activeCommandId });
  await appendJsonl(files.control, controlRecord(command, "started", commandInputSummary(command)));
  switch (command.type) {
    case "login": return authenticate(context);
    case "preflight": return preflightSummary();
    case "goto": return gotoTarget(command.path || command.url || targetPath);
    case "newSession": return createSessionFromUi();
    case "sendPrompt": return sendPrompt(String(command.text || ""));
    case "selectProvider": return selectProvider(String(command.provider || command.value || command.text || ""));
    case "clickSession": return clickSession(String(command.sessionId || command.value || ""));
    case "screenshot": return captureScreenshot(command.reason || "manual", command.imageType || "png");
    case "mark": return { mark: truncate(command.label || command.text || "mark", 200), currentUrl: currentPageUrl(), pageId };
    case "stop": stopping = true; return { stopping: true, currentUrl: currentPageUrl(), pageId };
    default: throw new Error("unsupported observer command type: " + command.type);
  }
}

async function runControlCommand(command, fn) {
  activeCommandId = command.id;
  commandSeq += 1;
  const beforeUrl = currentPageUrl();
  const started = Date.now();
  await appendJsonl(files.control, controlRecord(command, "started", { beforeUrl, input: commandInputSummary(command) }));
  try {
    const result = await fn();
    await appendJsonl(files.control, controlRecord(command, "completed", { beforeUrl, afterUrl: currentPageUrl(), durationMs: Date.now() - started, result: sanitize(result) }));
    return result;
  } catch (error) {
    await appendJsonl(files.control, controlRecord(command, "failed", { beforeUrl, afterUrl: currentPageUrl(), durationMs: Date.now() - started, error: errorSummary(error) }));
    throw error;
  } finally {
    activeCommandId = null;
  }
}

async function authenticate(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: 12000,
      });
      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",
        status: response.status(),
        statusText: response.statusText(),
        retryable,
        cookiePresent: cookieState.cookiePresent,
        cookieNames: cookieState.cookieNames,
        valuesRedacted: true,
      };
      attempts.push(item);
      if (response.ok() && cookieState.cookiePresent) {
        return {
          ok: true,
          method: "api",
          loginPath: new URL(loginUrl).pathname,
          status: response.status(),
          statusText: response.statusText(),
          cookiePresent: true,
          cookieNames: cookieState.cookieNames,
          attempts,
          retryCount: attempt - 1,
          retryMaxAttempts: maxAttempts,
          lastRetryLabel: attempt + "/" + maxAttempts,
          retryExhausted: false,
          retryable: false,
          valuesRedacted: true,
        };
      }
      if (!retryable) break;
    } catch (error) {
      const retryable = isRetryableAuthError(error);
      attempts.push({
        attempt,
        retryAttempt: attempt,
        retryMaxAttempts: maxAttempts,
        retryLabel: attempt + "/" + maxAttempts,
        retryDelayMs: retryable && attempt < maxAttempts ? retryDelayMs : 0,
        method: "api",
        status: 0,
        statusText: "request-error",
        retryable,
        error: error && error.message ? truncate(error.message, 500) : truncate(String(error), 500),
        cookiePresent: false,
        cookieNames: [],
        valuesRedacted: true,
      });
      if (!retryable) break;
    }
    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 = attempts.some((attempt) => attempt && attempt.retryable === true);
  const failure = {
    ok: false,
    method: "api",
    loginPath: new URL(loginUrl).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: Math.max(0, attempts.length - 1),
    retryMaxAttempts: maxAttempts,
    lastRetryLabel: last?.retryLabel || null,
    retryExhausted: retryable && attempts.length >= maxAttempts,
    retryable,
    lastError: last?.error || null,
    valuesRedacted: true,
  };
  const error = new Error(authFailureMessage(failure));
  error.webProbeAuth = failure;
  throw error;
}

function publicAuth(value) {
  if (!value) return null;
  return {
    ok: value.ok === true,
    method: value.method,
    status: value.status,
    cookiePresent: value.cookiePresent === true,
    cookieNames: value.cookieNames || [],
    retryCount: value.retryCount ?? null,
    retryMaxAttempts: value.retryMaxAttempts ?? null,
    lastRetryLabel: value.lastRetryLabel ?? null,
    retryExhausted: value.retryExhausted === true,
    valuesRedacted: true
  };
}

async function readAuthCookieState(browserContext) {
  const cookies = await browserContext.cookies(baseUrl);
  const cookieNames = cookies.map((cookie) => cookie.name).sort();
  return {
    cookiePresent: cookieNames.includes("hwlab_session") || cookieNames.some((name) => /session|auth|token/iu.test(name)),
    cookieNames: cookieNames.filter((name) => /session|auth|token/iu.test(name)),
  };
}

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

function isRetryableAuthError(error) {
  const message = error && error.message ? String(error.message) : String(error || "");
  return /EAI_AGAIN|ETIMEDOUT|ECONNRESET|ECONNREFUSED|ECONNABORTED|socket hang up|ERR_NETWORK_CHANGED|fetch failed|network|timeout/iu.test(message);
}

function authFailureMessage(failure) {
  const last = Array.isArray(failure.attempts) && failure.attempts.length > 0 ? failure.attempts[failure.attempts.length - 1] : null;
  const retry = failure.lastRetryLabel ? " retry=" + failure.lastRetryLabel : "";
  const exhausted = failure.retryExhausted ? " exhausted=true" : "";
  const status = last ? " status=" + (last.status ?? "-") + " " + (last.statusText ?? "") : "";
  const error = last?.error ? " error=" + truncate(last.error, 160) : "";
  return ("auth login failed:" + retry + exhausted + status + error).trim();
}

function proxyConfigFromEnv(targetBaseUrl) {
  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",
      proxyEnvCleared: true,
      valuesRedacted: true,
    },
    valuesRedacted: true,
  };
}

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]@");
  }
}

async function gotoTarget(rawTarget) {
  const target = new URL(String(rawTarget || targetPath), baseUrl).toString();
  const beforeUrl = currentPageUrl();
  const attempts = [];
  for (let attempt = 1; attempt <= 3; attempt += 1) {
    try {
      const response = await page.goto(target, { waitUntil: "domcontentloaded", timeout: 30000 });
      await page.waitForTimeout(1000).catch(() => {});
      const httpStatus = response ? response.status() : null;
      const pageProvenance = await refreshPageProvenance("goto", httpStatus);
      attempts.push({ attempt, ok: true, httpStatus });
      return { beforeUrl, afterUrl: currentPageUrl(), httpStatus, pageId, pageProvenance: compactPageProvenance(pageProvenance), attempts };
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      attempts.push({ attempt, ok: false, failureKind: navigationFailureKind(message), message: redactErrorMessage(message) });
      if (attempt >= 3 || !isRetryableNavigationError(message)) {
        throw Object.assign(new Error(message), { attempts, target });
      }
      await page.waitForTimeout(500 * attempt).catch(() => {});
    }
  }
  return { beforeUrl, afterUrl: currentPageUrl(), httpStatus: null, pageId, attempts };
}

async function refreshPageProvenance(reason, httpStatus = null) {
  if (!page || page.isClosed()) return currentPageProvenance;
  const observed = await page.evaluate(() => {
    const assetPath = (raw) => {
      if (!raw) return null;
      try {
        const url = new URL(raw, location.href);
        const keys = Array.from(url.searchParams.keys()).sort();
        return url.pathname + (keys.length > 0 ? "?keys=" + keys.join(",") : "");
      } catch {
        return null;
      }
    };
    const meta = Array.from(document.querySelectorAll("meta[name], meta[property]")).map((element) => ({
      key: String(element.getAttribute("name") || element.getAttribute("property") || "").slice(0, 120),
      content: String(element.getAttribute("content") || "").slice(0, 200),
    })).filter((item) => item.key).sort((a, b) => a.key.localeCompare(b.key));
    const navigation = performance.getEntriesByType("navigation")[0] || null;
    return {
      url: location.href,
      path: location.pathname,
      title: document.title,
      readyState: document.readyState,
      timeOrigin: Math.round(performance.timeOrigin || 0),
      navigationStartTime: navigation ? Math.round(navigation.startTime) : null,
      scripts: Array.from(document.scripts).map((element) => assetPath(element.src)).filter(Boolean).sort(),
      stylesheets: Array.from(document.querySelectorAll('link[rel~="stylesheet"][href]')).map((element) => assetPath(element.href)).filter(Boolean).sort(),
      meta,
    };
  }).catch((error) => ({ error: errorSummary(error), url: currentPageUrl(), path: null, scripts: [], stylesheets: [], meta: [] }));
  pageLoadSeq += 1;
  currentPageProvenance = normalizePageProvenance(observed, { reason, httpStatus, pageLoadSeq });
  await appendJsonl(files.control, eventRecord("page-provenance", { reason, httpStatus, pageProvenance: compactPageProvenance(currentPageProvenance) }));
  return currentPageProvenance;
}

function normalizePageProvenance(value, options = {}) {
  const scripts = Array.isArray(value?.scripts) ? value.scripts.map(String).filter(Boolean) : [];
  const stylesheets = Array.isArray(value?.stylesheets) ? value.stylesheets.map(String).filter(Boolean) : [];
  const meta = Array.isArray(value?.meta) ? value.meta.map((item) => ({
    key: String(item?.key || "").slice(0, 120),
    contentHash: sha256Text(String(item?.content || "")),
  })).filter((item) => item.key) : [];
  const fingerprintInput = JSON.stringify({ scripts, stylesheets, meta });
  return {
    pageLoadSeq: options.pageLoadSeq ?? pageLoadSeq,
    reason: options.reason || "sample",
    observedAt: new Date().toISOString(),
    urlPath: safeUrlPath(value?.url || currentPageUrl()),
    documentPath: value?.path || null,
    titleHash: sha256Text(String(value?.title || "")),
    documentReadyState: value?.readyState || null,
    timeOrigin: Number.isFinite(Number(value?.timeOrigin)) ? Number(value.timeOrigin) : null,
    navigationStartTime: Number.isFinite(Number(value?.navigationStartTime)) ? Number(value.navigationStartTime) : null,
    httpStatus: options.httpStatus ?? null,
    assetFingerprint: sha256Text(fingerprintInput),
    scriptCount: scripts.length,
    stylesheetCount: stylesheets.length,
    metaCount: meta.length,
    scripts: scripts.slice(0, 30),
    stylesheets: stylesheets.slice(0, 30),
    meta: meta.slice(0, 30),
    error: value?.error || null,
    valuesRedacted: true,
  };
}

function compactPageProvenance(value) {
  if (!value) return null;
  return {
    pageLoadSeq: value.pageLoadSeq ?? null,
    reason: value.reason || null,
    observedAt: value.observedAt || null,
    urlPath: value.urlPath || null,
    documentReadyState: value.documentReadyState || null,
    timeOrigin: value.timeOrigin ?? null,
    httpStatus: value.httpStatus ?? null,
    assetFingerprint: value.assetFingerprint || null,
    scriptCount: value.scriptCount ?? 0,
    stylesheetCount: value.stylesheetCount ?? 0,
    metaCount: value.metaCount ?? 0,
    scripts: Array.isArray(value.scripts) ? value.scripts.slice(0, 12) : [],
    stylesheets: Array.isArray(value.stylesheets) ? value.stylesheets.slice(0, 12) : [],
    error: value.error || null,
    valuesRedacted: true,
  };
}

function isRetryableNavigationError(message) {
  return /net::ERR_NETWORK_CHANGED|net::ERR_ABORTED|net::ERR_CONNECTION_RESET|Navigation timeout/iu.test(String(message || ""));
}

function navigationFailureKind(message) {
  const text = String(message || "");
  if (/net::ERR_NETWORK_CHANGED/iu.test(text)) return "net::ERR_NETWORK_CHANGED";
  if (/net::ERR_ABORTED/iu.test(text)) return "net::ERR_ABORTED";
  if (/net::ERR_CONNECTION_RESET/iu.test(text)) return "net::ERR_CONNECTION_RESET";
  if (/Navigation timeout/iu.test(text)) return "navigation-timeout";
  return "navigation-error";
}

function redactErrorMessage(message) {
  return String(message || "")
    .replace(/([?&](?:token|key|password|secret|authorization)=)[^&\s]+/giu, "$1[redacted]")
    .replace(/(Bearer\s+)[A-Za-z0-9._~+/=-]+/gu, "$1[redacted]");
}

async function createSessionFromUi() {
  const beforeUrl = currentPageUrl();
  const before = await workbenchSessionSnapshot();
  const create = page.locator("#session-create").first();
  await create.waitFor({ state: "visible", timeout: 15000 });
  await create.click();
  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?.activeSessionId || "" }, { timeout: 30000 }).catch(() => null);
  const after = await workbenchSessionSnapshot();
  const beforeSessionId = before?.activeSessionId || before?.routeSessionId || "";
  const afterSessionId = after?.activeSessionId || after?.routeSessionId || "";
  const ok = Boolean(afterSessionId && afterSessionId !== beforeSessionId && after?.composerReady);
  if (!ok) {
    const error = new Error("newSession did not create or select a different workbench session");
    error.details = { beforeUrl, afterUrl: currentPageUrl(), before, after, pageId, valuesRedacted: true };
    throw error;
  }
  return {
    beforeUrl,
    afterUrl: currentPageUrl(),
    ok,
    before,
    after,
    sessionId: afterSessionId || null,
    pageId
  };
}

async function workbenchSessionSnapshot() {
  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 warning = document.querySelector(".composer-warning")?.textContent?.trim() || null;
    return {
      url: window.location.href,
      routeSessionId: routeMatch ? decodeURIComponent(routeMatch[1] || "") : null,
      activeSessionId: activeTab?.getAttribute("data-session-id") || null,
      activeConversationId: activeTab?.getAttribute("data-conversation-id") || null,
      activeStatus: activeTab?.getAttribute("data-status") || null,
      tabCount: document.querySelectorAll(".session-tab").length,
      composerReady: Boolean(activeTab && input && !input.disabled && !warning),
      warning
    };
  }).catch(() => null);
}

async function sendPrompt(text) {
  if (text.trim().length === 0) throw new Error("sendPrompt requires non-empty text");
  const beforeUrl = currentPageUrl();
  const beforeEvidence = await promptSideEffectSnapshot();
  const primaryEditor = page.locator("#command-input").last();
  const editor = await primaryEditor.isVisible().catch(() => false)
    ? primaryEditor
    : page.locator('textarea, [role="textbox"], [contenteditable="true"], input[type="text"]').last();
  await editor.waitFor({ state: "visible", timeout: 15000 });
  const tag = await editor.evaluate((element) => element.tagName.toLowerCase()).catch(() => "");
  const editable = await editor.evaluate((element) => element.getAttribute("contenteditable") === "true").catch(() => false);
  if (tag === "textarea" || tag === "input") await editor.fill(text);
  else if (editable) {
    await editor.click();
    await page.keyboard.insertText(text);
  } else {
    await editor.click();
    await page.keyboard.insertText(text);
  }
  const primarySubmit = page.locator('#command-submit, [data-testid="command-submit"], [data-testid="composer-submit"], [data-testid="send-command"]').last();
  const submit = await primarySubmit.isVisible().catch(() => false)
    ? primarySubmit
    : page.locator([
    'button[type="submit"]',
    'button:has-text("发送")',
    'button:has-text("Send")',
    '[data-testid*="send" i]',
    '[aria-label*="send" i]',
    '[aria-label*="发送"]'
  ].join(", ")).last();
  await submit.waitFor({ state: "visible", timeout: 15000 });
  const chatResponsePromise = page.waitForResponse((response) => {
    const request = response.request();
    if (request.method().toUpperCase() !== "POST") return false;
    try {
      return new URL(response.url()).pathname === "/v1/agent/chat";
    } catch {
      return false;
    }
  }, { timeout: 45000 }).catch((error) => ({ waitError: errorSummary(error) }));
  await submit.click();
  const chatResponse = await chatResponsePromise;
  await page.waitForTimeout(500);
  if (chatResponse?.waitError) {
    const sideEffect = await waitForPromptSideEffect(beforeEvidence, 5000);
    if (sideEffect.submitted) {
      return {
        beforeUrl,
        afterUrl: currentPageUrl(),
        textHash: sha256Text(text),
        textBytes: Buffer.byteLength(text),
        chatSubmit: { status: null, statusText: null, urlPath: "/v1/agent/chat", waitError: chatResponse.waitError, sideEffectObserved: true, sideEffect },
        pageId
      };
    }
    throw new Error("sendPrompt did not observe POST /v1/agent/chat response after submit: " + (chatResponse.waitError.message || chatResponse.waitError.name || "timeout"));
  }
  const chatStatus = chatResponse.status();
  if (chatStatus < 200 || chatStatus >= 300) {
    throw new Error("sendPrompt observed POST /v1/agent/chat HTTP " + chatStatus + " " + chatResponse.statusText());
  }
  let chatPayload = null;
  let chatPayloadError = null;
  try {
    chatPayload = await chatResponse.json();
  } catch (error) {
    chatPayloadError = errorSummary(error);
  }
  let chatUrlPath = "/v1/agent/chat";
  try {
    chatUrlPath = new URL(chatResponse.url()).pathname;
  } catch {
    chatUrlPath = "/v1/agent/chat";
  }
  const payloadText = chatPayload ? JSON.stringify(chatPayload) : "";
  const traceId = payloadText.match(/\btrc_[A-Za-z0-9_-]+\b/u)?.[0] || null;
  const otelTraceId = typeof chatPayload?.otelTrace?.traceId === "string" && /^[0-9a-f]{32}$/u.test(chatPayload.otelTrace.traceId)
    ? chatPayload.otelTrace.traceId
    : null;
  return {
    beforeUrl,
    afterUrl: currentPageUrl(),
    textHash: sha256Text(text),
    textBytes: Buffer.byteLength(text),
    chatSubmit: {
      status: chatStatus,
      statusText: chatResponse.statusText(),
      urlPath: chatUrlPath,
      traceId,
      otelTraceId,
      resultUrl: safeUrlPath(chatPayload?.resultUrl),
      turnUrl: safeUrlPath(chatPayload?.turnUrl),
      streamUrl: safeUrlPath(chatPayload?.streamUrl),
      responseParsed: chatPayload !== null,
      responseParseError: chatPayloadError,
      valuesRedacted: true
    },
    pageId
  };
}

async function waitForPromptSideEffect(beforeEvidence, timeoutMs) {
  await page.waitForFunction((before) => {
    const current = (() => {
      const text = document.body?.innerText || "";
      const runIds = Array.from(new Set(text.match(/run_[A-Za-z0-9_:-]+/gu) || [])).slice(-20);
      const traceIds = Array.from(new Set(text.match(/trc_[A-Za-z0-9_:-]+/gu) || [])).slice(-20);
      const running = /Trace running|最近\s*\d+\s*(?:秒|分钟|分|小时)前|Code Agent\s*耗时/iu.test(text);
      const executionError = /AgentRun error|agentrun:error:|provider-stream-disconnected|provider-unavailable/iu.test(text);
      const textBytes = new TextEncoder().encode(text).length;
      return { runIds, traceIds, running, executionError, textBytes };
    })();
    const beforeRuns = Array.isArray(before?.runIds) ? before.runIds : [];
    const beforeTraces = Array.isArray(before?.traceIds) ? before.traceIds : [];
    const newRun = current.runIds.some((id) => !beforeRuns.includes(id));
    const newTrace = current.traceIds.some((id) => !beforeTraces.includes(id));
    const meaningfulChange = Math.abs(current.textBytes - Number(before?.textBytes || 0)) > 20 && (current.running || current.executionError);
    return newRun || newTrace || meaningfulChange;
  }, beforeEvidence || {}, { timeout: timeoutMs }).catch(() => null);
  const after = await promptSideEffectSnapshot();
  const beforeRuns = Array.isArray(beforeEvidence?.runIds) ? beforeEvidence.runIds : [];
  const beforeTraces = Array.isArray(beforeEvidence?.traceIds) ? beforeEvidence.traceIds : [];
  const newRunIds = after.runIds.filter((id) => !beforeRuns.includes(id));
  const newTraceIds = after.traceIds.filter((id) => !beforeTraces.includes(id));
  const meaningfulChange = Math.abs(after.textBytes - Number(beforeEvidence?.textBytes || 0)) > 20 && (after.running || after.executionError);
  return { ...after, newRunIds, newTraceIds, submitted: newRunIds.length > 0 || newTraceIds.length > 0 || meaningfulChange, valuesRedacted: true };
}

async function promptSideEffectSnapshot() {
  return page.evaluate(() => {
    const text = document.body?.innerText || "";
    return {
      runIds: Array.from(new Set(text.match(/run_[A-Za-z0-9_:-]+/gu) || [])).slice(-20),
      traceIds: Array.from(new Set(text.match(/trc_[A-Za-z0-9_:-]+/gu) || [])).slice(-20),
      running: /Trace running|最近\s*\d+\s*(?:秒|分钟|分|小时)前|Code Agent\s*耗时/iu.test(text),
      executionError: /AgentRun error|agentrun:error:|provider-stream-disconnected|provider-unavailable/iu.test(text),
      textBytes: new TextEncoder().encode(text).length,
      valuesRedacted: true
    };
  }).catch(() => ({ runIds: [], traceIds: [], running: false, executionError: false, textBytes: 0, valuesRedacted: true }));
}

async function selectProvider(provider) {
  const target = String(provider || "").trim();
  if (!target) throw new Error("selectProvider requires provider name");
  const beforeUrl = currentPageUrl();
  const nativeSelect = await page.evaluate((name) => {
    const normalized = String(name).toLowerCase();
    const visible = (element) => {
      const rect = element.getBoundingClientRect();
      const style = window.getComputedStyle(element);
      return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
    };
    for (const select of Array.from(document.querySelectorAll("select")).filter(visible)) {
      const options = Array.from(select.options || []);
      const option = options.find((item) => String(item.value || "").toLowerCase().includes(normalized) || String(item.textContent || "").toLowerCase().includes(normalized));
      if (!option) continue;
      select.value = option.value;
      select.dispatchEvent(new Event("input", { bubbles: true }));
      select.dispatchEvent(new Event("change", { bubbles: true }));
      return { kind: "native-select", value: option.value, text: option.textContent || "" };
    }
    return null;
  }, target).catch(() => null);
  if (nativeSelect) {
    await page.waitForTimeout(500);
    return { beforeUrl, afterUrl: currentPageUrl(), provider: target, selected: nativeSelect, pageId };
  }
  const optionVisible = page.getByText(target, { exact: false }).last();
  if (await optionVisible.isVisible().catch(() => false)) {
    await optionVisible.click();
    await page.waitForTimeout(500);
    return { beforeUrl, afterUrl: currentPageUrl(), provider: target, selected: { kind: "visible-text" }, pageId };
  }
  const control = page.locator([
    '[data-testid*="provider" i]',
    '[data-testid*="profile" i]',
    '[data-testid*="model" i]',
    '[aria-label*="provider" i]',
    '[aria-label*="profile" i]',
    '[aria-label*="model" i]',
    '[aria-label*="模型"]',
    '[aria-label*="提供"]',
    '[role="combobox"]',
    '[aria-haspopup="listbox"]',
    'button'
  ].join(", "));
  const count = Math.min(await control.count().catch(() => 0), 60);
  const attempts = [];
  for (let index = count - 1; index >= 0; index -= 1) {
    const item = control.nth(index);
    const visible = await item.isVisible().catch(() => false);
    if (!visible) continue;
    const label = await item.evaluate((element) => String(element.getAttribute("aria-label") || element.getAttribute("data-testid") || element.textContent || "").slice(0, 200)).catch(() => "");
    if (!/provider|profile|model|模型|提供|codex|openai|moon|api/iu.test(label)) continue;
    attempts.push({ index, label });
    await item.click({ timeout: 3000 }).catch(() => null);
    await page.waitForTimeout(300);
    const option = page.getByText(target, { exact: false }).last();
    if (await option.isVisible().catch(() => false)) {
      await option.click();
      await page.waitForTimeout(700);
      return { beforeUrl, afterUrl: currentPageUrl(), provider: target, selected: { kind: "opened-control", controlIndex: index, label }, attempts, pageId };
    }
    await page.keyboard.press("Escape").catch(() => null);
  }
  throw new Error("provider option not found: " + target + "; attempts=" + JSON.stringify(attempts.slice(-10)));
}

async function clickSession(sessionId) {
  if (!sessionId) throw new Error("clickSession requires --session-id or --value");
  const beforeUrl = currentPageUrl();
  const escaped = cssEscape(sessionId);
  const candidate = page.locator("[data-session-id=\"" + escaped + "\"], [href*=\"" + escaped + "\"], text=" + sessionId).first();
  await candidate.waitFor({ state: "visible", timeout: 15000 });
  await candidate.click();
  await page.waitForTimeout(1000);
  return { beforeUrl, afterUrl: currentPageUrl(), sessionId, pageId };
}

async function preflightSummary() {
  return { currentUrl: currentPageUrl(), title: await page.title().catch(() => null), pageId, auth: publicAuth(auth) };
}

async function samplePage(reason) {
  if (!page || page.isClosed()) return;
  sampleSeq += 1;
  const dom = await page.evaluate(() => {
    const trim = (value, limit = 500) => String(value || "").replace(/\s+/g, " ").trim().slice(0, limit);
    const visible = (element) => {
      const rect = element.getBoundingClientRect();
      const style = window.getComputedStyle(element);
      return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
    };
    const textHashInput = (element) => trim(element.textContent || "", 800);
    const diagnosticSummaryText = (element) => {
      const summarySelectors = [
        ".api-error-diagnostic-summary-text p",
        ".api-error-diagnostic-summary-text",
        "[class*='diagnostic-summary-text' i] p",
        "[class*='diagnostic-summary-text' i]",
        "[data-testid*='diagnostic-summary' i]",
        "[data-testid*='error-summary' i]",
        "[role='alert'] p",
        "[role='alert']"
      ];
      const parts = [];
      for (const selector of summarySelectors) {
        for (const candidate of Array.from(element.querySelectorAll(selector))) {
          if (!visible(candidate)) continue;
          const text = trim(candidate.textContent || "", 800);
          if (text && !parts.includes(text)) parts.push(text);
        }
      }
      const ownText = textHashInput(element);
      const text = parts.length > 0 ? parts.join(" ") : ownText;
      return text.replace(/\s+(?:!|i诊断|诊断详情)$/u, "").trim();
    };
    const diagnosticToggleOnly = (element, text) => {
      const compact = String(text || "").trim();
      if (!/^(?:!|i诊断|诊断|诊断详情)$/u.test(compact)) return false;
      const tag = element.tagName.toLowerCase();
      const role = element.getAttribute("role");
      const aria = element.getAttribute("aria-label") || "";
      const title = element.getAttribute("title") || "";
      return tag === "button" || role === "button" || /诊断/u.test(aria) || /诊断/u.test(title);
    };
    const summarize = (selector, limit) => Array.from(document.querySelectorAll(selector)).filter(visible).slice(-limit).map((element, index) => {
      const rect = element.getBoundingClientRect();
      return {
        index,
        tag: element.tagName.toLowerCase(),
        testId: element.getAttribute("data-testid"),
        role: element.getAttribute("role"),
        status: element.getAttribute("data-status") || element.getAttribute("aria-busy") || null,
        sessionId: element.getAttribute("data-session-id") || null,
        messageId: element.getAttribute("data-message-id") || element.getAttribute("id") || null,
        traceId: element.getAttribute("data-trace-id") || null,
        turnId: element.getAttribute("data-turn-id") || null,
        text: textHashInput(element),
        rect: { x: Math.round(rect.x), y: Math.round(rect.y), width: Math.round(rect.width), height: Math.round(rect.height) },
      };
    });
    const url = location.href;
    const routeSessionMatch = url.match(/\/workbench\/sessions\/([^/?#]+)/u);
    const activeSession = document.querySelector('[data-active="true"][data-session-id], [aria-selected="true"][data-session-id], .active[data-session-id]');
    const activeSessionId = activeSession ? activeSession.getAttribute("data-session-id") : null;
    const messageSelector = '[data-testid*="message" i], [class*="message" i], article, [role="article"]';
    const traceSelector = '[data-testid*="trace" i], [class*="trace" i], [data-trace-id], [data-testid*="event" i]';
    const diagnosticSelector = '.api-error-diagnostic, [class*="api-error-diagnostic" i], [class*="message-diagnostic" i], [class*="projection-diagnostic" i], [data-testid="api-error-diagnostic" i], [data-testid="error-diagnostic" i], [data-testid*="diagnostic" i], [role="alert"], [aria-live="assertive"]';
    const messages = summarize(messageSelector, 20);
    const traceRows = summarize(traceSelector, 30);
    const diagnostics = Array.from(document.querySelectorAll(diagnosticSelector)).filter(visible).slice(-40).map((element, index) => {
      const rect = element.getBoundingClientRect();
      const text = diagnosticSummaryText(element);
      if (!text || diagnosticToggleOnly(element, text)) return null;
      const traceMatch = text.match(/\b(?:trace_id=)?(trc_[A-Za-z0-9_-]+|[a-f0-9]{16,64})\b/iu);
      const httpStatusMatch = text.match(/\bHTTP\s+([1-5][0-9]{2})\b/iu);
      const idleMatch = text.match(/\bidle\s+(\d+)s\b/iu);
      const waitingForMatch = text.match(/\bwaitingFor=([^\s；;，,)]+)/iu);
      const lastEventLabelMatch = text.match(/\blastEventLabel=([^\s；;，,)]+)/iu);
      const diagnosticCode = httpStatusMatch ? "http-" + httpStatusMatch[1] : /turn\s*超过|无新活动/iu.test(text) ? "turn-idle-no-activity" : /Failed to fetch/iu.test(text) ? "failed-to-fetch" : "diagnostic";
      return {
        index,
        tag: element.tagName.toLowerCase(),
        className: String(element.className || "").slice(0, 240),
        testId: element.getAttribute("data-testid"),
        role: element.getAttribute("role"),
        compact: element.getAttribute("data-compact"),
        expanded: element.getAttribute("data-expanded") || element.getAttribute("aria-expanded"),
        title: element.getAttribute("title"),
        diagnosticCode,
        traceId: traceMatch?.[1] || null,
        httpStatus: httpStatusMatch ? Number(httpStatusMatch[1]) : null,
        idleSeconds: idleMatch ? Number(idleMatch[1]) : null,
        waitingFor: waitingForMatch?.[1] || null,
        lastEventLabel: lastEventLabelMatch?.[1] || null,
        text,
        rect: { x: Math.round(rect.x), y: Math.round(rect.y), width: Math.round(rect.width), height: Math.round(rect.height) },
      };
    }).filter(Boolean);
    const turns = Array.from(document.querySelectorAll('article.message-card[data-role="agent"], .message-card[data-role="agent"], article[data-role="agent"]')).filter(visible).map((element, index) => {
      const rect = element.getBoundingClientRect();
      const text = textHashInput(element);
      const traceElement = element.matches("[data-trace-id]") ? element : element.querySelector("[data-trace-id]");
      const traceMatch = text.match(/\btrc_[A-Za-z0-9_-]+\b/u);
      const durationText = trim(element.querySelector(".message-duration-meta")?.textContent || "", 120);
      const activityText = trim(element.querySelector(".message-activity-meta")?.textContent || "", 120);
      const directTraceId = element.getAttribute("data-trace-id") || traceElement?.getAttribute("data-trace-id") || traceMatch?.[0] || null;
      return {
        index,
        role: element.getAttribute("data-role") || "agent",
        status: element.getAttribute("data-status") || null,
        sessionId: element.getAttribute("data-session-id") || null,
        messageId: element.getAttribute("data-message-id") || element.getAttribute("id") || null,
        traceId: directTraceId,
        turnId: element.getAttribute("data-turn-id") || directTraceId,
        durationText,
        activityText,
        text,
        rect: { x: Math.round(rect.x), y: Math.round(rect.y), width: Math.round(rect.width), height: Math.round(rect.height) },
      };
    }).slice(-20);
    const active = document.activeElement;
    return {
      url,
      path: location.pathname,
      routeSessionId: routeSessionMatch ? decodeURIComponent(routeSessionMatch[1]) : null,
      activeSessionId,
      title: document.title,
      focus: active ? { tag: active.tagName.toLowerCase(), testId: active.getAttribute("data-testid"), role: active.getAttribute("role") } : null,
      viewport: { width: window.innerWidth, height: window.innerHeight, devicePixelRatio: window.devicePixelRatio },
      scroll: { x: Math.round(window.scrollX), y: Math.round(window.scrollY), height: Math.round(document.documentElement.scrollHeight), width: Math.round(document.documentElement.scrollWidth) },
      messages,
      traceRows,
      diagnostics,
      turns,
      pageProvenance: {
        url: location.href,
        path: location.pathname,
        title: document.title,
        readyState: document.readyState,
        timeOrigin: Math.round(performance.timeOrigin || 0),
        navigationStartTime: (performance.getEntriesByType("navigation")[0] || null)?.startTime ?? null,
        scripts: Array.from(document.scripts).map((element) => {
          if (!element.src) return null;
          try {
            const url = new URL(element.src, location.href);
            const keys = Array.from(url.searchParams.keys()).sort();
            return url.pathname + (keys.length > 0 ? "?keys=" + keys.join(",") : "");
          } catch {
            return null;
          }
        }).filter(Boolean).sort(),
        stylesheets: Array.from(document.querySelectorAll('link[rel~="stylesheet"][href]')).map((element) => {
          try {
            const url = new URL(element.href, location.href);
            const keys = Array.from(url.searchParams.keys()).sort();
            return url.pathname + (keys.length > 0 ? "?keys=" + keys.join(",") : "");
          } catch {
            return null;
          }
        }).filter(Boolean).sort(),
        meta: Array.from(document.querySelectorAll("meta[name], meta[property]")).map((element) => ({
          key: String(element.getAttribute("name") || element.getAttribute("property") || "").slice(0, 120),
          content: String(element.getAttribute("content") || "").slice(0, 200),
        })).filter((item) => item.key).sort((a, b) => a.key.localeCompare(b.key)),
      },
      performance: performance.getEntriesByType("resource").slice(-30).map((entry) => ({ name: entry.name.split(/[?#]/u)[0].slice(0, 240), initiatorType: entry.initiatorType, startTime: Math.round(entry.startTime), duration: Math.round(entry.duration) })),
    };
  }).catch((error) => ({ error: errorSummary(error), url: currentPageUrl() }));
  const sample = {
    seq: sampleSeq,
    ts: new Date().toISOString(),
    reason,
    pageId,
    commandId: activeCommandId,
    observerInitiated: false,
    ...digestDom(dom),
  };
  await appendJsonl(files.samples, sample);
  if (screenshotIntervalMs > 0 && Date.now() - lastScreenshotAtMs >= screenshotIntervalMs) {
    await captureScreenshot("checkpoint", "jpeg").catch((error) => appendJsonl(files.errors, eventRecord("screenshot-error", { error: errorSummary(error) })));
  }
  await writeHeartbeat({ status: terminalStatus });
}

function digestDom(dom) {
  if (dom && dom.error) return dom;
  const messages = Array.isArray(dom.messages) ? dom.messages.map((item) => ({ ...item, textHash: sha256Text(item.text || ""), textPreview: truncate(item.text || "", 160), textBytes: Buffer.byteLength(item.text || "") })) : [];
  const traceRows = Array.isArray(dom.traceRows) ? dom.traceRows.map((item) => ({ ...item, textHash: sha256Text(item.text || ""), textPreview: truncate(item.text || "", 160), textBytes: Buffer.byteLength(item.text || "") })) : [];
  const diagnostics = Array.isArray(dom.diagnostics) ? dom.diagnostics.map((item) => ({ ...item, textHash: sha256Text(item.text || ""), textPreview: truncate(item.text || "", 260), textBytes: Buffer.byteLength(item.text || "") })) : [];
  const turns = Array.isArray(dom.turns) ? dom.turns.map((item) => ({ ...item, textHash: sha256Text(item.text || ""), textPreview: truncate(item.text || "", 200), textBytes: Buffer.byteLength(item.text || "") })) : [];
  const pageProvenance = normalizePageProvenance(dom.pageProvenance, { reason: "sample", pageLoadSeq: currentPageProvenance?.pageLoadSeq ?? pageLoadSeq });
  currentPageProvenance = pageProvenance;
  return { ...dom, messages, traceRows, diagnostics, turns, pageProvenance: compactPageProvenance(pageProvenance) };
}

async function captureScreenshot(reason, imageType = "png") {
  if (!page || page.isClosed()) throw new Error("page is not available for screenshot");
  artifactSeq += 1;
  const safeReason = safeId(String(reason || "manual")).slice(0, 40) || "manual";
  const type = imageType === "jpeg" || imageType === "jpg" ? "jpeg" : "png";
  const ext = type === "jpeg" ? "jpg" : "png";
  const file = path.join(dirs.screenshots, String(sampleSeq).padStart(6, "0") + "_" + String(artifactSeq).padStart(4, "0") + "_" + safeReason + "." + ext);
  const options = type === "jpeg" ? { path: file, type: "jpeg", quality: 70, fullPage: false } : { path: file, type: "png", fullPage: false };
  await page.screenshot(options);
  const meta = await fileMeta(file);
  const artifact = { seq: artifactSeq, sampleSeq, ts: new Date().toISOString(), kind: "screenshot", reason, path: file, type, byteCount: meta.byteCount, sha256: meta.sha256, pageId, currentUrl: currentPageUrl() };
  await appendJsonl(files.artifacts, artifact);
  lastScreenshotAtMs = Date.now();
  return artifact;
}

function eventRecord(type, data) {
  return { ts: new Date().toISOString(), type, jobId, pageId, sampleSeq, commandId: activeCommandId, ...sanitize(data) };
}

function controlRecord(command, phase, detail) {
  return {
    ts: new Date().toISOString(),
    seq: commandSeq,
    phase,
    commandId: command.id,
    type: command.type,
    source: command.source || "file",
    input: commandInputSummary(command),
    beforeUrl: command.beforeUrl || null,
    afterUrl: currentPageUrl(),
    pageId,
    detail: sanitize(detail),
  };
}

function commandInputSummary(command) {
  const text = typeof command.text === "string" ? command.text : null;
  return {
    type: command.type,
    path: command.path || null,
    url: command.url ? safeUrl(command.url) : null,
    sessionId: command.sessionId || command.value || null,
    provider: command.provider || null,
    label: command.label ? truncate(command.label, 200) : null,
    textHash: text === null ? null : sha256Text(text),
    textBytes: text === null ? null : Buffer.byteLength(text),
    textPreview: null,
    valuesRedacted: true,
  };
}

async function appendJsonl(file, value) {
  await appendFile(file, JSON.stringify(sanitize(value)) + "\n", { mode: 0o600 });
}

async function fileMeta(file) {
  const [buffer, stats] = await Promise.all([readFile(file), stat(file)]);
  return { byteCount: stats.size, sha256: "sha256:" + createHash("sha256").update(buffer).digest("hex") };
}

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

function safeFrameUrl(frame) {
  try { return frame ? safeUrl(frame.url()) : null; } catch { return null; }
}

function safeUrl(value) {
  try {
    const url = new URL(String(value), baseUrl);
    for (const key of Array.from(url.searchParams.keys())) {
      if (/token|key|secret|password|auth|cookie/iu.test(key)) url.searchParams.set(key, "[redacted]");
    }
    return url.toString();
  } catch {
    return truncate(String(value || ""), 300);
  }
}

function safeUrlPath(value) {
  try {
    return new URL(String(value || ""), baseUrl).pathname;
  } catch {
    return null;
  }
}

function normalizeBaseUrl(value) {
  const raw = value || "http://127.0.0.1:3000";
  const url = new URL(raw);
  return url.origin;
}

function parseViewport(value) {
  const match = String(value).match(/^(\d{3,5})x(\d{3,5})$/u);
  return match ? { width: Number(match[1]), height: Number(match[2]) } : { width: 1440, height: 900 };
}

function positiveInteger(value, fallback) {
  const parsed = Number(value);
  return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : fallback;
}

function sha256Text(value) {
  return "sha256:" + createHash("sha256").update(String(value)).digest("hex");
}

function safeId(value) {
  return String(value || "").replace(/[^A-Za-z0-9_.-]+/gu, "-").replace(/^-+|-+$/gu, "").slice(0, 120) || "item";
}

function cssEscape(value) {
  return String(value).replace(/\\/gu, "\\\\").replace(/"/gu, "\\\"");
}

function truncate(value, limit) {
  const text = String(value || "");
  return text.length > limit ? text.slice(0, limit) + "..." : text;
}

function sanitize(value) {
  if (value === null || value === undefined) return value;
  if (typeof value === "string") return value === password ? "[redacted]" : value.replaceAll(password, "[redacted]");
  if (typeof value === "number" || typeof value === "boolean") return value;
  if (Array.isArray(value)) return value.map(sanitize);
  if (typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, item]) => /password|cookie|authorization|token|secret/iu.test(key) ? [key, "[redacted]"] : [key, sanitize(item)]));
  return String(value);
}

function errorSummary(error) {
  const summary = { name: error && error.name ? error.name : "Error", message: error && error.message ? truncate(error.message, 1000) : truncate(String(error), 1000), stackTail: error && error.stack ? truncate(error.stack, 2000) : null };
  if (error && error.webProbeAuth) summary.auth = sanitize(error.webProbeAuth);
  return summary;
}

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
}
`;
}

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

const stateDir = path.resolve(process.env.UNIDESK_WEB_OBSERVE_STATE_DIR || process.argv[2] || ".state/web-observe/manual");
const analysisDir = path.join(stateDir, "analysis");
const reportJsonPath = path.join(analysisDir, "report.json");
const reportMdPath = path.join(analysisDir, "report.md");
const jsonlReadIssues = [];
const samples = await readJsonl(path.join(stateDir, "samples.jsonl"), { compact: compactSampleForAnalysis });
const control = await readJsonl(path.join(stateDir, "control.jsonl"));
const network = await readJsonl(path.join(stateDir, "network.jsonl"));
const consoleEvents = await readJsonl(path.join(stateDir, "console.jsonl"));
const errors = await readJsonl(path.join(stateDir, "errors.jsonl"));
const artifacts = await readJsonl(path.join(stateDir, "artifacts.jsonl"));
const manifest = await readJson(path.join(stateDir, "manifest.json"));
const heartbeat = await readJson(path.join(stateDir, "heartbeat.json"));

await mkdir(analysisDir, { recursive: true, mode: 0o700 });
const transitions = buildTransitions(samples);
const sampleMetrics = buildSampleMetrics(samples, control);
const pageProvenance = buildPageProvenanceReport(samples, control, manifest);
const pagePerformance = buildPagePerformanceReport(samples, manifest);
const promptNetwork = buildPromptNetworkReport(control, network);
const runtimeAlerts = buildRuntimeAlerts(samples, control, network, consoleEvents, errors);
const runnerErrors = errors.slice(-8).map((item) => ({
  ts: item.ts ?? null,
  type: item.type ?? null,
  commandId: item.commandId ?? null,
  sampleSeq: item.sampleSeq ?? null,
  message: limitText(item.error?.message ?? item.message ?? "", 240),
  retry: item.error?.auth?.lastRetryLabel ?? null,
  retryExhausted: item.error?.auth?.retryExhausted === true,
  lastError: limitText(item.error?.auth?.lastError ?? "", 160),
}));
const findings = buildFindings(samples, control, network, errors, sampleMetrics, promptNetwork, runtimeAlerts, pagePerformance, pageProvenance);
if (jsonlReadIssues.length > 0) findings.unshift({ id: "jsonl-read-issues", severity: "red", summary: "observer analyzer hit JSONL read/parse issues", count: jsonlReadIssues.length, issues: jsonlReadIssues.slice(0, 20) });
const recentWindow = buildRecentAnalysisWindow({ samples, control, network, consoleEvents, errors, manifest });
const commandTimeline = control.filter((item) => item.phase === "completed" || item.phase === "failed").map((item) => ({ ts: item.ts, phase: item.phase, commandId: item.commandId, type: item.type, input: item.input, afterUrl: item.afterUrl }));
const report = {
  ok: findings.filter((item) => item.severity === "red").length === 0,
  command: "web-probe-observe analyze",
  generatedAt: new Date().toISOString(),
  stateDir,
  manifest: compactManifest(manifest),
  heartbeat: compactHeartbeat(heartbeat),
  counts: { samples: samples.length, control: control.length, network: network.length, console: consoleEvents.length, errors: errors.length, artifacts: artifacts.length },
  commandTimeline,
  transitions,
  sampleMetrics,
  pageProvenance,
  pagePerformance,
  promptNetwork,
  runtimeAlerts,
  runnerErrors,
  findings,
  windows: { recent: recentWindow },
  readIssues: jsonlReadIssues,
  artifactSummary: await artifactSummary(artifacts),
  safety: { offlineOnly: true, browserDriven: false, apiFetch: false, valuesRedacted: true },
};
await writeFile(reportJsonPath, JSON.stringify(report, null, 2) + "\n", { mode: 0o600 });
await writeFile(reportMdPath, renderMarkdown(report), { mode: 0o600 });
const [jsonMeta, mdMeta] = await Promise.all([fileMeta(reportJsonPath), fileMeta(reportMdPath)]);
console.log(JSON.stringify({
  ok: true,
  command: "web-probe-observe analyze",
  stateDir,
  reportJsonPath,
  reportJsonSha256: jsonMeta.sha256,
  reportMdPath,
  reportMdSha256: mdMeta.sha256,
  counts: report.counts,
  archiveSummary: {
    sampleMetrics: sampleMetrics.summary,
    pagePerformance: pagePerformance.summary,
    runtimeAlerts: runtimeAlerts.summary,
    findingCount: findings.length,
    redFindingCount: findings.filter((item) => item.severity === "red").length,
  },
  analysisWindow: recentWindow.summary,
  jsonlReadIssues: jsonlReadIssues.slice(0, 3).map((item) => ({ file: item.file, line: item.line ?? null, error: String(item.error ?? "").slice(0, 160) })),
  readIssues: jsonlReadIssues.slice(0, 3).map((item) => ({ file: item.file, line: item.line ?? null, error: String(item.error ?? "").slice(0, 160) })),
  sampleMetrics: {
    ...recentWindow.sampleMetrics.summary,
    rounds: recentWindow.sampleMetrics.rounds.slice(-8).map((item) => ({ promptIndex: item.promptIndex, promptTextHash: item.promptTextHash, sampleCount: item.sampleCount, firstSeq: item.firstSeq, lastSeq: item.lastSeq, lastTotalElapsedSeconds: item.lastTotalElapsedSeconds, lastRecentUpdateSeconds: item.lastRecentUpdateSeconds, diagnosticSamples: item.diagnosticSamples, terminalSamples: item.terminalSamples, finalTextSamples: item.finalTextSamples, turnTimingTotalElapsedZeroResetCount: item.turnTimingTotalElapsedZeroResetCount, turnTimingRecentUpdateJumpCount: item.turnTimingRecentUpdateJumpCount, turnTimingRecentUpdateMaxIncreaseSeconds: item.turnTimingRecentUpdateMaxIncreaseSeconds })),
    turnColumns: recentWindow.sampleMetrics.turnColumns.slice(-12).map((item) => ({ label: item.label, source: item.source, promptIndex: item.promptIndex, lastPromptIndex: item.lastPromptIndex, firstSeq: item.firstSeq, lastSeq: item.lastSeq, traceId: item.traceId, messageId: item.messageId })),
  },
  pageProvenance: recentWindow.pageProvenance.summary,
  pagePerformance: recentWindow.pagePerformance.summary,
  promptNetwork: recentWindow.promptNetwork.summary,
  runtimeAlerts: recentWindow.runtimeAlerts.summary,
  runnerErrors,
  httpErrorGroups: recentWindow.runtimeAlerts.networkHttpErrorsByPath.slice(0, 8).map((item) => ({
    method: item.method ?? null,
    status: item.status ?? null,
    path: item.urlPath ?? null,
    count: item.count,
    firstAt: item.firstAt ?? null,
    lastAt: item.lastAt ?? null,
    promptIndexes: Array.isArray(item.promptIndexes) ? item.promptIndexes.slice(0, 6) : [],
    failureKinds: Array.isArray(item.failureKinds) ? item.failureKinds.slice(0, 4) : [],
  })),
  requestFailedGroups: recentWindow.runtimeAlerts.networkRequestFailedByPath.slice(0, 8).map((item) => ({
    method: item.method ?? null,
    status: item.status ?? null,
    path: item.urlPath ?? null,
    count: item.count,
    firstAt: item.firstAt ?? null,
    lastAt: item.lastAt ?? null,
    promptIndexes: Array.isArray(item.promptIndexes) ? item.promptIndexes.slice(0, 6) : [],
    failureKinds: Array.isArray(item.failureKinds) ? item.failureKinds.slice(0, 4) : [],
  })),
  domDiagnosticGroups: recentWindow.runtimeAlerts.domDiagnosticsByFingerprint.slice(0, 5).map((item) => ({
    diagnosticCode: item.diagnosticCode ?? null,
    count: item.count,
    firstAt: item.firstAt,
    lastAt: item.lastAt,
    promptIndexes: Array.isArray(item.promptIndexes) ? item.promptIndexes.slice(0, 6) : [],
    traceIds: Array.isArray(item.traceIds) ? item.traceIds.slice(0, 4) : [],
    text: String(item.preview ?? item.normalizedPreview ?? item.text ?? "").slice(0, 160),
  })),
  domDiagnosticSamples: recentWindow.runtimeAlerts.domDiagnostics.slice(-8).map((item) => ({
    seq: item.seq ?? null,
    ts: item.ts ?? null,
    promptIndex: item.promptIndex ?? null,
    source: item.source ?? null,
    diagnosticCode: item.diagnosticCode ?? null,
    traceId: item.traceId ?? null,
    httpStatus: item.httpStatus ?? null,
    idleSeconds: item.idleSeconds ?? null,
    waitingFor: item.waitingFor ?? null,
    lastEventLabel: item.lastEventLabel ?? null,
    text: String(item.preview ?? item.text ?? "").slice(0, 180),
  })),
  consoleAlertGroups: recentWindow.runtimeAlerts.consoleAlertsByPath.slice(0, 8).map((item) => ({
    type: item.type ?? null,
    status: item.status ?? null,
    path: item.urlPath ?? null,
    count: item.count,
    firstAt: item.firstAt ?? null,
    lastAt: item.lastAt ?? null,
    promptIndexes: Array.isArray(item.promptIndexes) ? item.promptIndexes.slice(0, 6) : [],
    traceIds: Array.isArray(item.traceIds) ? item.traceIds.slice(0, 4) : [],
  })),
  consoleAlertSamples: recentWindow.runtimeAlerts.consoleAlerts.slice(-8).map((item) => ({
    ts: item.ts ?? null,
    promptIndex: item.promptIndex ?? null,
    type: item.type ?? null,
    status: item.status ?? null,
    path: item.urlPath ?? null,
    traceId: item.traceId ?? null,
    text: String(item.preview ?? item.text ?? "").slice(0, 180),
  })),
  turnTimingRecentUpdateJumps: recentWindow.sampleMetrics.turnTimingRecentUpdateSawtoothJumps.slice(0, 8).map((item) => ({
    columnLabel: item.columnLabel ?? item.columnId ?? null,
    promptIndex: item.promptIndex ?? null,
    traceId: item.traceId ?? null,
    fromSeq: item.fromSeq ?? null,
    toSeq: item.toSeq ?? null,
    fromTs: item.fromTs ?? null,
    toTs: item.toTs ?? null,
    fromValue: item.fromValue ?? null,
    toValue: item.toValue ?? null,
    delta: item.delta ?? null,
    sampleDeltaSeconds: item.sampleDeltaSeconds ?? null,
    allowedIncreaseSeconds: item.allowedIncreaseSeconds ?? null,
    excessiveIncreaseSeconds: item.excessiveIncreaseSeconds ?? null,
  })),
  turnTimingElapsedZeroResets: recentWindow.sampleMetrics.turnTimingElapsedZeroResets.slice(0, 8).map((item) => ({
    columnLabel: item.columnLabel ?? item.columnId ?? null,
    promptIndex: item.promptIndex ?? null,
    traceId: item.traceId ?? null,
    fromSeq: item.fromSeq ?? null,
    toSeq: item.toSeq ?? null,
    fromTs: item.fromTs ?? null,
    toTs: item.toTs ?? null,
    fromValue: item.fromValue ?? null,
    toValue: item.toValue ?? null,
    delta: item.delta ?? null,
    anomaly: item.anomaly ?? null,
  })),
  pagePerformanceSlowApi: recentWindow.pagePerformance.sameOriginApiByPath.filter((item) => item.overFiveSecondCount > 0).slice(0, 5).map((item) => ({ path: item.path, route: item.route, sampleCount: item.sampleCount, p95Ms: item.p95Ms, maxMs: item.maxMs, overFiveSecondCount: item.overFiveSecondCount })),
  findings: recentWindow.findings.slice(0, 8).map((item) => ({ kind: item.id ?? item.kind ?? item.code, severity: item.severity, count: item.count ?? item.sampleCount ?? null, summary: String(item.summary ?? item.message ?? "").slice(0, 180) })),
  valuesRedacted: true,
}));

async function readJson(file) {
  try { return JSON.parse(await readFile(file, "utf8")); } catch { return null; }
}

async function readJsonl(file, options = {}) {
  const rows = [];
  try {
    const input = createReadStream(file, { encoding: "utf8" });
    const lines = createInterface({ input, crlfDelay: Infinity });
    let lineNo = 0;
    for await (const rawLine of lines) {
      lineNo += 1;
      const line = String(rawLine || "").trim();
      if (!line) continue;
      try {
        const parsed = JSON.parse(line);
        rows.push(typeof options.compact === "function" ? options.compact(parsed) : parsed);
      } catch (error) {
        const item = { parseError: true, lineNo, rawHash: sha256(line), errorMessage: limitText(error && error.message ? error.message : String(error), 240) };
        rows.push(item);
        if (jsonlReadIssues.length < 50) jsonlReadIssues.push({ file: path.basename(file), kind: "parse-error", lineNo, rawHash: item.rawHash, errorMessage: item.errorMessage });
      }
    }
    return rows;
  } catch (error) {
    if (error && error.code === "ENOENT") return [];
    if (jsonlReadIssues.length < 50) jsonlReadIssues.push({ file: path.basename(file), kind: "read-error", code: error && error.code ? String(error.code) : null, errorMessage: limitText(error && error.message ? error.message : String(error), 240) });
    return [];
  }
}

function compactSampleForAnalysis(sample) {
  if (!sample || typeof sample !== "object") return sample;
  return {
    seq: sample.seq ?? null,
    ts: sample.ts ?? null,
    reason: sample.reason ?? null,
    pageId: sample.pageId ?? null,
    commandId: sample.commandId ?? null,
    observerInitiated: sample.observerInitiated ?? null,
    url: sample.url ?? null,
    path: sample.path ?? null,
    routeSessionId: sample.routeSessionId ?? null,
    activeSessionId: sample.activeSessionId ?? null,
    messages: compactDomItems(sample.messages),
    traceRows: compactDomItems(sample.traceRows),
    turns: compactDomItems(sample.turns),
    diagnostics: compactDomItems(sample.diagnostics),
    pageProvenance: compactSamplePageProvenance(sample.pageProvenance),
    performance: compactPerformanceItems(sample.performance)
  };
}

function compactDomItems(items) {
  if (!Array.isArray(items)) return [];
  return items.map(compactDomItem);
}

function compactDomItem(item) {
  if (!item || typeof item !== "object") return item;
  const rawText = String(item.text ?? item.textPreview ?? "");
  const preview = String(item.textPreview ?? limitText(rawText, 240));
  return {
    index: item.index ?? null,
    tag: item.tag ?? null,
    testId: item.testId ?? null,
    role: item.role ?? null,
    status: item.status ?? null,
    sessionId: item.sessionId ?? null,
    messageId: item.messageId ?? null,
    traceId: item.traceId ?? null,
    turnId: item.turnId ?? null,
    durationText: item.durationText ?? null,
    activityText: item.activityText ?? null,
    className: item.className ?? null,
    diagnosticCode: item.diagnosticCode ?? null,
    source: item.source ?? null,
    sources: Array.isArray(item.sources) ? item.sources.slice(0, 8) : undefined,
    text: limitText(rawText, 4000),
    textPreview: limitText(preview, 600),
    textHash: item.textHash ?? sha256(rawText),
    textBytes: item.textBytes ?? Buffer.byteLength(rawText)
  };
}

function compactPerformanceItems(items) {
  if (!Array.isArray(items)) return [];
  return items.map((item) => ({
    name: item?.name ?? null,
    initiatorType: item?.initiatorType ?? null,
    startTime: item?.startTime ?? null,
    duration: item?.duration ?? null,
    workerStart: item?.workerStart ?? null,
    redirectStart: item?.redirectStart ?? null,
    redirectEnd: item?.redirectEnd ?? null,
    fetchStart: item?.fetchStart ?? null,
    domainLookupStart: item?.domainLookupStart ?? null,
    domainLookupEnd: item?.domainLookupEnd ?? null,
    connectStart: item?.connectStart ?? null,
    connectEnd: item?.connectEnd ?? null,
    secureConnectionStart: item?.secureConnectionStart ?? null,
    requestStart: item?.requestStart ?? null,
    responseStart: item?.responseStart ?? null,
    responseEnd: item?.responseEnd ?? null,
    transferSize: item?.transferSize ?? null,
    encodedBodySize: item?.encodedBodySize ?? null,
    decodedBodySize: item?.decodedBodySize ?? null,
    nextHopProtocol: item?.nextHopProtocol ?? null
  }));
}

function compactSamplePageProvenance(value) {
  if (!value || typeof value !== "object") return null;
  return {
    pageLoadSeq: value.pageLoadSeq ?? null,
    reason: value.reason ?? null,
    observedAt: value.observedAt ?? null,
    urlPath: value.urlPath ?? null,
    documentReadyState: value.documentReadyState ?? null,
    timeOrigin: value.timeOrigin ?? null,
    httpStatus: value.httpStatus ?? null,
    assetFingerprint: value.assetFingerprint ?? null,
    scriptCount: value.scriptCount ?? null,
    stylesheetCount: value.stylesheetCount ?? null,
    metaCount: value.metaCount ?? null,
    scripts: Array.isArray(value.scripts) ? value.scripts.slice(0, 20) : [],
    stylesheets: Array.isArray(value.stylesheets) ? value.stylesheets.slice(0, 20) : [],
    error: value.error ?? null,
    valuesRedacted: true
  };
}

function buildFindings(samples, control, network, errors, sampleMetrics, promptNetwork, runtimeAlerts, pagePerformance, pageProvenance) {
  const findings = [];
  const commandTimes = control.filter((item) => item.phase === "completed" || item.phase === "started").map((item) => Date.parse(item.ts)).filter(Number.isFinite);
  const routeSessions = new Set(samples.map((item) => item.routeSessionId).filter(Boolean));
  const activeSessions = new Set(samples.map((item) => item.activeSessionId).filter(Boolean));
  if (routeSessions.size > 1) findings.push({ id: "session-route-changed", severity: "amber", summary: "route session changed during observation", routeSessionCount: routeSessions.size, samples: sampleRefs(samples, (item) => item.routeSessionId) });
  if (activeSessions.size > 1) findings.push({ id: "active-session-changed", severity: "amber", summary: "active session changed during observation", activeSessionCount: activeSessions.size, samples: sampleRefs(samples, (item) => item.activeSessionId) });
  const mismatches = samples.filter((item) => item.routeSessionId && item.activeSessionId && item.routeSessionId !== item.activeSessionId);
  if (mismatches.length > 0) findings.push({ id: "route-active-session-mismatch", severity: "red", summary: "routeSessionId and activeSessionId diverged", count: mismatches.length, samples: mismatches.slice(0, 10).map(ref) });
  const uncommandedChanges = [];
  const commandedPromptSeqs = new Set((sampleMetrics?.timeline ?? []).filter((item) => Number(item?.promptIndex) > 0).map((item) => item.seq).filter((seq) => seq !== null && seq !== undefined));
  for (let i = 1; i < samples.length; i += 1) {
    const prev = digestSample(samples[i - 1]);
    const next = digestSample(samples[i]);
    if (prev !== next && !commandedPromptSeqs.has(samples[i]?.seq) && !nearCommand(samples[i], commandTimes, 10000)) uncommandedChanges.push(ref(samples[i]));
  }
  if (uncommandedChanges.length > 0) findings.push({ id: "uncommanded-visible-state-change", severity: "amber", summary: "visible message/trace digest changed without a nearby command", count: uncommandedChanges.length, samples: uncommandedChanges.slice(0, 20) });
  const finalFlicker = detectFinalFlicker(samples);
  if (finalFlicker.length > 0) findings.push({ id: "final-response-flicker", severity: "red", summary: "message text digest disappeared or switched to diagnostic-like text after non-empty final text", count: finalFlicker.length, samples: finalFlicker.slice(0, 20) });
  const scrollJumps = [];
  for (let i = 1; i < samples.length; i += 1) {
    const prevY = Number(samples[i - 1]?.scroll?.y ?? 0);
    const nextY = Number(samples[i]?.scroll?.y ?? 0);
    if (prevY > 250 && nextY < 40 && !nearCommand(samples[i], commandTimes, 8000)) scrollJumps.push({ from: ref(samples[i - 1]), to: ref(samples[i]) });
  }
  if (scrollJumps.length > 0) findings.push({ id: "scroll-jump-top", severity: "amber", summary: "scroll position jumped near top without nearby command", count: scrollJumps.length, samples: scrollJumps.slice(0, 10) });
  const traceTerminal = samples.some((item) => Array.isArray(item.traceRows) && item.traceRows.some((row) => isTerminalTraceText((row.status || "") + " " + (row.textPreview || ""))));
  const traceSeen = samples.some((item) => Array.isArray(item.traceRows) && item.traceRows.length > 0);
  if (traceSeen && !traceTerminal) findings.push({ id: "trace-without-terminal", severity: "amber", summary: "trace rows were visible but no terminal status was sampled", firstTraceSample: ref(samples.find((item) => Array.isArray(item.traceRows) && item.traceRows.length > 0)) });
  const promptFailures = Array.isArray(promptNetwork?.rounds) ? promptNetwork.rounds.filter((item) => item.chatPostOk === false) : [];
  if (promptFailures.length > 0) findings.push({ id: "prompt-chat-submit-failed", severity: "red", summary: "sendPrompt command had no successful /v1/agent/chat POST response in the sampling window", count: promptFailures.length, rounds: promptFailures.slice(0, 10) });
  const elapsedZeroResets = Array.isArray(sampleMetrics?.turnTimingElapsedZeroResets) ? sampleMetrics.turnTimingElapsedZeroResets : [];
  if (elapsedZeroResets.length > 0) findings.push({ id: "turn-timing-total-elapsed-zero-reset", severity: "red", summary: "Code Agent total elapsed jumped from a non-zero value back to 0 seconds", count: elapsedZeroResets.length, samples: elapsedZeroResets.slice(0, 20) });
  const recentUpdateSawtoothJumps = Array.isArray(sampleMetrics?.turnTimingRecentUpdateSawtoothJumps)
    ? sampleMetrics.turnTimingRecentUpdateSawtoothJumps
    : Array.isArray(sampleMetrics?.turnTimingNonMonotonic)
      ? sampleMetrics.turnTimingNonMonotonic.filter((item) => item.metric === "recentUpdateSeconds" && item.anomaly === "jump")
      : [];
  if (recentUpdateSawtoothJumps.length > 0) findings.push({ id: "turn-timing-recent-update-sawtooth-jump", severity: "amber", summary: "最近更新 value jumped faster than sample interval; expected sawtooth increase-or-reset", count: recentUpdateSawtoothJumps.length, samples: recentUpdateSawtoothJumps.slice(0, 20) });
  if ((runtimeAlerts?.summary?.httpErrorCount ?? 0) > 0) findings.push({ id: "runtime-http-errors", severity: "amber", summary: "natural page requests returned HTTP error status during observation", count: runtimeAlerts.summary.httpErrorCount, groups: runtimeAlerts.networkHttpErrorsByPath.slice(0, 12) });
  if ((runtimeAlerts?.summary?.requestFailedCount ?? 0) > 0) findings.push({ id: "runtime-requestfailed", severity: "amber", summary: "browser requestfailed events were captured during observation", count: runtimeAlerts.summary.requestFailedCount, groups: runtimeAlerts.networkRequestFailedByPath.slice(0, 12) });
  if ((runtimeAlerts?.summary?.domDiagnosticSampleCount ?? 0) > 0) findings.push({ id: "runtime-dom-diagnostics", severity: "amber", summary: "diagnostic/error/warning-like text was visible in sampled DOM", count: runtimeAlerts.summary.domDiagnosticSampleCount, groupCount: runtimeAlerts.summary.domDiagnosticGroupCount ?? 0, groups: runtimeAlerts.domDiagnosticsByText.slice(0, 12), samples: runtimeAlerts.domDiagnostics.slice(0, 12) });
  if ((runtimeAlerts?.summary?.executionErrorCount ?? 0) > 0) findings.push({ id: "runtime-execution-errors", severity: "red", summary: "Workbench rendered execution failure/error rows during observation", count: runtimeAlerts.summary.executionErrorCount, groups: runtimeAlerts.runtimeExecutionErrorsByCode.slice(0, 12) });
  if ((runtimeAlerts?.summary?.consoleAlertCount ?? 0) > 0) findings.push({ id: "runtime-console-alerts", severity: "amber", summary: "browser console warning/error entries were captured during observation", count: runtimeAlerts.summary.consoleAlertCount, groups: runtimeAlerts.consoleAlertsByPath.slice(0, 12) });
  const slowApi = Array.isArray(pagePerformance?.sameOriginApiByPath) ? pagePerformance.sameOriginApiByPath.filter((item) => item.overFiveSecondCount > 0) : [];
  if (slowApi.length > 0) findings.push({ id: "page-performance-slow-same-origin-api", severity: "red", summary: "same-origin API resource timing exceeded 5s usability budget", count: slowApi.length, groups: slowApi.slice(0, 20) });
  const longLivedStreams = Array.isArray(pagePerformance?.sameOriginApiByPath) ? pagePerformance.sameOriginApiByPath.filter((item) => item.isLongLivedStream) : [];
  if (longLivedStreams.length > 0) findings.push({ id: "page-performance-long-lived-streams", severity: "info", summary: "same-origin long-lived streams are reported separately; lifetime is not treated as API load latency", count: longLivedStreams.length, groups: longLivedStreams.slice(0, 20) });
  if ((pageProvenance?.summary?.segmentCount ?? 0) > 1) findings.push({ id: "page-provenance-segments", severity: "info", summary: "observer crossed page asset provenance segments; interpret runtime findings by segment", segmentCount: pageProvenance.summary.segmentCount, segments: pageProvenance.segments.slice(0, 20) });
  const naturalApi = network.filter((item) => item.observerInitiated === false && item.type === "response" && /\/v1\/|\/auth\//u.test(String(item.url || "")));
  findings.push({ id: "natural-api-dom-lag-baseline", severity: "info", summary: "natural API responses and DOM samples are available for manual lag correlation", naturalApiResponses: naturalApi.length, sampleCount: samples.length });
  if (errors.length > 0) findings.push({ id: "browser-console-or-page-errors", severity: "amber", summary: "pageerror/runner errors were captured", count: errors.length, first: errors.slice(0, 5) });
  if (samples.length === 0) findings.push({ id: "no-samples", severity: "red", summary: "observer produced no samples" });
  return findings;
}

function buildRecentAnalysisWindow({ samples, control, network, consoleEvents, errors, manifest }) {
  const latestSampleMs = latestTimestampMs(samples);
  const windowMs = 5 * 60 * 1000;
  const fromMs = Number.isFinite(latestSampleMs) ? latestSampleMs - windowMs : Number.NEGATIVE_INFINITY;
  const toMs = Number.isFinite(latestSampleMs) ? latestSampleMs : Number.POSITIVE_INFINITY;
  const inWindow = (item) => {
    const tsMs = Date.parse(item?.ts);
    return Number.isFinite(tsMs) && tsMs >= fromMs && tsMs <= toMs;
  };
  const windowSamples = samples.filter(inWindow);
  const windowControl = control.filter(inWindow);
  const windowNetwork = network.filter(inWindow);
  const windowConsole = consoleEvents.filter(inWindow);
  const windowErrors = errors.filter(inWindow);
  const sampleMetrics = buildSampleMetrics(windowSamples, control);
  const pageProvenance = buildPageProvenanceReport(windowSamples, windowControl, manifest);
  const pagePerformance = buildPagePerformanceReport(windowSamples, manifest);
  const promptNetwork = buildPromptNetworkReport(windowControl, windowNetwork);
  const runtimeAlerts = buildRuntimeAlerts(windowSamples, control, windowNetwork, windowConsole, windowErrors);
  const findings = buildFindings(windowSamples, control, windowNetwork, windowErrors, sampleMetrics, promptNetwork, runtimeAlerts, pagePerformance, pageProvenance);
  return {
    summary: {
      name: "recent-5m",
      windowMs,
      fromAt: Number.isFinite(fromMs) ? new Date(fromMs).toISOString() : null,
      toAt: Number.isFinite(toMs) ? new Date(toMs).toISOString() : null,
      samples: windowSamples.length,
      control: windowControl.length,
      network: windowNetwork.length,
      console: windowConsole.length,
      errors: windowErrors.length,
      valuesRedacted: true
    },
    sampleMetrics,
    pageProvenance,
    pagePerformance,
    promptNetwork,
    runtimeAlerts,
    findings,
    valuesRedacted: true
  };
}

function latestTimestampMs(items) {
  let latest = Number.NEGATIVE_INFINITY;
  for (const item of items || []) {
    const tsMs = Date.parse(item?.ts);
    if (Number.isFinite(tsMs) && tsMs > latest) latest = tsMs;
  }
  return latest;
}

function buildPageProvenanceReport(samples, control, manifest) {
  const groups = new Map();
  for (const sample of samples) {
    const provenance = sample?.pageProvenance;
    if (!provenance) continue;
    const key = provenance.assetFingerprint || "unknown";
    const group = groups.get(key) || {
      assetFingerprint: provenance.assetFingerprint || null,
      pageLoadSeqs: [],
      sampleCount: 0,
      firstSeq: sample.seq ?? null,
      lastSeq: sample.seq ?? null,
      firstAt: sample.ts ?? null,
      lastAt: sample.ts ?? null,
      urlPaths: [],
      scriptCount: provenance.scriptCount ?? null,
      stylesheetCount: provenance.stylesheetCount ?? null,
      metaCount: provenance.metaCount ?? null,
      scripts: Array.isArray(provenance.scripts) ? provenance.scripts.slice(0, 12) : [],
      stylesheets: Array.isArray(provenance.stylesheets) ? provenance.stylesheets.slice(0, 12) : [],
      valuesRedacted: true
    };
    group.sampleCount += 1;
    group.lastSeq = sample.seq ?? null;
    group.lastAt = sample.ts ?? null;
    if (provenance.pageLoadSeq !== null && provenance.pageLoadSeq !== undefined && !group.pageLoadSeqs.includes(provenance.pageLoadSeq)) group.pageLoadSeqs.push(provenance.pageLoadSeq);
    if (provenance.urlPath && !group.urlPaths.includes(provenance.urlPath)) group.urlPaths.push(provenance.urlPath);
    groups.set(key, group);
  }
  const segments = Array.from(groups.values()).sort((a, b) => Number(a.firstSeq ?? 0) - Number(b.firstSeq ?? 0));
  const controlSegments = control
    .filter((item) => item.type === "page-provenance" || item?.pageProvenance)
    .map((item) => ({
      ts: item.ts ?? null,
      reason: item.reason ?? item.detail?.reason ?? null,
      httpStatus: item.httpStatus ?? item.detail?.httpStatus ?? null,
      pageProvenance: item.pageProvenance ?? item.detail?.pageProvenance ?? null,
    }))
    .slice(0, 80);
  return {
    summary: {
      segmentCount: segments.length,
      sampleCount: segments.reduce((sum, item) => sum + item.sampleCount, 0),
      manifestFingerprint: manifest?.pageProvenance?.assetFingerprint ?? null,
      controlSegmentCount: controlSegments.length
    },
    segments,
    controlSegments,
    valuesRedacted: true
  };
}

function buildPagePerformanceReport(samples, manifest) {
  const base = manifest?.baseUrl || "http://invalid.local";
  const seen = new Set();
  const groups = new Map();
  const sampleTimes = samples.map((sample) => Date.parse(sample?.ts || "")).filter(Number.isFinite);
  const windowStartMs = sampleTimes.length > 0 ? Math.min(...sampleTimes) : null;
  const windowEndMs = sampleTimes.length > 0 ? Math.max(...sampleTimes) : null;
  for (const sample of samples) {
    const entries = Array.isArray(sample?.performance) ? sample.performance : [];
    for (const entry of entries) {
      const durationMs = Number(entry?.duration);
      if (!Number.isFinite(durationMs) || durationMs < 0) continue;
      const entryCompletedMs = performanceEntryCompletedEpochMs(sample, entry);
      if (windowStartMs !== null && entryCompletedMs !== null && entryCompletedMs < windowStartMs) continue;
      if (windowEndMs !== null && entryCompletedMs !== null && entryCompletedMs > windowEndMs + 1000) continue;
      const entryTs = entryCompletedMs === null ? (sample.ts ?? null) : new Date(entryCompletedMs).toISOString();
      const parsed = parsePerformanceUrl(entry?.name, base);
      if (!parsed.sameOrigin || !isApiLikePath(parsed.path)) continue;
      const normalizedPath = normalizeApiPath(parsed.path);
      const routeKind = classifyApiPerformanceRoute(normalizedPath, entry);
      const isLongLivedStream = routeKind === "same-origin-api-stream";
      const streamOpenMs = streamOpenLatencyMs(entry);
      const dedupeKey = [parsed.path, entry.initiatorType || "", sample?.pageProvenance?.pageLoadSeq ?? "", sample?.pageProvenance?.timeOrigin ?? "", entry.startTime ?? "", Math.round(durationMs)].join("|");
      if (seen.has(dedupeKey)) continue;
      seen.add(dedupeKey);
      const group = groups.get(normalizedPath) || {
        routeKind,
        path: normalizedPath,
        isLongLivedStream,
        budgetMetric: isLongLivedStream ? "streamOpenMs" : "durationMs",
        rawPathSamples: [],
        sampleCount: 0,
        durationsMs: [],
        streamOpenDurationsMs: [],
        overFiveSecondCount: 0,
        streamLifetimeOverFiveSecondCount: 0,
        streamOpenOverFiveSecondCount: 0,
        firstAt: entryTs,
        lastAt: entryTs,
        firstSeq: sample.seq ?? null,
        lastSeq: sample.seq ?? null,
        initiatorTypes: [],
        pageAssetFingerprints: [],
        slowSamples: [],
        valuesRedacted: true
      };
      group.sampleCount += 1;
      group.durationsMs.push(durationMs);
      let overBudget = false;
      if (isLongLivedStream) {
        if (durationMs > 5000) group.streamLifetimeOverFiveSecondCount += 1;
        if (streamOpenMs !== null) {
          group.streamOpenDurationsMs.push(streamOpenMs);
          if (streamOpenMs > 5000) {
            group.streamOpenOverFiveSecondCount += 1;
            group.overFiveSecondCount += 1;
            overBudget = true;
          }
        }
      } else if (durationMs > 5000) {
        group.overFiveSecondCount += 1;
        overBudget = true;
      }
      if (overBudget && group.slowSamples.length < 80) group.slowSamples.push(compactPagePerformanceSlowSample({ sample, entry, entryTs, normalizedPath, rawPath: parsed.path, durationMs, streamOpenMs }));
      group.lastAt = entryTs;
      group.lastSeq = sample.seq ?? null;
      if (parsed.path && !group.rawPathSamples.includes(parsed.path)) group.rawPathSamples.push(parsed.path);
      if (entry.initiatorType && !group.initiatorTypes.includes(entry.initiatorType)) group.initiatorTypes.push(entry.initiatorType);
      const assetFingerprint = sample?.pageProvenance?.assetFingerprint;
      if (assetFingerprint && !group.pageAssetFingerprints.includes(assetFingerprint)) group.pageAssetFingerprints.push(assetFingerprint);
      groups.set(normalizedPath, group);
    }
  }
  const sameOriginApiByPath = Array.from(groups.values()).map((group) => {
    const durations = group.durationsMs.slice().sort((a, b) => a - b);
    const streamOpenDurations = group.streamOpenDurationsMs.slice().sort((a, b) => a - b);
    return {
      routeKind: group.routeKind,
      path: group.path,
      isLongLivedStream: group.isLongLivedStream === true,
      budgetMetric: group.budgetMetric,
      sampleCount: group.sampleCount,
      p50Ms: percentile(durations, 50),
      p75Ms: percentile(durations, 75),
      p95Ms: percentile(durations, 95),
      maxMs: durations.length > 0 ? durations[durations.length - 1] : null,
      streamOpenSampleCount: streamOpenDurations.length,
      streamOpenP50Ms: percentile(streamOpenDurations, 50),
      streamOpenP75Ms: percentile(streamOpenDurations, 75),
      streamOpenP95Ms: percentile(streamOpenDurations, 95),
      streamOpenMaxMs: streamOpenDurations.length > 0 ? streamOpenDurations[streamOpenDurations.length - 1] : null,
      streamOpenOverFiveSecondCount: group.streamOpenOverFiveSecondCount,
      streamLifetimeOverFiveSecondCount: group.streamLifetimeOverFiveSecondCount,
      overFiveSecondCount: group.overFiveSecondCount,
      overFiveSecondRatio: group.sampleCount > 0 ? Number((group.overFiveSecondCount / group.sampleCount).toFixed(3)) : 0,
      firstAt: group.firstAt,
      lastAt: group.lastAt,
      firstSeq: group.firstSeq,
      lastSeq: group.lastSeq,
      initiatorTypes: group.initiatorTypes,
      rawPathSamples: group.rawPathSamples.slice(0, 8),
      pageAssetFingerprints: group.pageAssetFingerprints.slice(0, 8),
      slowSamples: group.slowSamples
        .slice()
        .sort((a, b) => Number(b.durationMs ?? 0) - Number(a.durationMs ?? 0))
        .slice(0, 12),
      valuesRedacted: true
    };
  }).sort((a, b) => (b.overFiveSecondCount - a.overFiveSecondCount) || (Number(b.p95Ms ?? 0) - Number(a.p95Ms ?? 0)) || a.path.localeCompare(b.path));
  const slow = sameOriginApiByPath.filter((item) => item.overFiveSecondCount > 0);
  const longLivedStreams = sameOriginApiByPath.filter((item) => item.isLongLivedStream);
  const budgetP95Values = sameOriginApiByPath
    .map((item) => Number(item.isLongLivedStream ? (item.streamOpenP95Ms ?? 0) : (item.p95Ms ?? 0)))
    .filter((value) => Number.isFinite(value));
  return {
    summary: {
      budgetMs: 5000,
      sameOriginApiPathCount: sameOriginApiByPath.length,
      sameOriginApiSampleCount: sameOriginApiByPath.reduce((sum, item) => sum + item.sampleCount, 0),
      longLivedStreamPathCount: longLivedStreams.length,
      longLivedStreamSampleCount: longLivedStreams.reduce((sum, item) => sum + item.sampleCount, 0),
      slowPathCount: slow.length,
      slowSampleCount: slow.reduce((sum, item) => sum + item.overFiveSecondCount, 0),
      worstP95Ms: budgetP95Values.length > 0 ? Math.max(...budgetP95Values) : null,
      valuesRedacted: true
    },
    sameOriginApiByPath,
    valuesRedacted: true
  };
}

function performanceEntryCompletedEpochMs(sample, entry) {
  const origin = Number(sample?.pageProvenance?.timeOrigin);
  const responseEnd = Number(entry?.responseEnd);
  const startTime = Number(entry?.startTime);
  const offset = Number.isFinite(responseEnd) && responseEnd > 0 ? responseEnd : startTime;
  if (Number.isFinite(origin) && origin > 0 && Number.isFinite(offset) && offset >= 0) return Math.round(origin + offset);
  const sampleTs = Date.parse(sample?.ts || "");
  return Number.isFinite(sampleTs) ? sampleTs : null;
}

function compactPagePerformanceSlowSample({ sample, entry, entryTs, normalizedPath, rawPath, durationMs, streamOpenMs }) {
  return {
    ts: entryTs ?? sample?.ts ?? null,
    sampleTs: sample?.ts ?? null,
    seq: sample?.seq ?? null,
    path: normalizedPath ?? null,
    rawPath: rawPath ?? null,
    initiatorType: entry?.initiatorType ?? null,
    durationMs: roundFinite(durationMs),
    startTimeMs: roundFinite(entry?.startTime),
    fetchStartMs: roundFinite(entry?.fetchStart),
    requestStartMs: roundFinite(entry?.requestStart),
    responseStartMs: roundFinite(entry?.responseStart),
    responseEndMs: roundFinite(entry?.responseEnd),
    streamOpenMs: roundFinite(streamOpenMs),
    dnsMs: phaseDeltaMs(entry, "domainLookupEnd", "domainLookupStart"),
    tcpMs: phaseDeltaMs(entry, "connectEnd", "connectStart"),
    tlsStartMs: roundFinite(entry?.secureConnectionStart),
    requestToResponseStartMs: phaseDeltaMs(entry, "responseStart", "requestStart"),
    responseTransferMs: phaseDeltaMs(entry, "responseEnd", "responseStart"),
    transferSize: Number.isFinite(Number(entry?.transferSize)) ? Number(entry.transferSize) : null,
    encodedBodySize: Number.isFinite(Number(entry?.encodedBodySize)) ? Number(entry.encodedBodySize) : null,
    decodedBodySize: Number.isFinite(Number(entry?.decodedBodySize)) ? Number(entry.decodedBodySize) : null,
    nextHopProtocol: entry?.nextHopProtocol ?? null,
    valuesRedacted: true
  };
}

function phaseDeltaMs(entry, endKey, startKey) {
  const end = Number(entry?.[endKey]);
  const start = Number(entry?.[startKey]);
  if (!Number.isFinite(end) || !Number.isFinite(start) || end <= 0 || start < 0 || end < start) return null;
  return Math.round(end - start);
}

function roundFinite(value) {
  const numeric = Number(value);
  return Number.isFinite(numeric) ? Math.round(numeric) : null;
}

function classifyApiPerformanceRoute(normalizedPath, entry = {}) {
  if (normalizedPath === "/v1/workbench/events") return "same-origin-api-stream";
  if (String(entry?.initiatorType ?? "").toLowerCase() === "eventsource") return "same-origin-api-stream";
  return "same-origin-api";
}

function streamOpenLatencyMs(entry = {}) {
  const responseStart = Number(entry?.responseStart);
  const startTime = Number(entry?.startTime);
  if (!Number.isFinite(responseStart) || responseStart <= 0) return null;
  if (!Number.isFinite(startTime) || startTime < 0) return Math.max(0, responseStart);
  if (responseStart < startTime) return null;
  return Math.max(0, responseStart - startTime);
}

function parsePerformanceUrl(value, base) {
  try {
    const url = new URL(String(value || ""), base);
    const origin = new URL(String(base || "http://invalid.local")).origin;
    return { sameOrigin: url.origin === origin, path: url.pathname };
  } catch {
    return { sameOrigin: false, path: "-" };
  }
}

function isApiLikePath(path) {
  return /^\/(?:v1(?:\/|$)|auth(?:\/|$)|health(?:\/|$))/u.test(String(path || ""));
}

function normalizeApiPath(path) {
  return String(path || "-")
    .replace(/\/v1\/workbench\/sessions\/ses_[^/]+/gu, "/v1/workbench/sessions/:id")
    .replace(/\/v1\/workbench\/turns\/trc_[^/]+/gu, "/v1/workbench/turns/:traceId")
    .replace(/\/v1\/workbench\/traces\/trc_[^/]+/gu, "/v1/workbench/traces/:traceId")
    .replace(/\/v1\/workbench\/sessions\/[0-9a-f-]{12,}/giu, "/v1/workbench/sessions/:id")
    .replace(/\/v1\/[^/]+\/[0-9a-f-]{16,}(?=\/|$)/giu, (match) => match.replace(/\/[0-9a-f-]{16,}$/iu, "/:id"));
}

function percentile(sortedValues, percentileValue) {
  if (!Array.isArray(sortedValues) || sortedValues.length === 0) return null;
  if (sortedValues.length === 1) return Math.round(sortedValues[0]);
  const rank = (percentileValue / 100) * (sortedValues.length - 1);
  const lower = Math.floor(rank);
  const upper = Math.ceil(rank);
  if (lower === upper) return Math.round(sortedValues[lower]);
  const weight = rank - lower;
  return Math.round(sortedValues[lower] * (1 - weight) + sortedValues[upper] * weight);
}

function buildPromptNetworkReport(control, network) {
  const promptsById = new Map();
  for (const item of control) {
    if (item?.type !== "sendPrompt" || !item.commandId) continue;
    const existing = promptsById.get(item.commandId) || {
      commandId: item.commandId,
      promptIndex: promptsById.size + 1,
      promptTextHash: item.input?.textHash ?? null,
      promptTextBytes: item.input?.textBytes ?? null,
      startedAt: null,
      completedAt: null,
      failedAt: null,
      phase: null
    };
    if (!existing.promptTextHash && item.input?.textHash) existing.promptTextHash = item.input.textHash;
    if (!existing.promptTextBytes && item.input?.textBytes) existing.promptTextBytes = item.input.textBytes;
    if (item.phase === "started") existing.startedAt = item.ts ?? existing.startedAt;
    if (item.phase === "completed") existing.completedAt = item.ts ?? existing.completedAt;
    if (item.phase === "failed") existing.failedAt = item.ts ?? existing.failedAt;
    existing.phase = item.phase ?? existing.phase;
    promptsById.set(item.commandId, existing);
  }
  const prompts = Array.from(promptsById.values()).sort((a, b) => Date.parse(a.startedAt || a.completedAt || a.failedAt || "") - Date.parse(b.startedAt || b.completedAt || b.failedAt || ""));
  prompts.forEach((item, index) => { item.promptIndex = index + 1; });
  const chatEvents = network
    .filter((item) => String(item?.method || "").toUpperCase() === "POST" && /\/v1\/agent\/chat(?:\?|$)/u.test(String(item?.url || "")))
    .map((item) => {
      const failureText = item.failureKind ?? item.failure ?? item.errorText ?? null;
      return {
        ts: item.ts ?? null,
        tsMs: Date.parse(item.ts),
        type: item.type ?? null,
        status: Number.isFinite(Number(item.status)) ? Number(item.status) : null,
        commandId: item.commandId ?? null,
        urlPath: urlPath(item.url),
        failureKind: failureText ? String(failureText) : null,
        errorTextHash: failureText ? sha256(failureText) : null
      };
    })
    .filter((item) => Number.isFinite(item.tsMs))
    .sort((a, b) => a.tsMs - b.tsMs);
  const rounds = prompts.map((prompt) => {
    const startMs = Date.parse(prompt.startedAt || prompt.completedAt || prompt.failedAt || "");
    const endAnchorMs = Date.parse(prompt.completedAt || prompt.failedAt || prompt.startedAt || "");
    const fromMs = Number.isFinite(startMs) ? startMs - 3000 : Number.NEGATIVE_INFINITY;
    const toMs = Number.isFinite(endAnchorMs) ? endAnchorMs + 30000 : Number.POSITIVE_INFINITY;
    const events = chatEvents.filter((event) => {
      if (event.commandId && prompt.commandId && event.commandId === prompt.commandId) return true;
      return event.tsMs >= fromMs && event.tsMs <= toMs;
    });
    const responses = events.filter((event) => event.type === "response");
    const failures = events.filter((event) => event.type === "requestfailed");
    const responseStatuses = responses.map((event) => event.status).filter((status) => status !== null);
    const chatPostOk = responseStatuses.some((status) => status >= 200 && status < 300) && failures.length === 0;
    const failureKind = chatPostOk
      ? null
      : failures.length > 0
        ? "requestfailed"
        : responseStatuses.length === 0
          ? "missing-response"
          : "http-status";
    return {
      promptIndex: prompt.promptIndex,
      promptCommandId: prompt.commandId,
      promptTextHash: prompt.promptTextHash,
      promptTextBytes: prompt.promptTextBytes,
      startedAt: prompt.startedAt,
      completedAt: prompt.completedAt,
      failedAt: prompt.failedAt,
      chatPostOk,
      failureKind,
      requestCount: events.filter((event) => event.type === "request").length,
      responseCount: responses.length,
      requestFailedCount: failures.length,
      responseStatuses,
      firstChatEventAt: events[0]?.ts ?? null,
      lastChatEventAt: events[events.length - 1]?.ts ?? null,
      events: events.slice(0, 12).map((event) => ({ ts: event.ts, type: event.type, status: event.status, urlPath: event.urlPath, failureKind: event.failureKind, errorTextHash: event.errorTextHash }))
    };
  });
  return {
    summary: {
      promptCount: rounds.length,
      chatPostOk: rounds.filter((item) => item.chatPostOk === true).length,
      chatPostFailed: rounds.filter((item) => item.chatPostOk === false).length,
      chatPostMissing: rounds.filter((item) => item.failureKind === "missing-response").length
    },
    rounds
  };
}

function parseDomDiagnosticSummary(text) {
  const value = String(text || "");
  const traceMatch = value.match(/\b(?:trace_id=)?(trc_[A-Za-z0-9_-]+|[a-f0-9]{16,64})\b/iu);
  const httpStatusMatch = value.match(/\bHTTP\s+([1-5][0-9]{2})\b/iu);
  const idleMatch = value.match(/\bidle\s+(\d+)s\b/iu);
  const waitingForMatch = value.match(/\bwaitingFor=([^\s；;，,)]+)/iu);
  const lastEventLabelMatch = value.match(/\blastEventLabel=([^\s；;，,)]+)/iu);
  const diagnosticCode = httpStatusMatch
    ? "http-" + httpStatusMatch[1]
    : /turn\s*超过|无新活动/iu.test(value)
      ? "turn-idle-no-activity"
      : /Failed to fetch/iu.test(value)
        ? "failed-to-fetch"
        : "diagnostic";
  return {
    diagnosticCode,
    traceId: traceMatch?.[1] || null,
    httpStatus: httpStatusMatch ? Number(httpStatusMatch[1]) : null,
    idleSeconds: idleMatch ? Number(idleMatch[1]) : null,
    waitingFor: waitingForMatch?.[1] || null,
    lastEventLabel: lastEventLabelMatch?.[1] || null
  };
}

function isDomDiagnosticSampleText(text) {
  const value = String(text || "").replace(/\s+/g, " ").trim();
  if (!value) return false;
  const strongDiagnostic = [
    /\bHTTP\s+[45][0-9]{2}\b(?:[\s\S]{0,120}\btrace_id=|\b)/iu,
    /\btrace_id=(?:trc_[A-Za-z0-9_-]+|[a-f0-9]{16,64})\b/iu,
    /workbench\s+turn\s*超过\s*\d+ms\s*无新活动/iu,
    /\bturn\s*超过\b[\s\S]{0,120}\b无新活动\b/iu,
    /\bprojection-resume:sync-failed\b/iu,
    /\bAgentRun\s+GET\b[\s\S]*\/result\b[\s\S]*timed out after\s+\d+ms\b/iu,
    /\bFailed to fetch\b/iu,
    /\bFailed to load resource\b[\s\S]{0,180}\bstatus of\s+[45][0-9]{2}\b/iu,
    /\bserver responded with a status of\s+[45][0-9]{2}\b/iu,
    /Code Agent\b[\s\S]{0,120}(?:无法连接上游|请求已结束)/iu
  ].some((pattern) => pattern.test(value));
  if (!strongDiagnostic) return false;
  const looksLikeToolStdout = /\b(?:stdout|stderr):/iu.test(value)
    && /(?:\becho\s+["']?===|\bnode\s+|\.tspy\b|tspy\/|===\s*[A-Za-z0-9_.-]+\s*===)/iu.test(value);
  if (!looksLikeToolStdout) return true;
  return /\b(?:trace_id=|HTTP\s+[45][0-9]{2}|workbench\s+turn\s*超过|projection-resume:sync-failed|Failed to fetch|Failed to load resource|server responded with a status of\s+[45][0-9]{2}|Code Agent\b[\s\S]{0,120}(?:无法连接上游|请求已结束))\b/iu.test(value);
}

function buildRuntimeAlerts(samples, control, network, consoleEvents, errors) {
  const promptTimes = control
    .filter((item) => item.type === "sendPrompt" && item.phase === "completed")
    .map((item) => Date.parse(item.ts))
    .filter(Number.isFinite)
    .sort((a, b) => a - b);
  const naturalNetwork = network.filter((item) => item?.observerInitiated !== true);
  const httpErrors = naturalNetwork
    .filter((item) => item?.type === "response" && Number(item.status) >= 400)
    .map((item) => networkAlertEvent(item, promptTimes));
  const requestFailed = naturalNetwork
    .filter((item) => item?.type === "requestfailed")
    .map((item) => networkAlertEvent(item, promptTimes));
  const domDiagnostics = [];
  const executionErrors = [];
  const baselineExecutionErrors = [];
  const firstPromptMs = promptTimes.length > 0 ? promptTimes[0] : Infinity;
  const firstSeenExecutionErrorMs = new Map();
  for (const sample of samples) {
    const tsMs = Date.parse(sample?.ts);
    const promptIndex = Number.isFinite(tsMs) ? latestPromptIndex(promptTimes, tsMs) : 0;
    if (Array.isArray(sample?.diagnostics)) {
      for (const diagnostic of sample.diagnostics.slice(0, 12)) {
        const text = diagnostic?.textPreview || diagnostic?.text || "";
        if (!String(text).trim()) continue;
        const parsedDiagnostic = parseDomDiagnosticSummary(text);
        domDiagnostics.push({
          seq: sample.seq ?? null,
          ts: sample.ts ?? null,
          promptIndex,
          source: "diagnostic-node",
          className: diagnostic.className ?? null,
          diagnosticCode: diagnostic.diagnosticCode ?? parsedDiagnostic.diagnosticCode,
          traceId: diagnostic.traceId ?? parsedDiagnostic.traceId,
          httpStatus: diagnostic.httpStatus ?? parsedDiagnostic.httpStatus,
          idleSeconds: diagnostic.idleSeconds ?? parsedDiagnostic.idleSeconds,
          waitingFor: diagnostic.waitingFor ?? parsedDiagnostic.waitingFor,
          lastEventLabel: diagnostic.lastEventLabel ?? parsedDiagnostic.lastEventLabel,
          compact: diagnostic.compact ?? null,
          expanded: diagnostic.expanded ?? null,
          routeSessionId: sample.routeSessionId ?? null,
          activeSessionId: sample.activeSessionId ?? null,
          textHash: diagnostic.textHash || sha256(text),
          preview: limitText(text, 260)
        });
      }
    }
    const texts = sampleTexts(sample).filter(isDomDiagnosticSampleText);
    for (const text of texts.slice(0, 4)) {
      const parsedDiagnostic = parseDomDiagnosticSummary(text);
      domDiagnostics.push({
        seq: sample.seq ?? null,
        ts: sample.ts ?? null,
        promptIndex,
        source: "sample-text",
        diagnosticCode: parsedDiagnostic.diagnosticCode,
        traceId: parsedDiagnostic.traceId,
        httpStatus: parsedDiagnostic.httpStatus,
        idleSeconds: parsedDiagnostic.idleSeconds,
        waitingFor: parsedDiagnostic.waitingFor,
        lastEventLabel: parsedDiagnostic.lastEventLabel,
        routeSessionId: sample.routeSessionId ?? null,
        activeSessionId: sample.activeSessionId ?? null,
        textHash: sha256(text),
        preview: limitText(text, 220)
      });
    }
    const seenExecutionErrors = new Set();
    for (const candidate of sampleExecutionErrorCandidates(sample)) {
      const parsed = parseExecutionErrorText(candidate.text);
      if (!parsed) continue;
      const textHash = sha256(candidate.text);
      const dedupeKey = [candidate.source, candidate.traceId || "-", parsed.backend || "-", parsed.code || "-", parsed.status || "-", textHash].join("|");
      if (seenExecutionErrors.has(dedupeKey)) continue;
      seenExecutionErrors.add(dedupeKey);
      const firstSeenMs = firstSeenExecutionErrorMs.has(dedupeKey) ? firstSeenExecutionErrorMs.get(dedupeKey) : tsMs;
      if (!firstSeenExecutionErrorMs.has(dedupeKey) && Number.isFinite(tsMs)) firstSeenExecutionErrorMs.set(dedupeKey, tsMs);
      const baseline = Number.isFinite(firstSeenMs) && firstSeenMs < firstPromptMs;
      const event = {
        seq: sample.seq ?? null,
        ts: sample.ts ?? null,
        promptIndex,
        baseline,
        firstSeenAt: Number.isFinite(firstSeenMs) ? new Date(firstSeenMs).toISOString() : null,
        source: candidate.source,
        backend: parsed.backend,
        status: parsed.status,
        code: parsed.code,
        rawCode: parsed.rawCode,
        totalSeconds: parsed.totalSeconds,
        traceId: candidate.traceId || parsed.traceId || null,
        messageId: candidate.messageId || null,
        routeSessionId: sample.routeSessionId ?? null,
        activeSessionId: sample.activeSessionId ?? null,
        textHash,
        preview: limitText(candidate.text, 260)
      };
      if (baseline) baselineExecutionErrors.push(event);
      else executionErrors.push(event);
      domDiagnostics.push({
        seq: sample.seq ?? null,
        ts: sample.ts ?? null,
        promptIndex,
        source: "execution-row",
        diagnosticCode: parsed.rawCode || parsed.code || "execution-error",
        traceId: candidate.traceId || parsed.traceId || null,
        routeSessionId: sample.routeSessionId ?? null,
        activeSessionId: sample.activeSessionId ?? null,
        textHash,
        preview: limitText(candidate.text, 220)
      });
    }
  }
  const consoleAlerts = consoleEvents
    .filter((item) => /error|warning|warn|assert/iu.test(String(item?.type || "")) || isDiagnosticText(item?.text))
    .map((item) => consoleAlertEvent(item, promptTimes));
  const pageErrors = errors.map((item) => ({
    ts: item.ts ?? null,
    promptIndex: promptIndexForTs(promptTimes, item.ts),
    type: item.type ?? null,
    errorName: item.error?.name ?? item.name ?? null,
    messageHash: item.error?.message ? sha256(item.error.message) : item.message ? sha256(item.message) : null,
    preview: limitText(item.error?.message || item.message || item.error || "", 220)
  }));
  return {
    summary: {
      httpErrorCount: httpErrors.length,
      requestFailedCount: requestFailed.length,
      domDiagnosticSampleCount: domDiagnostics.length,
      domDiagnosticGroupCount: groupDomDiagnostics(domDiagnostics).length,
      executionErrorCount: executionErrors.length,
      baselineExecutionErrorCount: baselineExecutionErrors.length,
      consoleAlertCount: consoleAlerts.length,
      pageErrorCount: pageErrors.length,
      networkErrorGroupCount: groupNetworkAlerts(httpErrors).length,
      requestFailedGroupCount: groupNetworkAlerts(requestFailed).length,
      executionErrorGroupCount: groupExecutionErrors(executionErrors).length,
      baselineExecutionErrorGroupCount: groupExecutionErrors(baselineExecutionErrors).length,
      consoleAlertGroupCount: groupConsoleAlerts(consoleAlerts).length
    },
    networkHttpErrorsByPath: groupNetworkAlerts(httpErrors),
    networkRequestFailedByPath: groupNetworkAlerts(requestFailed),
    domDiagnostics: domDiagnostics.slice(-80),
    domDiagnosticsByText: groupDomDiagnostics(domDiagnostics),
    domDiagnosticsByFingerprint: groupDomDiagnostics(domDiagnostics).slice(0, 80),
    runtimeExecutionErrors: executionErrors.slice(0, 120),
    runtimeExecutionErrorsByCode: groupExecutionErrors(executionErrors),
    baselineRuntimeExecutionErrors: baselineExecutionErrors.slice(0, 80),
    baselineRuntimeExecutionErrorsByCode: groupExecutionErrors(baselineExecutionErrors),
    consoleAlerts: consoleAlerts.slice(0, 80),
    consoleAlertsByPath: groupConsoleAlerts(consoleAlerts),
    pageErrors: pageErrors.slice(0, 40)
  };
}

function groupDomDiagnostics(events) {
  const groups = new Map();
  for (const item of events || []) {
    const preview = String(item?.preview || "").trim();
    if (!isReportableDomDiagnostic(item, preview)) continue;
    const normalizedPreview = normalizeDiagnosticPreview(preview);
    const key = [
      item?.diagnosticCode || "",
      normalizedPreview
    ].join("|");
    const existing = groups.get(key) || {
      source: item?.source || null,
      sources: new Set(),
      diagnosticCode: item?.diagnosticCode || null,
      textHash: item?.textHash || null,
      normalizedPreview,
      preview,
      count: 0,
      firstAt: item?.ts || null,
      lastAt: item?.ts || null,
      promptIndexes: new Set(),
      traceIds: new Set(),
      sampleSeqs: []
    };
    if (item?.source) existing.sources.add(String(item.source));
    existing.count += 1;
    existing.firstAt = minIso(existing.firstAt, item?.ts || null);
    existing.lastAt = maxIso(existing.lastAt, item?.ts || null);
    if (Number.isFinite(Number(item?.promptIndex))) existing.promptIndexes.add(Number(item.promptIndex));
    for (const traceId of extractDiagnosticTraceIds(item, preview)) existing.traceIds.add(traceId);
    if (existing.sampleSeqs.length < 12 && item?.seq !== undefined && item?.seq !== null) existing.sampleSeqs.push(item.seq);
    groups.set(key, existing);
  }
  return Array.from(groups.values())
    .map((item) => ({
      source: item.source,
      sources: Array.from(item.sources).sort(),
      diagnosticCode: item.diagnosticCode,
      textHash: item.textHash,
      normalizedPreview: item.normalizedPreview,
      preview: item.preview,
      count: item.count,
      firstAt: item.firstAt,
      lastAt: item.lastAt,
      promptIndexes: Array.from(item.promptIndexes).sort((a, b) => a - b),
      traceIds: Array.from(item.traceIds).sort(),
      sampleSeqs: item.sampleSeqs
    }))
    .sort((a, b) => (b.count - a.count) || String(a.firstAt || "").localeCompare(String(b.firstAt || "")));
}

function isReportableDomDiagnostic(item, preview) {
  if (item?.source === "diagnostic-node" || item?.source === "execution-row") return true;
  return /trace_id=|HTTP\s+\d{3}\b|Failed to load resource|ERR_[A-Z_]+|provider-unavailable|AgentRun error|超过\s*\d+\s*ms\s*无新活动|代理暂时无法连接上游|Trace 更新超时|加载失败/iu.test(String(preview || ""));
}

function normalizeDiagnosticPreview(text) {
  return String(text || "")
    .replace(/trace_id=[A-Za-z0-9_-]+/gu, "trace_id=:traceId")
    .replace(/\btrc_[A-Za-z0-9_-]+\b/gu, "trc_:traceId")
    .replace(/\bses_[A-Za-z0-9_-]+\b/gu, "ses_:sessionId")
    .replace(/\brun_[A-Za-z0-9_-]+\b/gu, "run_:runId")
    .replace(/\bcmd_[A-Za-z0-9_-]+\b/gu, "cmd_:commandId")
    .replace(/[!！]+$/gu, "")
    .replace(/\s+/gu, " ")
    .trim();
}

function extractDiagnosticTraceIds(item, preview) {
  const ids = new Set();
  if (item?.traceId) ids.add(String(item.traceId));
  const text = String(preview || "");
  for (const match of text.matchAll(/\btrc_[A-Za-z0-9_-]+\b/gu)) ids.add(match[0]);
  for (const match of text.matchAll(/trace_id=([A-Za-z0-9_-]+)/gu)) ids.add(match[1]);
  return ids;
}

function minIso(a, b) {
  if (!a) return b || null;
  if (!b) return a || null;
  return Date.parse(a) <= Date.parse(b) ? a : b;
}

function maxIso(a, b) {
  if (!a) return b || null;
  if (!b) return a || null;
  return Date.parse(a) >= Date.parse(b) ? a : b;
}

function sampleExecutionErrorCandidates(sample) {
  const candidates = [];
  const add = (source, items) => {
    if (!Array.isArray(items)) return;
    for (const item of items) {
      const text = String(item?.textPreview || item?.text || item?.preview || "").trim();
      if (!text) continue;
      if (!parseExecutionErrorText(text)) continue;
      candidates.push({
        source,
        text,
        traceId: item?.traceId ?? null,
        messageId: item?.messageId ?? null,
        status: item?.status ?? null
      });
    }
  };
  add("diagnostic-node", sample?.diagnostics);
  add("message", sample?.messages);
  add("trace-row", sample?.traceRows);
  add("turn", sample?.turns);
  const specific = candidates.filter((candidate) => {
    const parsed = parseExecutionErrorText(candidate.text);
    return parsed && parsed.code !== "error";
  });
  return specific.length > 0 ? specific : candidates;
}

function parseExecutionErrorText(text) {
  const value = String(text || "");
  const agentRunCodeMatch = value.match(/\bagentrun:error:([A-Za-z0-9_.:-]+)/u);
  const agentRunText = /\bAgentRun\s+error\b|\bagentrun:error:/iu.test(value);
  const providerUnavailable = /\bprovider[-_\s]*unavailable\b/iu.test(value);
  if (!agentRunCodeMatch && !agentRunText && !providerUnavailable) return null;
  const statusMatch = value.match(/\b(fail(?:ed)?|error|blocked|cancel(?:ed)?)\b/iu);
  const traceMatch = value.match(/\btrc_[A-Za-z0-9_-]+\b/u);
  const totalMatch = value.match(/\btotal\s*=\s*([0-9]{1,2}:[0-9]{2}(?::[0-9]{2})?)\b/iu)
    || value.match(/总耗时\s*[:：]?\s*([0-9]{1,2}:[0-9]{2}(?::[0-9]{2})?)/iu);
  const agentRunCode = cleanExecutionCode(agentRunCodeMatch?.[1] || "");
  const rawCode = agentRunCode ? "agentrun:error:" + agentRunCode : providerUnavailable ? "provider-unavailable" : "agentrun:error";
  return {
    backend: agentRunText || agentRunCodeMatch ? "agentrun" : "unknown",
    status: normalizeExecutionStatus(statusMatch?.[1] || "error"),
    code: agentRunCode || (providerUnavailable ? "provider-unavailable" : "error"),
    rawCode,
    totalSeconds: totalMatch ? parseClockDurationSeconds(totalMatch[1]) : null,
    traceId: traceMatch?.[0] || null
  };
}

function cleanExecutionCode(code) {
  const value = String(code || "").replace(/(?:AgentRun|Error|Failed).*$/u, "").replace(/[^A-Za-z0-9_.:-].*$/u, "");
  return value || null;
}

function normalizeExecutionStatus(status) {
  const value = String(status || "").toLowerCase();
  if (value === "failed") return "fail";
  if (value === "cancelled" || value === "canceled") return "cancel";
  return value || "error";
}

function parseClockDurationSeconds(value) {
  const parts = String(value || "").split(":").map((part) => Number(part));
  if (parts.length === 2 && parts.every(Number.isFinite)) return parts[0] * 60 + parts[1];
  if (parts.length === 3 && parts.every(Number.isFinite)) return parts[0] * 3600 + parts[1] * 60 + parts[2];
  return null;
}

function groupExecutionErrors(events) {
  const groups = new Map();
  for (const event of events) {
    const key = [event.backend || "-", event.status || "-", event.code || "-"].join(" ");
    const group = groups.get(key) || {
      backend: event.backend ?? null,
      status: event.status ?? null,
      code: event.code ?? null,
      rawCode: event.rawCode ?? null,
      count: 0,
      firstAt: event.ts,
      lastAt: event.ts,
      promptIndexes: [],
      traceIds: [],
      sources: []
    };
    group.count += 1;
    group.lastAt = event.ts;
    if (event.promptIndex && !group.promptIndexes.includes(event.promptIndex)) group.promptIndexes.push(event.promptIndex);
    if (event.traceId && !group.traceIds.includes(event.traceId)) group.traceIds.push(event.traceId);
    if (event.source && !group.sources.includes(event.source)) group.sources.push(event.source);
    groups.set(key, group);
  }
  return Array.from(groups.values()).sort((a, b) => b.count - a.count || String(a.code).localeCompare(String(b.code)));
}

function consoleAlertEvent(item, promptTimes) {
  const text = String(item?.text || "");
  const statusMatch = text.match(/\bstatus\s+of\s+([1-5][0-9]{2})\b/iu) || text.match(/\bHTTP\s+([1-5][0-9]{2})\b/iu);
  const location = compactLocation(item.location);
  const traceMatch = (location?.urlPath || text).match(/\btrc_[A-Za-z0-9_-]+\b/u);
  return {
    ts: item.ts ?? null,
    promptIndex: promptIndexForTs(promptTimes, item.ts),
    type: item.type ?? null,
    status: statusMatch ? Number(statusMatch[1]) : null,
    urlPath: location?.urlPath || "-",
    traceId: traceMatch?.[0] || null,
    textHash: item.text ? sha256(item.text) : null,
    preview: limitText(text, 220),
    location
  };
}

function groupConsoleAlerts(events) {
  const groups = new Map();
  for (const event of events) {
    const key = [event.type || "-", event.status ?? "-", event.urlPath || "-"].join(" ");
    const group = groups.get(key) || {
      type: event.type ?? null,
      status: event.status ?? null,
      urlPath: event.urlPath || "-",
      count: 0,
      firstAt: event.ts,
      lastAt: event.ts,
      promptIndexes: [],
      traceIds: []
    };
    group.count += 1;
    group.lastAt = event.ts;
    if (event.promptIndex && !group.promptIndexes.includes(event.promptIndex)) group.promptIndexes.push(event.promptIndex);
    if (event.traceId && !group.traceIds.includes(event.traceId)) group.traceIds.push(event.traceId);
    groups.set(key, group);
  }
  return Array.from(groups.values()).sort((a, b) => b.count - a.count || String(a.urlPath).localeCompare(String(b.urlPath)));
}

function networkAlertEvent(item, promptTimes) {
  const failureText = item.failureKind ?? item.failure ?? item.errorText ?? null;
  return {
    ts: item.ts ?? null,
    promptIndex: promptIndexForTs(promptTimes, item.ts),
    method: String(item.method || "GET").toUpperCase(),
    status: Number.isFinite(Number(item.status)) ? Number(item.status) : null,
    type: item.type ?? null,
    urlPath: urlPath(item.url),
    urlHash: item.url ? sha256(item.url) : null,
    failureKind: failureText ? String(failureText) : null,
    errorTextHash: failureText ? sha256(failureText) : null,
    errorPreview: failureText ? limitText(failureText, 160) : null
  };
}

function groupNetworkAlerts(events) {
  const groups = new Map();
  for (const event of events) {
    const key = [event.method, event.urlPath, event.status ?? "-", event.type].join(" ");
    const group = groups.get(key) || {
      method: event.method,
      urlPath: event.urlPath,
      status: event.status,
      type: event.type,
      count: 0,
      firstAt: event.ts,
      lastAt: event.ts,
      promptIndexes: [],
      failureKinds: [],
      errorTextHashes: []
    };
    group.count += 1;
    group.lastAt = event.ts;
    if (event.promptIndex && !group.promptIndexes.includes(event.promptIndex)) group.promptIndexes.push(event.promptIndex);
    if (event.failureKind && !group.failureKinds.includes(event.failureKind)) group.failureKinds.push(event.failureKind);
    if (event.errorTextHash && !group.errorTextHashes.includes(event.errorTextHash)) group.errorTextHashes.push(event.errorTextHash);
    groups.set(key, group);
  }
  return Array.from(groups.values()).sort((a, b) => b.count - a.count || String(a.urlPath).localeCompare(String(b.urlPath)));
}

function isDiagnosticText(text) {
  const value = String(text || "");
  return /Failed to (?:fetch|load resource)|request failed|net::ERR_[A-Z0-9_:-]+|server responded with a status of [45][0-9]{2}|HTTP\s+[45][0-9]{2}\b|trace_id=|workbench turn\s*超过|turn\s*超过|无新活动|idle\s+\d+s|waitingFor=|lastEventLabel=|无法连接上游|代理暂时无法连接上游|provider-unavailable|agentrun:error|AgentRun error|projection-resume|sync-failed|durable projection store|realtime-gap|Trace 更新超时|加载失败|请求失败|请求已失败/iu.test(value);
}

function isTerminalTraceText(text) {
  return /轮次完成|轮次失败|轮次取消|已记录|已完成第\d+轮|final response|sealed final response|turn completed|turn failed|turn canceled|terminal result|\bcompleted\b|\bfailed\b|\bcanceled\b|\bcancelled\b|\bterminal\b|\bdone\b/iu.test(String(text || ""));
}

function isFinalResultText(text) {
  return /已完成第\d+轮|final response|sealed final response|最终结果|已完成[:：]/iu.test(String(text || ""));
}

function buildSampleMetrics(samples, control) {
  const promptCommands = control
    .filter((item) => item.type === "sendPrompt" && item.phase === "completed")
    .map((item) => ({ ts: item.ts, tsMs: Date.parse(item.ts), commandId: item.commandId ?? null, textHash: item.input?.textHash ?? null, textBytes: item.input?.textBytes ?? null }))
    .filter((item) => Number.isFinite(item.tsMs))
    .sort((a, b) => a.tsMs - b.tsMs);
  const promptTimes = promptCommands.map((item) => item.tsMs);
  const timeline = samples.map((sample) => {
    const texts = sampleTexts(sample);
    const tsMs = Date.parse(sample.ts);
    const promptIndex = Number.isFinite(tsMs) ? latestPromptIndex(promptTimes, tsMs) : 0;
    const totalElapsedValues = texts.flatMap(parseTotalElapsedSeconds).filter(Number.isFinite);
    const recentUpdateValues = texts.flatMap(parseRecentUpdateSeconds).filter(Number.isFinite);
    const diagnosticTexts = texts.filter(isDiagnosticText).slice(0, 5);
    const terminalTexts = texts.filter(isTerminalTraceText).slice(0, 5);
    const finalResultTexts = texts.filter(isFinalResultText).slice(0, 5);
    return {
      seq: sample.seq ?? null,
      ts: sample.ts ?? null,
      routeSessionId: sample.routeSessionId ?? null,
      activeSessionId: sample.activeSessionId ?? null,
      promptIndex,
      messageCount: Array.isArray(sample.messages) ? sample.messages.length : 0,
      traceRowCount: Array.isArray(sample.traceRows) ? sample.traceRows.length : 0,
      totalElapsedSeconds: totalElapsedValues.length > 0 ? Math.max(...totalElapsedValues) : null,
      recentUpdateSeconds: recentUpdateValues.length > 0 ? Math.max(...recentUpdateValues) : null,
      terminalSeen: terminalTexts.length > 0,
      finalResultTextSeen: finalResultTexts.length > 0,
      diagnosticSeen: diagnosticTexts.length > 0,
      diagnosticTextHashes: diagnosticTexts.map(sha256).slice(0, 5),
      textDigest: digestSample(sample)
    };
  });
  const turnTiming = buildTurnTimingTable(samples, timeline);
  const turnCells = turnTiming.rows.flatMap((row) => Object.values(row.cells || {}));
  const turnTimingNonMonotonic = Array.isArray(turnTiming.nonMonotonic) ? turnTiming.nonMonotonic : [];
  const turnTimingElapsedZeroResets = Array.isArray(turnTiming.elapsedZeroResets) ? turnTiming.elapsedZeroResets : [];
  const turnTimingRecentUpdateSawtoothJumps = turnTimingNonMonotonic.filter((item) => item.metric === "recentUpdateSeconds" && item.anomaly === "jump");
  const turnTimingTerminalElapsedGrowth = Array.isArray(turnTiming.terminalElapsedGrowth) ? turnTiming.terminalElapsedGrowth : [];
  const turnTimingRecentUpdateResets = Array.isArray(turnTiming.recentUpdateResets) ? turnTiming.recentUpdateResets : [];
  const turnTimingRecentUpdateSteps = Array.isArray(turnTiming.recentUpdateSteps) ? turnTiming.recentUpdateSteps : [];
  const turnTimingTerminalElapsedGrowthDeltas = turnTimingTerminalElapsedGrowth
    .map((item) => Number(item.delta))
    .filter((value) => Number.isFinite(value) && value > 0);
  const turnTimingRecentUpdateLargestSteps = turnTimingRecentUpdateSteps
    .filter((item) => Number.isFinite(Number(item.delta)))
    .slice()
    .sort((a, b) => Number(b.delta) - Number(a.delta))
    .slice(0, 200);
  const turnTimingRecentUpdatePositiveSteps = turnTimingRecentUpdateSteps
    .map((item) => Number(item.delta))
    .filter((value) => Number.isFinite(value) && value >= 0);
  const turnTimingRecentUpdateExcessSteps = turnTimingRecentUpdateSteps
    .map((item) => Number(item.excessiveIncreaseSeconds))
    .filter((value) => Number.isFinite(value) && value > 0);
  const withTotal = timeline.filter((item) => item.totalElapsedSeconds !== null).length;
  const withRecent = timeline.filter((item) => item.recentUpdateSeconds !== null).length;
  const diagnostics = timeline.filter((item) => item.diagnosticSeen).length;
  const rounds = buildRoundMetricSummaries(timeline, promptCommands, {
    nonMonotonic: turnTimingNonMonotonic,
    elapsedZeroResets: turnTimingElapsedZeroResets,
    terminalElapsedGrowth: turnTimingTerminalElapsedGrowth,
    recentUpdateResets: turnTimingRecentUpdateResets,
    recentUpdateSteps: turnTimingRecentUpdateSteps
  });
  const recentUpdateJumpCount = turnTimingRecentUpdateSawtoothJumps.length;
  return {
    summary: {
      sampleCount: timeline.length,
      withTotalElapsed: withTotal,
      withRecentUpdate: withRecent,
      diagnostics,
      promptSegments: Math.max(0, promptTimes.length),
      rounds: rounds.length,
      turnColumns: turnTiming.columns.length,
      turnTimingRows: turnTiming.rows.length,
      turnCellsWithTotalElapsed: turnCells.filter((item) => item.totalElapsedSeconds !== null).length,
      turnCellsWithRecentUpdate: turnCells.filter((item) => item.recentUpdateSeconds !== null).length,
      turnTimingNonMonotonicCount: turnTimingNonMonotonic.length,
      turnTimingTotalElapsedDecreaseCount: turnTimingNonMonotonic.filter((item) => item.metric === "totalElapsedSeconds").length,
      turnTimingTotalElapsedZeroResetCount: turnTimingElapsedZeroResets.length,
      turnTimingTerminalElapsedGrowthCount: turnTimingTerminalElapsedGrowth.length,
      turnTimingTerminalElapsedGrowthMaxSeconds: turnTimingTerminalElapsedGrowthDeltas.length > 0 ? Math.max(...turnTimingTerminalElapsedGrowthDeltas) : 0,
      turnTimingRecentUpdateJumpCount: recentUpdateJumpCount,
      turnTimingRecentUpdateSawtoothJumpCount: recentUpdateJumpCount,
      turnTimingRecentUpdateStepCount: turnTimingRecentUpdateSteps.length,
      turnTimingRecentUpdateMaxIncreaseSeconds: turnTimingRecentUpdatePositiveSteps.length > 0 ? Math.max(...turnTimingRecentUpdatePositiveSteps) : null,
      turnTimingRecentUpdateMaxExcessSeconds: turnTimingRecentUpdateExcessSteps.length > 0 ? Math.max(...turnTimingRecentUpdateExcessSteps) : 0,
      turnTimingRecentUpdateResetCount: turnTimingRecentUpdateResets.length,
      turnTimingRecentUpdateDecreaseCount: turnTimingRecentUpdateResets.length,
      roundsWithTurnTimingNonMonotonic: rounds.filter((item) => item.turnTimingNonMonotonicCount > 0).length,
      roundsWithTerminalElapsedGrowth: rounds.filter((item) => item.turnTimingTerminalElapsedGrowthCount > 0).length,
      roundsWithRecentUpdateJumps: rounds.filter((item) => item.turnTimingRecentUpdateJumpCount > 0).length
    },
    rounds,
    turnColumns: turnTiming.columns,
    turnTimingTable: turnTiming.rows,
    turnTimingNonMonotonic,
    turnTimingElapsedZeroResets,
    turnTimingTerminalElapsedGrowth,
    turnTimingRecentUpdateSawtoothJumps,
    turnTimingRecentUpdateSteps,
    turnTimingRecentUpdateLargestSteps,
    turnTimingRecentUpdateResets,
    timeline
  };
}

function buildTurnTimingTable(samples, timeline) {
  const columns = [];
  const registry = new Map();
  const rows = [];
  for (let index = 0; index < samples.length; index += 1) {
    const sample = samples[index];
    const timelineItem = timeline[index] || {};
    const cells = {};
    for (const metric of turnMetricItems(sample, timelineItem)) {
      let column = registry.get(metric.key);
      if (!column) {
        column = {
          id: "T" + String(columns.length + 1),
          label: "T" + String(columns.length + 1),
          keyHash: sha256(metric.key),
          source: metric.source,
          firstSeq: sample.seq ?? null,
          firstTs: sample.ts ?? null,
          lastSeq: sample.seq ?? null,
          lastTs: sample.ts ?? null,
          promptIndex: metric.promptIndex ?? null,
          lastPromptIndex: metric.promptIndex ?? null,
          traceId: metric.traceId ?? null,
          messageId: metric.messageId ?? null,
          domIndex: metric.domIndex ?? null
        };
        registry.set(metric.key, column);
        columns.push(column);
      } else {
        column.lastSeq = sample.seq ?? null;
        column.lastTs = sample.ts ?? null;
        column.lastPromptIndex = metric.promptIndex ?? column.lastPromptIndex ?? null;
        if (!column.traceId && metric.traceId) column.traceId = metric.traceId;
        if (!column.messageId && metric.messageId) column.messageId = metric.messageId;
      }
      cells[column.id] = {
        totalElapsedSeconds: metric.totalElapsedSeconds,
        recentUpdateSeconds: metric.recentUpdateSeconds,
        status: metric.status ?? null,
        promptIndex: metric.promptIndex ?? null,
        source: metric.source,
        traceId: metric.traceId ?? null,
        messageId: metric.messageId ?? null,
        textHash: metric.textHash ?? null
      };
    }
    rows.push({
      ts: sample.ts ?? null,
      seq: sample.seq ?? null,
      promptIndex: timelineItem.promptIndex ?? 0,
      routeSessionId: sample.routeSessionId ?? null,
      activeSessionId: sample.activeSessionId ?? null,
      cells
    });
  }
  const timingEvents = detectTurnTimingNonMonotonic(columns, rows);
  return {
    columns,
    rows,
    nonMonotonic: timingEvents.anomalies,
    elapsedZeroResets: timingEvents.elapsedZeroResets,
    terminalElapsedGrowth: timingEvents.terminalElapsedGrowth,
    recentUpdateResets: timingEvents.recentUpdateResets,
    recentUpdateSteps: timingEvents.recentUpdateSteps
  };
}

function detectTurnTimingNonMonotonic(columns, rows) {
  const anomalies = [];
  const elapsedZeroResets = [];
  const terminalElapsedGrowth = [];
  const recentUpdateResets = [];
  const recentUpdateSteps = [];
  for (const column of columns) {
    const previousByMetric = new Map();
    let previousTerminalTotal = null;
    for (const row of rows) {
      const cell = row.cells?.[column.id];
      if (!cell) continue;
      for (const metric of ["totalElapsedSeconds", "recentUpdateSeconds"]) {
        const value = cell[metric];
        if (value === null || value === undefined || !Number.isFinite(Number(value))) continue;
        const current = Number(value);
        const previous = previousByMetric.get(metric);
        if (previous && metric === "totalElapsedSeconds" && current < previous.value) {
          const anomaly = previous.value > 0 && current === 0 ? "zero-reset" : "decrease";
          const event = {
            columnId: column.id,
            columnLabel: column.label,
            metric,
            anomaly,
            expectedPattern: anomaly === "zero-reset" ? "total-elapsed-should-not-return-to-zero" : "total-elapsed-monotonic",
            fromSeq: previous.seq,
            fromTs: previous.ts,
            fromValue: previous.value,
            toSeq: row.seq ?? null,
            toTs: row.ts ?? null,
            toValue: current,
            delta: current - previous.value,
            traceId: cell.traceId ?? column.traceId ?? null,
            messageId: cell.messageId ?? column.messageId ?? null,
            promptIndex: cell.promptIndex ?? row.promptIndex ?? null,
            source: cell.source ?? column.source ?? null,
            valuesRedacted: true
          };
          anomalies.push(event);
          if (anomaly === "zero-reset") elapsedZeroResets.push(event);
        }
        if (metric === "totalElapsedSeconds" && isTerminalTurnStatus(cell.status)) {
          if (previousTerminalTotal && current > previousTerminalTotal.value) {
            terminalElapsedGrowth.push({
              columnId: column.id,
              columnLabel: column.label,
              metric,
              anomaly: "terminal-growth",
              expectedPattern: "terminal-total-elapsed-sealed",
              fromSeq: previousTerminalTotal.seq,
              fromTs: previousTerminalTotal.ts,
              fromValue: previousTerminalTotal.value,
              fromStatus: previousTerminalTotal.status,
              toSeq: row.seq ?? null,
              toTs: row.ts ?? null,
              toValue: current,
              toStatus: cell.status ?? null,
              delta: current - previousTerminalTotal.value,
              traceId: cell.traceId ?? column.traceId ?? null,
              messageId: cell.messageId ?? column.messageId ?? null,
              promptIndex: cell.promptIndex ?? row.promptIndex ?? null,
              source: cell.source ?? column.source ?? null,
              valuesRedacted: true
            });
          }
          previousTerminalTotal = { value: current, seq: row.seq ?? null, ts: row.ts ?? null, status: cell.status ?? null };
        }
        if (previous && metric === "recentUpdateSeconds") {
          const elapsedMs = Date.parse(String(row.ts ?? "")) - Date.parse(String(previous.ts ?? ""));
          const elapsedSeconds = Number.isFinite(elapsedMs) && elapsedMs >= 0 ? elapsedMs / 1000 : null;
          const increase = current - previous.value;
          const allowedIncrease = elapsedSeconds === null ? 3 : Math.max(3, elapsedSeconds + 2);
          const excessiveIncrease = increase > allowedIncrease ? increase - allowedIncrease : 0;
          recentUpdateSteps.push({
            columnId: column.id,
            columnLabel: column.label,
            metric: "recentUpdateSeconds",
            event: increase < 0 ? "reset" : excessiveIncrease > 0 ? "jump" : "increase",
            expectedPattern: "sawtooth-increase-or-reset",
            fromSeq: previous.seq,
            fromTs: previous.ts,
            fromValue: previous.value,
            toSeq: row.seq ?? null,
            toTs: row.ts ?? null,
            toValue: current,
            delta: increase,
            sampleDeltaSeconds: elapsedSeconds,
            allowedIncreaseSeconds: allowedIncrease,
            excessiveIncreaseSeconds: excessiveIncrease,
            traceId: cell.traceId ?? column.traceId ?? null,
            messageId: cell.messageId ?? column.messageId ?? null,
            promptIndex: cell.promptIndex ?? row.promptIndex ?? null,
            source: cell.source ?? column.source ?? null,
            valuesRedacted: true
          });
          if (increase < 0) {
            recentUpdateResets.push({
              columnId: column.id,
              columnLabel: column.label,
              metric: "recentUpdateSeconds",
              event: "reset",
              fromSeq: previous.seq,
              fromTs: previous.ts,
              fromValue: previous.value,
              toSeq: row.seq ?? null,
              toTs: row.ts ?? null,
              toValue: current,
              delta: increase,
              sampleDeltaSeconds: elapsedSeconds,
              traceId: cell.traceId ?? column.traceId ?? null,
              messageId: cell.messageId ?? column.messageId ?? null,
              promptIndex: cell.promptIndex ?? row.promptIndex ?? null,
              source: cell.source ?? column.source ?? null,
              valuesRedacted: true
            });
          }
          if (excessiveIncrease > 0) {
            anomalies.push({
              columnId: column.id,
              columnLabel: column.label,
              metric: "recentUpdateSeconds",
              anomaly: "jump",
              expectedPattern: "sawtooth-increase-or-reset",
              fromSeq: previous.seq,
              fromTs: previous.ts,
              fromValue: previous.value,
              toSeq: row.seq ?? null,
              toTs: row.ts ?? null,
              toValue: current,
              delta: increase,
              sampleDeltaSeconds: elapsedSeconds,
              allowedIncreaseSeconds: allowedIncrease,
              excessiveIncreaseSeconds: excessiveIncrease,
              traceId: cell.traceId ?? column.traceId ?? null,
              messageId: cell.messageId ?? column.messageId ?? null,
              promptIndex: cell.promptIndex ?? row.promptIndex ?? null,
              source: cell.source ?? column.source ?? null,
              valuesRedacted: true
            });
          }
        }
        previousByMetric.set(metric, { value: current, seq: row.seq ?? null, ts: row.ts ?? null });
      }
    }
  }
  return { anomalies, elapsedZeroResets, terminalElapsedGrowth, recentUpdateResets, recentUpdateSteps };
}

function isTerminalTurnStatus(value) {
  const status = String(value ?? "").trim().toLowerCase().replace(/_/gu, "-");
  return ["completed", "failed", "blocked", "timeout", "canceled", "cancelled", "stale", "thread-resume-failed"].includes(status);
}

function turnMetricItems(sample, timelineItem) {
  const promptIndex = timelineItem.promptIndex ?? 0;
  const sessionKey = sample?.routeSessionId || sample?.activeSessionId || "unknown-session";
  const roundKey = String(promptIndex);
  const items = [];
  if (Array.isArray(sample?.turns) && sample.turns.length > 0) {
    for (const turn of sample.turns) {
      const texts = turnTexts(turn);
      const totalElapsedValues = texts.flatMap(parseTotalElapsedSeconds).filter(Number.isFinite);
      const recentUpdateValues = texts.flatMap(parseRecentUpdateSeconds).filter(Number.isFinite);
      const traceId = turn.traceId || firstTraceId(texts);
      const messageId = turn.messageId || null;
      const turnId = turn.turnId || traceId || null;
      const stableId = traceId || messageId || turnId || null;
      const domIndex = Number.isFinite(Number(turn.index)) ? Number(turn.index) : items.length;
      const key = stableId
        ? "turn:" + sessionKey + ":id-" + stableId
        : "turn:" + sessionKey + ":round-" + roundKey + ":dom-index-" + String(domIndex);
      items.push({
        key,
        source: "turn",
        promptIndex,
        traceId,
        messageId,
        turnId,
        domIndex,
        status: turn.status ?? null,
        totalElapsedSeconds: totalElapsedValues.length > 0 ? Math.max(...totalElapsedValues) : null,
        recentUpdateSeconds: recentUpdateValues.length > 0 ? Math.max(...recentUpdateValues) : null,
        textHash: turn.textHash || sha256(texts.join("\n"))
      });
    }
    return items;
  }
  if (Array.isArray(sample?.messages) && sample.messages.length > 0) {
    for (const message of sample.messages) {
      const text = String(message?.textPreview || "");
      const totalElapsedValues = parseTotalElapsedSeconds(text).filter(Number.isFinite);
      const recentUpdateValues = parseRecentUpdateSeconds(text).filter(Number.isFinite);
      if (totalElapsedValues.length === 0 && recentUpdateValues.length === 0) continue;
      const domIndex = Number.isFinite(Number(message.index)) ? Number(message.index) : items.length;
      const traceId = message.traceId || firstTraceId([text]);
      const messageId = message.messageId || null;
      const stableId = traceId || messageId || message.turnId || null;
      items.push({
        key: stableId
          ? "message:" + sessionKey + ":id-" + stableId
          : "message:" + sessionKey + ":round-" + roundKey + ":dom-index-" + String(domIndex),
        source: "message",
        promptIndex,
        traceId,
        messageId,
        turnId: message.turnId || traceId || null,
        domIndex,
        status: message.status ?? null,
        totalElapsedSeconds: totalElapsedValues.length > 0 ? Math.max(...totalElapsedValues) : null,
        recentUpdateSeconds: recentUpdateValues.length > 0 ? Math.max(...recentUpdateValues) : null,
        textHash: message.textHash || sha256(text)
      });
    }
    if (items.length > 0) return items;
  }
  if (timelineItem.totalElapsedSeconds !== null || timelineItem.recentUpdateSeconds !== null) {
    return [{
      key: "aggregate:" + sessionKey + ":round-" + roundKey,
      source: "aggregate",
      promptIndex,
      traceId: null,
      messageId: null,
      domIndex: null,
      status: null,
      totalElapsedSeconds: timelineItem.totalElapsedSeconds ?? null,
      recentUpdateSeconds: timelineItem.recentUpdateSeconds ?? null,
      textHash: timelineItem.textDigest ?? null
    }];
  }
  return [];
}

function turnTexts(turn) {
  return [
    turn?.durationText,
    turn?.activityText,
    turn?.textPreview,
    turn?.text
  ].map((value) => String(value || "")).filter((value) => value.trim().length > 0);
}

function firstTraceId(texts) {
  for (const text of texts) {
    const match = String(text || "").match(/\btrc_[A-Za-z0-9_-]+\b/u);
    if (match) return match[0];
  }
  return null;
}

function buildRoundMetricSummaries(timeline, promptCommands, timing = {}) {
  const rounds = [];
  const nonMonotonic = Array.isArray(timing.nonMonotonic) ? timing.nonMonotonic : [];
  const elapsedZeroResets = Array.isArray(timing.elapsedZeroResets) ? timing.elapsedZeroResets : [];
  const terminalElapsedGrowth = Array.isArray(timing.terminalElapsedGrowth) ? timing.terminalElapsedGrowth : [];
  const recentUpdateResets = Array.isArray(timing.recentUpdateResets) ? timing.recentUpdateResets : [];
  const recentUpdateSteps = Array.isArray(timing.recentUpdateSteps) ? timing.recentUpdateSteps : [];
  for (let index = 0; index < promptCommands.length; index += 1) {
    const promptIndex = index + 1;
    const items = timeline.filter((item) => item.promptIndex === promptIndex);
    const totalElapsed = items.map((item) => item.totalElapsedSeconds).filter((value) => value !== null);
    const recentUpdate = items.map((item) => item.recentUpdateSeconds).filter((value) => value !== null);
    const timingAnomalies = nonMonotonic.filter((item) => item.promptIndex === promptIndex);
    const timingZeroResets = elapsedZeroResets.filter((item) => item.promptIndex === promptIndex);
    const timingTerminalGrowth = terminalElapsedGrowth.filter((item) => item.promptIndex === promptIndex);
    const timingResets = recentUpdateResets.filter((item) => item.promptIndex === promptIndex);
    const timingSteps = recentUpdateSteps.filter((item) => item.promptIndex === promptIndex);
    const terminalGrowthDeltas = timingTerminalGrowth.map((item) => Number(item.delta)).filter((value) => Number.isFinite(value) && value > 0);
    const timingStepDeltas = timingSteps.map((item) => Number(item.delta)).filter((value) => Number.isFinite(value) && value >= 0);
    const timingStepExcess = timingSteps.map((item) => Number(item.excessiveIncreaseSeconds)).filter((value) => Number.isFinite(value) && value > 0);
    rounds.push({
      promptIndex,
      promptCommandId: promptCommands[index].commandId,
      promptTextHash: promptCommands[index].textHash,
      promptTextBytes: promptCommands[index].textBytes,
      promptCompletedAt: promptCommands[index].ts,
      sampleCount: items.length,
      firstSeq: items[0]?.seq ?? null,
      lastSeq: items[items.length - 1]?.seq ?? null,
      firstSampleAt: items[0]?.ts ?? null,
      lastSampleAt: items[items.length - 1]?.ts ?? null,
      withTotalElapsed: totalElapsed.length,
      withRecentUpdate: recentUpdate.length,
      maxTotalElapsedSeconds: totalElapsed.length > 0 ? Math.max(...totalElapsed) : null,
      lastTotalElapsedSeconds: lastNonNull(items.map((item) => item.totalElapsedSeconds)),
      maxRecentUpdateSeconds: recentUpdate.length > 0 ? Math.max(...recentUpdate) : null,
      lastRecentUpdateSeconds: lastNonNull(items.map((item) => item.recentUpdateSeconds)),
      diagnosticSamples: items.filter((item) => item.diagnosticSeen).length,
      terminalSamples: items.filter((item) => item.terminalSeen).length,
      finalTextSamples: items.filter((item) => item.finalResultTextSeen).length,
      turnTimingNonMonotonicCount: timingAnomalies.length,
      turnTimingTotalElapsedDecreaseCount: timingAnomalies.filter((item) => item.metric === "totalElapsedSeconds").length,
      turnTimingTotalElapsedZeroResetCount: timingZeroResets.length,
      turnTimingTerminalElapsedGrowthCount: timingTerminalGrowth.length,
      turnTimingTerminalElapsedGrowthMaxSeconds: terminalGrowthDeltas.length > 0 ? Math.max(...terminalGrowthDeltas) : 0,
      turnTimingRecentUpdateJumpCount: timingAnomalies.filter((item) => item.metric === "recentUpdateSeconds" && item.anomaly === "jump").length,
      turnTimingRecentUpdateSawtoothJumpCount: timingAnomalies.filter((item) => item.metric === "recentUpdateSeconds" && item.anomaly === "jump").length,
      turnTimingRecentUpdateStepCount: timingSteps.length,
      turnTimingRecentUpdateMaxIncreaseSeconds: timingStepDeltas.length > 0 ? Math.max(...timingStepDeltas) : null,
      turnTimingRecentUpdateMaxExcessSeconds: timingStepExcess.length > 0 ? Math.max(...timingStepExcess) : 0,
      turnTimingRecentUpdateResetCount: timingResets.length,
      turnTimingRecentUpdateDecreaseCount: timingResets.length
    });
  }
  return rounds;
}

function sampleTexts(sample) {
  const rows = [];
  for (const group of [sample?.messages, sample?.traceRows, sample?.diagnostics]) {
    if (!Array.isArray(group)) continue;
    for (const item of group) {
      const text = String(item?.textPreview || "");
      if (text.trim()) rows.push(text);
    }
  }
  return rows;
}

function parseTotalElapsedSeconds(text) {
  const values = [];
  for (const match of String(text || "").matchAll(/(?:总耗时|耗时)\s*[=:：]?\s*(\d{1,2}):(\d{2}):(\d{2})/giu)) {
    values.push(Number(match[1]) * 3600 + Number(match[2]) * 60 + Number(match[3]));
  }
  for (const match of String(text || "").matchAll(/(?:总耗时|耗时)\s*[=:：]?\s*(\d{1,2}):(\d{2})(?!:)/giu)) {
    values.push(Number(match[1]) * 60 + Number(match[2]));
  }
  for (const match of String(text || "").matchAll(/(?:总耗时|耗时)\s*[=:：]?\s*(?:(\d+)\s*天)?\s*(?:(\d+)\s*小时)?\s*(?:(\d+)\s*(?:分钟|分))?\s*(?:(\d+)\s*秒)?/giu)) {
    const days = Number(match[1] || 0);
    const hours = Number(match[2] || 0);
    const minutes = Number(match[3] || 0);
    const seconds = Number(match[4] || 0);
    if (days || hours || minutes || seconds) values.push(days * 86400 + hours * 3600 + minutes * 60 + seconds);
  }
  return values;
}

function lastNonNull(values) {
  for (let index = values.length - 1; index >= 0; index -= 1) if (values[index] !== null && values[index] !== undefined) return values[index];
  return null;
}

function parseRecentUpdateSeconds(text) {
  const values = [];
  for (const match of String(text || "").matchAll(/最近\s*(?:(\d+)\s*天)?\s*(?:(\d+)\s*小时)?\s*(?:(\d+)\s*(?:分钟|分))?\s*(?:(\d+)\s*秒)?\s*前/giu)) {
    const days = Number(match[1] || 0);
    const hours = Number(match[2] || 0);
    const minutes = Number(match[3] || 0);
    const seconds = Number(match[4] || 0);
    values.push(days * 86400 + hours * 3600 + minutes * 60 + seconds);
  }
  return values;
}

function latestPromptIndex(promptTimes, tsMs) {
  let index = 0;
  for (let i = 0; i < promptTimes.length; i += 1) {
    if (promptTimes[i] <= tsMs) index = i + 1;
    else break;
  }
  return index;
}

function promptIndexForTs(promptTimes, ts) {
  const tsMs = Date.parse(ts);
  return Number.isFinite(tsMs) ? latestPromptIndex(promptTimes, tsMs) : 0;
}

function buildTransitions(samples) {
  const rows = [];
  let last = null;
  for (const sample of samples) {
    const digest = digestSample(sample);
    if (digest !== last) {
      rows.push({ seq: sample.seq, ts: sample.ts, url: sample.url, routeSessionId: sample.routeSessionId || null, activeSessionId: sample.activeSessionId || null, messageCount: Array.isArray(sample.messages) ? sample.messages.length : 0, traceRowCount: Array.isArray(sample.traceRows) ? sample.traceRows.length : 0, digest });
      last = digest;
    }
  }
  return rows.slice(0, 200);
}

function detectFinalFlicker(samples) {
  const flickers = [];
  let lastNonEmpty = null;
  for (const sample of samples) {
    const messageText = Array.isArray(sample.messages) ? sample.messages.map((item) => item.textPreview || "").join("\n") : "";
    const nonEmpty = messageText.trim().length > 0;
    const finalLike = /轮次完成|已记录|已完成第\d+轮|final response|terminal result/iu.test(messageText);
    if (nonEmpty && finalLike && !/temporarily|timeout|无法连接|暂时|error|failed|超时/iu.test(messageText)) lastNonEmpty = { sample, messageText };
    if (lastNonEmpty && nonEmpty && /temporarily|timeout|无法连接|暂时|error|failed|超时/iu.test(messageText)) flickers.push({ from: ref(lastNonEmpty.sample), to: ref(sample) });
    if (lastNonEmpty && !nonEmpty) flickers.push({ from: ref(lastNonEmpty.sample), to: ref(sample), reason: "text-disappeared" });
  }
  return flickers;
}

function digestSample(sample) {
  const messages = Array.isArray(sample.messages) ? sample.messages.map((item) => item.textHash || item.textPreview || "").join("|") : "";
  const trace = Array.isArray(sample.traceRows) ? sample.traceRows.map((item) => (item.status || "") + ":" + (item.textHash || item.textPreview || "")).join("|") : "";
  const diagnostics = Array.isArray(sample.diagnostics) ? sample.diagnostics.map((item) => (item.className || "") + ":" + (item.textHash || item.textPreview || "")).join("|") : "";
  return sha256((sample.routeSessionId || "") + "|" + (sample.activeSessionId || "") + "|" + messages + "|" + trace + "|" + diagnostics);
}

function nearCommand(sample, commandTimes, windowMs) {
  const ts = Date.parse(sample.ts);
  return Number.isFinite(ts) && commandTimes.some((item) => Math.abs(ts - item) <= windowMs);
}

function sampleRefs(samples, pick) {
  const seen = new Set();
  const refs = [];
  for (const sample of samples) {
    const value = pick(sample);
    if (!value || seen.has(value)) continue;
    seen.add(value);
    refs.push({ ...ref(sample), value });
  }
  return refs.slice(0, 20);
}

function ref(sample) {
  if (!sample) return null;
  return { seq: sample.seq ?? null, ts: sample.ts ?? null, url: sample.url ?? null, routeSessionId: sample.routeSessionId ?? null, activeSessionId: sample.activeSessionId ?? null };
}

async function artifactSummary(artifacts) {
  const items = artifacts.slice(-30).map((item) => ({ kind: item.kind, reason: item.reason, sampleSeq: item.sampleSeq, path: item.path, sha256: item.sha256, byteCount: item.byteCount }));
  return { count: artifacts.length, latest: items };
}

function compactManifest(value) {
  if (!value) return null;
  return { jobId: value.jobId, stateDir: value.stateDir, baseUrl: value.baseUrl, targetPath: value.targetPath, startedAt: value.startedAt, status: value.status, sampling: value.sampling, pageProvenance: value.pageProvenance ?? null, safety: value.safety };
}

function compactHeartbeat(value) {
  if (!value) return null;
  return { jobId: value.jobId, pid: value.pid, status: value.status, sampleSeq: value.sampleSeq, commandSeq: value.commandSeq, currentUrl: value.currentUrl, pageProvenance: value.pageProvenance ?? null, updatedAt: value.updatedAt, uptimeMs: value.uptimeMs };
}

function renderTurnTimingTable(sampleMetrics) {
  const columns = Array.isArray(sampleMetrics?.turnColumns) ? sampleMetrics.turnColumns : [];
  const rows = Array.isArray(sampleMetrics?.turnTimingTable) ? sampleMetrics.turnTimingTable : [];
  if (columns.length === 0 || rows.length === 0) return "- 无 turn 时间表。";
  const header = ["时间戳"];
  for (const column of columns) {
    header.push(column.label + " 总耗时(s)");
    header.push(column.label + " 最近更新(s)");
  }
  const lines = [];
  lines.push("| " + header.map(escapeMarkdownCell).join(" | ") + " |");
  lines.push("| " + header.map(() => "---").join(" | ") + " |");
  for (const row of rows) {
    const cells = [row.ts || "-"];
    for (const column of columns) {
      const cell = row.cells?.[column.id] || {};
      cells.push(formatMetricCell(cell.totalElapsedSeconds));
      cells.push(formatMetricCell(cell.recentUpdateSeconds));
    }
    lines.push("| " + cells.map(escapeMarkdownCell).join(" | ") + " |");
  }
  const columnLines = columns.map((column) => "- " + column.label + ": source=" + (column.source || "-") + " prompt=" + (column.promptIndex ?? "-") + " lastPrompt=" + (column.lastPromptIndex ?? "-") + " firstSeq=" + (column.firstSeq ?? "-") + " lastSeq=" + (column.lastSeq ?? "-") + " traceId=" + (column.traceId || "-") + " messageId=" + (column.messageId || "-")).join("\n");
  const nonMonotonic = Array.isArray(sampleMetrics?.turnTimingNonMonotonic) ? sampleMetrics.turnTimingNonMonotonic : [];
  const nonMonotonicLines = nonMonotonic.length > 0
    ? nonMonotonic.slice(0, 80).map((item) => "- " + (item.columnLabel || item.columnId || "-") + " " + item.metric + (item.anomaly ? " " + item.anomaly : "") + " " + (item.fromValue ?? "-") + " -> " + (item.toValue ?? "-") + " delta=" + (item.delta ?? "-") + " sampleDelta=" + (item.sampleDeltaSeconds ?? "-") + " allowed=" + (item.allowedIncreaseSeconds ?? "-") + " seq " + (item.fromSeq ?? "-") + " -> " + (item.toSeq ?? "-") + " ts " + (item.fromTs || "-") + " -> " + (item.toTs || "-") + " traceId=" + (item.traceId || "-")).join("\n")
    : "- 未观察到总耗时下降或最近更新异常跳增。";
  const terminalGrowth = Array.isArray(sampleMetrics?.turnTimingTerminalElapsedGrowth) ? sampleMetrics.turnTimingTerminalElapsedGrowth : [];
  const terminalGrowthLines = terminalGrowth.length > 0
    ? terminalGrowth.slice(0, 80).map((item) => "- " + (item.columnLabel || item.columnId || "-") + " terminal totalElapsed growth " + (item.fromValue ?? "-") + " -> " + (item.toValue ?? "-") + " delta=" + (item.delta ?? "-") + " status " + (item.fromStatus || "-") + " -> " + (item.toStatus || "-") + " seq " + (item.fromSeq ?? "-") + " -> " + (item.toSeq ?? "-") + " ts " + (item.fromTs || "-") + " -> " + (item.toTs || "-") + " traceId=" + (item.traceId || "-")).join("\n")
    : "- 未观察到 terminal 后总耗时增长。";
  const elapsedZeroResets = Array.isArray(sampleMetrics?.turnTimingElapsedZeroResets) ? sampleMetrics.turnTimingElapsedZeroResets : [];
  const elapsedZeroResetLines = elapsedZeroResets.length > 0
    ? elapsedZeroResets.slice(0, 80).map((item) => "- " + (item.columnLabel || item.columnId || "-") + " totalElapsed zero-reset " + (item.fromValue ?? "-") + " -> " + (item.toValue ?? "-") + " delta=" + (item.delta ?? "-") + " seq " + (item.fromSeq ?? "-") + " -> " + (item.toSeq ?? "-") + " ts " + (item.fromTs || "-") + " -> " + (item.toTs || "-") + " traceId=" + (item.traceId || "-")).join("\n")
    : "- 未观察到总耗时从非零跳回 0 秒。";
  const sawtoothJumps = Array.isArray(sampleMetrics?.turnTimingRecentUpdateSawtoothJumps)
    ? sampleMetrics.turnTimingRecentUpdateSawtoothJumps
    : nonMonotonic.filter((item) => item.metric === "recentUpdateSeconds" && item.anomaly === "jump");
  const sawtoothJumpLines = sawtoothJumps.length > 0
    ? sawtoothJumps.slice(0, 80).map((item) => "- " + (item.columnLabel || item.columnId || "-") + " recentUpdate sawtooth-jump " + (item.fromValue ?? "-") + " -> " + (item.toValue ?? "-") + " delta=" + (item.delta ?? "-") + " sampleDelta=" + (item.sampleDeltaSeconds ?? "-") + " allowed=" + (item.allowedIncreaseSeconds ?? "-") + " seq " + (item.fromSeq ?? "-") + " -> " + (item.toSeq ?? "-") + " ts " + (item.fromTs || "-") + " -> " + (item.toTs || "-") + " traceId=" + (item.traceId || "-")).join("\n")
    : "- 未观察到最近更新三角波异常跳增。";
  const recentUpdateSteps = Array.isArray(sampleMetrics?.turnTimingRecentUpdateLargestSteps)
    ? sampleMetrics.turnTimingRecentUpdateLargestSteps
    : Array.isArray(sampleMetrics?.turnTimingRecentUpdateSteps)
      ? sampleMetrics.turnTimingRecentUpdateSteps.filter((item) => Number.isFinite(Number(item.delta))).slice().sort((a, b) => Number(b.delta) - Number(a.delta)).slice(0, 200)
      : [];
  const stepLines = recentUpdateSteps.length > 0
    ? recentUpdateSteps.slice(0, 80).map((item) => "- " + (item.columnLabel || item.columnId || "-") + " recentUpdate step " + (item.fromValue ?? "-") + " -> " + (item.toValue ?? "-") + " delta=" + (item.delta ?? "-") + " sampleDelta=" + (item.sampleDeltaSeconds ?? "-") + " allowed=" + (item.allowedIncreaseSeconds ?? "-") + " excess=" + (item.excessiveIncreaseSeconds ?? 0) + " event=" + (item.event || "-") + " seq " + (item.fromSeq ?? "-") + " -> " + (item.toSeq ?? "-") + " ts " + (item.fromTs || "-") + " -> " + (item.toTs || "-") + " traceId=" + (item.traceId || "-")).join("\n")
    : "- 未观察到最近更新相邻采样 step。";
  const recentUpdateResets = Array.isArray(sampleMetrics?.turnTimingRecentUpdateResets) ? sampleMetrics.turnTimingRecentUpdateResets : [];
  const resetLines = recentUpdateResets.length > 0
    ? recentUpdateResets.slice(0, 80).map((item) => "- " + (item.columnLabel || item.columnId || "-") + " reset " + (item.fromValue ?? "-") + " -> " + (item.toValue ?? "-") + " delta=" + (item.delta ?? "-") + " sampleDelta=" + (item.sampleDeltaSeconds ?? "-") + " seq " + (item.fromSeq ?? "-") + " -> " + (item.toSeq ?? "-") + " ts " + (item.fromTs || "-") + " -> " + (item.toTs || "-") + " traceId=" + (item.traceId || "-")).join("\n")
    : "- 未观察到最近更新归零/回落。";
  return lines.join("\n") + "\n\n列说明：\n" + columnLines + "\n\n异常事件（仅报表暴露，不做下游 repair；最近更新按三角波模型检测异常跳增）：\n" + nonMonotonicLines + "\n\nTerminal 后总耗时增长事件（终态 turn 的总耗时应 sealed，不应继续增长）：\n" + terminalGrowthLines + "\n\n总耗时归零跳变事件（例如已显示真实耗时后又变成 0 秒）：\n" + elapsedZeroResetLines + "\n\n最近更新 sawtooth jump 事件（预期每秒增长约 1，遇到新活动归零；例如 1 秒 -> 1 分 4 秒应被列入这里）：\n" + sawtoothJumpLines + "\n\n最近更新相邻采样 step（按 delta 降序；用于人工识别一秒跳几十秒/一分钟的瞬态）：\n" + stepLines + "\n\n最近更新 reset 事件（预期三角波归零，不计为异常）：\n" + resetLines;
}

function formatMetricCell(value) {
  if (value === null || value === undefined || !Number.isFinite(Number(value))) return "-";
  return String(Number(value));
}

function escapeMarkdownCell(value) {
  return String(value ?? "-").replace(/\|/gu, "\\|");
}

function renderMarkdown(report) {
  const findingLines = report.findings.length === 0 ? "- 无红灯项。" : report.findings.map((item) => "- " + item.severity + ": " + item.id + " - " + item.summary).join("\n");
  const commandLines = report.commandTimeline.length === 0 ? "- 无控制命令。" : report.commandTimeline.map((item) => "- " + item.ts + " " + item.phase + " " + item.type + " " + item.commandId + " " + (item.afterUrl || "")).join("\n");
  const transitionLines = report.transitions.length === 0 ? "- 无状态变化。" : report.transitions.slice(0, 80).map((item) => "- #" + item.seq + " " + item.ts + " messages=" + item.messageCount + " traceRows=" + item.traceRowCount + " route=" + (item.routeSessionId || "-") + " active=" + (item.activeSessionId || "-")).join("\n");
  const metricSummary = report.sampleMetrics?.summary || {};
  const alertSummary = report.runtimeAlerts?.summary || {};
  const httpAlertLines = Array.isArray(report.runtimeAlerts?.networkHttpErrorsByPath) && report.runtimeAlerts.networkHttpErrorsByPath.length > 0
    ? report.runtimeAlerts.networkHttpErrorsByPath.slice(0, 40).map((item) => "- HTTP " + (item.status ?? "-") + " " + item.method + " " + item.urlPath + " count=" + item.count + " prompts=" + (item.promptIndexes?.join(",") || "-") + " first=" + (item.firstAt || "-") + " last=" + (item.lastAt || "-")).join("\n")
    : "- 无 HTTP 错误。";
  const requestFailedLines = Array.isArray(report.runtimeAlerts?.networkRequestFailedByPath) && report.runtimeAlerts.networkRequestFailedByPath.length > 0
    ? report.runtimeAlerts.networkRequestFailedByPath.slice(0, 40).map((item) => "- requestfailed " + item.method + " " + item.urlPath + " count=" + item.count + " failure=" + (item.failureKinds?.slice(0, 4).join(",") || "-") + " prompts=" + (item.promptIndexes?.join(",") || "-") + " first=" + (item.firstAt || "-") + " last=" + (item.lastAt || "-")).join("\n")
    : "- 无 requestfailed。";
  const domDiagnosticLines = Array.isArray(report.runtimeAlerts?.domDiagnostics) && report.runtimeAlerts.domDiagnostics.length > 0
    ? report.runtimeAlerts.domDiagnostics.slice(0, 40).map((item) => "- #" + (item.seq ?? "-") + " " + (item.ts || "-") + " prompt=" + (item.promptIndex ?? "-") + " source=" + (item.source || "-") + " code=" + (item.diagnosticCode || "-") + " traceId=" + (item.traceId || "-") + " http=" + (item.httpStatus ?? "-") + " idle=" + (item.idleSeconds ?? "-") + " waitingFor=" + (item.waitingFor || "-") + " lastEventLabel=" + (item.lastEventLabel || "-") + " textHash=" + (item.textHash || "-") + " preview=" + escapeMarkdownCell(item.preview || "")).join("\n")
    : "- 无 DOM 诊断文本。";
  const consoleAlertLines = Array.isArray(report.runtimeAlerts?.consoleAlerts) && report.runtimeAlerts.consoleAlerts.length > 0
    ? report.runtimeAlerts.consoleAlerts.slice(0, 40).map((item) => "- " + (item.ts || "-") + " prompt=" + (item.promptIndex ?? "-") + " type=" + (item.type || "-") + " status=" + (item.status ?? "-") + " path=" + (item.urlPath || "-") + " traceId=" + (item.traceId || "-") + " textHash=" + (item.textHash || "-") + " preview=" + escapeMarkdownCell(item.preview || "")).join("\n")
    : "- 无 console warning/error。";
  const consoleAlertGroupLines = Array.isArray(report.runtimeAlerts?.consoleAlertsByPath) && report.runtimeAlerts.consoleAlertsByPath.length > 0
    ? report.runtimeAlerts.consoleAlertsByPath.slice(0, 40).map((item) => "- console " + (item.type || "-") + " status=" + (item.status ?? "-") + " path=" + (item.urlPath || "-") + " count=" + item.count + " prompts=" + (item.promptIndexes?.join(",") || "-") + " traces=" + (item.traceIds?.slice(0, 6).join(",") || "-")).join("\n")
    : "- 无 console 分组。";
  const promptNetworkLines = Array.isArray(report.promptNetwork?.rounds) && report.promptNetwork.rounds.length > 0
    ? report.promptNetwork.rounds.map((item) => "- round " + item.promptIndex + " promptHash=" + (item.promptTextHash || "-") + " chatPostOk=" + String(item.chatPostOk) + " failure=" + (item.failureKind || "-") + " statuses=" + (Array.isArray(item.responseStatuses) && item.responseStatuses.length > 0 ? item.responseStatuses.join(",") : "-") + " firstChat=" + (item.firstChatEventAt || "-") + " lastChat=" + (item.lastChatEventAt || "-")).join("\n")
    : "- 无 prompt 网络记录。";
  const roundLines = Array.isArray(report.sampleMetrics?.rounds) && report.sampleMetrics.rounds.length > 0
    ? report.sampleMetrics.rounds.map((item) => "- round " + item.promptIndex + " promptHash=" + (item.promptTextHash || "-") + " samples=" + item.sampleCount + " totalMax=" + (item.maxTotalElapsedSeconds ?? "-") + " totalLast=" + (item.lastTotalElapsedSeconds ?? "-") + " recentMax=" + (item.maxRecentUpdateSeconds ?? "-") + " recentLast=" + (item.lastRecentUpdateSeconds ?? "-") + " totalDecrease=" + (item.turnTimingTotalElapsedDecreaseCount ?? 0) + " terminalGrowth=" + (item.turnTimingTerminalElapsedGrowthCount ?? 0) + " terminalGrowthMax=" + (item.turnTimingTerminalElapsedGrowthMaxSeconds ?? 0) + " recentJump=" + (item.turnTimingRecentUpdateJumpCount ?? 0) + " recentSawtoothJump=" + (item.turnTimingRecentUpdateSawtoothJumpCount ?? item.turnTimingRecentUpdateJumpCount ?? 0) + " recentStep=" + (item.turnTimingRecentUpdateStepCount ?? 0) + " recentMaxIncrease=" + (item.turnTimingRecentUpdateMaxIncreaseSeconds ?? "-") + " recentMaxExcess=" + (item.turnTimingRecentUpdateMaxExcessSeconds ?? 0) + " recentReset=" + (item.turnTimingRecentUpdateResetCount ?? 0) + " diagnostics=" + item.diagnosticSamples + " terminal=" + item.terminalSamples + " finalText=" + item.finalTextSamples).join("\n")
    : "- 无轮次指标。";
  const provenanceLines = Array.isArray(report.pageProvenance?.segments) && report.pageProvenance.segments.length > 0
    ? report.pageProvenance.segments.slice(0, 40).map((item) => "- fingerprint=" + (item.assetFingerprint || "-") + " samples=" + item.sampleCount + " seq=" + (item.firstSeq ?? "-") + ".." + (item.lastSeq ?? "-") + " ts=" + (item.firstAt || "-") + ".." + (item.lastAt || "-") + " scripts=" + (item.scriptCount ?? "-") + " styles=" + (item.stylesheetCount ?? "-") + " urlPaths=" + (Array.isArray(item.urlPaths) ? item.urlPaths.slice(0, 4).join(",") : "-")).join("\n")
    : "- 无页面 provenance segment。";
  const performanceLines = Array.isArray(report.pagePerformance?.sameOriginApiByPath) && report.pagePerformance.sameOriginApiByPath.length > 0
    ? report.pagePerformance.sameOriginApiByPath.slice(0, 80).map((item) => "- " + item.path + " kind=" + (item.routeKind || "same-origin-api") + " budgetMetric=" + (item.budgetMetric || "durationMs") + " samples=" + item.sampleCount + " p50=" + (item.p50Ms ?? "-") + "ms p75=" + (item.p75Ms ?? "-") + "ms p95=" + (item.p95Ms ?? "-") + "ms max=" + (item.maxMs ?? "-") + "ms >5s=" + (item.overFiveSecondCount ?? 0) + " streamOpenP95=" + (item.streamOpenP95Ms ?? "-") + "ms streamLifetime>5s=" + (item.streamLifetimeOverFiveSecondCount ?? 0) + " window=" + (item.firstAt || "-") + ".." + (item.lastAt || "-")).join("\n")
    : "- 无同源 API Resource Timing 样本。";
  const metricLines = Array.isArray(report.sampleMetrics?.timeline) && report.sampleMetrics.timeline.length > 0
    ? report.sampleMetrics.timeline.slice(0, 120).map((item) => "- #" + item.seq + " " + item.ts + " prompt=" + item.promptIndex + " totalElapsedSeconds=" + (item.totalElapsedSeconds ?? "-") + " recentUpdateSeconds=" + (item.recentUpdateSeconds ?? "-") + " terminal=" + item.terminalSeen + " finalText=" + item.finalResultTextSeen + " diagnostic=" + item.diagnosticSeen).join("\n")
    : "- 无采样指标。";
  const turnTimingTable = renderTurnTimingTable(report.sampleMetrics);
  return "# web-probe observe analysis\n\n"
    + "- stateDir: " + report.stateDir + "\n"
    + "- generatedAt: " + report.generatedAt + "\n"
    + "- samples: " + report.counts.samples + "\n"
    + "- control: " + report.counts.control + "\n"
    + "- network: " + report.counts.network + "\n"
    + "- console: " + (report.counts.console ?? 0) + "\n"
    + "- errors: " + report.counts.errors + "\n\n"
    + "## Findings\n\n" + findingLines + "\n\n"
    + "## Sample metrics\n\n"
    + "- sampleCount: " + (metricSummary.sampleCount ?? 0) + "\n"
    + "- withTotalElapsed: " + (metricSummary.withTotalElapsed ?? 0) + "\n"
    + "- withRecentUpdate: " + (metricSummary.withRecentUpdate ?? 0) + "\n"
    + "- diagnostics: " + (metricSummary.diagnostics ?? 0) + "\n"
    + "- promptSegments: " + (metricSummary.promptSegments ?? 0) + "\n\n"
    + "- turnColumns: " + (metricSummary.turnColumns ?? 0) + "\n"
    + "- turnTimingRows: " + (metricSummary.turnTimingRows ?? 0) + "\n"
    + "- turnTimingNonMonotonicCount: " + (metricSummary.turnTimingNonMonotonicCount ?? 0) + "\n"
    + "- turnTimingTotalElapsedDecreaseCount: " + (metricSummary.turnTimingTotalElapsedDecreaseCount ?? 0) + "\n"
    + "- turnTimingTerminalElapsedGrowthCount: " + (metricSummary.turnTimingTerminalElapsedGrowthCount ?? 0) + "\n"
    + "- turnTimingTerminalElapsedGrowthMaxSeconds: " + (metricSummary.turnTimingTerminalElapsedGrowthMaxSeconds ?? 0) + "\n"
    + "- turnTimingRecentUpdateJumpCount: " + (metricSummary.turnTimingRecentUpdateJumpCount ?? 0) + "\n"
    + "- turnTimingRecentUpdateSawtoothJumpCount: " + (metricSummary.turnTimingRecentUpdateSawtoothJumpCount ?? metricSummary.turnTimingRecentUpdateJumpCount ?? 0) + "\n"
    + "- turnTimingRecentUpdateStepCount: " + (metricSummary.turnTimingRecentUpdateStepCount ?? 0) + "\n"
    + "- turnTimingRecentUpdateMaxIncreaseSeconds: " + (metricSummary.turnTimingRecentUpdateMaxIncreaseSeconds ?? "-") + "\n"
    + "- turnTimingRecentUpdateMaxExcessSeconds: " + (metricSummary.turnTimingRecentUpdateMaxExcessSeconds ?? 0) + "\n"
    + "- turnTimingRecentUpdateResetCount: " + (metricSummary.turnTimingRecentUpdateResetCount ?? 0) + "\n\n"
    + "### Rounds\n\n" + roundLines + "\n\n"
    + "### Page provenance\n\n"
    + "- segmentCount: " + (report.pageProvenance?.summary?.segmentCount ?? 0) + "\n"
    + "- controlSegmentCount: " + (report.pageProvenance?.summary?.controlSegmentCount ?? 0) + "\n\n"
    + provenanceLines + "\n\n"
    + "### Page performance: same-origin API Resource Timing\n\n"
    + "- budgetMs: " + (report.pagePerformance?.summary?.budgetMs ?? 5000) + "\n"
    + "- sameOriginApiPathCount: " + (report.pagePerformance?.summary?.sameOriginApiPathCount ?? 0) + "\n"
    + "- sameOriginApiSampleCount: " + (report.pagePerformance?.summary?.sameOriginApiSampleCount ?? 0) + "\n"
    + "- longLivedStreamPathCount: " + (report.pagePerformance?.summary?.longLivedStreamPathCount ?? 0) + "\n"
    + "- longLivedStreamSampleCount: " + (report.pagePerformance?.summary?.longLivedStreamSampleCount ?? 0) + "\n"
    + "- slowPathCount: " + (report.pagePerformance?.summary?.slowPathCount ?? 0) + "\n"
    + "- slowSampleCount: " + (report.pagePerformance?.summary?.slowSampleCount ?? 0) + "\n"
    + "- worstP95Ms: " + (report.pagePerformance?.summary?.worstP95Ms ?? "-") + "\n\n"
    + performanceLines + "\n\n"
    + "### Prompt network\n\n" + promptNetworkLines + "\n\n"
    + "### Runtime alerts\n\n"
    + "- httpErrorCount: " + (alertSummary.httpErrorCount ?? 0) + "\n"
    + "- requestFailedCount: " + (alertSummary.requestFailedCount ?? 0) + "\n"
    + "- domDiagnosticSampleCount: " + (alertSummary.domDiagnosticSampleCount ?? 0) + "\n"
    + "- consoleAlertCount: " + (alertSummary.consoleAlertCount ?? 0) + "\n"
    + "- pageErrorCount: " + (alertSummary.pageErrorCount ?? 0) + "\n\n"
    + "#### HTTP errors\n\n" + httpAlertLines + "\n\n"
    + "#### Request failed\n\n" + requestFailedLines + "\n\n"
    + "#### DOM diagnostics\n\n" + domDiagnosticLines + "\n\n"
    + "#### Console alerts\n\n" + consoleAlertLines + "\n\n"
    + "#### Console alert groups\n\n" + consoleAlertGroupLines + "\n\n"
    + "### Turn timing table\n\n"
    + turnTimingTable + "\n\n"
    + "### Aggregate timeline\n\n"
    + metricLines + "\n\n"
    + "## Command timeline\n\n" + commandLines + "\n\n"
    + "## State transitions\n\n" + transitionLines + "\n";
}

async function fileMeta(file) {
  const [buffer, stats] = await Promise.all([readFile(file), stat(file)]);
  return { byteCount: stats.size, sha256: "sha256:" + createHash("sha256").update(buffer).digest("hex") };
}

function sha256(value) {
  return "sha256:" + createHash("sha256").update(String(value)).digest("hex");
}

function urlPath(value) {
  try {
    const url = new URL(String(value || "http://invalid.local/"));
    return url.pathname;
  } catch {
    return "-";
  }
}

function compactLocation(value) {
  if (!value || typeof value !== "object") return null;
  return { urlPath: urlPath(value.url), lineNumber: value.lineNumber ?? null, columnNumber: value.columnNumber ?? null };
}

function limitText(value, limit) {
  const text = String(value ?? "");
  if (text.length <= limit) return text;
  return text.slice(0, Math.max(0, limit - 1)) + "…";
}
`;
}
