← Files Codex Browser RecorderARCHIVED FILE

skills/record-browser/scripts/record-browser-flow.mjs

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

↓ Download file

import { tmpdir } from "node:os";

import { createRecording } from "./create-recording.mjs";
import { inspectLocalRecordingEnvironment } from "./doctor.mjs";
import { planSavedRecording } from "./recording-artifacts.mjs";
import {
  describeRecordingFailure,
  getRecordingCleanupDetails,
  sanitizeRecordingFailure,
} from "./recording-outcome.mjs";
import {
  DEFAULT_RECORDING_DURATION_MS,
  validateRecordingRequest,
} from "./recording-policy.mjs";

const ACTION_MODALITIES = new Set(["keyboard", "pointer", "programmatic"]);
const CONTENT_WARNING =
  "The complete approved page viewport may include private, authenticated, or sensitive content. Continue only if you are authorized to record it and will handle the local file appropriately.";
const RECORDING_SURFACE = "Codex In-app Browser";
const SETUP_CLEANUP_TIMEOUT_MS = 5000;
const SETUP_OPERATION_TIMEOUT_MS = 5000;

function dependenciesWith(overrides = {}) {
  return {
    createSession: createRecording,
    inspectLocalEnvironment: inspectLocalRecordingEnvironment,
    planOutput: planSavedRecording,
    setupCleanupTimeoutMs: SETUP_CLEANUP_TIMEOUT_MS,
    setupOperationTimeoutMs: SETUP_OPERATION_TIMEOUT_MS,
    validateRequest: validateRecordingRequest,
    ...overrides,
  };
}

function setupOperationFailure(code) {
  return Object.assign(new Error(code), { code });
}

function runSetupOperation(
  operation,
  { signal, timeoutMs },
) {
  const operationPromise = Promise.resolve().then(operation);
  return new Promise((resolve, reject) => {
    let settled = false;
    const finish = (callback, value) => {
      if (settled) return;
      settled = true;
      clearTimeout(timer);
      signal?.removeEventListener("abort", abort);
      callback(value);
    };
    const abort = () => {
      finish(reject, setupOperationFailure("setup_cancelled"));
    };
    const timer = setTimeout(() => {
      finish(reject, setupOperationFailure("setup_timeout"));
    }, timeoutMs);

    signal?.addEventListener("abort", abort, { once: true });
    if (signal?.aborted === true) abort();
    operationPromise.then(
      (value) => finish(resolve, value),
      (error) => finish(reject, error),
    );
  });
}

function blocker(code) {
  const failure = describeRecordingFailure(code);
  return Object.freeze({ code, ...failure });
}

function blocked(codes, output = null) {
  return Object.freeze({
    blockers: Object.freeze(codes.map(blocker)),
    output,
    status: "blocked",
  });
}

function publicEnvironment(
  environment,
  { codexInAppBrowserAvailable, fullCdpAvailable } = {},
) {
  return Object.freeze({
    ...(typeof codexInAppBrowserAvailable === "boolean"
      ? { codexInAppBrowserAvailable }
      : {}),
    ffmpegH264Available: environment?.ffmpegH264Available === true,
    ffmpegMp4Available: environment?.ffmpegMp4Available === true,
    ffprobeUsable: environment?.ffprobeUsable === true,
    ...(typeof fullCdpAvailable === "boolean"
      ? { fullCdpAvailable }
      : {}),
    outputDirectoryWritable: environment?.outputDirectoryWritable === true,
    platform:
      typeof environment?.platform === "string"
        ? environment.platform
        : "unknown",
    supported: environment?.supported === true,
  });
}

function publicOutput(savedRecording) {
  return Object.freeze({
    destinationDirectory: savedRecording.destinationDirectory,
    outputFilename: savedRecording.outputFilename,
  });
}

