@browserbasehq/eve 0.2.2 → 0.2.3-alpha-99e855dc16394f85652b080eac74f1053cea05b4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/extension/lib/core/facade/batch-sources.d.ts +56 -0
- package/dist/extension/lib/core/facade/batch-sources.mjs +84 -0
- package/dist/extension/lib/core/facade/contract.d.ts +49 -3
- package/dist/extension/lib/core/facade/contract.mjs +25 -1
- package/dist/extension/lib/core/facade/runtime.d.ts +22 -0
- package/dist/extension/lib/core/facade/runtime.mjs +59 -3
- package/dist/extension/lib/core/facade/tools.d.ts +13 -1
- package/dist/extension/lib/core/facade/tools.mjs +62 -84
- package/package.json +3 -3
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ExperimentalBatchCallback } from "@browserbasehq/stagehand";
|
|
2
|
+
import type { RefAction } from "./contract.js";
|
|
3
|
+
import { type PlaywrightCompatTelemetry } from "./runtime.js";
|
|
4
|
+
/**
|
|
5
|
+
* The JavaScript the facade sends to `experimentalBatch`. The SDK sends a
|
|
6
|
+
* callback's source text, so the exported *_SOURCE / RUN_PRELUDE / RUN_EPILOGUE
|
|
7
|
+
* strings are exactly what reaches the executor and go into the facade bundle
|
|
8
|
+
* unchanged for other hosts.
|
|
9
|
+
*/
|
|
10
|
+
export type ActionInput = {
|
|
11
|
+
actions: RefAction[];
|
|
12
|
+
xpathById: Record<string, string>;
|
|
13
|
+
};
|
|
14
|
+
/** `staleId` is the first action ID that resolved to no XPath; no action ran. */
|
|
15
|
+
export type ActionResult = {
|
|
16
|
+
completed: number;
|
|
17
|
+
staleId?: never;
|
|
18
|
+
} | {
|
|
19
|
+
staleId: string;
|
|
20
|
+
completed?: never;
|
|
21
|
+
};
|
|
22
|
+
export type ScreenshotArtifact = {
|
|
23
|
+
path: string;
|
|
24
|
+
base64: string;
|
|
25
|
+
};
|
|
26
|
+
export type RunInput = {
|
|
27
|
+
hiddenPageIds?: string[];
|
|
28
|
+
quietWait?: boolean;
|
|
29
|
+
};
|
|
30
|
+
export type RunEnvelope = {
|
|
31
|
+
__stagehandPlaywrightCompat: true;
|
|
32
|
+
value: unknown;
|
|
33
|
+
executionError?: {
|
|
34
|
+
name: string;
|
|
35
|
+
message: string;
|
|
36
|
+
stack?: string;
|
|
37
|
+
};
|
|
38
|
+
telemetry: PlaywrightCompatTelemetry;
|
|
39
|
+
artifacts: ScreenshotArtifact[];
|
|
40
|
+
closeRequested: boolean;
|
|
41
|
+
batchRuntimeMs: number;
|
|
42
|
+
};
|
|
43
|
+
/** createPlaywrightCompatRuntime as one self-contained function source. */
|
|
44
|
+
export declare const RUNTIME_SOURCE: string;
|
|
45
|
+
/** Resolves snapshot IDs against the page's stored XPath map, then runs the actions. */
|
|
46
|
+
export declare const actionRunner: ExperimentalBatchCallback<ActionInput, ActionResult>;
|
|
47
|
+
export declare const ACTION_RUNNER_SOURCE: string;
|
|
48
|
+
/** The source of `runCallback(code)` is `RUN_PRELUDE + code + RUN_EPILOGUE`. */
|
|
49
|
+
export declare const RUN_PRELUDE: string;
|
|
50
|
+
export declare const RUN_EPILOGUE: string;
|
|
51
|
+
/**
|
|
52
|
+
* Agent code runs with Playwright-shaped page, context and browser in scope and
|
|
53
|
+
* resolves to a RunEnvelope. The AsyncFunction constructor parses `code` as a
|
|
54
|
+
* whole function body, so it cannot close the wrapper early.
|
|
55
|
+
*/
|
|
56
|
+
export declare function runCallback(code: string): ExperimentalBatchCallback<RunInput, RunEnvelope>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { fileURLToPath as __eveFileURLToPath } from "node:url";
|
|
2
|
+
import { dirname as __eveDirname } from "node:path";
|
|
3
|
+
import { createRequire as __eveCreateRequire } from "node:module";
|
|
4
|
+
const __filename = __eveFileURLToPath(import.meta.url);
|
|
5
|
+
__eveDirname(__filename);
|
|
6
|
+
__eveCreateRequire(import.meta.url);
|
|
7
|
+
import { createPlaywrightCompatRuntime } from "./runtime.mjs";
|
|
8
|
+
const AsyncFunction = Object.getPrototypeOf(async function() {}).constructor;
|
|
9
|
+
const dynamicFunctionHead = (params) => `async function anonymous(${params.join(",")}\n) {\n`;
|
|
10
|
+
const DYNAMIC_FUNCTION_TAIL = "\n}";
|
|
11
|
+
const RUNTIME_SOURCE = createPlaywrightCompatRuntime.toString();
|
|
12
|
+
const ACTION_RUNNER_BODY = `"use strict";
|
|
13
|
+
const resolveSnapshotXPath = (id) => {
|
|
14
|
+
const exact = input.xpathById[id];
|
|
15
|
+
if (exact !== undefined) return exact;
|
|
16
|
+
if (id.includes("-")) return undefined;
|
|
17
|
+
const suffix = "-" + id;
|
|
18
|
+
const matches = Object.keys(input.xpathById).filter((key) => key.endsWith(suffix));
|
|
19
|
+
return matches.length === 1 ? input.xpathById[matches[0]] : undefined;
|
|
20
|
+
};
|
|
21
|
+
const selectors = [];
|
|
22
|
+
for (const action of input.actions) {
|
|
23
|
+
const xpath = resolveSnapshotXPath(action.id)?.replace(/\\/text\\(\\)(\\[\\d+\\])?$/iu, "");
|
|
24
|
+
if (!xpath) return { staleId: action.id };
|
|
25
|
+
selectors.push("xpath=" + xpath);
|
|
26
|
+
}
|
|
27
|
+
let completed = 0;
|
|
28
|
+
for (const [index, action] of input.actions.entries()) {
|
|
29
|
+
const locator = stagehand.page.locator(selectors[index]);
|
|
30
|
+
switch (action.op) {
|
|
31
|
+
case "click": await locator.click(); break;
|
|
32
|
+
case "hover": await locator.hover(); break;
|
|
33
|
+
case "fill": await locator.fill(action.value); break;
|
|
34
|
+
case "type": await locator.type(action.text, action.delay === undefined ? undefined : { delay: action.delay }); break;
|
|
35
|
+
case "press": await locator.click(); await stagehand.page.keyPress(action.key); break;
|
|
36
|
+
case "select": await locator.selectOption(action.values); break;
|
|
37
|
+
default: throw new Error("Unsupported ref action: " + String(action.op));
|
|
38
|
+
}
|
|
39
|
+
completed += 1;
|
|
40
|
+
}
|
|
41
|
+
return { completed };`;
|
|
42
|
+
const RUN_PARAMS = ["batchStagehand", "input"];
|
|
43
|
+
const RUN_PRELUDE_BODY = `"use strict";
|
|
44
|
+
const __stagehandCompatIdentity = (target) => target;
|
|
45
|
+
for (let index = 0; index <= 32; index += 1) {
|
|
46
|
+
globalThis[index === 0 ? "__name" : "__name" + index] = __stagehandCompatIdentity;
|
|
47
|
+
}
|
|
48
|
+
const createRuntime = ${RUNTIME_SOURCE};
|
|
49
|
+
const runtime = await createRuntime(batchStagehand, { hiddenPageIds: input.hiddenPageIds ?? [], quietWait: input.quietWait === true });
|
|
50
|
+
const page = runtime.page;
|
|
51
|
+
const context = runtime.context;
|
|
52
|
+
const browser = runtime.browser;
|
|
53
|
+
const console = globalThis.console;
|
|
54
|
+
const __stagehandBatchStartedAt = performance.now();
|
|
55
|
+
let value;
|
|
56
|
+
let executionError;
|
|
57
|
+
try {
|
|
58
|
+
value = await (async () => { `;
|
|
59
|
+
const RUN_EPILOGUE_BODY = `
|
|
60
|
+
})();
|
|
61
|
+
} catch (error) {
|
|
62
|
+
executionError = {
|
|
63
|
+
name: typeof error?.name === "string" ? error.name : "Error",
|
|
64
|
+
message: typeof error?.message === "string" ? error.message : String(error),
|
|
65
|
+
...(typeof error?.stack === "string" ? { stack: error.stack } : {}),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
__stagehandPlaywrightCompat: true,
|
|
70
|
+
value,
|
|
71
|
+
executionError,
|
|
72
|
+
telemetry: runtime.telemetry(),
|
|
73
|
+
artifacts: runtime.artifacts(),
|
|
74
|
+
closeRequested: runtime.closeRequested(),
|
|
75
|
+
batchRuntimeMs: performance.now() - __stagehandBatchStartedAt,
|
|
76
|
+
};`;
|
|
77
|
+
const actionRunner = new AsyncFunction("stagehand", "input", ACTION_RUNNER_BODY);
|
|
78
|
+
const ACTION_RUNNER_SOURCE = Function.prototype.toString.call(actionRunner);
|
|
79
|
+
const RUN_PRELUDE = dynamicFunctionHead(RUN_PARAMS) + RUN_PRELUDE_BODY;
|
|
80
|
+
const RUN_EPILOGUE = RUN_EPILOGUE_BODY + DYNAMIC_FUNCTION_TAIL;
|
|
81
|
+
function runCallback(code) {
|
|
82
|
+
return new AsyncFunction(...RUN_PARAMS, RUN_PRELUDE_BODY + code + RUN_EPILOGUE_BODY);
|
|
83
|
+
}
|
|
84
|
+
export { ACTION_RUNNER_SOURCE, RUNTIME_SOURCE, RUN_EPILOGUE, RUN_PRELUDE, actionRunner, runCallback };
|
|
@@ -716,9 +716,11 @@ export declare const SNAPSHOT_ACTIONS_WORKFLOW = "Workflow: after navigating or
|
|
|
716
716
|
export declare const SNAPSHOT_TOOL_DESCRIPTION_EXPLICIT = "Capture the active page's Stagehand accessibility tree and hydrate its displayed IDs for subsequent run actions. Every call replaces the active page's ID map. Call it after every navigation or page change and before acting by ID.";
|
|
717
717
|
export declare const SCREENSHOT_TOOL_DESCRIPTION_EXPLICIT = "Capture a screenshot of the active page. For size-constrained MCP clients, prefer a viewport JPEG: {\"type\":\"jpeg\",\"quality\":40,\"fullPage\":false}. Use it to verify the visible state before reporting done, on visual or canvas content, and when text evidence is ambiguous.";
|
|
718
718
|
export declare const RUN_TOOL_DESCRIPTION_EXPLICIT = "Browse and automate websites in the persistent browser by executing JavaScript against a Playwright-shaped API. The code runs inside an async function with page, context, and browser in scope (Playwright Page, BrowserContext, and Browser); use await directly and return a JSON-serializable value when useful. Navigate with await page.goto(\"https://example.com\"); there is no separate navigate or start tool. Alternatively, provide a batch of actions using IDs from the latest snapshot. Provide exactly one of code or actions. Each action must use \"op\" (never \"kind\") and \"id\" (never \"ref\"). Copy the bracketed snapshot ID as a string. Examples: {\"actions\":[{\"op\":\"click\",\"id\":\"1-42\"}]}, {\"actions\":[{\"op\":\"fill\",\"id\":\"2-14\",\"value\":\"Miami\"}]}, {\"actions\":[{\"op\":\"select\",\"id\":\"3-9\",\"values\":\"Lowest price\"}]}. Prefer \"actions\" with snapshot IDs for simple clicks/fills/selects on visible elements; use \"code\" for multi-step logic and extraction.";
|
|
719
|
-
|
|
719
|
+
/** Environment variables as read by the facade; `process.env` satisfies it. */
|
|
720
|
+
export type FacadeEnv = Readonly<Record<string, string | undefined>>;
|
|
721
|
+
export declare function explicitSnapshotActionsEnabled(env?: FacadeEnv): boolean;
|
|
720
722
|
export declare const FACADE_TOOLS_EXPLICIT_SNAPSHOT: typeof FACADE_TOOLS;
|
|
721
|
-
export declare function facadeToolsFor(surface: FacadeSurface, env?:
|
|
723
|
+
export declare function facadeToolsFor(surface: FacadeSurface, env?: FacadeEnv): readonly [{
|
|
722
724
|
readonly name: "run";
|
|
723
725
|
readonly description: "Browse and automate websites in the persistent browser by executing JavaScript against a Playwright-shaped API. The code runs inside an async function with page, context, and browser in scope (Playwright Page, BrowserContext, and Browser); use await directly and return a JSON-serializable value when useful. Navigate with await page.goto(\"https://example.com\"); there is no separate navigate or start tool. Alternatively, provide a batch of actions using IDs from the latest snapshot. Provide exactly one of code or actions. Each action must use \"op\" (never \"kind\") and \"id\" (never \"ref\"). Copy the bracketed snapshot ID as a string. Examples: {\"actions\":[{\"op\":\"click\",\"id\":\"1-42\"}]}, {\"actions\":[{\"op\":\"fill\",\"id\":\"2-14\",\"value\":\"Miami\"}]}, {\"actions\":[{\"op\":\"select\",\"id\":\"3-9\",\"values\":\"Lowest price\"}]}.";
|
|
724
726
|
readonly inputSchema: {
|
|
@@ -997,7 +999,7 @@ export declare function facadeToolsFor(surface: FacadeSurface, env?: NodeJS.Proc
|
|
|
997
999
|
readonly additionalProperties: false;
|
|
998
1000
|
};
|
|
999
1001
|
}];
|
|
1000
|
-
export declare function facadeAgentInstructions(env?:
|
|
1002
|
+
export declare function facadeAgentInstructions(env?: FacadeEnv): string;
|
|
1001
1003
|
/**
|
|
1002
1004
|
* Canonical agent system prompt for the facade tool surface. Host examples
|
|
1003
1005
|
* (Eve, Vercel AI SDK, deepagents) should use this text rather than authoring
|
|
@@ -1013,6 +1015,50 @@ export declare const NO_HYDRATED_SNAPSHOT_ERROR = "No hydrated snapshot exists f
|
|
|
1013
1015
|
export declare const NAVIGATED_SNAPSHOT_ERROR = "The active page navigated after its snapshot; call snapshot again.";
|
|
1014
1016
|
export declare const STALE_SNAPSHOT_ID_ERROR = "Snapshot ID \"${id}\" is stale or not actionable; call snapshot again.";
|
|
1015
1017
|
export declare function staleSnapshotIdError(id: string): string;
|
|
1018
|
+
export declare const SCREENSHOT_ARTIFACT_PATH_ERROR = "Screenshot artifact path must stay within artifactRoot.";
|
|
1019
|
+
export declare const SCREENSHOT_ARTIFACT_SYMLINK_ERROR = "Screenshot artifact directory must not be a symlink.";
|
|
1020
|
+
export declare const SCREENSHOT_ARTIFACT_FILE_SYMLINK_ERROR = "Screenshot artifact file must not be a symlink.";
|
|
1021
|
+
export declare const FACADE_BROWSER_CLOSED_ERROR = "Stagehand facade browser is closed.";
|
|
1022
|
+
/**
|
|
1023
|
+
* Part of the error a Stagehand SDK raises from `context.activePage()` when the
|
|
1024
|
+
* focused window's active tab is not one Stagehand registered (another window
|
|
1025
|
+
* has focus). Hosts then act on the newest registered tab instead of failing.
|
|
1026
|
+
*/
|
|
1027
|
+
export declare const UNMANAGED_ACTIVE_PAGE_ERROR_MARKER = "active target not registered";
|
|
1028
|
+
/**
|
|
1029
|
+
* Caps on what one `run` batch may hand back to its host. They bound memory and
|
|
1030
|
+
* disk use against a runaway or hostile batch, and sit far above any real agent
|
|
1031
|
+
* run: a full-page PNG is a few MB, and a 10 MB value is already far more than a
|
|
1032
|
+
* model can read. Value size is the UTF-8 length of the value's compact JSON.
|
|
1033
|
+
*/
|
|
1034
|
+
export declare const RUN_RESULT_LIMITS: Readonly<{
|
|
1035
|
+
readonly maxArtifacts: 32;
|
|
1036
|
+
readonly maxArtifactBytes: number;
|
|
1037
|
+
readonly maxTotalArtifactBytes: number;
|
|
1038
|
+
readonly maxValueBytes: number;
|
|
1039
|
+
}>;
|
|
1040
|
+
/**
|
|
1041
|
+
* Run options a host passes in the `run` batch input; all are off unless the
|
|
1042
|
+
* host turns them on. `quietWait`: page.waitForTimeout(ms) returns once the
|
|
1043
|
+
* page is quiet (readyState past "loading", no mutation in the main document
|
|
1044
|
+
* for `windowMs`; shadow roots and iframes are not observed), checked in the
|
|
1045
|
+
* page every `pollMs` through page.waitForFunction, never later than ms; at or
|
|
1046
|
+
* below `windowMs` it just sleeps, as it does on pages without waitForFunction. The runtime (src/facade/runtime.ts) holds the same numbers.
|
|
1047
|
+
*/
|
|
1048
|
+
export declare const RUN_OPTIONS: {
|
|
1049
|
+
readonly quietWait: {
|
|
1050
|
+
readonly default: false;
|
|
1051
|
+
readonly windowMs: 400;
|
|
1052
|
+
readonly pollMs: 100;
|
|
1053
|
+
};
|
|
1054
|
+
};
|
|
1055
|
+
export declare const RUN_RESULT_MALFORMED_ERROR = "The run batch returned a malformed result.";
|
|
1056
|
+
export declare const SCREENSHOT_ARTIFACT_COUNT_ERROR: string;
|
|
1057
|
+
export declare const SCREENSHOT_ARTIFACT_SIZE_ERROR: string;
|
|
1058
|
+
export declare const SCREENSHOT_ARTIFACT_TOTAL_SIZE_ERROR: string;
|
|
1059
|
+
export declare const RUN_VALUE_SIZE_ERROR: string;
|
|
1060
|
+
export declare const SCREENSHOT_CAPTURED_MESSAGE = "Screenshot captured.";
|
|
1061
|
+
export declare const SCREENSHOT_COMPRESSED_MESSAGE = "Screenshot captured with transport-safe compression.";
|
|
1016
1062
|
/**
|
|
1017
1063
|
* Prefix of the terminal error every facade tool returns once the browser
|
|
1018
1064
|
* session is gone. Harnesses match on it to tell consequences from agent errors.
|
|
@@ -162,6 +162,30 @@ const STALE_SNAPSHOT_ID_ERROR = "Snapshot ID \"${id}\" is stale or not actionabl
|
|
|
162
162
|
function staleSnapshotIdError(id) {
|
|
163
163
|
return STALE_SNAPSHOT_ID_ERROR.replace("${id}", id);
|
|
164
164
|
}
|
|
165
|
+
const SCREENSHOT_ARTIFACT_PATH_ERROR = "Screenshot artifact path must stay within artifactRoot.";
|
|
166
|
+
const SCREENSHOT_ARTIFACT_SYMLINK_ERROR = "Screenshot artifact directory must not be a symlink.";
|
|
167
|
+
const SCREENSHOT_ARTIFACT_FILE_SYMLINK_ERROR = "Screenshot artifact file must not be a symlink.";
|
|
168
|
+
const FACADE_BROWSER_CLOSED_ERROR = "Stagehand facade browser is closed.";
|
|
169
|
+
const UNMANAGED_ACTIVE_PAGE_ERROR_MARKER = "active target not registered";
|
|
170
|
+
const MB = 1048576;
|
|
171
|
+
const RUN_RESULT_LIMITS = Object.freeze({
|
|
172
|
+
maxArtifacts: 32,
|
|
173
|
+
maxArtifactBytes: 25 * MB,
|
|
174
|
+
maxTotalArtifactBytes: 100 * MB,
|
|
175
|
+
maxValueBytes: 10 * MB
|
|
176
|
+
});
|
|
177
|
+
const RUN_OPTIONS = { quietWait: {
|
|
178
|
+
default: false,
|
|
179
|
+
windowMs: 400,
|
|
180
|
+
pollMs: 100
|
|
181
|
+
} };
|
|
182
|
+
const RUN_RESULT_MALFORMED_ERROR = "The run batch returned a malformed result.";
|
|
183
|
+
const SCREENSHOT_ARTIFACT_COUNT_ERROR = `A run can save at most ${RUN_RESULT_LIMITS.maxArtifacts} screenshot artifacts.`;
|
|
184
|
+
const SCREENSHOT_ARTIFACT_SIZE_ERROR = `A screenshot artifact can be at most ${RUN_RESULT_LIMITS.maxArtifactBytes / MB} MB.`;
|
|
185
|
+
const SCREENSHOT_ARTIFACT_TOTAL_SIZE_ERROR = `A run's screenshot artifacts can total at most ${RUN_RESULT_LIMITS.maxTotalArtifactBytes / MB} MB.`;
|
|
186
|
+
const RUN_VALUE_SIZE_ERROR = `A run can return at most ${RUN_RESULT_LIMITS.maxValueBytes / MB} MB of JSON; return less data.`;
|
|
187
|
+
const SCREENSHOT_CAPTURED_MESSAGE = "Screenshot captured.";
|
|
188
|
+
const SCREENSHOT_COMPRESSED_MESSAGE = "Screenshot captured with transport-safe compression.";
|
|
165
189
|
const BROWSER_SESSION_LOST_ERROR_PREFIX = "Browser session lost (";
|
|
166
190
|
const BROWSER_SESSION_LOST_ERROR = "Browser session lost (${cause}). The task cannot continue; report your final result now.";
|
|
167
191
|
const SESSION_LOST_TELEMETRY_PREFIX = "stagehand_facade_session_lost ";
|
|
@@ -212,4 +236,4 @@ const ScreenshotInputSchema = z.strictObject({
|
|
|
212
236
|
type: z.enum(["png", "jpeg"]).optional(),
|
|
213
237
|
quality: z.number().min(0).max(100).optional()
|
|
214
238
|
});
|
|
215
|
-
export { BROWSER_SESSION_LOST_ERROR, BROWSER_SESSION_LOST_ERROR_PREFIX, CodeModeRunInputSchema, FACADE_AGENT_INSTRUCTIONS, FACADE_LEGACY_TOOLS, FACADE_TOOLS, FACADE_TOOLS_EXPLICIT_SNAPSHOT, LEGACY_FACADE_AGENT_INSTRUCTIONS, LEGACY_RUN_TOOL_DESCRIPTION, NAVIGATED_SNAPSHOT_ERROR, NO_HYDRATED_SNAPSHOT_ERROR, RUN_INPUT_SCHEMA, RUN_TOOL_DESCRIPTION, RUN_TOOL_DESCRIPTION_EXPLICIT, RefActionSchema, SCREENSHOT_INPUT_SCHEMA, SCREENSHOT_TOOL_DESCRIPTION, SCREENSHOT_TOOL_DESCRIPTION_EXPLICIT, SESSION_INFO_TOOL_NAME, SESSION_LOST_TELEMETRY_PREFIX, SNAPSHOT_ACTIONS_WORKFLOW, SNAPSHOT_INPUT_SCHEMA, SNAPSHOT_TOOL_DESCRIPTION, SNAPSHOT_TOOL_DESCRIPTION_EXPLICIT, STALE_SNAPSHOT_ID_ERROR, ScreenshotInputSchema, SnapshotInputSchema, browserSessionLostError, explicitSnapshotActionsEnabled, facadeAgentInstructions, facadeSurfaceFromArgs, facadeToolsFor, facadeToolsForSurface, isBrowserSessionLostError, staleSnapshotIdError };
|
|
239
|
+
export { BROWSER_SESSION_LOST_ERROR, BROWSER_SESSION_LOST_ERROR_PREFIX, CodeModeRunInputSchema, FACADE_AGENT_INSTRUCTIONS, FACADE_BROWSER_CLOSED_ERROR, FACADE_LEGACY_TOOLS, FACADE_TOOLS, FACADE_TOOLS_EXPLICIT_SNAPSHOT, LEGACY_FACADE_AGENT_INSTRUCTIONS, LEGACY_RUN_TOOL_DESCRIPTION, NAVIGATED_SNAPSHOT_ERROR, NO_HYDRATED_SNAPSHOT_ERROR, RUN_INPUT_SCHEMA, RUN_OPTIONS, RUN_RESULT_LIMITS, RUN_RESULT_MALFORMED_ERROR, RUN_TOOL_DESCRIPTION, RUN_TOOL_DESCRIPTION_EXPLICIT, RUN_VALUE_SIZE_ERROR, RefActionSchema, SCREENSHOT_ARTIFACT_COUNT_ERROR, SCREENSHOT_ARTIFACT_FILE_SYMLINK_ERROR, SCREENSHOT_ARTIFACT_PATH_ERROR, SCREENSHOT_ARTIFACT_SIZE_ERROR, SCREENSHOT_ARTIFACT_SYMLINK_ERROR, SCREENSHOT_ARTIFACT_TOTAL_SIZE_ERROR, SCREENSHOT_CAPTURED_MESSAGE, SCREENSHOT_COMPRESSED_MESSAGE, SCREENSHOT_INPUT_SCHEMA, SCREENSHOT_TOOL_DESCRIPTION, SCREENSHOT_TOOL_DESCRIPTION_EXPLICIT, SESSION_INFO_TOOL_NAME, SESSION_LOST_TELEMETRY_PREFIX, SNAPSHOT_ACTIONS_WORKFLOW, SNAPSHOT_INPUT_SCHEMA, SNAPSHOT_TOOL_DESCRIPTION, SNAPSHOT_TOOL_DESCRIPTION_EXPLICIT, STALE_SNAPSHOT_ID_ERROR, ScreenshotInputSchema, SnapshotInputSchema, UNMANAGED_ACTIVE_PAGE_ERROR_MARKER, browserSessionLostError, explicitSnapshotActionsEnabled, facadeAgentInstructions, facadeSurfaceFromArgs, facadeToolsFor, facadeToolsForSurface, isBrowserSessionLostError, staleSnapshotIdError };
|
|
@@ -41,6 +41,10 @@ type RawPage = {
|
|
|
41
41
|
waitForLoadState(state: string, timeout?: number): Promise<void>;
|
|
42
42
|
waitForTimeout(ms: number): Promise<void>;
|
|
43
43
|
waitForSelector(selector: string, options?: Record<string, unknown>): Promise<boolean>;
|
|
44
|
+
waitForFunction?(expression: string, arg?: undefined, options?: {
|
|
45
|
+
polling?: "raf" | number;
|
|
46
|
+
timeout?: number;
|
|
47
|
+
}): Promise<unknown>;
|
|
44
48
|
url(): Promise<string>;
|
|
45
49
|
title(): Promise<string>;
|
|
46
50
|
close(): Promise<void>;
|
|
@@ -88,6 +92,17 @@ type BatchStagehandRuntime = {
|
|
|
88
92
|
export type PlaywrightCompatTelemetry = {
|
|
89
93
|
calls: Record<string, number>;
|
|
90
94
|
misses: Record<string, number>;
|
|
95
|
+
/**
|
|
96
|
+
* page.waitForTimeout calls: the time agent code asked for and the time it
|
|
97
|
+
* waited (capped at the request). They differ only with `quietWait`, where
|
|
98
|
+
* earlyReturns counts the waits that ended because the page was quiet.
|
|
99
|
+
*/
|
|
100
|
+
waits: {
|
|
101
|
+
count: number;
|
|
102
|
+
requestedMs: number;
|
|
103
|
+
waitedMs: number;
|
|
104
|
+
earlyReturns: number;
|
|
105
|
+
};
|
|
91
106
|
};
|
|
92
107
|
export type PlaywrightCompatRuntime = {
|
|
93
108
|
page: unknown;
|
|
@@ -108,6 +123,13 @@ export type PlaywrightCompatRuntime = {
|
|
|
108
123
|
export type PlaywrightCompatRuntimeOptions = {
|
|
109
124
|
/** Pages the host keeps for itself (the facade's keeper tab); never surfaced to agent code. */
|
|
110
125
|
hiddenPageIds?: string[];
|
|
126
|
+
/**
|
|
127
|
+
* page.waitForTimeout(ms) returns once the page is quiet (DOM content loaded
|
|
128
|
+
* and no mutation in the main document for 400 ms; shadow roots and iframes
|
|
129
|
+
* are not observed), never later than ms. Waits of 400 ms or less always
|
|
130
|
+
* sleep in full. Off by default: the wait then sleeps exactly ms.
|
|
131
|
+
*/
|
|
132
|
+
quietWait?: boolean;
|
|
111
133
|
};
|
|
112
134
|
export declare function createPlaywrightCompatRuntime(stagehand: BatchStagehandRuntime, options?: PlaywrightCompatRuntimeOptions): Promise<PlaywrightCompatRuntime>;
|
|
113
135
|
export {};
|
|
@@ -13,7 +13,13 @@ async function createPlaywrightCompatRuntime(stagehand, options = {}) {
|
|
|
13
13
|
};
|
|
14
14
|
const stats = {
|
|
15
15
|
calls: {},
|
|
16
|
-
misses: {}
|
|
16
|
+
misses: {},
|
|
17
|
+
waits: {
|
|
18
|
+
count: 0,
|
|
19
|
+
requestedMs: 0,
|
|
20
|
+
waitedMs: 0,
|
|
21
|
+
earlyReturns: 0
|
|
22
|
+
}
|
|
17
23
|
};
|
|
18
24
|
const record = (bucket, method) => {
|
|
19
25
|
stats[bucket][method] = (stats[bucket][method] ?? 0) + 1;
|
|
@@ -634,6 +640,55 @@ async function createPlaywrightCompatRuntime(stagehand, options = {}) {
|
|
|
634
640
|
const pageKey = (page) => page.pageId ?? page;
|
|
635
641
|
const hiddenPageIds = new Set(options.hiddenPageIds ?? []);
|
|
636
642
|
const visibleRawPages = async () => (await rawContext.pages()).filter((page) => page.pageId === void 0 || !hiddenPageIds.has(page.pageId));
|
|
643
|
+
const QUIET_WAIT_WINDOW_MS = 400;
|
|
644
|
+
const QUIET_WAIT_POLL_MS = 100;
|
|
645
|
+
const quietWaitPredicate = (key) => `(() => {
|
|
646
|
+
const key = ${JSON.stringify(key)};
|
|
647
|
+
let state = document[key];
|
|
648
|
+
if (!state) {
|
|
649
|
+
state = { lastMutation: performance.now() };
|
|
650
|
+
state.observer = new MutationObserver(() => {
|
|
651
|
+
state.lastMutation = performance.now();
|
|
652
|
+
});
|
|
653
|
+
Object.defineProperty(document, key, { value: state, configurable: true });
|
|
654
|
+
state.observer.observe(document, { subtree: true, childList: true, attributes: true, characterData: true });
|
|
655
|
+
}
|
|
656
|
+
return document.readyState !== "loading" && performance.now() - state.lastMutation >= ${QUIET_WAIT_WINDOW_MS};
|
|
657
|
+
})()`;
|
|
658
|
+
const quietWaitCleanup = (key) => `(() => {
|
|
659
|
+
const key = ${JSON.stringify(key)};
|
|
660
|
+
const state = document[key];
|
|
661
|
+
if (state) {
|
|
662
|
+
state.observer.disconnect();
|
|
663
|
+
delete document[key];
|
|
664
|
+
}
|
|
665
|
+
})()`;
|
|
666
|
+
const waitForTimeoutOrQuiet = async (page, ms) => {
|
|
667
|
+
const requestedMs = Number.isFinite(ms) && ms > 0 ? ms : 0;
|
|
668
|
+
stats.waits.count += 1;
|
|
669
|
+
stats.waits.requestedMs += requestedMs;
|
|
670
|
+
const waitForFunction = page.waitForFunction;
|
|
671
|
+
if (!options.quietWait || !(requestedMs > QUIET_WAIT_WINDOW_MS) || typeof waitForFunction !== "function") {
|
|
672
|
+
await page.waitForTimeout(ms);
|
|
673
|
+
stats.waits.waitedMs += requestedMs;
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
const key = `_${crypto.randomUUID()}`;
|
|
677
|
+
const startedAt = Date.now();
|
|
678
|
+
let timer;
|
|
679
|
+
const early = await Promise.race([waitForFunction.call(page, quietWaitPredicate(key), void 0, {
|
|
680
|
+
polling: QUIET_WAIT_POLL_MS,
|
|
681
|
+
timeout: requestedMs
|
|
682
|
+
}).then(() => true, () => false), new Promise((resolve) => {
|
|
683
|
+
timer = setTimeout(() => resolve(false), requestedMs);
|
|
684
|
+
})]);
|
|
685
|
+
clearTimeout(timer);
|
|
686
|
+
const left = startedAt + requestedMs - Date.now();
|
|
687
|
+
if (!early && left > 0) await page.waitForTimeout(left);
|
|
688
|
+
stats.waits.waitedMs += Math.min(Date.now() - startedAt, requestedMs);
|
|
689
|
+
if (early) stats.waits.earlyReturns += 1;
|
|
690
|
+
Promise.resolve().then(() => page.evaluate(quietWaitCleanup(key))).catch(() => void 0);
|
|
691
|
+
};
|
|
637
692
|
const compatPages = new Map();
|
|
638
693
|
const closedPages = new Set();
|
|
639
694
|
let closeRequested = false;
|
|
@@ -2068,7 +2123,7 @@ async function createPlaywrightCompatRuntime(stagehand, options = {}) {
|
|
|
2068
2123
|
},
|
|
2069
2124
|
waitForTimeout: async (ms) => {
|
|
2070
2125
|
record("calls", "page.waitForTimeout");
|
|
2071
|
-
await page
|
|
2126
|
+
await waitForTimeoutOrQuiet(page, ms);
|
|
2072
2127
|
for (const candidate of await visibleRawPages()) await createPage(candidate);
|
|
2073
2128
|
},
|
|
2074
2129
|
waitForLoadState: (state = "load", options = {}) => {
|
|
@@ -2428,7 +2483,8 @@ async function createPlaywrightCompatRuntime(stagehand, options = {}) {
|
|
|
2428
2483
|
}),
|
|
2429
2484
|
telemetry: () => ({
|
|
2430
2485
|
calls: { ...stats.calls },
|
|
2431
|
-
misses: { ...stats.misses }
|
|
2486
|
+
misses: { ...stats.misses },
|
|
2487
|
+
waits: { ...stats.waits }
|
|
2432
2488
|
}),
|
|
2433
2489
|
artifacts: () => screenshotArtifacts.map((artifact) => ({ ...artifact })),
|
|
2434
2490
|
closeRequested: () => closeRequested
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { Stagehand } from "@browserbasehq/stagehand";
|
|
2
2
|
import { type FacadeSessionLoss, type RefAction } from "./contract.js";
|
|
3
|
-
import {
|
|
3
|
+
import type { PlaywrightCompatTelemetry } from "./runtime.js";
|
|
4
4
|
export type StagehandFacadeRunReport = {
|
|
5
5
|
telemetry: PlaywrightCompatTelemetry;
|
|
6
|
+
/** Whether the run used StagehandFacadeToolsOptions.quietWait. */
|
|
7
|
+
quietWait: boolean;
|
|
6
8
|
/** Wall-clock time of the whole experimentalBatch round trip. */
|
|
7
9
|
batchRoundTripMs: number;
|
|
8
10
|
/** Time the agent's code spent executing inside the batch. */
|
|
@@ -43,6 +45,13 @@ export type StagehandFacadeToolsOptions = {
|
|
|
43
45
|
* (`context.pages()` / `waitForEvent("page")` never list it).
|
|
44
46
|
*/
|
|
45
47
|
keeperPage?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Experimental, off by default: page.waitForTimeout(ms) longer than 400 ms
|
|
50
|
+
* in run code returns once the main document is quiet, capped at ms (see
|
|
51
|
+
* RUN_OPTIONS.quietWait). The run
|
|
52
|
+
* report's telemetry.waits shows requested against waited time.
|
|
53
|
+
*/
|
|
54
|
+
quietWait?: boolean;
|
|
46
55
|
};
|
|
47
56
|
/** Every facade tool returns this once the browser session is gone. */
|
|
48
57
|
export declare class StagehandFacadeSessionLostError extends Error {
|
|
@@ -101,6 +110,9 @@ export declare class StagehandFacadeTools {
|
|
|
101
110
|
* The page the agent is working on. A closed or crashed tab leaves the
|
|
102
111
|
* context without an active page (or with only the keeper); the agent then
|
|
103
112
|
* gets a fresh blank page rather than a dead end, and the keeper stays hidden.
|
|
113
|
+
* When another window has focus on a tab Stagehand does not manage, the agent
|
|
114
|
+
* keeps working on the newest tab Stagehand registered; nothing is reordered,
|
|
115
|
+
* since activating a tab does not move window focus.
|
|
104
116
|
*/
|
|
105
117
|
private activePage;
|
|
106
118
|
/**
|
|
@@ -4,8 +4,8 @@ import { createRequire as __eveCreateRequire } from "node:module";
|
|
|
4
4
|
const __filename = __eveFileURLToPath(import.meta.url);
|
|
5
5
|
__eveDirname(__filename);
|
|
6
6
|
__eveCreateRequire(import.meta.url);
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { actionRunner, runCallback } from "./batch-sources.mjs";
|
|
8
|
+
import { FACADE_BROWSER_CLOSED_ERROR, NAVIGATED_SNAPSHOT_ERROR, NO_HYDRATED_SNAPSHOT_ERROR, RUN_OPTIONS, RUN_RESULT_LIMITS, RUN_RESULT_MALFORMED_ERROR, RUN_VALUE_SIZE_ERROR, RefActionSchema, SCREENSHOT_ARTIFACT_COUNT_ERROR, SCREENSHOT_ARTIFACT_FILE_SYMLINK_ERROR, SCREENSHOT_ARTIFACT_PATH_ERROR, SCREENSHOT_ARTIFACT_SIZE_ERROR, SCREENSHOT_ARTIFACT_SYMLINK_ERROR, SCREENSHOT_ARTIFACT_TOTAL_SIZE_ERROR, browserSessionLostError, staleSnapshotIdError } from "./contract.mjs";
|
|
9
9
|
import { sanitizeErrorMessage } from "../harness/redact.mjs";
|
|
10
10
|
import fsp from "node:fs/promises";
|
|
11
11
|
import { constants } from "node:fs";
|
|
@@ -41,58 +41,7 @@ var StagehandFacadeSessionLostError = class extends Error {
|
|
|
41
41
|
};
|
|
42
42
|
const RUN_BATCH_TIMEOUT_MS = 6e4;
|
|
43
43
|
const PAGE_CAPTURE_DEADLINE_MS = 12e4;
|
|
44
|
-
const AsyncFunction = Object.getPrototypeOf(async function() {}).constructor;
|
|
45
|
-
const actionRunner = new AsyncFunction("stagehand", "input", `"use strict";
|
|
46
|
-
let completed = 0;
|
|
47
|
-
for (const action of input.actions) {
|
|
48
|
-
const locator = stagehand.page.locator(action.selector);
|
|
49
|
-
switch (action.op) {
|
|
50
|
-
case "click": await locator.click(); break;
|
|
51
|
-
case "hover": await locator.hover(); break;
|
|
52
|
-
case "fill": await locator.fill(action.value); break;
|
|
53
|
-
case "type": await locator.type(action.text, action.delay === undefined ? undefined : { delay: action.delay }); break;
|
|
54
|
-
case "press": await locator.click(); await stagehand.page.keyPress(action.key); break;
|
|
55
|
-
case "select": await locator.selectOption(action.values); break;
|
|
56
|
-
default: throw new Error("Unsupported ref action: " + String(action.op));
|
|
57
|
-
}
|
|
58
|
-
completed += 1;
|
|
59
|
-
}
|
|
60
|
-
return { completed };`);
|
|
61
44
|
const FACADE_KEEPER_PAGE_URL = "about:blank";
|
|
62
|
-
const FACADE_PRELUDE = `"use strict";
|
|
63
|
-
const __stagehandCompatIdentity = (target) => target;
|
|
64
|
-
for (let index = 0; index <= 32; index += 1) {
|
|
65
|
-
globalThis[index === 0 ? "__name" : "__name" + index] = __stagehandCompatIdentity;
|
|
66
|
-
}
|
|
67
|
-
const createRuntime = ${createPlaywrightCompatRuntime.toString()};
|
|
68
|
-
const runtime = await createRuntime(batchStagehand, { hiddenPageIds: input.hiddenPageIds ?? [] });
|
|
69
|
-
const page = runtime.page;
|
|
70
|
-
const context = runtime.context;
|
|
71
|
-
const browser = runtime.browser;
|
|
72
|
-
const console = globalThis.console;
|
|
73
|
-
const __stagehandBatchStartedAt = performance.now();
|
|
74
|
-
let value;
|
|
75
|
-
let executionError;
|
|
76
|
-
try {
|
|
77
|
-
value = await (async () => { `;
|
|
78
|
-
const FACADE_EPILOGUE = `
|
|
79
|
-
})();
|
|
80
|
-
} catch (error) {
|
|
81
|
-
executionError = {
|
|
82
|
-
name: typeof error?.name === "string" ? error.name : "Error",
|
|
83
|
-
message: typeof error?.message === "string" ? error.message : String(error),
|
|
84
|
-
...(typeof error?.stack === "string" ? { stack: error.stack } : {}),
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
return {
|
|
88
|
-
__stagehandPlaywrightCompat: true,
|
|
89
|
-
value,
|
|
90
|
-
executionError,
|
|
91
|
-
telemetry: runtime.telemetry(),
|
|
92
|
-
artifacts: runtime.artifacts(),
|
|
93
|
-
closeRequested: runtime.closeRequested(),
|
|
94
|
-
batchRuntimeMs: performance.now() - __stagehandBatchStartedAt,
|
|
95
|
-
};`;
|
|
96
45
|
var StagehandFacadeTools = class StagehandFacadeTools {
|
|
97
46
|
stagehand;
|
|
98
47
|
options;
|
|
@@ -184,40 +133,44 @@ var StagehandFacadeTools = class StagehandFacadeTools {
|
|
|
184
133
|
this.snapshotsByPage.delete(page.pageId);
|
|
185
134
|
throw new StagehandFacadeInputError(NAVIGATED_SNAPSHOT_ERROR);
|
|
186
135
|
}
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
};
|
|
136
|
+
const result = await this.stagehand.experimentalBatch(actionRunner, {
|
|
137
|
+
actions: parsed,
|
|
138
|
+
xpathById: snapshot.xpathById
|
|
139
|
+
}, {
|
|
140
|
+
page,
|
|
141
|
+
timeout: RUN_BATCH_TIMEOUT_MS
|
|
194
142
|
});
|
|
143
|
+
if (result?.staleId !== void 0) throw new StagehandFacadeInputError(staleSnapshotIdError(result.staleId));
|
|
195
144
|
return {
|
|
196
|
-
completed:
|
|
197
|
-
page,
|
|
198
|
-
timeout: RUN_BATCH_TIMEOUT_MS
|
|
199
|
-
}))?.completed ?? hydrated.length,
|
|
145
|
+
completed: result?.completed ?? parsed.length,
|
|
200
146
|
url: await page.url()
|
|
201
147
|
};
|
|
202
148
|
}
|
|
203
149
|
async runNow(code) {
|
|
204
150
|
const page = await this.activePage();
|
|
205
|
-
const callback =
|
|
151
|
+
const callback = runCallback(code);
|
|
206
152
|
const startedAt = performance.now();
|
|
207
153
|
const keeperPageId = await this.keeper;
|
|
208
|
-
const
|
|
154
|
+
const quietWait = this.options.quietWait ?? RUN_OPTIONS.quietWait.default;
|
|
155
|
+
const input = {
|
|
156
|
+
...keeperPageId && { hiddenPageIds: [keeperPageId] },
|
|
157
|
+
...quietWait && { quietWait: true }
|
|
158
|
+
};
|
|
209
159
|
const envelope = await this.runBatchWithActivePageFallback(callback, input, page);
|
|
160
|
+
if (!isRunEnvelope(envelope)) throw new StagehandFacadeInputError(RUN_RESULT_MALFORMED_ERROR);
|
|
210
161
|
let runError;
|
|
211
162
|
let runFailed = false;
|
|
212
163
|
try {
|
|
213
164
|
this.options.onRunReport?.({
|
|
214
165
|
telemetry: envelope.telemetry,
|
|
166
|
+
quietWait,
|
|
215
167
|
batchRoundTripMs: performance.now() - startedAt,
|
|
216
168
|
batchRuntimeMs: envelope.batchRuntimeMs,
|
|
217
169
|
closeRequested: envelope.closeRequested
|
|
218
170
|
});
|
|
219
171
|
await this.writeScreenshotArtifacts(envelope.artifacts);
|
|
220
172
|
if (envelope.executionError) throw new StagehandFacadeExecutionError(envelope.executionError);
|
|
173
|
+
if (Buffer.byteLength(JSON.stringify(envelope.value) ?? "") > RUN_RESULT_LIMITS.maxValueBytes) throw new StagehandFacadeInputError(RUN_VALUE_SIZE_ERROR);
|
|
221
174
|
} catch (error) {
|
|
222
175
|
runError = error;
|
|
223
176
|
runFailed = true;
|
|
@@ -248,13 +201,25 @@ var StagehandFacadeTools = class StagehandFacadeTools {
|
|
|
248
201
|
}
|
|
249
202
|
async writeScreenshotArtifacts(artifacts) {
|
|
250
203
|
if (artifacts.length === 0) return;
|
|
204
|
+
if (artifacts.length > RUN_RESULT_LIMITS.maxArtifacts) throw new StagehandFacadeInputError(SCREENSHOT_ARTIFACT_COUNT_ERROR);
|
|
205
|
+
let totalBytes = 0;
|
|
206
|
+
for (const artifact of artifacts) {
|
|
207
|
+
const size = decodedBase64Length(artifact.base64);
|
|
208
|
+
if (size > RUN_RESULT_LIMITS.maxArtifactBytes) throw new StagehandFacadeInputError(SCREENSHOT_ARTIFACT_SIZE_ERROR);
|
|
209
|
+
totalBytes += size;
|
|
210
|
+
if (totalBytes > RUN_RESULT_LIMITS.maxTotalArtifactBytes) throw new StagehandFacadeInputError(SCREENSHOT_ARTIFACT_TOTAL_SIZE_ERROR);
|
|
211
|
+
}
|
|
212
|
+
const decoded = artifacts.map((artifact) => ({
|
|
213
|
+
path: artifact.path,
|
|
214
|
+
bytes: Buffer.from(artifact.base64, "base64")
|
|
215
|
+
}));
|
|
251
216
|
const configuredRoot = path.resolve(this.options.artifactRoot ?? process.cwd());
|
|
252
217
|
await fsp.mkdir(configuredRoot, { recursive: true });
|
|
253
218
|
const root = await fsp.realpath(configuredRoot);
|
|
254
|
-
for (const artifact of
|
|
219
|
+
for (const artifact of decoded) {
|
|
255
220
|
const target = path.resolve(configuredRoot, artifact.path);
|
|
256
221
|
const relative = path.relative(configuredRoot, target);
|
|
257
|
-
if (!relative || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) throw new StagehandFacadeInputError(
|
|
222
|
+
if (!relative || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) throw new StagehandFacadeInputError(SCREENSHOT_ARTIFACT_PATH_ERROR);
|
|
258
223
|
let directory = root;
|
|
259
224
|
const components = relative.split(path.sep);
|
|
260
225
|
for (const component of components.slice(0, -1)) {
|
|
@@ -262,11 +227,14 @@ var StagehandFacadeTools = class StagehandFacadeTools {
|
|
|
262
227
|
await fsp.mkdir(directory).catch((error) => {
|
|
263
228
|
if (error.code !== "EEXIST") throw error;
|
|
264
229
|
});
|
|
265
|
-
if (!(await fsp.lstat(directory)).isDirectory()) throw new StagehandFacadeInputError(
|
|
230
|
+
if (!(await fsp.lstat(directory)).isDirectory()) throw new StagehandFacadeInputError(SCREENSHOT_ARTIFACT_SYMLINK_ERROR);
|
|
266
231
|
}
|
|
267
|
-
const file = await fsp.open(path.join(directory, components.at(-1)), constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW, 384)
|
|
232
|
+
const file = await fsp.open(path.join(directory, components.at(-1)), constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW, 384).catch((error) => {
|
|
233
|
+
if (error.code === "ELOOP") throw new StagehandFacadeInputError(SCREENSHOT_ARTIFACT_FILE_SYMLINK_ERROR);
|
|
234
|
+
throw error;
|
|
235
|
+
});
|
|
268
236
|
try {
|
|
269
|
-
await file.writeFile(
|
|
237
|
+
await file.writeFile(artifact.bytes);
|
|
270
238
|
} finally {
|
|
271
239
|
await file.close();
|
|
272
240
|
}
|
|
@@ -275,7 +243,13 @@ var StagehandFacadeTools = class StagehandFacadeTools {
|
|
|
275
243
|
async activePage() {
|
|
276
244
|
const context = this.stagehand.browser.context;
|
|
277
245
|
const keeperPageId = await this.keeper;
|
|
278
|
-
|
|
246
|
+
let active;
|
|
247
|
+
try {
|
|
248
|
+
active = await context.activePage();
|
|
249
|
+
} catch (error) {
|
|
250
|
+
if (!(error instanceof Error) || !error.message.includes("active target not registered")) throw error;
|
|
251
|
+
return (await context.pages()).filter((page) => page.pageId !== keeperPageId).at(-1) ?? await context.newPage();
|
|
252
|
+
}
|
|
279
253
|
if (active && active.pageId !== keeperPageId) return active;
|
|
280
254
|
const page = (await context.pages()).filter((page) => page.pageId !== keeperPageId)[0] ?? await context.newPage();
|
|
281
255
|
await context.setActivePage(page);
|
|
@@ -303,7 +277,7 @@ var StagehandFacadeTools = class StagehandFacadeTools {
|
|
|
303
277
|
enqueue(tool, operation) {
|
|
304
278
|
const guarded = async () => {
|
|
305
279
|
if (this.loss) throw new StagehandFacadeSessionLostError(this.loss);
|
|
306
|
-
if (this.closed) throw new StagehandFacadeInputError(
|
|
280
|
+
if (this.closed) throw new StagehandFacadeInputError(FACADE_BROWSER_CLOSED_ERROR);
|
|
307
281
|
try {
|
|
308
282
|
await this.ensureKeeperPage();
|
|
309
283
|
const value = await operation();
|
|
@@ -343,6 +317,21 @@ var StagehandFacadeTools = class StagehandFacadeTools {
|
|
|
343
317
|
} catch {}
|
|
344
318
|
}
|
|
345
319
|
};
|
|
320
|
+
function isRecord(value) {
|
|
321
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
322
|
+
}
|
|
323
|
+
function isRunEnvelope(value) {
|
|
324
|
+
if (!isRecord(value) || value.__stagehandPlaywrightCompat !== true) return false;
|
|
325
|
+
const { executionError, artifacts } = value;
|
|
326
|
+
return typeof value.closeRequested === "boolean" && typeof value.batchRuntimeMs === "number" && isRecord(value.telemetry) && Array.isArray(artifacts) && artifacts.every((artifact) => isRecord(artifact) && typeof artifact.path === "string" && isCanonicalBase64(artifact.base64)) && (executionError === void 0 || executionError === null || isRecord(executionError) && typeof executionError.name === "string" && typeof executionError.message === "string" && (executionError.stack === void 0 || typeof executionError.stack === "string"));
|
|
327
|
+
}
|
|
328
|
+
function isCanonicalBase64(value) {
|
|
329
|
+
return typeof value === "string" && value.length % 4 === 0 && /^[A-Za-z0-9+/]*={0,2}$/.test(value);
|
|
330
|
+
}
|
|
331
|
+
function decodedBase64Length(value) {
|
|
332
|
+
const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
|
|
333
|
+
return value.length / 4 * 3 - padding;
|
|
334
|
+
}
|
|
346
335
|
var FacadeDeadlineError = class extends Error {
|
|
347
336
|
name = "FacadeDeadlineError";
|
|
348
337
|
constructor(operation, timeoutMs) {
|
|
@@ -387,15 +376,4 @@ function cdpSessionLossCause(error) {
|
|
|
387
376
|
}
|
|
388
377
|
return sanitizeErrorMessage(messages.join("; caused by "));
|
|
389
378
|
}
|
|
390
|
-
function trimTrailingTextNode(path) {
|
|
391
|
-
return path?.replace(/\/text\(\)(\[\d+\])?$/iu, "");
|
|
392
|
-
}
|
|
393
|
-
function resolveSnapshotXPath(xpathById, id) {
|
|
394
|
-
const exact = xpathById[id];
|
|
395
|
-
if (exact !== void 0) return exact;
|
|
396
|
-
if (id.includes("-")) return void 0;
|
|
397
|
-
const suffix = `-${id}`;
|
|
398
|
-
const matches = Object.keys(xpathById).filter((key) => key.endsWith(suffix));
|
|
399
|
-
return matches.length === 1 ? xpathById[matches[0]] : void 0;
|
|
400
|
-
}
|
|
401
379
|
export { FACADE_KEEPER_PAGE_URL, StagehandFacadeCleanupError, StagehandFacadeExecutionError, StagehandFacadeInputError, StagehandFacadeSessionLostError, StagehandFacadeTools };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@browserbasehq/eve",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3-alpha-99e855dc16394f85652b080eac74f1053cea05b4",
|
|
4
4
|
"description": "Browserbase for Eve, powered by Stagehand V4 Code Mode.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@browserbasehq/sdk": "^2.16.0",
|
|
43
|
-
"zod": "4.
|
|
44
|
-
"@browserbasehq/stagehand": "4.3.
|
|
43
|
+
"zod": "4.6.6",
|
|
44
|
+
"@browserbasehq/stagehand": "4.3.1-alpha-99e855dc16394f85652b080eac74f1053cea05b4"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/node": "^24",
|