← Files Codex Browser RecorderARCHIVED FILE

skills/record-browser/scripts/create-recording.mjs

33.5 KB · Sep 30, 2026 · 23:13 UTC

↓ Download file

import { tmpdir } from "node:os";

import { startBrowserRecordingForTab } from "./browser-recording.mjs";
import { doctor as inspectRecordingEnvironment } from "./doctor.mjs";
import {
  createRecordingArtifactTransaction,
  planSavedRecording,
} from "./recording-artifacts.mjs";
import {
  captureFailureCode,
  describeRecordingFailure,
  getRecordingCleanupDetails,
  sanitizeCaptureStatus,
  sanitizeRecordingFailure,
} from "./recording-outcome.mjs";
import {
  RECORDING_HARD_LIMIT_MS,
  validateRecordingRequest,
} from "./recording-policy.mjs";

export { describeRecordingFailure };

const ACTIVE_RECORDING_KEY = Symbol.for("codex-browser-recorder.active");
const ACTION_EVIDENCE_INTERVAL_MS = 50;
const ACTION_EVIDENCE_TIMEOUT_MS = 1000;
const BROWSER_VISIBILITY_POLL_INTERVAL_MS = 100;
const BROWSER_VISIBILITY_TIMEOUT_MS = 5000;
const POINTER_VISUAL_TAIL_MS = 200;
const CLEANUP_DEADLINE_MS = 5000;
const FINALIZATION_DEADLINE_MS = 10_000;
const TERMINAL_STATES = new Set(["cancelled", "completed", "failed"]);
const NATIVE_ADD_EVENT_LISTENER = AbortSignal.prototype.addEventListener;
const NATIVE_REMOVE_EVENT_LISTENER = AbortSignal.prototype.removeEventListener;
const NATIVE_ABORTED_GETTER = Object.getOwnPropertyDescriptor(
  AbortSignal.prototype,
  "aborted",
).get;

function addAbortListener(signal, listener) {
  Reflect.apply(NATIVE_ADD_EVENT_LISTENER, signal, ["abort", listener, {
    once: true,
  }]);
}

function isAborted(signal) {
  return Reflect.apply(NATIVE_ABORTED_GETTER, signal, []);
}

function removeAbortListener(signal, listener) {
  Reflect.apply(NATIVE_REMOVE_EVENT_LISTENER, signal, ["abort", listener]);
}

function awaitAbortable(operation, signal) {
  const operationPromise = Promise.resolve(operation);
  return new Promise((resolve, reject) => {
    let settled = false;
    const abort = () => {
      if (settled) return;
      settled = true;
      removeAbortListener(signal, abort);
      reject(sanitizeRecordingFailure({ code: "recording_cancelled" }));
    };

    addAbortListener(signal, abort);
    if (isAborted(signal)) abort();
    operationPromise.then(
      (value) => {
        if (settled) return;
        settled = true;
        removeAbortListener(signal, abort);
        resolve(value);
      },
      (error) => {
        if (settled) return;
        settled = true;
        removeAbortListener(signal, abort);
        reject(error);
      },
    );
  });
}

function clockNow(clock) {
  return typeof clock.now === "function" ? clock.now() : Date.now();
}

function hasPointerEvidenceAfterActionBoundary({
  actionStartedAtEpochMs,
  beforeEvents,
  capture,
}) {
  return (
    Number.isFinite(actionStartedAtEpochMs) &&
    Number.isInteger(beforeEvents) &&
    Number.isInteger(capture?.cursorEventsCaptured) &&
    capture.cursorEventsCaptured > beforeEvents &&
    Number.isFinite(capture?.cursorLastEventEpochMs) &&
    capture.cursorLastEventEpochMs >= actionStartedAtEpochMs
  );
}

function hasChildFramePointerEvidenceAfterActionBoundary({
  beforeEvents,
  capture,
}) {
  return (
    Number.isInteger(beforeEvents) &&
    Number.isInteger(capture?.cursorChildFrameEventsCaptured) &&
    capture.cursorChildFrameEventsCaptured > beforeEvents
  );
}

function hasFrameEvidenceAfterActionBoundary({
  beforeFrames,
  capture,
}) {
  return (
    Number.isInteger(beforeFrames) &&
    Number.isInteger(capture?.framesReceived) &&
    capture.framesReceived > beforeFrames
  );
}