function normalizeActions(actions, durationWasExplicit) {
  if (!Array.isArray(actions)) {
    throw sanitizeRecordingFailure({ code: "invalid_configuration" });
  }
  if (actions.length === 0 && durationWasExplicit !== true) {
    throw sanitizeRecordingFailure({ code: "invalid_configuration" });
  }
  return Object.freeze(
    actions.map((action) => {
      const label = action?.label?.trim();
      const requiresChildFramePointerEvidence =
        action?.requiresChildFramePointerEvidence === true;
      const requiresFrameEvidence =
        action?.requiresFrameEvidence === true;
      if (
        typeof label !== "string" ||
        label.length === 0 ||
        label.length > 200 ||
        !ACTION_MODALITIES.has(action?.modality) ||
        typeof action?.perform !== "function" ||
        (action?.requiresChildFramePointerEvidence !== undefined &&
          typeof action.requiresChildFramePointerEvidence !== "boolean") ||
        (action?.requiresFrameEvidence !== undefined &&
          typeof action.requiresFrameEvidence !== "boolean") ||
        ((requiresChildFramePointerEvidence || requiresFrameEvidence) &&
          action.modality !== "pointer")
      ) {
        throw sanitizeRecordingFailure({ code: "invalid_configuration" });
      }
      return Object.freeze({
        label,
        modality: action.modality,
        perform: action.perform,
        requiresChildFramePointerEvidence,
        requiresFrameEvidence,
      });
    }),
  );
}

function cleanupDetails({ errors = [], output } = {}) {
  const details = errors
    .map((error) => getRecordingCleanupDetails(error))
    .filter((value) => value !== null);
  return Object.freeze({
    artifactCleanupIncomplete:
      output?.cleanup?.artifactCleanupIncomplete === true ||
      details.some(
        ({ artifactCleanupIncomplete }) =>
          artifactCleanupIncomplete === true,
      ),
    browserTabCleanupIncomplete:
      output?.cleanup?.browserTabCleanupIncomplete === true ||
      details.some(
        ({ browserTabCleanupIncomplete }) =>
          browserTabCleanupIncomplete === true,
      ),
    resourceCleanupIncomplete:
      output?.cleanup?.resourceCleanupIncomplete === true ||
      details.some(
        ({ resourceCleanupIncomplete }) =>
          resourceCleanupIncomplete === true,
      ),
    directory:
      details.find(({ directory }) => typeof directory === "string")
        ?.directory ?? output?.paths?.cleanupDirectory ?? null,
    file:
      details.find(({ cleanupFile }) => typeof cleanupFile === "string")
        ?.cleanupFile ?? output?.paths?.cleanupFile ?? null,
  });
}

function failureOutcome(error, { output } = {}) {
  const publicError = sanitizeRecordingFailure(error);
  return Object.freeze({
    cleanup: cleanupDetails({ errors: [publicError], output }),
    failure: Object.freeze({
      code: publicError.code,
      remediation: publicError.remediation,
      summary: publicError.summary,
    }),
    paths: output?.paths ?? null,
    result: output?.result ?? null,
    status: ["cancelled", "recording_cancelled"].includes(publicError.code)
      ? "cancelled"
      : "failed",
  });
}

function terminalOutcome(output) {
  if (
    output?.result?.status === "passed" &&
    typeof output?.paths?.outputPath === "string" &&
    output.paths.outputPath.length > 0
  ) {
    return Object.freeze({
      cleanup: cleanupDetails({ output }),
      paths: output.paths,
      result: output.result,
      status: "completed",
    });
  }
  if (output?.result?.status === "failed") {
    return failureOutcome(
      { code: output.result.failureCode ?? "recording_failed" },
      { output },
    );
  }
  return failureOutcome({ code: "integration_failed" }, { output });
}

