// SPEC: PJ2026-01040111 long-running Workbench observation.
// Responsibility: Runner filesystem setup, heartbeat, browser process monitoring, freeze policy, and passive page listeners source fragment.

export function nodeWebObserveRunnerRuntimeSource(): string {
  return String.raw`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 rotateExistingJsonlArtifacts() {
  for (const [key, file] of Object.entries(files)) {
    if (!file.endsWith(".jsonl")) continue;
    let meta = null;
    try {
      meta = await stat(file);
    } catch (error) {
      if (error?.code === "ENOENT") continue;
      throw error;
    }
    if (!meta?.isFile()) continue;
    const archiveFile = await uniqueArchiveFile(jsonlRotation.stamp + "-" + path.basename(file));
    await rename(file, archiveFile);
    jsonlRotation.files.push({ key, from: path.relative(stateDir, file), to: path.relative(stateDir, archiveFile), byteCount: meta.size });
  }
}

async function uniqueArchiveFile(name) {
  let candidate = path.join(dirs.archive, name);
  const parsed = path.parse(name);
  for (let index = 1; ; index += 1) {
    try {
      await stat(candidate);
      candidate = path.join(dirs.archive, parsed.name + "-" + index + parsed.ext);
    } catch (error) {
      if (error?.code === "ENOENT") return candidate;
      throw error;
    }
  }
}

function compactFileTimestamp(value) {
  return new Date(value).toISOString().replace(/[-:]/gu, "").replace(/[.]\d{3}Z$/u, "Z");
}

async function writeManifest(extra = {}) {
  const manifest = {
    ok: extra.status !== "failed",
    command: "web-probe-observe",
    specRef,
    jobId,
    pid: process.pid,
    stateDir,
    baseUrl,
    origin,
    targetPath,
    network: publicNetwork(playwrightProxy),
    navigation: { maxAttempts: navigationMaxAttempts, valuesRedacted: true },
    pageAuthority: { browser: "chromium", context: "shared-auth", pageMode: "dual-control-observer", controlPageId: pageId, observerPageId, continuityBreaksRecorded: true },
    pageProvenance: compactPageProvenance(currentPageProvenance),
    sampling: {
      mode: "passive",
      sampleIntervalMs,
      screenshotIntervalMs,
      maxSamples,
      maxRunMs,
      maxRunSeconds: maxRunMs > 0 ? Math.ceil(maxRunMs / 1000) : 0,
      observerRefreshIntervalMs,
      observerInitiatedDefault: false,
      responseBodyReadDefault: false,
    },
    resourceLimits: {
      maxScreenshots,
      maxPerformanceEntries,
      maxBrowserProcessHistoryEntries,
      maxBrowserFreezeSignalHistoryEntries,
      maxRealtimeEventEntries,
      maxRealtimeEventBodyBytes,
      maxDomEvidenceRows,
      maxProcessScanEntries,
      maxPerformanceCaptureMs,
      responseBodyMaxBytes,
      webPerformanceRequestBodyMaxBytes,
      maxJsonlQueueEntries,
      maxJsonlQueueBytes,
      maxJsonlLineBytes,
      jsonlFlushTimeoutMs,
      maxResponseBodyInFlight,
      maxPassiveListenerTasks,
      passiveTaskFlushTimeoutMs,
      closeStepTimeoutMs,
      maxProcessReadConcurrency,
      maxRealtimeTotalEntries,
      maxRealtimeTotalBodyBytes,
      valuesRedacted: true,
    },
    alertThresholds,
    browserFreezePolicy,
    projectManagement,
    jsonlRotation,
    jsonlWriter: jsonlWriterStats(),
    responseBodies: responseBodyStats(),
    passiveTasks: passiveTaskStats(),
    realtimeAggregate: realtimeAggregateLast,
    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,
    observerPageId,
    baseUrl,
    currentUrl: currentPageUrl(),
    observerUrl: pageUrl(observerPage),
    observerRefreshIntervalMs,
    observerLifecycle: {
      maxSamples,
      maxRunMs,
      maxRunSeconds: maxRunMs > 0 ? Math.ceil(maxRunMs / 1000) : 0,
      valuesRedacted: true,
    },
    resourceLimits: {
      screenshotCount,
      maxScreenshots,
      maxPerformanceEntries,
      maxBrowserProcessHistoryEntries,
      maxBrowserFreezeSignalHistoryEntries,
      maxRealtimeEventEntries,
      maxRealtimeEventBodyBytes,
      maxDomEvidenceRows,
      maxProcessScanEntries,
      maxPerformanceCaptureMs,
      responseBodyMaxBytes,
      webPerformanceRequestBodyMaxBytes,
      maxJsonlQueueEntries,
      maxJsonlQueueBytes,
      maxJsonlLineBytes,
      jsonlFlushTimeoutMs,
      maxResponseBodyInFlight,
      maxPassiveListenerTasks,
      passiveTaskFlushTimeoutMs,
      closeStepTimeoutMs,
      maxProcessReadConcurrency,
      maxRealtimeTotalEntries,
      maxRealtimeTotalBodyBytes,
      valuesRedacted: true,
    },
    lastObserverRefreshAt: Number.isFinite(lastObserverRefreshAtMs) ? new Date(lastObserverRefreshAtMs).toISOString() : null,
    pageProvenance: compactPageProvenance(currentPageProvenance),
    sampleSeq,
    commandSeq,
    activeCommandId,
    jsonlWriter: jsonlWriterStats(),
    responseBodies: responseBodyStats(),
    passiveTasks: passiveTaskStats(),
    realtimeAggregate: realtimeAggregateLast,
    updatedAt: new Date().toISOString(),
    uptimeMs: Date.now() - startedAtMs,
    ...extra,
  };
  await writeFile(files.heartbeat, JSON.stringify(heartbeat, null, 2) + "\n", { mode: 0o600 });
}

async function writeTerminalResourceStats(extra = {}) {
  for (const file of [files.manifest, files.heartbeat]) {
    let existing = {};
    try { existing = JSON.parse(await readFile(file, "utf8")); } catch {}
    await writeFile(file, JSON.stringify({
      ...existing,
      jsonlWriter: jsonlWriterStats(),
      responseBodies: responseBodyStats(),
      passiveTasks: passiveTaskStats(),
      terminalResources: sanitize(extra),
      updatedAt: new Date().toISOString(),
      valuesRedacted: true,
    }, null, 2) + "\n", { mode: 0o600 });
  }
}

function startHeartbeatPulse() {
  const timer = setInterval(() => {
    if (stopping) return;
    void writeHeartbeat({ status: terminalStatus, heartbeatPulse: true })
      .catch((error) => appendJsonl(files.errors, eventRecord("heartbeat-pulse-error", { error: errorSummary(error) })));
  }, 5000);
  if (timer && typeof timer.unref === "function") timer.unref();
  return timer;
}

function startBrowserProcessMonitor() {
  const intervalMs = Math.max(250, Math.floor(Number(alertThresholds.browserProcessSampleIntervalMs) || 1000));
  let stopped = false;
  let running = false;
  const tick = async (reason) => {
    if (stopped || running || stopping || browserFreezeBlocker) return;
    running = true;
    try {
      await collectBrowserProcessSample(reason || "interval");
    } catch (error) {
      await appendJsonl(files.errors, eventRecord("browser-process-monitor-error", { error: errorSummary(error), valuesRedacted: true })).catch(() => {});
    } finally {
      running = false;
    }
  };
  void tick("startup");
  const timer = setInterval(() => {
    void tick("interval");
  }, intervalMs);
  if (timer && typeof timer.unref === "function") timer.unref();
  return () => {
    stopped = true;
    clearInterval(timer);
  };
}

async function collectBrowserProcessSample(reason) {
  browserProcessMonitorSeq += 1;
  const tsMs = Date.now();
  const browserPid = browserProcessPid();
  const processSummary = await collectChromiumProcessSummary(browserPid);
  const growth = updateBrowserProcessHistory(tsMs, processSummary);
  const pages = [];
  if (page && !page.isClosed()) {
    pages.push(await collectBrowserPageRuntimeMetrics(page, { pageRole: "control", targetPageId: pageId, pageEpoch: controlPageEpoch }).catch((error) => browserPageRuntimeMetricError("control", pageId, controlPageEpoch, error)));
  }
  if (observerPage && !observerPage.isClosed()) {
    pages.push(await collectBrowserPageRuntimeMetrics(observerPage, { pageRole: "observer", targetPageId: observerPageId, pageEpoch: observerPageEpoch }).catch((error) => browserPageRuntimeMetricError("observer", observerPageId, observerPageEpoch, error)));
  }
  const sample = eventRecord("browser-process-sample", {
    seq: browserProcessMonitorSeq,
    reason,
    monitorIntervalMs: Math.max(250, Math.floor(Number(alertThresholds.browserProcessSampleIntervalMs) || 1000)),
    browserPid,
    process: processSummary,
    growth,
    pages,
    valuesRedacted: true,
  });
  await appendJsonl(files.browserProcess, sample);
  await enforceBrowserFreezePolicy(sample);
}

async function enforceBrowserFreezePolicy(sample) {
  if (browserFreezePolicy.enabled !== true || browserFreezeBlocker) return;
  const suppressRuntimeProbeFreeze = activeCommandType === "performanceCapture";
  const processSummary = sample && typeof sample.process === "object" ? sample.process : {};
  const growth = sample && typeof sample.growth === "object" ? sample.growth : {};
  const totalRssMb = Number(processSummary.totalRssMb);
  const processRssMb = Number(processSummary.maxProcessRssMb);
  const totalGrowthMb = Number(growth.totalRssGrowthMb);
  const processGrowthMb = Number(growth.maxProcessRssGrowthMb);

  for (const pageMetric of Array.isArray(sample.pages) ? sample.pages : []) {
    const effectiveMemory = pageMetric?.effectiveMemory && typeof pageMetric.effectiveMemory === "object" ? pageMetric.effectiveMemory : {};
    const effectiveHeapUsedMb = Number(effectiveMemory.effectiveHeapUsedMb);
    const effectiveJsHeapUsedMb = Number(effectiveMemory.effectiveJsHeapUsedMb);
    const heapGrowthMb = Number(effectiveMemory.heapUsedGrowthMb);
    const jsHeapGrowthMb = Number(effectiveMemory.jsHeapUsedGrowthMb);
    if (
      (Number.isFinite(effectiveHeapUsedMb) && effectiveHeapUsedMb >= browserFreezePolicy.memory.processRssBlockerMb)
      || (Number.isFinite(effectiveJsHeapUsedMb) && effectiveJsHeapUsedMb >= browserFreezePolicy.memory.processRssBlockerMb)
    ) {
      await triggerBrowserFreezeBlocker({
        kind: "memory-page-effective",
        rootCause: "frontend_browser_page_effective_memory_pressure",
        observed: {
          pageRole: pageMetric?.pageRole ?? null,
          pageId: pageMetric?.pageId ?? null,
          pageEpoch: pageMetric?.pageEpoch ?? null,
          totalRssMb,
          processRssMb,
          totalGrowthMb,
          processGrowthMb,
          effectiveHeapUsedMb: Number.isFinite(effectiveHeapUsedMb) ? effectiveHeapUsedMb : null,
          effectiveJsHeapUsedMb: Number.isFinite(effectiveJsHeapUsedMb) ? effectiveJsHeapUsedMb : null,
          baseline: pageMetric?.baseline ?? null,
          valuesRedacted: true,
        },
        threshold: { processRssBlockerMb: browserFreezePolicy.memory.processRssBlockerMb, policyScope: "per-page-effective-memory", valuesRedacted: true },
        sample: browserProcessSampleRef(sample),
        page: browserPageMetricRef(pageMetric),
      });
      return;
    }
    if (
      (Number.isFinite(heapGrowthMb) && heapGrowthMb >= browserFreezePolicy.memory.growthBlockerMb)
      || (Number.isFinite(jsHeapGrowthMb) && jsHeapGrowthMb >= browserFreezePolicy.memory.growthBlockerMb)
    ) {
      await triggerBrowserFreezeBlocker({
        kind: "memory-page-effective-growth",
        rootCause: "frontend_browser_page_memory_leak_or_unbounded_render_growth",
        observed: {
          pageRole: pageMetric?.pageRole ?? null,
          pageId: pageMetric?.pageId ?? null,
          pageEpoch: pageMetric?.pageEpoch ?? null,
          totalRssMb,
          processRssMb,
          totalGrowthMb,
          processGrowthMb,
          heapGrowthMb: Number.isFinite(heapGrowthMb) ? heapGrowthMb : null,
          jsHeapGrowthMb: Number.isFinite(jsHeapGrowthMb) ? jsHeapGrowthMb : null,
          baseline: pageMetric?.baseline ?? null,
          valuesRedacted: true,
        },
        threshold: { growthBlockerMb: browserFreezePolicy.memory.growthBlockerMb, windowMs: browserFreezePolicy.blockerWindowMs, policyScope: "per-page-effective-memory", valuesRedacted: true },
        sample: browserProcessSampleRef(sample),
        page: browserPageMetricRef(pageMetric),
      });
      return;
    }
    const responsiveness = pageMetric?.responsiveness && typeof pageMetric.responsiveness === "object" ? pageMetric.responsiveness : {};
    const responsivenessLatencyMs = Number(responsiveness.latencyMs);
    if (responsiveness.timeout === true || (Number.isFinite(responsivenessLatencyMs) && responsivenessLatencyMs >= browserFreezePolicy.responsiveness.latencyBlockerMs)) {
      const detail = {
        rootCause: "frontend_browser_page_unresponsive_to_playwright",
        observed: {
          responsivenessLatencyMs: Number.isFinite(responsivenessLatencyMs) ? responsivenessLatencyMs : null,
          responsivenessTimeout: responsiveness.timeout === true,
          valuesRedacted: true,
        },
        threshold: {
          latencyBlockerMs: browserFreezePolicy.responsiveness.latencyBlockerMs,
          eventBlockerCount: browserFreezePolicy.responsiveness.eventBlockerCount,
          windowMs: browserFreezePolicy.blockerWindowMs,
          valuesRedacted: true,
        },
      };
      if (suppressRuntimeProbeFreeze) {
        await appendBrowserFreezeSuppressedSignal("playwright-responsiveness", sample, pageMetric, detail, "performance-capture-active");
      } else {
        const signal = recordBrowserFreezeSignal("playwright-responsiveness", sample, pageMetric, detail);
        if (signal.burst.length >= browserFreezePolicy.responsiveness.eventBlockerCount) {
          await triggerBrowserFreezeBlocker(signal);
          return;
        }
      }
    }
    const cdp = pageMetric?.cdp && typeof pageMetric.cdp === "object" ? pageMetric.cdp : {};
    const calls = Array.isArray(cdp.calls) ? cdp.calls : [];
    const metricTimeoutCalls = calls.filter((call) => call?.timeout === true && call?.method !== "Runtime.evaluate");
    const sessionTimeoutCount = calls.length === 0 ? Number(cdp.timeoutCount || 0) : 0;
    const metricTimeoutCount = metricTimeoutCalls.length + (Number.isFinite(sessionTimeoutCount) ? sessionTimeoutCount : 0);
    if (metricTimeoutCount > 0) {
      const detail = {
        rootCause: "frontend_browser_cdp_metrics_unresponsive",
        observed: {
          cdpMetricsTimeoutCount: metricTimeoutCount,
          methods: metricTimeoutCalls.map((call) => call.method || "unknown").slice(0, 8),
          valuesRedacted: true,
        },
        threshold: {
          metricsTimeoutBlockerCount: browserFreezePolicy.cdp.metricsTimeoutBlockerCount,
          windowMs: browserFreezePolicy.blockerWindowMs,
          valuesRedacted: true,
        },
      };
      if (suppressRuntimeProbeFreeze) {
        await appendBrowserFreezeSuppressedSignal("cdp-metrics-timeout", sample, pageMetric, detail, "performance-capture-active");
      } else {
        const signal = recordBrowserFreezeSignal("cdp-metrics-timeout", sample, pageMetric, detail);
        if (signal.burst.length >= browserFreezePolicy.cdp.metricsTimeoutBlockerCount) {
          await triggerBrowserFreezeBlocker(signal);
          return;
        }
      }
    }
  }
}

async function appendBrowserFreezeSuppressedSignal(kind, sample, pageMetric, detail, reason) {
  await appendJsonl(files.browserProcess, eventRecord("browser-freeze-signal-suppressed", {
    kind,
    reason,
    activeCommandId,
    activeCommandType,
    rootCause: detail?.rootCause ?? null,
    observed: detail?.observed ?? null,
    threshold: detail?.threshold ?? null,
    sample: browserProcessSampleRef(sample),
    page: browserPageMetricRef(pageMetric),
    valuesRedacted: true,
  })).catch(() => {});
}

function recordBrowserFreezeSignal(kind, sample, pageMetric, detail) {
  const tsMs = Date.parse(String(sample?.ts || ""));
  const signal = {
    kind,
    ts: sample?.ts ?? new Date().toISOString(),
    tsMs: Number.isFinite(tsMs) ? tsMs : Date.now(),
    pageRole: pageMetric?.pageRole ?? null,
    pageId: pageMetric?.pageId ?? null,
    pageEpoch: pageMetric?.pageEpoch ?? null,
    sample: browserProcessSampleRef(sample),
    page: browserPageMetricRef(pageMetric),
    ...detail,
    valuesRedacted: true,
  };
  browserFreezeSignalHistory.push(signal);
  const windowMs = browserFreezePolicy.blockerWindowMs;
  const cutoff = signal.tsMs - windowMs;
  while (browserFreezeSignalHistory.length > 0 && Number(browserFreezeSignalHistory[0].tsMs || 0) < cutoff) browserFreezeSignalHistory.shift();
  while (browserFreezeSignalHistory.length > maxBrowserFreezeSignalHistoryEntries) browserFreezeSignalHistory.shift();
  return {
    kind,
    rootCause: detail.rootCause,
    observed: detail.observed,
    threshold: detail.threshold,
    sample: browserProcessSampleRef(sample),
    page: browserPageMetricRef(pageMetric),
    burst: browserFreezeSignalHistory.filter((item) => item.kind === kind && item.tsMs >= cutoff).slice(-20),
    valuesRedacted: true,
  };
}

async function triggerBrowserFreezeBlocker(trigger) {
  if (browserFreezeBlocker) return;
  stopping = true;
  terminalStatus = "failed";
  const base = {
    id: "browser-freeze-policy-blocker",
    severity: "red",
    blocking: true,
    kind: trigger.kind,
    summary: "web-probe runner matched YAML browserFreezePolicy and stopped the browser; this run must stay red instead of refreshing or falling back",
    rootCause: trigger.rootCause,
    rootCauseStatus: "confirmed-from-runner-browser-freeze-policy",
    rootCauseConfidence: "high",
    fallbackAllowed: false,
    observerRefreshAllowed: false,
    policySource: "config/hwlab-node-lanes.yaml#webProbe.browserFreezePolicy via UNIDESK_WEB_OBSERVE_BROWSER_FREEZE_POLICY_JSON",
    observed: trigger.observed || null,
    threshold: trigger.threshold || null,
    sample: trigger.sample || null,
    page: trigger.page || null,
    burst: Array.isArray(trigger.burst) ? trigger.burst.slice(0, 20) : [],
    valuesRedacted: true,
  };
  browserFreezeBlocker = { ...base, browserKill: { ok: false, pending: true, valuesRedacted: true }, valuesRedacted: true };
  const browserKill = browserFreezePolicy.kill.enabled === true
    ? await terminateBrowserForFreezeBlocker(base).catch((error) => ({ ok: false, error: errorSummary(error), valuesRedacted: true }))
    : { ok: true, skipped: true, reason: "kill-disabled-by-yaml-policy", valuesRedacted: true };
  browserFreezeBlocker = { ...base, browserKill, valuesRedacted: true };
  await appendJsonl(files.browserProcess, eventRecord("browser-freeze-blocker", browserFreezeBlocker)).catch(() => {});
  await appendJsonl(files.errors, eventRecord("browser-freeze-blocker", {
    ...browserFreezeBlocker,
    error: {
      name: "BrowserFreezeBlocker",
      message: "YAML browserFreezePolicy matched: " + String(trigger.kind || "unknown"),
      details: browserFreezeBlocker,
      valuesRedacted: true,
    },
  })).catch(() => {});
  await writeHeartbeat({ status: "failed", blocker: browserFreezeBlocker }).catch(() => {});
  await writeManifest({ status: "failed", blocker: browserFreezeBlocker }).catch(() => {});
}

async function terminateBrowserForFreezeBlocker(blocker) {
  const result = await terminateTrackedBrowserProcesses("freeze-blocker:" + String(blocker.kind || "unknown"));
  return { ...result, blockerKind: blocker.kind, valuesRedacted: true };
}

function rememberTrackedBrowserProcesses(processes) {
  const merged = new Map(trackedBrowserProcessIdentities.map((item) => [item.pid + ":" + item.startTicks, item]));
  for (const item of Array.isArray(processes) ? processes : []) {
    if (!Number.isInteger(item.pid) || !Number.isInteger(item.startTicks) || !item.role) continue;
    merged.set(item.pid + ":" + item.startTicks, {
      pid: item.pid,
      ppid: item.ppid,
      pgid: item.pgid,
      sid: item.sid,
      startTicks: item.startTicks,
      role: item.role,
      commandHash: item.commandHash || null,
      valuesRedacted: true,
    });
  }
  trackedBrowserProcessIdentities = [...merged.values()].slice(-Math.min(maxProcessScanEntries, 1024));
}

async function matchingTrackedBrowserProcesses() {
  const rows = await Promise.all(trackedBrowserProcessIdentities.map(async (identity) => {
    const current = await readOneProcProcessStat(identity.pid).catch(() => null);
    return current && current.startTicks === identity.startTicks ? { ...identity, currentPpid: current.ppid, currentPgid: current.pgid, currentSid: current.sid } : null;
  }));
  return rows.filter(Boolean);
}

async function waitForTrackedBrowserExit(timeoutMs, pollIntervalMs) {
  const deadline = Date.now() + timeoutMs;
  let remaining = await matchingTrackedBrowserProcesses();
  while (remaining.length > 0 && Date.now() <= deadline) {
    await boundedCleanupSleep(pollIntervalMs);
    remaining = await matchingTrackedBrowserProcesses();
  }
  return remaining;
}

async function terminateTrackedBrowserProcesses(reason, requiredDiscovery = null) {
  let refreshDiscovery;
  try {
    const summary = await collectChromiumProcessSummary(null);
    refreshDiscovery = { ok: true, processScan: summary.processScan, chromiumProcessCount: summary.chromiumProcessCount, valuesRedacted: true };
  } catch (error) {
    refreshDiscovery = { ok: false, processScan: lastProcessScan, error: errorSummary(error), valuesRedacted: true };
  }
  const discovery = requiredDiscovery || refreshDiscovery;
  const identityScanUnavailable = browserLaunchAttempted === true
    && (discovery?.ok !== true || discovery?.processScan?.complete !== true);
  const initial = await matchingTrackedBrowserProcesses();
  const result = {
    ok: initial.length === 0 && !identityScanUnavailable,
    reason,
    browserLaunchAttempted,
    identityScanUnavailable,
    requiredDiscovery: discovery,
    refreshDiscovery,
    trackedIdentityCount: trackedBrowserProcessIdentities.length,
    initialLiveCount: initial.length,
    gracefulSignal: browserFreezePolicy.kill.gracefulSignal,
    forceSignal: browserFreezePolicy.kill.forceSignal,
    graceMs: browserFreezePolicy.kill.graceMs,
    pollIntervalMs: browserFreezePolicy.kill.pollIntervalMs,
    gracefulSentCount: 0,
    forceSentCount: 0,
    remainingIdentityCount: initial.length,
    valuesRedacted: true,
  };
  for (const item of [...initial].sort((a, b) => b.pid - a.pid)) {
    try {
      process.kill(item.pid, browserFreezePolicy.kill.gracefulSignal);
      result.gracefulSentCount += 1;
    } catch {}
  }
  let remaining = await waitForTrackedBrowserExit(browserFreezePolicy.kill.graceMs, browserFreezePolicy.kill.pollIntervalMs);
  for (const item of [...remaining].sort((a, b) => b.pid - a.pid)) {
    try {
      process.kill(item.pid, browserFreezePolicy.kill.forceSignal);
      result.forceSentCount += 1;
    } catch {}
  }
  remaining = await waitForTrackedBrowserExit(browserFreezePolicy.kill.graceMs, browserFreezePolicy.kill.pollIntervalMs);
  result.remainingIdentityCount = remaining.length;
  result.remainingIdentities = remaining.slice(0, 50).map((item) => ({ pid: item.pid, startTicks: item.startTicks, role: item.role, originalPpid: item.ppid, currentPpid: item.currentPpid, originalSid: item.sid, currentSid: item.currentSid, valuesRedacted: true }));
  result.ok = remaining.length === 0 && !identityScanUnavailable;
  return result;
}

function browserProcessSampleRef(sample) {
  return {
    ts: sample?.ts ?? null,
    seq: sample?.seq ?? null,
    sampleSeq: sample?.sampleSeq ?? null,
    browserPid: sample?.browserPid ?? null,
    totalRssMb: sample?.process?.totalRssMb ?? null,
    maxProcessRssMb: sample?.process?.maxProcessRssMb ?? null,
    totalRssGrowthMb: sample?.growth?.totalRssGrowthMb ?? null,
    maxProcessRssGrowthMb: sample?.growth?.maxProcessRssGrowthMb ?? null,
    valuesRedacted: true,
  };
}

function browserPageMetricRef(pageMetric) {
  const responsiveness = pageMetric?.responsiveness && typeof pageMetric.responsiveness === "object" ? pageMetric.responsiveness : {};
  const cdp = pageMetric?.cdp && typeof pageMetric.cdp === "object" ? pageMetric.cdp : {};
  const effectiveMemory = pageMetric?.effectiveMemory && typeof pageMetric.effectiveMemory === "object" ? pageMetric.effectiveMemory : {};
  return {
    pageRole: pageMetric?.pageRole ?? null,
    pageId: pageMetric?.pageId ?? null,
    pageEpoch: pageMetric?.pageEpoch ?? null,
    timeoutMs: pageMetric?.timeoutMs ?? null,
    responsivenessLatencyMs: responsiveness.latencyMs ?? null,
    responsivenessTimeout: responsiveness.timeout === true,
    cdpTimeoutCount: cdp.timeoutCount ?? null,
    cdpErrorCount: cdp.errorCount ?? null,
    effectiveHeapUsedMb: Number.isFinite(Number(effectiveMemory.effectiveHeapUsedMb)) ? Number(effectiveMemory.effectiveHeapUsedMb) : null,
    effectiveJsHeapUsedMb: Number.isFinite(Number(effectiveMemory.effectiveJsHeapUsedMb)) ? Number(effectiveMemory.effectiveJsHeapUsedMb) : null,
    heapUsedGrowthMb: Number.isFinite(Number(effectiveMemory.heapUsedGrowthMb)) ? Number(effectiveMemory.heapUsedGrowthMb) : null,
    jsHeapUsedGrowthMb: Number.isFinite(Number(effectiveMemory.jsHeapUsedGrowthMb)) ? Number(effectiveMemory.jsHeapUsedGrowthMb) : null,
    baselineCapturedAt: pageMetric?.baseline?.capturedAt ?? null,
    valuesRedacted: true,
  };
}

function browserProcessPid() {
  try {
    const childProcess = browser && typeof browser.process === "function" ? browser.process() : null;
    const pid = Number(childProcess?.pid);
    return Number.isFinite(pid) && pid > 0 ? Math.floor(pid) : null;
  } catch {
    return null;
  }
}

async function collectChromiumProcessSummary(browserPid) {
  const all = await readProcProcessTable();
  const childrenByPpid = new Map();
  for (const item of all) {
    if (!Number.isFinite(item.ppid)) continue;
    const list = childrenByPpid.get(item.ppid) || [];
    list.push(item.pid);
    childrenByPpid.set(item.ppid, list);
  }
  const roots = [process.pid, browserPid].filter((pid, index, items) => Number.isFinite(Number(pid)) && Number(pid) > 0 && items.indexOf(pid) === index).map((pid) => Number(pid));
  const descendants = new Set(roots);
  const queue = roots.slice();
  while (queue.length > 0) {
    const pid = queue.shift();
    for (const child of childrenByPpid.get(pid) || []) {
      if (descendants.has(child)) continue;
      descendants.add(child);
      queue.push(child);
    }
  }
  const detailed = await readProcProcessDetails(all.filter((item) => descendants.has(item.pid)));
  const processes = detailed
    .map((item) => ({ ...item, role: classifyChromiumProcess(item) }))
    .filter((item) => item.role)
    .map((item) => ({
      pid: item.pid,
      ppid: item.ppid,
      pgid: item.pgid,
      sid: item.sid,
      startTicks: item.startTicks,
      name: item.name || null,
      role: item.role,
      rssBytes: item.rssBytes,
      vmSizeBytes: item.vmSizeBytes,
      commandHash: item.cmdline ? sha256Text(item.cmdline) : null,
      commandPreview: redactProcessCommandPreview(item.cmdline),
      valuesRedacted: true,
    }))
    .sort((a, b) => Number(b.rssBytes || 0) - Number(a.rssBytes || 0));
  rememberTrackedBrowserProcesses(processes);
  const roles = {};
  for (const item of processes) {
    const role = item.role || "unknown";
    const summary = roles[role] || { count: 0, rssBytes: 0, maxRssBytes: 0 };
    summary.count += 1;
    summary.rssBytes += Number(item.rssBytes || 0);
    summary.maxRssBytes = Math.max(summary.maxRssBytes, Number(item.rssBytes || 0));
    roles[role] = summary;
  }
  const totalRssBytes = processes.reduce((sum, item) => sum + Number(item.rssBytes || 0), 0);
  const maxProcess = processes[0] || null;
  return {
    sampledRootPids: roots,
    processScan: lastProcessScan,
    chromiumProcessCount: processes.length,
    totalRssBytes,
    totalRssMb: bytesToMb(totalRssBytes),
    maxProcessRssBytes: maxProcess ? Number(maxProcess.rssBytes || 0) : 0,
    maxProcessRssMb: maxProcess ? bytesToMb(maxProcess.rssBytes) : 0,
    maxProcess,
    roles,
    processes: processes.slice(0, 20),
    valuesRedacted: true,
  };
}

async function readProcProcessTable() {
  let entries = [];
  try {
    entries = await readdir("/proc");
  } catch (error) {
    lastProcessScan = { ok: false, complete: false, totalPidEntries: 0, scannedPidEntries: 0, failedPidEntries: 0, truncated: false, readConcurrency: maxProcessReadConcurrency, error: errorSummary(error), valuesRedacted: true };
    throw new Error("proc-process-table-unavailable");
  }
  const allNumeric = entries.map((name) => Number(name)).filter((pid) => Number.isInteger(pid) && pid > 0).sort((a, b) => b - a);
  const numeric = allNumeric.slice(0, maxProcessScanEntries);
  const rows = [];
  for (let offset = 0; offset < numeric.length; offset += maxProcessReadConcurrency) {
    const batch = await Promise.all(numeric.slice(offset, offset + maxProcessReadConcurrency).map((pid) => readOneProcProcessStat(pid).catch(() => null)));
    rows.push(...batch.filter(Boolean));
  }
  const failedPidEntries = numeric.length - rows.length;
  const truncated = numeric.length < allNumeric.length;
  const unusable = numeric.length > 0 && rows.length === 0;
  lastProcessScan = {
    ok: !unusable,
    complete: !unusable && !truncated,
    totalPidEntries: allNumeric.length,
    scannedPidEntries: numeric.length,
    readablePidEntries: rows.length,
    failedPidEntries,
    truncated,
    readConcurrency: maxProcessReadConcurrency,
    error: unusable ? "all-proc-stat-reads-failed" : null,
    valuesRedacted: true,
  };
  if (unusable) throw new Error("proc-process-table-unreadable");
  return rows;
}

async function readOneProcProcessStat(pid) {
  const statText = await readFile(path.join("/proc", String(pid), "stat"), "utf8");
  if (!statText) return null;
  return { pid, ...procIdentityFromStat(statText) };
}

async function readProcProcessDetails(rows) {
  const detailed = [];
  for (let offset = 0; offset < rows.length; offset += maxProcessReadConcurrency) {
    const batch = await Promise.all(rows.slice(offset, offset + maxProcessReadConcurrency).map((row) => readOneProcProcessDetails(row).catch(() => null)));
    detailed.push(...batch.filter(Boolean));
  }
  return detailed;
}

async function readOneProcProcessDetails(row) {
  const pid = row.pid;
  const [statusText, cmdlineText] = await Promise.all([
    readFile(path.join("/proc", String(pid), "status"), "utf8").catch(() => ""),
    readFile(path.join("/proc", String(pid), "cmdline"), "utf8").catch(() => ""),
  ]);
  if (!statusText && !cmdlineText) return null;
  const ppid = row.ppid;
  const name = procStatusField(statusText, "Name") || null;
  const rssKb = procStatusKb(statusText, "VmRSS");
  const vmSizeKb = procStatusKb(statusText, "VmSize");
  return {
    pid,
    ppid,
    pgid: row.pgid,
    sid: row.sid,
    startTicks: row.startTicks,
    name,
    cmdline: String(cmdlineText || "").replace(/\0/gu, " ").replace(/\s+/gu, " ").trim(),
    rssBytes: Number.isFinite(rssKb) ? rssKb * 1024 : 0,
    vmSizeBytes: Number.isFinite(vmSizeKb) ? vmSizeKb * 1024 : 0,
  };
}

function procIdentityFromStat(value) {
  const text = String(value || "");
  const end = text.lastIndexOf(")");
  if (end < 0) return { ppid: null, pgid: null, sid: null, startTicks: null };
  const parts = text.slice(end + 1).trim().split(/\s+/u);
  const integerAt = (index) => Number.isInteger(Number(parts[index])) ? Number(parts[index]) : null;
  return { ppid: integerAt(1), pgid: integerAt(2), sid: integerAt(3), startTicks: integerAt(19) };
}

function procStatusField(value, key) {
  const prefix = String(key || "") + ":";
  for (const line of String(value || "").split(/\n/u)) {
    if (line.startsWith(prefix)) return line.slice(prefix.length).trim();
  }
  return null;
}

function procStatusKb(value, key) {
  const raw = procStatusField(value, key);
  const match = String(raw || "").match(/^(\d+)\s+kB$/u);
  return match ? Number(match[1]) : null;
}

function classifyChromiumProcess(item) {
  const cmdline = String(item?.cmdline || "");
  const combined = (cmdline + " " + String(item?.name || "")).toLowerCase();
  if (!/(?:chromium|chrome|headless_shell)/u.test(combined)) return null;
  if (/--type=renderer\b/u.test(cmdline)) return "renderer";
  if (/--type=gpu-process\b/u.test(cmdline)) return "gpu";
  if (/--type=utility\b/u.test(cmdline)) return "utility";
  if (/--type=zygote\b/u.test(cmdline)) return "zygote";
  if (/--type=broker\b/u.test(cmdline)) return "broker";
  if (/--type=/u.test(cmdline)) return "other";
  return "browser";
}

function redactProcessCommandPreview(value) {
  const text = String(value || "");
  if (!text) return null;
  const parts = text.split(/\s+/u).slice(0, 18).map((part) => {
    if (/--(?:proxy|token|secret|password|cookie|auth|key)[^=]*=/iu.test(part)) return part.replace(/=.*/u, "=[redacted]");
    return part.replace(/:\/\/[^/@\s]+@/u, "://[redacted]@");
  });
  return truncate(parts.join(" "), 260);
}

function updateBrowserProcessHistory(tsMs, processSummary) {
  const windowMs = Math.max(1000, Number(alertThresholds.browserRssGrowthWindowMs) || 30000);
  const totalRssBytes = Number(processSummary?.totalRssBytes || 0);
  const maxProcessRssBytes = Number(processSummary?.maxProcessRssBytes || 0);
  browserProcessHistory.push({ tsMs, totalRssBytes, maxProcessRssBytes });
  const retentionMs = Math.max(windowMs * 3, 180000);
  while (browserProcessHistory.length > 0 && tsMs - browserProcessHistory[0].tsMs > retentionMs) browserProcessHistory.shift();
  while (browserProcessHistory.length > maxBrowserProcessHistoryEntries) browserProcessHistory.shift();
  const windowStartMs = tsMs - windowMs;
  const candidates = browserProcessHistory.filter((item) => item.tsMs >= windowStartMs && item.tsMs <= tsMs);
  const baseline = candidates[0] || browserProcessHistory[0] || null;
  const totalRssGrowthBytes = baseline ? totalRssBytes - Number(baseline.totalRssBytes || 0) : 0;
  const maxProcessRssGrowthBytes = baseline ? maxProcessRssBytes - Number(baseline.maxProcessRssBytes || 0) : 0;
  return {
    windowMs,
    baselineAt: baseline ? new Date(baseline.tsMs).toISOString() : null,
    baselineTotalRssBytes: baseline ? baseline.totalRssBytes : null,
    baselineMaxProcessRssBytes: baseline ? baseline.maxProcessRssBytes : null,
    totalRssGrowthBytes,
    totalRssGrowthMb: bytesToMb(totalRssGrowthBytes),
    maxProcessRssGrowthBytes,
    maxProcessRssGrowthMb: bytesToMb(maxProcessRssGrowthBytes),
    valuesRedacted: true,
  };
}

async function collectBrowserPageRuntimeMetrics(targetPage, { pageRole, targetPageId, pageEpoch }) {
  const timeoutMs = Math.max(1000, Math.floor(Number(alertThresholds.playwrightResponsivenessRedMs) || 5000));
  const startedAtMs = Date.now();
  let session = null;
  const result = {
    pageRole,
    pageId: targetPageId,
    pageEpoch: Number.isFinite(Number(pageEpoch)) ? Number(pageEpoch) : 0,
    url: pageUrl(targetPage),
    timeoutMs,
    responsiveness: null,
    cdp: { timeoutCount: 0, errorCount: 0, calls: [] },
    valuesRedacted: true,
  };
  try {
    session = await withHardTimeout(targetPage.context().newCDPSession(targetPage), timeoutMs, "newCDPSession exceeded " + timeoutMs + "ms");
    const enable = await timedCdpSend(session, "Performance.enable", {}, timeoutMs);
    if (enable.ok !== true) result.cdp.calls.push({ method: "Performance.enable", ...enable });
    result.responsiveness = await timedCdpSend(session, "Runtime.evaluate", { expression: "1", returnByValue: true }, timeoutMs);
    result.cdp.calls.push({ method: "Runtime.evaluate", ...result.responsiveness });
    const performance = await timedCdpSend(session, "Performance.getMetrics", {}, timeoutMs);
    result.cdp.calls.push({ method: "Performance.getMetrics", ...performance });
    result.performance = performance.value;
    const heap = await timedCdpSend(session, "Runtime.getHeapUsage", {}, timeoutMs);
    result.cdp.calls.push({ method: "Runtime.getHeapUsage", ...heap });
    result.heapUsage = heap.value;
    const domCounters = await timedCdpSend(session, "Memory.getDOMCounters", {}, timeoutMs);
    result.cdp.calls.push({ method: "Memory.getDOMCounters", ...domCounters });
    result.domCounters = domCounters.value;
    result.cdp.timeoutCount = result.cdp.calls.filter((item) => item.timeout === true).length;
    result.cdp.errorCount = result.cdp.calls.filter((item) => item.ok !== true).length;
  } catch (error) {
    result.sessionError = errorSummary(error);
    result.cdp.timeoutCount = isTimeoutErrorMessage(error?.message) ? 1 : 0;
    result.cdp.errorCount = 1;
  } finally {
    result.latencyMs = Date.now() - startedAtMs;
    applyBrowserPageRuntimeBaseline(result);
    if (session) await withHardTimeout(session.detach(), 1000, "CDP session detach exceeded 1000ms").catch(() => {});
  }
  return result;
}

function applyBrowserPageRuntimeBaseline(result) {
  const key = [result.pageRole || "unknown", result.pageId || "unknown", Number.isFinite(Number(result.pageEpoch)) ? Number(result.pageEpoch) : 0].join(":");
  const current = browserPageRuntimeMemorySnapshot(result);
  const existing = browserPageRuntimeBaselines.get(key);
  if (!existing && current) {
    const prefix = [result.pageRole || "unknown", result.pageId || "unknown"].join(":") + ":";
    for (const existingKey of browserPageRuntimeBaselines.keys()) {
      if (existingKey.startsWith(prefix)) browserPageRuntimeBaselines.delete(existingKey);
    }
    browserPageRuntimeBaselines.set(key, { ...current, capturedAt: new Date().toISOString(), source: "first-page-runtime-sample", valuesRedacted: true });
  }
  const baseline = browserPageRuntimeBaselines.get(key) || null;
  result.baseline = baseline ? {
    capturedAt: baseline.capturedAt,
    source: baseline.source,
    heapUsedMb: baseline.heapUsedMb,
    jsHeapUsedMb: baseline.jsHeapUsedMb,
    domNodes: baseline.domNodes,
    valuesRedacted: true,
  } : null;
  result.effectiveMemory = browserPageEffectiveMemory(current, baseline);
}

function browserPageRuntimeMemorySnapshot(result) {
  const heapUsedBytes = Number(result?.heapUsage?.usedSize);
  const metrics = result?.performance?.metrics && typeof result.performance.metrics === "object" ? result.performance.metrics : {};
  const jsHeapUsedBytes = Number(metrics.JSHeapUsedSize);
  const domNodes = Number(result?.domCounters?.nodes ?? metrics.Nodes);
  if (!Number.isFinite(heapUsedBytes) && !Number.isFinite(jsHeapUsedBytes) && !Number.isFinite(domNodes)) return null;
  return {
    heapUsedBytes: Number.isFinite(heapUsedBytes) ? heapUsedBytes : null,
    heapUsedMb: Number.isFinite(heapUsedBytes) ? bytesToMb(heapUsedBytes) : null,
    jsHeapUsedBytes: Number.isFinite(jsHeapUsedBytes) ? jsHeapUsedBytes : null,
    jsHeapUsedMb: Number.isFinite(jsHeapUsedBytes) ? bytesToMb(jsHeapUsedBytes) : null,
    domNodes: Number.isFinite(domNodes) ? domNodes : null,
    valuesRedacted: true,
  };
}

function browserPageEffectiveMemory(current, baseline) {
  if (!current) return { available: false, baselineAvailable: Boolean(baseline), valuesRedacted: true };
  const heapUsedGrowthBytes = numericDelta(current.heapUsedBytes, baseline?.heapUsedBytes);
  const jsHeapUsedGrowthBytes = numericDelta(current.jsHeapUsedBytes, baseline?.jsHeapUsedBytes);
  const domNodesGrowth = numericDelta(current.domNodes, baseline?.domNodes);
  return {
    available: true,
    baselineAvailable: Boolean(baseline),
    heapUsedMb: current.heapUsedMb,
    jsHeapUsedMb: current.jsHeapUsedMb,
    effectiveHeapUsedMb: Number.isFinite(heapUsedGrowthBytes) ? bytesToMb(heapUsedGrowthBytes) : current.heapUsedMb,
    effectiveJsHeapUsedMb: Number.isFinite(jsHeapUsedGrowthBytes) ? bytesToMb(jsHeapUsedGrowthBytes) : current.jsHeapUsedMb,
    heapUsedGrowthMb: Number.isFinite(heapUsedGrowthBytes) ? bytesToMb(heapUsedGrowthBytes) : null,
    jsHeapUsedGrowthMb: Number.isFinite(jsHeapUsedGrowthBytes) ? bytesToMb(jsHeapUsedGrowthBytes) : null,
    domNodes: current.domNodes,
    domNodesGrowth: Number.isFinite(domNodesGrowth) ? domNodesGrowth : null,
    valuesRedacted: true,
  };
}

function numericDelta(current, baseline) {
  const value = Number(current);
  const base = Number(baseline);
  if (!Number.isFinite(value) || !Number.isFinite(base)) return null;
  return value - base;
}

function browserPageRuntimeMetricError(pageRole, targetPageId, pageEpoch, error) {
  return {
    pageRole,
    pageId: targetPageId,
    pageEpoch: Number.isFinite(Number(pageEpoch)) ? Number(pageEpoch) : 0,
    url: null,
    timeoutMs: Math.max(1000, Math.floor(Number(alertThresholds.playwrightResponsivenessRedMs) || 5000)),
    responsiveness: { ok: false, timeout: isTimeoutErrorMessage(error?.message), error: errorSummary(error), valuesRedacted: true },
    cdp: { timeoutCount: isTimeoutErrorMessage(error?.message) ? 1 : 0, errorCount: 1, calls: [] },
    valuesRedacted: true,
  };
}

async function timedCdpSend(session, method, params, timeoutMs) {
  const startedAtMs = Date.now();
  try {
    const value = await withHardTimeout(session.send(method, params || {}), timeoutMs, method + " exceeded " + timeoutMs + "ms");
    return { ok: true, timeout: false, latencyMs: Date.now() - startedAtMs, value: compactCdpValue(method, value), valuesRedacted: true };
  } catch (error) {
    return { ok: false, timeout: isTimeoutErrorMessage(error?.message), latencyMs: Date.now() - startedAtMs, error: errorSummary(error), valuesRedacted: true };
  }
}

function compactCdpValue(method, value) {
  if (!value || typeof value !== "object") return value ?? null;
  if (method === "Performance.getMetrics") {
    const wanted = new Set(["Timestamp", "Documents", "Frames", "JSEventListeners", "Nodes", "LayoutCount", "RecalcStyleCount", "LayoutDuration", "RecalcStyleDuration", "ScriptDuration", "TaskDuration", "JSHeapUsedSize", "JSHeapTotalSize"]);
    const metrics = {};
    for (const item of Array.isArray(value.metrics) ? value.metrics : []) {
      if (wanted.has(item?.name)) metrics[item.name] = Number.isFinite(Number(item?.value)) ? Number(item.value) : null;
    }
    return { metricCount: Array.isArray(value.metrics) ? value.metrics.length : 0, metrics, valuesRedacted: true };
  }
  if (method === "Runtime.getHeapUsage") {
    return {
      usedSize: Number.isFinite(Number(value.usedSize)) ? Number(value.usedSize) : null,
      totalSize: Number.isFinite(Number(value.totalSize)) ? Number(value.totalSize) : null,
      embedderHeapUsedSize: Number.isFinite(Number(value.embedderHeapUsedSize)) ? Number(value.embedderHeapUsedSize) : null,
      valuesRedacted: true,
    };
  }
  if (method === "Memory.getDOMCounters") {
    return {
      documents: Number.isFinite(Number(value.documents)) ? Number(value.documents) : null,
      nodes: Number.isFinite(Number(value.nodes)) ? Number(value.nodes) : null,
      jsEventListeners: Number.isFinite(Number(value.jsEventListeners)) ? Number(value.jsEventListeners) : null,
      valuesRedacted: true,
    };
  }
  if (method === "Runtime.evaluate") {
    return { resultType: value.result?.type ?? null, unserializableValue: value.result?.unserializableValue ?? null, exceptionDetails: value.exceptionDetails ? { text: truncate(value.exceptionDetails.text || "", 200), valuesRedacted: true } : null, valuesRedacted: true };
  }
  return { valuesRedacted: true };
}

function isTimeoutErrorMessage(value) {
  return /timeout|timed\s*out|exceeded\s+\d+\s*ms/iu.test(String(value || ""));
}

function bytesToMb(value) {
  const numeric = Number(value);
  if (!Number.isFinite(numeric)) return null;
  return Number((numeric / 1024 / 1024).toFixed(1));
}

function attachPassiveListeners(targetPage, pageRole = "control", targetPageId = pageId) {
  trackPassiveTask(
    () => installPagePerformanceProbe(targetPage, pageRole, targetPageId),
    "performance-probe-install:" + pageRole,
  );
  targetPage.on("request", (request) => {
    const webPerformancePayload = summarizeWebPerformanceRequestPayload(request);
    void appendJsonl(files.network, eventRecord("request", {
      pageRole,
      pageId: targetPageId,
      observerInitiated: false,
      commandId: activeCommandId,
      method: request.method(),
      url: safeUrl(request.url()),
      resourceType: request.resourceType(),
      frameUrl: safeFrameUrl(request.frame()),
      ...(webPerformancePayload ? { webPerformancePayload } : {}),
    }));
  });
  targetPage.on("response", (response) => {
    const request = response.request();
    const base = {
      pageRole,
      pageId: targetPageId,
      sampleSeq,
      observerInitiated: false,
      commandId: activeCommandId,
      method: request.method(),
      url: safeUrl(response.url()),
      resourceType: request.resourceType(),
      status: response.status(),
      statusText: response.statusText(),
      fromServiceWorker: response.fromServiceWorker(),
    };
    trackPassiveTask(async () => {
      const bodyFields = await summarizeWorkbenchResponseBody(response, request);
      await appendJsonl(files.network, eventRecord("response", { ...base, ...bodyFields }));
    }, "response-body-summary:" + pageRole);
  });
  targetPage.on("requestfailed", (request) => {
    void appendJsonl(files.network, eventRecord("requestfailed", {
      pageRole,
      pageId: targetPageId,
      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", { pageRole, pageId: targetPageId, type: message.type(), text: truncate(message.text(), 1000), location: message.location() }));
  });
  targetPage.on("pageerror", (error) => {
    void appendJsonl(files.errors, eventRecord("pageerror", { pageRole, pageId: targetPageId, error: errorSummary(error) }));
  });
  targetPage.on("crash", () => {
    void appendJsonl(files.errors, eventRecord("page-crash", { pageRole, pageId: targetPageId }));
  });
  targetPage.on("close", () => {
    void appendJsonl(files.control, eventRecord("continuity-break", { pageRole, pageId: targetPageId, reason: "page-closed" }));
  });
}

function summarizeWebPerformanceRequestPayload(request) {
  if (!request || request.method() !== "POST" || !isSameOriginWebPerformanceRequestUrl(request.url())) return null;
  let raw = null;
  try {
    raw = request.postData();
  } catch (error) {
    return {
      captureStatus: "post-data-read-failed",
      parseStatus: "post-data-read-failed",
      byteCount: null,
      byteLimit: webPerformanceRequestBodyMaxBytes,
      error: errorSummary(error),
      eventCount: null,
      events: [],
      valuesRedacted: true,
    };
  }
  if (raw === null || raw === undefined || raw === "") {
    return {
      captureStatus: "missing-body",
      parseStatus: "missing-body",
      byteCount: 0,
      byteLimit: webPerformanceRequestBodyMaxBytes,
      eventCount: 0,
      events: [],
      valuesRedacted: true,
    };
  }
  const byteCount = Buffer.byteLength(raw, "utf8");
  const bodyHash = sha256Text(raw);
  if (byteCount > webPerformanceRequestBodyMaxBytes) {
    return {
      captureStatus: "skipped-over-limit",
      parseStatus: "not-parsed-over-limit",
      byteCount,
      byteLimit: webPerformanceRequestBodyMaxBytes,
      bodyHash,
      truncated: true,
      eventCount: null,
      events: [],
      valuesRedacted: true,
    };
  }
  try {
    const parsed = JSON.parse(raw);
    const schemaVersion = typeof parsed?.schemaVersion === "string" ? parsed.schemaVersion : null;
    if (schemaVersion !== "hwlab-web-performance-v2") {
      return {
        captureStatus: "captured",
        parseStatus: "unsupported-schema",
        schemaVersion,
        byteCount,
        byteLimit: webPerformanceRequestBodyMaxBytes,
        bodyHash,
        eventCount: Array.isArray(parsed?.events) ? parsed.events.length : null,
        events: [],
        valuesRedacted: true,
      };
    }
    const events = (Array.isArray(parsed.events) ? parsed.events : []).slice(0, 80).map(compactWebPerformancePayloadEvent).filter(Boolean);
    return {
      captureStatus: "captured",
      parseStatus: "parsed",
      schemaVersion,
      byteCount,
      byteLimit: webPerformanceRequestBodyMaxBytes,
      bodyHash,
      truncated: false,
      eventCount: Array.isArray(parsed.events) ? parsed.events.length : 0,
      storedEventCount: events.length,
      events,
      valuesRedacted: true,
    };
  } catch (error) {
    return {
      captureStatus: "captured",
      parseStatus: "invalid-json",
      byteCount,
      byteLimit: webPerformanceRequestBodyMaxBytes,
      bodyHash,
      parseError: truncate(error?.message || String(error), 180),
      eventCount: null,
      events: [],
      valuesRedacted: true,
    };
  }
}

function isSameOriginWebPerformanceRequestUrl(value) {
  try {
    const url = new URL(String(value || ""), baseUrl);
    return url.origin === baseUrl && url.pathname === "/v1/web-performance";
  } catch {
    return false;
  }
}

function compactWebPerformancePayloadEvent(event) {
  if (!event || typeof event !== "object") return null;
  const detail = firstObject(event.detail, event.details, event.payload, event.data, event.metrics);
  const read = (key) => event?.[key] ?? detail?.[key] ?? null;
  const diagnosticCode = compactString(read("diagnosticCode") ?? read("code") ?? read("name"), 96);
  const reason = compactString(read("reason"), 96);
  const module = compactString(read("module"), 96);
  const eventType = compactString(read("eventType") ?? read("type") ?? read("kind"), 96);
  const traceId = compactTraceId(read("traceId"));
  const sessionId = compactString(read("sessionId"), 120);
  const replacedByKey = compactScalar(read("replacedByKey"));
  const eventId = compactString(read("eventId") ?? read("id"), 120);
  return {
    ts: compactString(read("ts") ?? read("timestamp") ?? read("time"), 64),
    eventType,
    diagnosticCode,
    reason,
    module,
    traceId,
    sessionIdHash: sessionId ? sha256Text(sessionId) : null,
    eventIdHash: eventId ? sha256Text(eventId) : null,
    eventCount: numberOrNull(read("eventCount")),
    deliveredCount: numberOrNull(read("deliveredCount")),
    chunkCount: numberOrNull(read("chunkCount")),
    flushDurationMs: numberOrNull(read("flushDurationMs")),
    droppedCount: numberOrNull(read("droppedCount")),
    maxItemsPerChunk: numberOrNull(read("maxItemsPerChunk")),
    maxChunkMs: numberOrNull(read("maxChunkMs")),
    replacedByKey: typeof replacedByKey === "boolean" || typeof replacedByKey === "number" ? replacedByKey : null,
    replacedByKeyHash: typeof replacedByKey === "string" ? sha256Text(replacedByKey) : null,
    valuesRedacted: true,
  };
}

function firstObject(...values) {
  for (const value of values) if (value && typeof value === "object" && !Array.isArray(value)) return value;
  return {};
}

function compactString(value, limit) {
  if (value === null || value === undefined) return null;
  return truncate(String(value), limit);
}

function compactTraceId(value) {
  const text = String(value || "");
  return /^trc_[A-Za-z0-9_-]+$/u.test(text) ? text : null;
}

function compactScalar(value) {
  if (value === null || value === undefined) return null;
  if (typeof value === "boolean") return value;
  const numeric = Number(value);
  if (Number.isFinite(numeric) && String(value).trim() !== "") return numeric;
  return truncate(String(value), 120);
}
`;
}