function waitForClockDelay(clock, delayMs, signal) {
  return new Promise((resolve, reject) => {
    let settled = false;
    let timer;
    const finish = (error) => {
      if (settled) return;
      settled = true;
      clock.clearTimeout(timer);
      removeAbortListener(signal, abort);
      if (error == null) {
        resolve();
      } else {
        reject(error);
      }
    };
    const abort = () => {
      finish(sanitizeRecordingFailure({ code: "recording_cancelled" }));
    };

    timer = clock.setTimeout(() => finish(), delayMs);
    addAbortListener(signal, abort);
    if (isAborted(signal)) abort();
  });
}

function settleBeforeDeadline(
  operation,
  clock,
  deadlineMs = CLEANUP_DEADLINE_MS,
) {
  const operationPromise = Promise.resolve(operation);
  return new Promise((resolve) => {
    let settled = false;
    let timer;
    const finish = (settlement) => {
      if (settled) return;
      settled = true;
      clock.clearTimeout(timer);
      resolve(settlement);
    };
    timer = clock.setTimeout(
      () => finish({ status: "timed_out" }),
      deadlineMs,
    );
    operationPromise.then(
      (value) => finish({ status: "fulfilled", value }),
      (reason) => finish({ reason, status: "rejected" }),
    );
  });
}

async function tabRemainsListed(browser, tab) {
  if (
    typeof tab?.id !== "string" ||
    tab.id.length === 0 ||
    typeof browser?.tabs?.list !== "function"
  ) {
    throw new Error("Browser tab inventory is unavailable");
  }
  const tabs = await browser.tabs.list();
  if (!Array.isArray(tabs)) {
    throw new Error("Browser tab inventory is unavailable");
  }
  return tabs.some((candidate) => candidate?.id === tab.id);
}

async function closeTabWithinRetryBudget(browser, tab, clock) {
  let requiresClose = true;
  let cleanup;
  for (let attempt = 0; attempt < 2; attempt += 1) {
    cleanup = await settleBeforeDeadline(
      (async () => {
        if (requiresClose) {
          if (typeof tab?.close !== "function") {
            throw new Error("Fresh Browser tab cannot be closed");
          }
          await tab.close();
          requiresClose = false;
        }
        if (await tabRemainsListed(browser, tab)) {
          requiresClose = true;
          throw new Error("Fresh Browser tab remained open after closure");
        }
      })(),
      clock,
    );
    if (cleanup.status !== "rejected") break;
  }
  return cleanup;
}

async function createFreshTab(browser, signal, clock) {
  const operation = Promise.resolve().then(() => browser.tabs.new());
  try {
    return await awaitAbortable(operation, signal);
  } catch (error) {
    if (error?.code !== "recording_cancelled") throw error;

    const creation = await settleBeforeDeadline(operation, clock);
    if (creation.status === "rejected") {
      throw error;
    }
    if (creation.status === "timed_out") {
      void operation.then(
        (lateTab) =>
          closeTabWithinRetryBudget(browser, lateTab, clock).catch(() => {}),
        () => {},
      );
      throw sanitizeRecordingFailure(error, {
        browserTabCleanupIncomplete: true,
      });
    }
    const cleanup = await closeTabWithinRetryBudget(
      browser,
      creation.value,
      clock,
    );
    if (cleanup.status !== "fulfilled") {
      throw sanitizeRecordingFailure(error, {
        browserTabCleanupIncomplete: true,
      });
    }
    throw error;
  }
}

async function establishBrowserVisibility(
  browser,
  targetVisible,
  signal,
  clock,
) {
  const operationCancellation = new AbortController();
  const cancelOperation = () => operationCancellation.abort();
  addAbortListener(signal, cancelOperation);
  if (isAborted(signal)) cancelOperation();
  const visibilityOperation = (async () => {
    const visibility = await awaitAbortable(
      Promise.resolve().then(() =>
        browser.capabilities.get("visibility"),
      ),
      operationCancellation.signal,
    );
    if (
      typeof visibility?.get !== "function" ||
      typeof visibility?.set !== "function"
    ) {
      throw new Error("Browser visibility capability is unavailable");
    }
    await awaitAbortable(
      Promise.resolve().then(() => visibility.set(targetVisible)),
      operationCancellation.signal,
    );
    while (true) {
      const observedVisible = await awaitAbortable(
        Promise.resolve().then(() => visibility.get()),
        operationCancellation.signal,
      );
      if (observedVisible === targetVisible) return;
      await waitForClockDelay(
        clock,
        BROWSER_VISIBILITY_POLL_INTERVAL_MS,
        operationCancellation.signal,
      );
    }
  })();
  const settlement = await settleBeforeDeadline(
    visibilityOperation,
    clock,
    BROWSER_VISIBILITY_TIMEOUT_MS,
  );
  if (settlement.status === "timed_out") {
    operationCancellation.abort();
  }
  removeAbortListener(signal, cancelOperation);
  if (settlement.status === "fulfilled") return;
  if (
    settlement.status === "rejected" &&
    settlement.reason?.code === "recording_cancelled"
  ) {
    throw settlement.reason;
  }
  throw sanitizeRecordingFailure({
    code: "browser_visibility_unavailable",
  });
}

