/** * Wrap a browser agent's checkout step with ARC: before_checkout first, after_checkout last. * Works with Stagehand, Playwright, Browser Use or any agent that can run an async function. * No browser import here, so it type-checks and tests without Stagehand installed. * * const result = await withArcCheckout(productUrl, async (arc) => { * if (arc.route !== "browser" && arc.route !== "none" && arc.endpoint) { * // A machine route works: use arc.endpoint instead of clicking through. * } * await page.act("add this product to the cart"); * await page.act("go to checkout"); * return { furthest_step: "checkout", blocker: "none" }; * }, { agentName: "my-stagehand-agent" }); */ import { afterCheckout, beforeCheckout, userAgentFor, type ArcOptions, type BeforeCheckoutResult, type Blocker, type FurthestStep } from "../core/arc.ts"; export const STAGEHAND_REF = "int-stagehand"; export type AttemptOutcome = { furthest_step: FurthestStep; blocker: Blocker; store_error_text?: string }; export type GuardConfig = ArcOptions & { agentName?: string; agentFamily?: string; /** Skip the report, for dry runs. */ report?: boolean; }; export type GuardResult = { arc: BeforeCheckoutResult | null; outcome: AttemptOutcome; reported: boolean }; const UNKNOWN_ROUTE: BeforeCheckoutResult = { host: null, known: null, route: "none", endpoint: null, checked_at: null, stale: true, confidence: null, reaches_checkout: false, blockers: [], next_step: "ARC did not answer. Go ahead with the browser and report the outcome.", }; /** * Calls before_checkout, runs attempt(arc), then reports what attempt returned. * An ARC error never blocks the attempt. If attempt throws, the furthest step it * last set through mark() is reported with blocker none, then the error is rethrown. * The agent's own error is not sent: store_error_text is for the store's error message only. */ export async function withArcCheckout( storeUrl: string, attempt: (arc: BeforeCheckoutResult, mark: (step: FurthestStep) => void) => Promise, config: GuardConfig = {}, ): Promise { const options: ArcOptions = { userAgent: userAgentFor("stagehand"), ref: STAGEHAND_REF, ...config }; const arc = await beforeCheckout({ url: storeUrl }, options).catch(() => null); let reached: FurthestStep = "product"; const mark = (step: FurthestStep) => { reached = step; }; let outcome: AttemptOutcome; let failure: unknown = null; try { outcome = await attempt(arc ?? UNKNOWN_ROUTE, mark); } catch (error) { failure = error; outcome = { furthest_step: reached, blocker: "none" }; } let reported = false; if (config.report !== false) { const res = await afterCheckout( { store: storeUrl, ...outcome, agent_name: config.agentName ?? "stagehand-agent", agent_family: config.agentFamily ?? "browser", }, options, ).catch(() => null); reported = res?.ok === true; } if (failure) throw failure; return { arc, outcome, reported }; }