export function createRecordingFlow({ dependencies: overrides } = {}) {
  const dependencies = dependenciesWith(overrides);
  const preparedPlans = new WeakMap();
  const consumedPlans = new WeakSet();
  const setupPlans = new WeakMap();
  const consumedSetupPlans = new WeakSet();

  async function prepareRecording(spec = {}) {
    let savedRecording;
    try {
      savedRecording = dependencies.planOutput({
        destinationDirectory: spec.destinationDirectory,
        now: spec.now,
        recordingName: spec.recordingName,
      });
    } catch (error) {
      return blocked([sanitizeRecordingFailure(error).code]);
    }
    const output = publicOutput(savedRecording);

    if (
      spec.preflightOnly !== true &&
      spec.preflightOnly !== false &&
      spec.preflightOnly !== undefined
    ) {
      return blocked(["invalid_configuration"], output);
    }
    if (Object.hasOwn(spec, "browserSurface")) {
      return blocked(["invalid_configuration"], output);
    }

    let actions;
    let request;
    let durationWasExplicit;
    if (spec.preflightOnly !== true) {
      try {
        durationWasExplicit = spec.durationWasExplicit === true;
        if (
          spec.durationWasExplicit !== true &&
          spec.durationWasExplicit !== false
        ) {
          throw sanitizeRecordingFailure({ code: "invalid_configuration" });
        }
        actions = normalizeActions(spec.actions, durationWasExplicit);
        request = dependencies.validateRequest({
          durationMs: durationWasExplicit
            ? spec.durationMs
            : DEFAULT_RECORDING_DURATION_MS,
          recordingMode: spec.recordingMode,
          requirePointerEvents: actions.some(
            ({ modality }) => modality === "pointer",
          ),
          targetUrl: spec.targetUrl,
        });
      } catch (error) {
        return blocked([sanitizeRecordingFailure(error).code], output);
      }
    }

    let environment;
    try {
      environment = await dependencies.inspectLocalEnvironment({
        outputDirectory: savedRecording.destinationDirectory,
      });
    } catch (error) {
      return blocked([sanitizeRecordingFailure(error).code], output);
    }
    const blockerCodes = Array.isArray(environment?.blockingReasons)
      ? environment.blockingReasons
      : ["integration_failed"];
    if (environment?.supported !== true || blockerCodes.length > 0) {
      return blocked(
        blockerCodes.length > 0 ? blockerCodes : ["integration_failed"],
        output,
      );
    }

    if (spec.preflightOnly === true) {
      const prepared = Object.freeze({
        environment: publicEnvironment(environment),
        output,
        status: "preflight_prepared",
      });
      setupPlans.set(prepared, Object.freeze({ environment, output }));
      return prepared;
    }

    const consentActions = Object.freeze(
      actions.map(({ label, modality }) => Object.freeze({ label, modality })),
    );
    const consent = Object.freeze({
      actions: consentActions,
      approvedOrigin: request.approvedOrigin,
      browserSurface: RECORDING_SURFACE,
      contentWarning: CONTENT_WARNING,
      end: Object.freeze(
        durationWasExplicit
          ? { durationMs: request.durationMs, kind: "duration" }
          : {
              hardLimitMs: request.durationMs,
              kind: "actions_complete",
            },
      ),
      output,
      recordingMode: request.recordingMode,
      requirePointerEvents: request.requirePointerEvents,
    });
    const prepared = Object.freeze({ consent, status: "prepared" });
    preparedPlans.set(
      prepared,
      Object.freeze({
        actions,
        durationWasExplicit,
        request,
        savedRecording,
        temporaryRoot: spec.temporaryRoot ?? tmpdir(),
      }),
    );
    return prepared;
  }

  async function checkSetup(
    prepared,
    { acquireBrowser, signal } = {},
  ) {
    const plan = setupPlans.get(prepared);
    if (
      plan === undefined ||
      consumedSetupPlans.has(prepared) ||
      typeof acquireBrowser !== "function" ||
      (signal != null && !(signal instanceof AbortSignal))
    ) {
      return blocked(["invalid_configuration"]);
    }
    consumedSetupPlans.add(prepared);
    if (signal?.aborted === true) {
      return blocked(["setup_cancelled"], plan.output);
    }

    let browser;
    let cleanupFailed = false;
    let diagnosticTab;
    let failureCode = null;
    let stage = "acquire_browser";
    let tabCreationSettlement;
    const cleanupDiagnosticTab = async (tab) => {
      const tabId =
        typeof tab?.id === "string" && tab.id.length > 0
          ? tab.id
          : null;
      await tab.close();
      const tabs = await browser.tabs.list();
      if (
        tabId === null ||
        !Array.isArray(tabs) ||
        tabs.some(({ id }) => id === tabId)
      ) {
        throw new Error("Owned setup diagnostic tab remains open");
      }
    };
    const cleanupOwnedDiagnosticTab = (tab) =>
      runSetupOperation(() => cleanupDiagnosticTab(tab), {
        timeoutMs: dependencies.setupCleanupTimeoutMs,
      });
    const bounded = (operation) =>
      runSetupOperation(operation, {
        signal,
        timeoutMs: dependencies.setupOperationTimeoutMs,
      });
    try {
      browser = await bounded(acquireBrowser);
      if (typeof browser?.tabs?.new !== "function") {
        failureCode = "browser_plugin_unavailable";
      } else {
        stage = "create_tab";
        const tabCreation = Promise.resolve().then(() =>
          browser.tabs.new(),
        );
        tabCreationSettlement = tabCreation.then(
          (value) => ({ status: "fulfilled", value }),
          (reason) => ({ reason, status: "rejected" }),
        );
        diagnosticTab = await bounded(() => tabCreation);
        if (typeof diagnosticTab?.capabilities?.get !== "function") {
          failureCode = "cdp_unavailable";
        } else {
          stage = "acquire_cdp";
          const cdp = await bounded(() =>
            diagnosticTab.capabilities.get("cdp"),
          );
          if (
            typeof cdp?.readEvents !== "function" ||
            typeof cdp?.send !== "function"
          ) {
            failureCode = "cdp_unavailable";
          }
        }
      }
    } catch (error) {
      failureCode = ["setup_cancelled", "setup_timeout"].includes(error?.code)
        ? error.code
        : ["acquire_browser", "create_tab"].includes(stage)
          ? "browser_plugin_unavailable"
          : "cdp_unavailable";
      if (
        ["setup_cancelled", "setup_timeout"].includes(failureCode) &&
        stage === "create_tab" &&
        tabCreationSettlement != null
      ) {
        try {
          const settlement = await runSetupOperation(
            () => tabCreationSettlement,
            { timeoutMs: dependencies.setupCleanupTimeoutMs },
          );
          if (settlement.status === "fulfilled") {
            diagnosticTab = settlement.value;
          }
        } catch {
          cleanupFailed = true;
          // The terminal result already reports cleanup incomplete. If the tab
          // appears later, make one best-effort close with its own deadline.
          void tabCreationSettlement
            .then((settlement) =>
              settlement.status === "fulfilled"
                ? cleanupOwnedDiagnosticTab(settlement.value)
                : undefined,
            )
            .catch(() => {});
        }
      }
    } finally {
      if (diagnosticTab != null) {
        try {
          await cleanupOwnedDiagnosticTab(diagnosticTab);
        } catch {
          cleanupFailed = true;
        }
      }
    }

    if (failureCode === null && signal?.aborted === true) {
      failureCode = "setup_cancelled";
    }
    if (failureCode !== null || cleanupFailed) {
      return blocked(
        [
          failureCode,
          ...(cleanupFailed ? ["browser_tab_cleanup_failed"] : []),
        ].filter(Boolean),
        plan.output,
      );
    }
    return Object.freeze({
      environment: publicEnvironment(plan.environment, {
        codexInAppBrowserAvailable: true,
        fullCdpAvailable: true,
      }),
      output: plan.output,
      status: "preflight_passed",
    });
  }

  async function recordApproved(prepared, { browser, signal } = {}) {
    const plan = preparedPlans.get(prepared);
    if (
      plan === undefined ||
      consumedPlans.has(prepared) ||
      browser == null ||
      (signal != null && !(signal instanceof AbortSignal))
    ) {
      return failureOutcome({ code: "invalid_configuration" });
    }
    consumedPlans.add(prepared);

    let session;
    let terminal = false;
    try {
      session = dependencies.createSession({
        browser,
        destinationDirectory: plan.savedRecording.destinationDirectory,
        durationMs: plan.request.durationMs,
        recordingName: plan.savedRecording.outputFilename.replace(
          /[.]mp4$/u,
          "",
        ),
        recordingMode: plan.request.recordingMode,
        requirePointerEvents: plan.request.requirePointerEvents,
        signal,
        targetUrl: plan.request.targetUrl,
        temporaryRoot: plan.temporaryRoot,
      });
      const tab = await session.ready;
      for (const action of plan.actions) {
        await session.runAction({
          perform: () => action.perform({ tab }),
          requiresChildFramePointerEvidence:
            action.requiresChildFramePointerEvidence,
          requiresFrameEvidence: action.requiresFrameEvidence,
          requiresPointerEvidence: action.modality === "pointer",
        });
      }
      const output = plan.durationWasExplicit
        ? await session.finished
        : await session.stop();
      terminal = true;
      return terminalOutcome(output);
    } catch (error) {
      let output;
      let cleanupError;
      if (session != null && !terminal) {
        try {
          output = await session.stop();
        } catch (stopError) {
          cleanupError = stopError;
        }
      }
      const primary = sanitizeRecordingFailure(error);
      const cleanup = cleanupDetails({
        errors: [primary, cleanupError].filter(Boolean),
        output,
      });
      const outcome = failureOutcome(primary, { output });
      return Object.freeze({ ...outcome, cleanup });
    }
  }

  return Object.freeze({ checkSetup, prepareRecording, recordApproved });
}

const defaultFlow = createRecordingFlow();

export const checkSetup = defaultFlow.checkSetup;
export const prepareRecording = defaultFlow.prepareRecording;
export const recordApproved = defaultFlow.recordApproved;

SHA-256: e46c940b8196bee9f9c0eb22fb35e2fc4c0446d2256796df1e950c133062c4cf