async function prepareArtifactTransaction({
  dependencies,
  options,
  savedRecording,
  signal,
}) {
  let lateCleanupStarted = false;
  const cleanupLateTransaction = (transaction) => {
    if (lateCleanupStarted) return;
    lateCleanupStarted = true;
    void Promise.resolve()
      .then(() => transaction.rollback())
      .catch(() => {});
  };
  const preparation = Promise.resolve().then(() =>
    dependencies.createRecordingArtifactTransaction({
      destinationDirectory: savedRecording.destinationDirectory,
      outputFilename: savedRecording.outputFilename,
      signal,
      temporaryRoot: options.temporaryRoot ?? tmpdir(),
    }),
  );
  try {
    return await awaitAbortable(preparation, signal);
  } catch (error) {
    if (error?.code !== "recording_cancelled") throw error;

    const prepared = await settleBeforeDeadline(
      preparation,
      dependencies.clock,
    );
    if (prepared.status === "rejected") {
      throw error;
    }
    if (prepared.status === "timed_out") {
      void preparation.then(
        cleanupLateTransaction,
        () => {},
      );
      throw sanitizeRecordingFailure(error, {
        artifactCleanupIncomplete: true,
      });
    }
    const transaction = prepared.value;
    if (transaction == null) throw error;
    const cleanup = await settleBeforeDeadline(
      Promise.resolve().then(() => transaction.rollback()),
      dependencies.clock,
    );
    if (cleanup.status !== "fulfilled") {
      const details = getRecordingCleanupDetails(cleanup.reason);
      throw sanitizeRecordingFailure(error, {
        artifactCleanupIncomplete: details == null,
        cleanupDirectory: details?.directory,
        cleanupFile: details?.cleanupFile,
      });
    }
    throw error;
  }
}

function stateForFailureCode(code) {
  return ["cancelled", "recording_cancelled"].includes(code)
    ? "cancelled"
    : "failed";
}

function isBrowserApprovalDenial(error) {
  const message = error instanceof Error ? error.message : "";
  return /Browser Use rejected this action due to browser security policy[.] Reason: The user has requested that .+(?:should not be used|not be used on)/su.test(
    message,
  );
}

function sanitizeBrowserFailure(error, options) {
  return sanitizeRecordingFailure(
    {
      code: isBrowserApprovalDenial(error) ? "cancelled" : "integration_failed",
    },
    options,
  );
}

function sanitizeActionFailure(error) {
  if (!isBrowserApprovalDenial(error)) {
    return sanitizeRecordingFailure(error);
  }
  const cleanup = getRecordingCleanupDetails(error);
  return sanitizeRecordingFailure(
    { code: "cancelled" },
    {
      artifactCleanupIncomplete:
        cleanup?.artifactCleanupIncomplete === true,
      browserTabCleanupIncomplete:
        cleanup?.browserTabCleanupIncomplete === true,
      cleanupDirectory: cleanup?.directory,
      cleanupFile: cleanup?.cleanupFile,
      resourceCleanupIncomplete:
        cleanup?.resourceCleanupIncomplete === true,
    },
  );
}

function failedHandle(code) {
  const error = sanitizeRecordingFailure({ code });
  const failure = Promise.reject(error);
  void failure.catch(() => {});
  return {
    finished: failure,
    ready: failure,
    runAction: () => failure,
    stop: () => failure,
  };
}

async function startRecordingTransaction({
  artifacts,
  dependencies,
  getForcedFailureCode,
  options,
  request,
  signal,
  tab,
}) {
  let session;
  let startup;
  try {
    startup = Promise.resolve().then(() =>
      dependencies.startBrowserRecordingForTab({
        approvedOrigin: request.approvedOrigin,
        ffmpegPath: options.ffmpegPath,
        outputPath: artifacts.capturePath,
        requirePointerEvents: request.requirePointerEvents,
        signal,
        tab,
      }),
    );
    session = await awaitAbortable(startup, signal);
  } catch (error) {
    let resourceCleanupIncomplete = false;
    if (error?.code === "recording_cancelled" && startup != null) {
      const startupSettlement = await settleBeforeDeadline(
        startup,
        dependencies.clock,
      );
      if (startupSettlement.status === "fulfilled") {
        if (typeof startupSettlement.value?.stop !== "function") {
          resourceCleanupIncomplete = true;
        } else {
          const stopped = await settleBeforeDeadline(
            Promise.resolve().then(() => startupSettlement.value.stop()),
            dependencies.clock,
          );
          resourceCleanupIncomplete = stopped.status !== "fulfilled";
        }
      } else if (startupSettlement.status === "timed_out") {
        resourceCleanupIncomplete = true;
        void startup
          .then((lateSession) => lateSession?.stop?.())
          .catch(() => {});
      }
    }
    const cleanup = await settleBeforeDeadline(
      Promise.resolve().then(() =>
        artifacts.rollback(),
      ),
      dependencies.clock,
    );
    const cleanupDetails = getRecordingCleanupDetails(cleanup.reason);
    throw sanitizeRecordingFailure(
      {
        code:
          typeof error?.code === "string"
            ? error.code
            : "integration_failed",
      },
      {
        artifactCleanupIncomplete:
          cleanup.status !== "fulfilled" && cleanupDetails == null,
        cleanupDirectory: cleanupDetails?.directory,
        cleanupFile: cleanupDetails?.cleanupFile,
        resourceCleanupIncomplete,
      },
    );
  }

  let captureError = null;
  let finalizationPromise;
  const ready = Promise.resolve(session.ready).catch((error) => {
    captureError = error;
    throw error;
  });
  if (typeof session.completion?.then === "function") {
    void session.completion.then(
      (outcome) => {
        if (outcome?.error) captureError ??= outcome.error;
      },
      (error) => {
        captureError ??= error;
      },
    );
  }

  return {
    assertApprovedOrigin() {
      if (typeof session.assertApprovedOrigin !== "function") {
        throw sanitizeRecordingFailure({ code: "integration_failed" });
      }
      return session.assertApprovedOrigin();
    },
    completion: session.completion,
    ready,
    captureSnapshot() {
      return sanitizeCaptureStatus({
        ...session.stats?.cursor,
        ...session.stats?.framePump,
        ...session.stats?.resources,
        ...session.stats?.sink,
      });
    },
    stop() {
      finalizationPromise ??= (async () => {
        let capture;
        try {
          capture = await session.stop();
        } catch (error) {
          capture = {
            ...session.stats?.cursor,
            ...session.stats?.framePump,
            ...session.stats?.resources,
            ...session.stats?.sink,
            elapsedMs: session.stats?.resources?.elapsedMs ?? null,
          };
          captureError ??= error;
        }
        if (signal.aborted) {
          captureError ??= Object.assign(new Error("Recording was cancelled"), {
            code: "recording_cancelled",
          });
        }
        return artifacts.finalize({
          capture,
          failureCode:
            getForcedFailureCode() ?? captureFailureCode(captureError),
          ffprobePath: options.ffprobePath,
        });
      })();
      return finalizationPromise;
    },
  };
}

export function createRecording(options) {
  let callerSignal;
  let dependencies;
  try {
    callerSignal = options?.signal;
    if (callerSignal != null && !(callerSignal instanceof AbortSignal)) {
      return failedHandle("invalid_configuration");
    }
    if (callerSignal != null) isAborted(callerSignal);
    dependencies = options?._dependencies ?? {
      clock: { clearTimeout, setTimeout },
      createRecordingArtifactTransaction,
      doctor: inspectRecordingEnvironment,
      startBrowserRecordingForTab,
    };
  } catch {
    return failedHandle("invalid_configuration");
  }
  let state = "preparing";
  let inner;
  let durationTimer;
  let freshTab;
  let artifactTransaction;
  let actionFailure;
  let actionInFlight = false;
  let pointerActionInFlight = false;
  let forcedFailureCode;
  let recordingDeadlineMs;
  let sessionRequiresPointerEvidence;
  let finalizationPromise;
  let tabClosePromise;
  let terminal = false;
  let ownsFreshTab = false;
  let rejectFinished;
  let resolveFinished;
  const reservation = {};
  const cancellation = new AbortController();
  const cancelFromCaller = () => {
    cancellation.abort();
    queueMicrotask(() => {
      if (!terminal) void finish({ cancelPending: true }).catch(() => {});
    });
  };

  if (globalThis[ACTIVE_RECORDING_KEY] != null) {
    return failedHandle("recording_already_active");
  }
  globalThis[ACTIVE_RECORDING_KEY] = reservation;
  try {
    if (callerSignal != null) {
      addAbortListener(callerSignal, cancelFromCaller);
      if (isAborted(callerSignal)) cancelFromCaller();
    }
  } catch {
    if (globalThis[ACTIVE_RECORDING_KEY] === reservation) {
      delete globalThis[ACTIVE_RECORDING_KEY];
    }
    return failedHandle("invalid_configuration");
  }

  const finished = new Promise((resolve, reject) => {
    rejectFinished = reject;
    resolveFinished = resolve;
  });
  void finished.catch(() => {});

  let handle;
  let ready;

  function release() {
    if (callerSignal != null) {
      try {
        removeAbortListener(callerSignal, cancelFromCaller);
      } catch {
        // Releasing the singleton must not depend on caller-controlled state.
      }
    }
    if (
      globalThis[ACTIVE_RECORDING_KEY] === reservation ||
      globalThis[ACTIVE_RECORDING_KEY] === handle
    ) {
      delete globalThis[ACTIVE_RECORDING_KEY];
    }
  }

  function setTerminalState(output) {
    dependencies.clock.clearTimeout(durationTimer);
    if (terminal) return;
    terminal = true;
    state =
      output?.result?.status === "passed"
        ? "completed"
        : stateForFailureCode(output?.result?.failureCode);
    release();
    resolveFinished(output);
  }

  function setTerminalFailure(error) {
    dependencies.clock.clearTimeout(durationTimer);
    if (terminal) return;
    terminal = true;
    state = stateForFailureCode(error?.code);
    release();
    rejectFinished(error);
  }

  function latchActionFailure(error) {
    actionFailure ??= sanitizeActionFailure(error);
    forcedFailureCode ??=
      actionFailure.code === "cancelled"
        ? "recording_cancelled"
        : actionFailure.code;
    return actionFailure;
  }

  async function failAction(error) {
    const primaryFailure = latchActionFailure(error);
    cancellation.abort();

    let cleanupOptions = {};
    try {
      const output = await finish({ cancelPending: true });
      cleanupOptions = {
        artifactCleanupIncomplete:
          output?.cleanup?.artifactCleanupIncomplete === true,
        browserTabCleanupIncomplete:
          output?.cleanup?.browserTabCleanupIncomplete === true,
        cleanupDirectory: output?.paths?.cleanupDirectory,
        cleanupFile: output?.paths?.cleanupFile,
        resourceCleanupIncomplete:
          output?.cleanup?.resourceCleanupIncomplete === true,
      };
    } catch (cleanupError) {
      const cleanup = getRecordingCleanupDetails(cleanupError);
      cleanupOptions = {
        artifactCleanupIncomplete:
          cleanup?.artifactCleanupIncomplete === true,
        browserTabCleanupIncomplete:
          cleanup?.browserTabCleanupIncomplete === true,
        cleanupDirectory: cleanup?.directory,
        cleanupFile: cleanup?.cleanupFile,
        resourceCleanupIncomplete:
          cleanup?.resourceCleanupIncomplete === true,
      };
    }
    throw sanitizeRecordingFailure(primaryFailure, cleanupOptions);
  }

  async function waitForActionEvidence({
    actionStartedAtEpochMs,
    beforeChildFrameEvents,
    beforeEvents,
    beforeFrames,
    requiresChildFramePointerEvidence,
    requiresFrameEvidence,
  }) {
    const evidenceDeadline = Math.min(
      clockNow(dependencies.clock) + ACTION_EVIDENCE_TIMEOUT_MS,
      recordingDeadlineMs ?? Number.POSITIVE_INFINITY,
    );
    while (true) {
      if (state !== "recording") {
        throw sanitizeRecordingFailure({ code: "integration_failed" });
      }
      const capture = inner.captureSnapshot();
      const hasPointerEvidence = hasPointerEvidenceAfterActionBoundary({
        actionStartedAtEpochMs,
        beforeEvents,
        capture,
      });
      const hasRequiredChildFrameEvidence =
        requiresChildFramePointerEvidence !== true ||
        hasChildFramePointerEvidenceAfterActionBoundary({
          beforeEvents: beforeChildFrameEvents,
          capture,
        });
      const hasRequiredFrameEvidence =
        requiresFrameEvidence !== true ||
        hasFrameEvidenceAfterActionBoundary({
          beforeFrames,
          capture,
        });
      if (
        hasPointerEvidence &&
        hasRequiredChildFrameEvidence &&
        hasRequiredFrameEvidence
      ) {
        return;
      }
      const remainingMs = evidenceDeadline - clockNow(dependencies.clock);
      if (remainingMs <= 0) {
        throw sanitizeRecordingFailure({
          code:
            requiresFrameEvidence && !hasRequiredFrameEvidence
              ? "frame_stream_stalled"
              : "cursor_recording_failed",
        });
      }
      await waitForClockDelay(
        dependencies.clock,
        Math.min(ACTION_EVIDENCE_INTERVAL_MS, remainingMs),
        cancellation.signal,
      );
    }
  }

  async function assertActionBoundaryOrigin() {
    await awaitAbortable(
      Promise.resolve().then(() => inner.assertApprovedOrigin()),
      cancellation.signal,
    );
    if (state !== "recording") {
      throw sanitizeRecordingFailure({ code: "integration_failed" });
    }
  }

  async function runAction({
    perform,
    requiresChildFramePointerEvidence = false,
    requiresFrameEvidence = false,
    requiresPointerEvidence,
  } = {}) {
    if (
      typeof perform !== "function" ||
      typeof requiresChildFramePointerEvidence !== "boolean" ||
      typeof requiresFrameEvidence !== "boolean" ||
      typeof requiresPointerEvidence !== "boolean"
    ) {
      return failAction({ code: "invalid_configuration" });
    }
    if (
      (requiresChildFramePointerEvidence || requiresFrameEvidence) &&
      requiresPointerEvidence !== true
    ) {
      return failAction({ code: "invalid_configuration" });
    }
    if (
      requiresPointerEvidence &&
      sessionRequiresPointerEvidence !== true
    ) {
      return failAction({ code: "invalid_configuration" });
    }
    if (actionInFlight || state !== "recording") {
      return failAction({ code: "integration_failed" });
    }

    actionInFlight = true;
    pointerActionInFlight = requiresPointerEvidence;
    try {
      await assertActionBoundaryOrigin();
      const beforeCapture = inner.captureSnapshot();
      const beforeChildFrameEvents =
        beforeCapture?.cursorChildFrameEventsCaptured;
      const beforeEvents = beforeCapture?.cursorEventsCaptured;
      const beforeFrames = beforeCapture?.framesReceived;
      const actionStartedAtEpochMs = clockNow(dependencies.clock);
      const result = await awaitAbortable(
        Promise.resolve().then(perform),
        cancellation.signal,
      );
      await assertActionBoundaryOrigin();
      if (requiresPointerEvidence) {
        await waitForActionEvidence({
          actionStartedAtEpochMs,
          beforeChildFrameEvents,
          beforeEvents,
          beforeFrames,
          requiresChildFramePointerEvidence,
          requiresFrameEvidence,
        });
        await waitForClockDelay(
          dependencies.clock,
          POINTER_VISUAL_TAIL_MS,
          cancellation.signal,
        );
        await assertActionBoundaryOrigin();
        if (state !== "recording") {
          throw sanitizeRecordingFailure({
            code: "cursor_recording_failed",
          });
        }
      }
      return result;
    } catch (error) {
      return failAction(error);
    } finally {
      actionInFlight = false;
      pointerActionInFlight = false;
    }
  }

  function closeFreshTab() {
    if (!ownsFreshTab || freshTab == null) return;
    if (tabClosePromise !== undefined) return tabClosePromise;
    const tab = freshTab;
    tabClosePromise = (async () => {
      const cleanup = await closeTabWithinRetryBudget(
        options.browser,
        tab,
        dependencies.clock,
      );
      if (cleanup?.status !== "fulfilled") {
        throw sanitizeBrowserFailure(cleanup?.reason, {
          browserTabCleanupIncomplete: true,
        });
      }
      if (freshTab === tab) {
        freshTab = null;
        ownsFreshTab = false;
      }
    })();
    return tabClosePromise;
  }

  function finish({ cancelPending }) {
    if (actionInFlight) {
      latchActionFailure({
        code: pointerActionInFlight
          ? "cursor_recording_failed"
          : "integration_failed",
      });
      cancellation.abort();
    }
    if (cancelPending && ["preparing", "awaiting_frame"].includes(state)) {
      cancellation.abort();
    }
    if (state === "recording") state = "stopping";
    finalizationPromise ??= ready
      .then(async () => {
        dependencies.clock.clearTimeout(durationTimer);
        if (!TERMINAL_STATES.has(state)) state = "stopping";
        try {
          const finalization = await settleBeforeDeadline(
            inner.stop(),
            dependencies.clock,
            FINALIZATION_DEADLINE_MS,
          );
          if (finalization.status === "timed_out") {
            const cancellationWasRequested = cancellation.signal.aborted;
            cancellation.abort();
            throw sanitizeRecordingFailure(
              {
                code: cancellationWasRequested
                  ? "recording_cancelled"
                  : "integration_failed",
              },
              {
                artifactCleanupIncomplete: true,
                resourceCleanupIncomplete: true,
              },
            );
          }
          if (finalization.status === "rejected") {
            throw finalization.reason;
          }
          const output = finalization.value;
          let terminalOutput = output;
          try {
            await closeFreshTab();
          } catch (cleanupError) {
            const cleanup = getRecordingCleanupDetails(cleanupError);
            terminalOutput = {
              ...output,
              cleanup: {
                ...output?.cleanup,
                browserTabCleanupIncomplete:
                  cleanup?.browserTabCleanupIncomplete === true,
              },
            };
          }
          setTerminalState(terminalOutput);
          return terminalOutput;
        } catch (error) {
          let browserTabCleanupIncomplete = false;
          try {
            await closeFreshTab();
          } catch (cleanupError) {
            browserTabCleanupIncomplete =
              getRecordingCleanupDetails(cleanupError)
                ?.browserTabCleanupIncomplete === true;
          }
          const publicError = sanitizeRecordingFailure(error, {
            browserTabCleanupIncomplete,
          });
          setTerminalFailure(publicError);
          throw publicError;
        }
      })
      .finally(release);
    void finalizationPromise.catch(() => {});
    return finished;
  }

  function stop() {
    return finish({ cancelPending: true });
  }

  ready = Promise.resolve()
    .then(async () => {
      if (cancellation.signal.aborted) {
        throw sanitizeRecordingFailure({ code: "recording_cancelled" });
      }
      const request = validateRecordingRequest(options);
      sessionRequiresPointerEvidence = request.requirePointerEvents;
      const savedRecording = planSavedRecording(options);
      if (cancellation.signal.aborted) {
        throw sanitizeRecordingFailure({ code: "recording_cancelled" });
      }
      let ffmpegPath = options.ffmpegPath;
      let ffprobePath = options.ffprobePath;
      if (
        options.tab != null ||
        typeof options.browser?.tabs?.new !== "function"
      ) {
        throw sanitizeRecordingFailure({ code: "invalid_configuration" });
      }
      artifactTransaction = await prepareArtifactTransaction({
        dependencies: {
          clock: dependencies.clock,
          createRecordingArtifactTransaction:
            dependencies.createRecordingArtifactTransaction ??
            createRecordingArtifactTransaction,
        },
        options,
        savedRecording,
        signal: cancellation.signal,
      });
      try {
        freshTab = await createFreshTab(
          options.browser,
          cancellation.signal,
          dependencies.clock,
        );
        ownsFreshTab = true;
        await establishBrowserVisibility(
          options.browser,
          request.recordingMode === "interactive",
          cancellation.signal,
          dependencies.clock,
        );
        await awaitAbortable(
          freshTab.goto(request.targetUrl),
          cancellation.signal,
        );
        let preflightCdp = await awaitAbortable(
          freshTab.capabilities.get("cdp"),
          cancellation.signal,
        );
        const cdpAvailable =
          typeof preflightCdp?.send === "function" &&
          typeof preflightCdp?.readEvents === "function";
        preflightCdp = null;
        const environment = await awaitAbortable(
          (dependencies.doctor ?? inspectRecordingEnvironment)({
            cdpAvailable,
            outputDirectory: savedRecording.destinationDirectory,
          }),
          cancellation.signal,
        );
        if (environment?.supported !== true) {
          throw sanitizeRecordingFailure({
            code: environment?.blockingReasons?.[0] ?? "integration_failed",
          });
        }
        ffmpegPath = environment.ffmpegPath;
        ffprobePath = environment.ffprobePath;
      } catch (error) {
        if (error?.summary === undefined) {
          throw sanitizeBrowserFailure(error);
        }
        throw error;
      }
      const startingArtifacts = artifactTransaction;
      artifactTransaction = null;
      inner = await startRecordingTransaction({
        artifacts: startingArtifacts,
        dependencies: {
          clock: dependencies.clock,
          startBrowserRecordingForTab:
            dependencies.startBrowserRecordingForTab ??
            startBrowserRecordingForTab,
        },
        getForcedFailureCode: () => forcedFailureCode,
        options: { ...options, ffmpegPath, ffprobePath },
        request,
        signal: cancellation.signal,
        tab: freshTab,
      });
      state = "awaiting_frame";
      if (typeof inner.completion?.then === "function") {
        void inner.completion.then(
          (outcome) => {
            if (actionInFlight && outcome?.error != null) {
              latchActionFailure(outcome.error);
            }
            void finish({ cancelPending: false }).catch(() => {});
          },
          (error) => {
            if (actionInFlight) latchActionFailure(error);
            void finish({ cancelPending: false }).catch(() => {});
          },
        );
      }
      await inner.ready;
      state = "recording";
      recordingDeadlineMs = clockNow(dependencies.clock) + request.durationMs;
      durationTimer = dependencies.clock.setTimeout(() => {
        void finish({ cancelPending: false }).catch(() => {});
      }, request.durationMs);
      return freshTab;
    })
    .catch(async (error) => {
      dependencies.clock.clearTimeout(durationTimer);
      let artifactCleanupIncomplete = false;
      let cleanupDirectory;
      let cleanupFile;
      let resourceCleanupIncomplete = false;
      if (inner != null) {
        const cleanup = await settleBeforeDeadline(
          inner.stop(),
          dependencies.clock,
          FINALIZATION_DEADLINE_MS,
        );
        if (cleanup.status === "timed_out") {
          cancellation.abort();
          artifactCleanupIncomplete = true;
          resourceCleanupIncomplete = true;
        } else if (cleanup.status === "rejected") {
          const details = getRecordingCleanupDetails(cleanup.reason);
          artifactCleanupIncomplete =
            details?.artifactCleanupIncomplete === true;
          resourceCleanupIncomplete =
            details?.resourceCleanupIncomplete === true;
          if (details?.cleanupIncomplete === true) {
            cleanupDirectory = details.directory;
            cleanupFile = details.cleanupFile;
          }
        }
      } else if (artifactTransaction != null) {
        const cleanup = await settleBeforeDeadline(
          artifactTransaction.rollback(),
          dependencies.clock,
        );
        if (cleanup.status !== "fulfilled") {
          const details = getRecordingCleanupDetails(cleanup.reason);
          artifactCleanupIncomplete = details == null;
          cleanupDirectory = details?.directory;
          cleanupFile = details?.cleanupFile;
        }
      }
      let browserTabCleanupIncomplete = false;
      try {
        await closeFreshTab();
      } catch (cleanupError) {
        browserTabCleanupIncomplete =
          getRecordingCleanupDetails(cleanupError)
            ?.browserTabCleanupIncomplete === true;
      }
      const publicError = sanitizeRecordingFailure(error, {
        artifactCleanupIncomplete,
        browserTabCleanupIncomplete,
        cleanupDirectory,
        cleanupFile,
        resourceCleanupIncomplete,
      });
      setTerminalFailure(publicError);
      release();
      throw publicError;
    });
  void ready.catch(() => {});

  handle = { finished, ready, runAction, stop };
  globalThis[ACTIVE_RECORDING_KEY] = handle;
  return handle;
}

SHA-256: ecc2522f4bcc3ad37b63c06fd908f735281c65f0174c2cdfb6ef249c23b6ccf6