stagehand 4.2.0a0.dev1575__tar.gz → 4.2.0a0.dev1577__tar.gz

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.
Files changed (36) hide show
  1. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/PKG-INFO +1 -1
  2. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/pyproject.toml +1 -1
  3. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/_extension/service-worker.js +238 -193
  4. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/_generated/protocol_version.py +1 -1
  5. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/README.md +0 -0
  6. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/__init__.py +0 -0
  7. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/_extension/blank.html +0 -0
  8. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/_extension/content-script.js +0 -0
  9. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/_extension/manifest.json +0 -0
  10. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/_extension/offscreen/service-worker-heartbeat.html +0 -0
  11. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/_extension/offscreen/service-worker-heartbeat.js +0 -0
  12. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/_extension/wake-service-worker.html +0 -0
  13. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/_extension/wake-service-worker.js +0 -0
  14. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/_generated/__init__.py +0 -0
  15. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/_generated/input_types.py +0 -0
  16. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/_generated/models.py +0 -0
  17. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/_sdk_identity.py +0 -0
  18. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/_validation.py +0 -0
  19. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/browser.py +0 -0
  20. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/browser_clipboard.py +0 -0
  21. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/browser_context.py +0 -0
  22. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/browserbase_services.py +0 -0
  23. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/browserbase_session.py +0 -0
  24. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/cdp_client.py +0 -0
  25. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/client_models.py +0 -0
  26. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/client_types.py +0 -0
  27. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/extension_assets.py +0 -0
  28. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/file_upload.py +0 -0
  29. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/locator.py +0 -0
  30. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/page.py +0 -0
  31. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/py.typed +0 -0
  32. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/response.py +0 -0
  33. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/rpc_client.py +0 -0
  34. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/stagehand.py +0 -0
  35. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/timeouts.py +0 -0
  36. {stagehand-4.2.0a0.dev1575 → stagehand-4.2.0a0.dev1577}/src/stagehand/webmcp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stagehand
3
- Version: 4.2.0a0.dev1575
3
+ Version: 4.2.0a0.dev1577
4
4
  Summary: Browserbase's SDK for building browser agents
5
5
  Keywords: ai,agent,ai-agent,browser,browser-agent,browser-automation,computer-use,headless,playwright,cdp,llm,automation,web-scraping,testing
6
6
  Author: Browserbase
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "stagehand"
3
- version = "4.2.0a0.dev1575"
3
+ version = "4.2.0a0.dev1577"
4
4
  description = "Browserbase's SDK for building browser agents"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -7363,7 +7363,7 @@ function isRecord$1(value) {
7363
7363
  }
7364
7364
  var package_default$1 = {
7365
7365
  name: "@browserbasehq/stagehand-protocol",
7366
- version: "2.1.0-alpha-0336409092b85beeba38ea3cd4953f45eb96ed6b",
7366
+ version: "2.1.0-alpha-c9c8a41778b2000c9a9bdfc4b68e6c0c4866ab1a",
7367
7367
  "private": true,
7368
7368
  type: "module",
7369
7369
  exports: {
@@ -16188,12 +16188,12 @@ function stringifyArgument(value) {
16188
16188
  return "[unserializable]";
16189
16189
  }
16190
16190
  }
16191
- async function performUnderstudyMethod(page, frame, method, rawXPath, args, logger, domSettleTimeoutMs) {
16191
+ async function performUnderstudyMethod(page, frame, method, rawXPath, args, logger, progress, domSettleTimeoutMs) {
16192
16192
  const selectorRaw = normalizeRootXPath(rawXPath);
16193
16193
  try {
16194
16194
  await logger.span(`Understudy${toTitleCase(method)}`, { target: selectorRaw }, async (spanLogger) => {
16195
- const locator = await resolveLocatorWithHops(page, frame, selectorRaw);
16196
- const initialUrl = await getFrameUrl(frame);
16195
+ const locator = await resolveLocatorWithHops(page, frame, selectorRaw, progress);
16196
+ const initialUrl = await progress.run("reading frame URL", () => frame.evaluate("location.href", void 0, progress));
16197
16197
  spanLogger.debug("Performing understudy method", {
16198
16198
  category: "action",
16199
16199
  xpath: selectorRaw,
@@ -16209,11 +16209,12 @@ async function performUnderstudyMethod(page, frame, method, rawXPath, args, logg
16209
16209
  page,
16210
16210
  initialUrl,
16211
16211
  logger: spanLogger,
16212
- domSettleTimeoutMs
16212
+ domSettleTimeoutMs,
16213
+ progress
16213
16214
  };
16214
16215
  const handler = METHOD_HANDLER_MAP[method] ?? null;
16215
16216
  if (handler) {
16216
- await handler(ctx);
16217
+ await progress.run(`performing ${method}`, () => handler(ctx));
16217
16218
  return;
16218
16219
  }
16219
16220
  spanLogger.debug("Chosen method is invalid", {
@@ -16258,7 +16259,7 @@ async function selectOption(ctx) {
16258
16259
  const { locator, xpath, args, logger } = ctx;
16259
16260
  try {
16260
16261
  const text = args[0]?.toString() || "";
16261
- await locator.selectOption(text);
16262
+ await locator.selectOption(text, ctx.progress);
16262
16263
  } catch (e) {
16263
16264
  const msg = e instanceof Error ? e.message : String(e);
16264
16265
  const stack = e instanceof Error ? e.stack : void 0;
@@ -16277,10 +16278,13 @@ async function scrollIntoView(ctx) {
16277
16278
  category: "action",
16278
16279
  xpath
16279
16280
  });
16280
- const { objectId } = await locator.resolveNode();
16281
+ const { objectId } = await locator.resolveNode(ctx.progress);
16281
16282
  const ownerSession = locator.getFrame().session;
16282
- await ownerSession.send("DOM.scrollIntoViewIfNeeded", { objectId });
16283
- await ownerSession.send("Runtime.releaseObject", { objectId }).catch(() => {});
16283
+ try {
16284
+ await ctx.progress.run("scrolling element into view", () => ownerSession.send("DOM.scrollIntoViewIfNeeded", { objectId }));
16285
+ } finally {
16286
+ await ctx.progress.cleanup(() => ownerSession.send("Runtime.releaseObject", { objectId }));
16287
+ }
16284
16288
  }
16285
16289
  async function scrollElementToPercentage(ctx) {
16286
16290
  const { locator, xpath, args, logger } = ctx;
@@ -16290,15 +16294,15 @@ async function scrollElementToPercentage(ctx) {
16290
16294
  coordinate: [...args]
16291
16295
  });
16292
16296
  const [yArg = "0%"] = args;
16293
- await locator.scrollTo(yArg);
16297
+ await locator.scrollTo(yArg, ctx.progress);
16294
16298
  }
16295
16299
  /** Scroll the page by pixel offset, starting from the element's center. */
16296
16300
  async function scrollByPixelOffset(ctx) {
16297
16301
  const { locator, page, args } = ctx;
16298
16302
  const dx = Number(args[0] ?? 0);
16299
16303
  const dy = Number(args[1] ?? 0);
16300
- const { x, y } = await locator.centroid();
16301
- await page.scroll(x, y, dx, dy);
16304
+ const { x, y } = await locator.centroid(ctx.progress);
16305
+ await page.scroll(x, y, dx, dy, ctx.progress);
16302
16306
  }
16303
16307
  async function wheelScroll(ctx) {
16304
16308
  const { frame, args, logger } = ctx;
@@ -16307,19 +16311,19 @@ async function wheelScroll(ctx) {
16307
16311
  category: "action",
16308
16312
  deltaY
16309
16313
  });
16310
- await frame.session.send("Input.dispatchMouseEvent", {
16314
+ await ctx.progress.run("dispatching mouse wheel", () => frame.session.send("Input.dispatchMouseEvent", {
16311
16315
  type: "mouseWheel",
16312
16316
  x: 0,
16313
16317
  y: 0,
16314
16318
  deltaY,
16315
16319
  deltaX: 0
16316
- });
16320
+ }));
16317
16321
  }
16318
16322
  async function fillOrType(ctx) {
16319
16323
  const { locator, xpath, args, logger } = ctx;
16320
16324
  try {
16321
- await locator.fill("");
16322
- await locator.fill(args[0] ?? "");
16325
+ await locator.fill("", ctx.progress);
16326
+ await locator.fill(args[0] ?? "", ctx.progress);
16323
16327
  } catch (e) {
16324
16328
  const msg = e instanceof Error ? e.message : String(e);
16325
16329
  logger.debug("Error filling element", {
@@ -16333,7 +16337,7 @@ async function fillOrType(ctx) {
16333
16337
  async function typeText(ctx) {
16334
16338
  const { locator, xpath, args, logger } = ctx;
16335
16339
  try {
16336
- await locator.type(args[0] ?? "");
16340
+ await locator.type(args[0] ?? "", void 0, ctx.progress);
16337
16341
  } catch (e) {
16338
16342
  const msg = e instanceof Error ? e.message : String(e);
16339
16343
  logger.debug("Error typing into element", {
@@ -16353,7 +16357,7 @@ async function pressKey(ctx) {
16353
16357
  key,
16354
16358
  xpath
16355
16359
  });
16356
- await page.keyPress(key);
16360
+ await page.keyPress(key, void 0, ctx.progress);
16357
16361
  } catch (e) {
16358
16362
  const msg = e instanceof Error ? e.message : String(e);
16359
16363
  logger.debug("Error pressing key", {
@@ -16368,7 +16372,7 @@ async function pressKey(ctx) {
16368
16372
  async function clickElement(ctx) {
16369
16373
  const { locator, xpath, args, logger } = ctx;
16370
16374
  try {
16371
- await locator.click({ button: args[0] || void 0 });
16375
+ await locator.click({ button: args[0] || void 0 }, ctx.progress);
16372
16376
  } catch (e) {
16373
16377
  const msg = e instanceof Error ? e.message : String(e);
16374
16378
  logger.error("Error performing click", {
@@ -16382,7 +16386,7 @@ async function clickElement(ctx) {
16382
16386
  async function doubleClick(ctx) {
16383
16387
  const { locator, xpath, logger } = ctx;
16384
16388
  try {
16385
- await locator.click({ clickCount: 2 });
16389
+ await locator.click({ clickCount: 2 }, ctx.progress);
16386
16390
  } catch (e) {
16387
16391
  const msg = e instanceof Error ? e.message : String(e);
16388
16392
  logger.error("Error performing double click", {
@@ -16397,10 +16401,10 @@ async function dragAndDrop(ctx) {
16397
16401
  const { page, frame, locator, args, xpath, logger } = ctx;
16398
16402
  const toXPath = String(args[0] ?? "").trim();
16399
16403
  if (!toXPath) throw new Error("dragAndDrop requires a target XPath arg");
16400
- const targetLocator = await resolveLocatorWithHops(page, frame, toXPath);
16404
+ const targetLocator = await resolveLocatorWithHops(page, frame, toXPath, ctx.progress);
16401
16405
  try {
16402
- const { x: fromLocalX, y: fromLocalY } = await locator.centroid();
16403
- const { x: toLocalX, y: toLocalY } = await targetLocator.centroid();
16406
+ const { x: fromLocalX, y: fromLocalY } = await locator.centroid(ctx.progress);
16407
+ const { x: toLocalX, y: toLocalY } = await targetLocator.centroid(ctx.progress);
16404
16408
  const fromAbs = await locator.getFrame().evaluate(({ x, y }) => {
16405
16409
  let X = x;
16406
16410
  let Y = y;
@@ -16420,7 +16424,7 @@ async function dragAndDrop(ctx) {
16420
16424
  }, {
16421
16425
  x: fromLocalX,
16422
16426
  y: fromLocalY
16423
- });
16427
+ }, ctx.progress);
16424
16428
  const toAbs = await targetLocator.getFrame().evaluate(({ x, y }) => {
16425
16429
  let X = x;
16426
16430
  let Y = y;
@@ -16440,11 +16444,11 @@ async function dragAndDrop(ctx) {
16440
16444
  }, {
16441
16445
  x: toLocalX,
16442
16446
  y: toLocalY
16443
- });
16447
+ }, ctx.progress);
16444
16448
  await page.dragAndDrop(fromAbs.x, fromAbs.y, toAbs.x, toAbs.y, {
16445
16449
  steps: 10,
16446
16450
  delay: 5
16447
- });
16451
+ }, ctx.progress);
16448
16452
  } catch (e) {
16449
16453
  const msg = e instanceof Error ? e.message : String(e);
16450
16454
  logger.error("Error performing drag and drop", {
@@ -16468,9 +16472,10 @@ async function scrollByElementHeight(ctx, direction) {
16468
16472
  category: "action",
16469
16473
  xpath
16470
16474
  });
16471
- const { objectId } = await locator.resolveNode();
16475
+ const { objectId } = await locator.resolveNode(ctx.progress);
16472
16476
  try {
16473
- await locator.getFrame().session.send("Runtime.callFunctionOn", {
16477
+ const ownerSession = locator.getFrame().session;
16478
+ await ctx.progress.run("scrolling by element height", () => ownerSession.send("Runtime.callFunctionOn", {
16474
16479
  objectId,
16475
16480
  functionDeclaration: `
16476
16481
  function(dir) {
@@ -16500,15 +16505,16 @@ async function scrollByElementHeight(ctx, direction) {
16500
16505
  arguments: [{ value: direction }],
16501
16506
  awaitPromise: true,
16502
16507
  returnByValue: true
16503
- });
16508
+ }));
16504
16509
  } finally {
16505
- await locator.getFrame().session.send("Runtime.releaseObject", { objectId }).catch(() => {});
16510
+ const ownerSession = locator.getFrame().session;
16511
+ await ctx.progress.cleanup(() => ownerSession.send("Runtime.releaseObject", { objectId }));
16506
16512
  }
16507
16513
  }
16508
16514
  async function hover(ctx) {
16509
16515
  const { locator, xpath, logger } = ctx;
16510
16516
  try {
16511
- await locator.hover();
16517
+ await locator.hover(ctx.progress);
16512
16518
  } catch (e) {
16513
16519
  const msg = e instanceof Error ? e.message : String(e);
16514
16520
  const stack = e instanceof Error ? e.stack : void 0;
@@ -16521,31 +16527,31 @@ async function hover(ctx) {
16521
16527
  throw e;
16522
16528
  }
16523
16529
  }
16524
- async function getFrameUrl(frame) {
16525
- return await frame.evaluate("location.href");
16526
- }
16527
16530
  /**
16528
16531
  * More robust DOM settle using Network + Page events to detect network quiet.
16529
16532
  * Closely modeled after the provided snippet, adapted to our Frame/session + logger.
16530
16533
  */
16531
- async function waitForDomNetworkQuiet(frame, logger, timeout) {
16534
+ async function waitForDomNetworkQuiet(frame, logger, timeout, progress) {
16532
16535
  const overallTimeout = typeof timeout === "number" && Number.isFinite(timeout) ? Math.max(0, timeout) : 5e3;
16533
16536
  const client = frame.session;
16534
16537
  const settleStart = Date.now();
16535
16538
  let hasDoc;
16536
16539
  try {
16537
- const rs = await frame.evaluate("document.readyState");
16540
+ const rs = await progress.run("checking document readiness", () => frame.evaluate("document.readyState", void 0, progress));
16538
16541
  hasDoc = rs === "interactive" || rs === "complete";
16539
16542
  } catch {
16543
+ progress.throwIfStopped();
16540
16544
  hasDoc = false;
16541
16545
  }
16542
- if (!hasDoc && overallTimeout > 0) await frame.waitForLoadState("domcontentloaded", overallTimeout).catch(() => {});
16546
+ if (!hasDoc && overallTimeout > 0) await frame.waitForLoadState("domcontentloaded", overallTimeout, progress).catch(() => {
16547
+ progress.throwIfStopped();
16548
+ });
16543
16549
  const elapsed = Date.now() - settleStart;
16544
16550
  const remainingBudget = Math.max(0, overallTimeout - elapsed);
16545
16551
  if (remainingBudget === 0) return;
16546
- await client.send("Network.enable").catch(() => {});
16547
- await client.send("Page.enable").catch(() => {});
16548
- await client.send("Target.setAutoAttach", {
16552
+ await progress.run("enabling network events", () => client.send("Network.enable").catch(() => {}));
16553
+ await progress.run("enabling page events", () => client.send("Page.enable").catch(() => {}));
16554
+ await progress.run("attaching to frames", () => client.send("Target.setAutoAttach", {
16549
16555
  autoAttach: true,
16550
16556
  waitForDebuggerOnStart: false,
16551
16557
  flatten: true,
@@ -16556,8 +16562,9 @@ async function waitForDomNetworkQuiet(frame, logger, timeout) {
16556
16562
  type: "shared_worker",
16557
16563
  exclude: true
16558
16564
  }]
16559
- }).catch(() => {});
16560
- return new Promise((resolve) => {
16565
+ }).catch(() => {}));
16566
+ progress.throwIfStopped();
16567
+ await new Promise((resolve) => {
16561
16568
  const inflight = /* @__PURE__ */ new Set();
16562
16569
  const meta = /* @__PURE__ */ new Map();
16563
16570
  const docByFrame = /* @__PURE__ */ new Map();
@@ -16634,18 +16641,12 @@ async function waitForDomNetworkQuiet(frame, logger, timeout) {
16634
16641
  if (quietTimer) clearTimeout(quietTimer);
16635
16642
  if (stalledRequestSweepTimer) clearInterval(stalledRequestSweepTimer);
16636
16643
  clearTimeout(guard);
16644
+ progress.signal.removeEventListener("abort", resolveDone);
16637
16645
  resolve();
16638
16646
  };
16647
+ progress.signal.addEventListener("abort", resolveDone, { once: true });
16639
16648
  });
16640
- }
16641
- //#endregion
16642
- //#region handlers/handlerUtils/timeoutGuard.ts
16643
- function createTimeoutGuard(timeout, errorFactory) {
16644
- if (!timeout || timeout <= 0) return () => {};
16645
- const startTime = Date.now();
16646
- return () => {
16647
- if (Date.now() - startTime >= timeout) throw errorFactory?.(timeout) ?? new TimeoutError("operation", timeout);
16648
- };
16649
+ progress.throwIfStopped();
16649
16650
  }
16650
16651
  //#endregion
16651
16652
  //#region handlers/handlerUtils/variables.ts
@@ -16958,7 +16959,7 @@ function base64ToBytes$1(base64) {
16958
16959
  * owning session for `this.frameId`. Page is responsible for constructing
16959
16960
  * Frames with the correct session.
16960
16961
  */
16961
- var Frame = class Frame {
16962
+ var Frame = class {
16962
16963
  constructor(session, frameId, pageId, remoteBrowser, logger) {
16963
16964
  this.session = session;
16964
16965
  this.frameId = frameId;
@@ -16999,22 +17000,17 @@ var Frame = class Frame {
16999
17000
  height: model.height
17000
17001
  };
17001
17002
  }
17002
- /** Accessibility.getFullAXTree (+ recurse into child frames if requested) */
17003
- async getAccessibilityTree(withFrames = false) {
17004
- await this.session.send("Accessibility.enable");
17003
+ /** Read this frame's accessibility tree. */
17004
+ async getAccessibilityTree(progress) {
17005
+ await runLocatorStep(progress, "enabling accessibility", () => this.session.send("Accessibility.enable"));
17005
17006
  let nodes;
17006
17007
  try {
17007
- ({nodes} = await this.session.send("Accessibility.getFullAXTree", { frameId: this.frameId }));
17008
+ ({nodes} = await runLocatorStep(progress, "reading accessibility tree", () => this.session.send("Accessibility.getFullAXTree", { frameId: this.frameId })));
17008
17009
  } catch (e) {
17010
+ progress?.throwIfStopped();
17009
17011
  const msg = String(e?.message ?? e ?? "");
17010
17012
  if (!(msg.includes("Frame with the given") || msg.includes("does not belong to the target") || msg.includes("is not found"))) throw e;
17011
- ({nodes} = await this.session.send("Accessibility.getFullAXTree"));
17012
- }
17013
- if (!withFrames) return nodes;
17014
- const children = await this.childFrames();
17015
- for (const child of children) {
17016
- const childNodes = await child.getAccessibilityTree(false);
17017
- nodes.push(...childNodes);
17013
+ ({nodes} = await runLocatorStep(progress, "reading accessibility tree", () => this.session.send("Accessibility.getFullAXTree")));
17018
17014
  }
17019
17015
  return nodes;
17020
17016
  }
@@ -17125,22 +17121,12 @@ var Frame = class Frame {
17125
17121
  progress.throwIfStopped();
17126
17122
  return base64ToBytes$1(data);
17127
17123
  }
17128
- /** Child frames via Page.getFrameTree */
17129
- async childFrames() {
17130
- const { frameTree } = await this.session.send("Page.getFrameTree");
17131
- const frames = [];
17132
- const collect = (tree) => {
17133
- if (tree.frame.parentId === this.frameId) frames.push(new Frame(this.session, tree.frame.id, this.pageId, this.remoteBrowser, this.logger));
17134
- tree.childFrames?.forEach(collect);
17135
- };
17136
- collect(frameTree);
17137
- return frames;
17138
- }
17139
17124
  /** Wait for a lifecycle state (load/domcontentloaded/networkidle) */
17140
- async waitForLoadState(state = "load", timeout = 15e3) {
17141
- await this.session.send("Page.enable");
17125
+ async waitForLoadState(state = "load", timeout = 15e3, progress) {
17126
+ await runLocatorStep(progress, "enabling load events", () => this.session.send("Page.enable"));
17142
17127
  const targetState = state.toLowerCase();
17143
17128
  const effectiveTimeout = Math.max(0, timeout);
17129
+ progress?.throwIfStopped();
17144
17130
  await new Promise((resolve, reject) => {
17145
17131
  let done = false;
17146
17132
  let timer = null;
@@ -17148,6 +17134,7 @@ var Frame = class Frame {
17148
17134
  if (done) return;
17149
17135
  done = true;
17150
17136
  this.session.off("Page.lifecycleEvent", handler);
17137
+ progress?.signal.removeEventListener("abort", finish);
17151
17138
  if (timer) {
17152
17139
  clearTimeout(timer);
17153
17140
  timer = null;
@@ -17160,13 +17147,16 @@ var Frame = class Frame {
17160
17147
  if (sameFrame && lifecycleName === targetState) finish();
17161
17148
  };
17162
17149
  this.session.on("Page.lifecycleEvent", handler);
17150
+ progress?.signal.addEventListener("abort", finish, { once: true });
17163
17151
  timer = setTimeout(() => {
17164
17152
  if (done) return;
17165
17153
  done = true;
17166
17154
  this.session.off("Page.lifecycleEvent", handler);
17155
+ progress?.signal.removeEventListener("abort", finish);
17167
17156
  reject(/* @__PURE__ */ new Error(`waitForLoadState(${state}) timed out after ${effectiveTimeout}ms for frame ${this.frameId}`));
17168
17157
  }, effectiveTimeout);
17169
17158
  });
17159
+ progress?.throwIfStopped();
17170
17160
  }
17171
17161
  /** Simple placeholder for your own locator abstraction */
17172
17162
  locator(selector, options) {
@@ -18597,11 +18587,12 @@ function missMetadata(response, reason) {
18597
18587
  * or whenever any cache step fails, including `onHit` itself — falls back to
18598
18588
  * `execute` and then persists the outcome's `cacheValue`.
18599
18589
  */
18600
- async function withCache({ method, page, data, caching, bypass, context, logger, onHit, execute }) {
18590
+ async function withCache({ method, page, data, caching, bypass, context, logger, onHit, execute, progress }) {
18591
+ progress?.throwIfStopped();
18601
18592
  const resolvedCaching = caching ?? context?.defaultCaching ?? true;
18602
18593
  const cachePage = resolvedCaching !== false ? asCachePage(page) : null;
18603
18594
  if (bypass || !context || !cachePage) return (await execute()).result;
18604
- const cdpTree = await collectCdpTree(cachePage, logger);
18595
+ const cdpTree = await collectCdpTree(cachePage, logger, progress);
18605
18596
  if (!cdpTree) return (await execute()).result;
18606
18597
  const baseRequest = {
18607
18598
  method,
@@ -18612,8 +18603,9 @@ async function withCache({ method, page, data, caching, bypass, context, logger,
18612
18603
  };
18613
18604
  let getResponse = null;
18614
18605
  try {
18615
- getResponse = await context.client.get(baseRequest);
18606
+ getResponse = await runLocatorStep(progress, "reading cache", () => context.client.get(baseRequest));
18616
18607
  } catch (error) {
18608
+ progress?.throwIfStopped();
18617
18609
  logger.warn("Cache read failed; executing without cache", {
18618
18610
  category: "cache",
18619
18611
  method,
@@ -18622,7 +18614,7 @@ async function withCache({ method, page, data, caching, bypass, context, logger,
18622
18614
  }
18623
18615
  let cacheMetadata;
18624
18616
  if (getResponse?.hit && getResponse.value !== void 0 && getResponse.value !== null) try {
18625
- const result = await onHit(getResponse.value);
18617
+ const result = await runLocatorStep(progress, "applying cached result", async () => onHit(getResponse.value));
18626
18618
  result.metadata.cache = hitMetadata(getResponse);
18627
18619
  logger.debug("Cache hit", {
18628
18620
  category: "cache",
@@ -18633,6 +18625,7 @@ async function withCache({ method, page, data, caching, bypass, context, logger,
18633
18625
  });
18634
18626
  return result;
18635
18627
  } catch (error) {
18628
+ progress?.throwIfStopped();
18636
18629
  logger.warn("Cached value could not be applied; falling back to execution", {
18637
18630
  category: "cache",
18638
18631
  method,
@@ -18650,14 +18643,16 @@ async function withCache({ method, page, data, caching, bypass, context, logger,
18650
18643
  missReason: cacheMetadata.missReason ?? "unknown"
18651
18644
  });
18652
18645
  } else cacheMetadata = missMetadata(null, "read_failed");
18646
+ progress?.throwIfStopped();
18653
18647
  const outcome = await execute();
18648
+ progress?.throwIfStopped();
18654
18649
  outcome.result.metadata.cache = cacheMetadata;
18655
18650
  if (outcome.cacheValue !== void 0 && outcome.cacheValue !== null) try {
18656
- const setResponse = await context.client.set({
18651
+ const setResponse = await runLocatorStep(progress, "writing cache", () => context.client.set({
18657
18652
  ...baseRequest,
18658
18653
  value: outcome.cacheValue,
18659
18654
  llmUsage: outcome.llmUsage
18660
- });
18655
+ }));
18661
18656
  logger.debug(setResponse.written ? "Cache write completed" : "Cache write skipped", {
18662
18657
  category: "cache",
18663
18658
  method,
@@ -18665,6 +18660,7 @@ async function withCache({ method, page, data, caching, bypass, context, logger,
18665
18660
  skippedReason: setResponse.skippedReason ?? ""
18666
18661
  });
18667
18662
  } catch (error) {
18663
+ progress?.throwIfStopped();
18668
18664
  logger.warn("Cache write failed", {
18669
18665
  category: "cache",
18670
18666
  method,
@@ -18699,19 +18695,20 @@ function asCachePage(page) {
18699
18695
  * Collects the verbatim Accessibility.getFullAXTree nodes for every frame.
18700
18696
  * Returns null (skip caching) when the payload can't be assembled.
18701
18697
  */
18702
- async function collectCdpTree(page, logger) {
18698
+ async function collectCdpTree(page, logger, progress) {
18703
18699
  try {
18704
18700
  const mainFrame = page.mainFrame();
18705
18701
  const frames = [];
18706
18702
  for (const frame of page.frames()) frames.push({
18707
18703
  frameId: frame.frameId,
18708
- axNodes: await frame.getAccessibilityTree(false)
18704
+ axNodes: await runLocatorStep(progress, "reading cache accessibility tree", () => frame.getAccessibilityTree(progress))
18709
18705
  });
18710
18706
  return {
18711
18707
  rootFrameId: mainFrame.frameId,
18712
18708
  frames
18713
18709
  };
18714
18710
  } catch (error) {
18711
+ progress?.throwIfStopped();
18715
18712
  logger.warn("Failed to collect CDP tree for cache; executing without cache", {
18716
18713
  category: "cache",
18717
18714
  error: error instanceof Error ? error.message : String(error)
@@ -51231,11 +51228,15 @@ function disabledCacheMetadata() {
51231
51228
  }
51232
51229
  //#endregion
51233
51230
  //#region services/actService.ts
51234
- async function act({ params, page, model, clientLLMGenerate, logger, systemPrompt = "", selfHeal = false, domSettleTimeoutMs, cache, gateway }) {
51231
+ async function act(options) {
51232
+ return runWithProgress(options.progress ?? {
51233
+ name: "act()",
51234
+ timeout: options.params.options?.timeout ?? 0
51235
+ }, (progress) => actWithProgress(options, progress));
51236
+ }
51237
+ async function actWithProgress({ params, page, model, clientLLMGenerate, logger, systemPrompt = "", selfHeal = false, domSettleTimeoutMs, cache, gateway }, progress) {
51235
51238
  const { instruction: actInstruction, options } = params;
51236
51239
  const variables = options?.variables;
51237
- const timeout = options?.timeout;
51238
- const ensureTimeRemaining = createTimeoutGuard(timeout, (ms) => new TimeoutError("act()", ms));
51239
51240
  let operationUsage = zeroStagehandResultUsage();
51240
51241
  const recordUsage = (response) => {
51241
51242
  operationUsage = aggregateUsage(operationUsage, usageFromInference(response));
@@ -51248,11 +51249,11 @@ async function act({ params, page, model, clientLLMGenerate, logger, systemPromp
51248
51249
  systemPrompt,
51249
51250
  selfHeal,
51250
51251
  domSettleTimeoutMs,
51251
- ensureTimeRemaining,
51252
+ progress,
51252
51253
  gateway,
51253
51254
  recordUsage
51254
51255
  };
51255
- ensureTimeRemaining();
51256
+ progress.throwIfStopped();
51256
51257
  if (typeof actInstruction !== "string") return actResult(await takeDeterministicAction({
51257
51258
  action: actInstruction,
51258
51259
  variables,
@@ -51263,8 +51264,8 @@ async function act({ params, page, model, clientLLMGenerate, logger, systemPromp
51263
51264
  focusLocator: options?.locator,
51264
51265
  ignoreLocators: options?.ignoreLocators
51265
51266
  };
51266
- await waitForDomNetworkQuiet(page.mainFrame(), logger, domSettleTimeoutMs);
51267
- ensureTimeRemaining();
51267
+ await waitForDomNetworkQuiet(page.mainFrame(), logger, domSettleTimeoutMs, progress);
51268
+ progress.throwIfStopped();
51268
51269
  return await withCache({
51269
51270
  method: "act",
51270
51271
  page,
@@ -51273,6 +51274,7 @@ async function act({ params, page, model, clientLLMGenerate, logger, systemPromp
51273
51274
  bypass: shouldBypassCacheForLocatorScope(options),
51274
51275
  context: cache,
51275
51276
  logger,
51277
+ progress,
51276
51278
  onHit: (value) => replayCachedActions(value, instruction, variables, context),
51277
51279
  execute: async () => {
51278
51280
  const result = await runActPipeline();
@@ -51289,9 +51291,9 @@ async function act({ params, page, model, clientLLMGenerate, logger, systemPromp
51289
51291
  }
51290
51292
  });
51291
51293
  async function runActPipeline() {
51292
- const { combinedTree, combinedXpathMap } = await page.captureSnapshot(snapshotOptions);
51294
+ const { combinedTree, combinedXpathMap } = await page.captureSnapshot(snapshotOptions, progress);
51293
51295
  const actPrompt = buildActPrompt(instruction, Object.values(SupportedUnderstudyAction), variables);
51294
- ensureTimeRemaining();
51296
+ progress.throwIfStopped();
51295
51297
  const firstInference = await getActionFromLLM({
51296
51298
  instruction: actPrompt,
51297
51299
  domElements: combinedTree,
@@ -51307,18 +51309,18 @@ async function act({ params, page, model, clientLLMGenerate, logger, systemPromp
51307
51309
  actions: []
51308
51310
  }, operationUsage);
51309
51311
  }
51310
- ensureTimeRemaining();
51312
+ progress.throwIfStopped();
51311
51313
  const firstResult = await takeDeterministicAction({
51312
51314
  action: firstInference.action,
51313
51315
  variables,
51314
51316
  context
51315
51317
  });
51316
51318
  if (!firstInference.response.twoStep) return actResult(firstResult, operationUsage);
51317
- ensureTimeRemaining();
51318
- const { combinedTree: nextTree, combinedXpathMap: nextXpathMap } = await page.captureSnapshot(snapshotOptions);
51319
+ progress.throwIfStopped();
51320
+ const { combinedTree: nextTree, combinedXpathMap: nextXpathMap } = await page.captureSnapshot(snapshotOptions, progress);
51319
51321
  const changedTree = diffCombinedTrees(combinedTree, nextTree);
51320
51322
  const secondInstruction = buildStepTwoPrompt(instruction, describeAction(firstInference.action), Object.values(SupportedUnderstudyAction).filter((action) => action !== SupportedUnderstudyAction.SELECT_OPTION_FROM_DROPDOWN), variables);
51321
- ensureTimeRemaining();
51323
+ progress.throwIfStopped();
51322
51324
  const secondInference = await getActionFromLLM({
51323
51325
  instruction: secondInstruction,
51324
51326
  domElements: changedTree.trim() ? changedTree : nextTree,
@@ -51326,7 +51328,7 @@ async function act({ params, page, model, clientLLMGenerate, logger, systemPromp
51326
51328
  context
51327
51329
  });
51328
51330
  if (!secondInference.action) return actResult(firstResult, operationUsage);
51329
- ensureTimeRemaining();
51331
+ progress.throwIfStopped();
51330
51332
  const secondResult = await takeDeterministicAction({
51331
51333
  action: secondInference.action,
51332
51334
  variables,
@@ -51369,12 +51371,15 @@ async function replayCachedActions(value, instruction, variables, context) {
51369
51371
  });
51370
51372
  }
51371
51373
  async function getActionFromLLM({ instruction, domElements, xpathMap, context }) {
51372
- const response = await act$1({
51374
+ const response = await context.progress.run("waiting for act inference", () => act$1({
51373
51375
  instruction,
51374
51376
  domElements,
51375
- generate: (input) => generate(context.model, input, context.clientLLMGenerate, context.gateway),
51377
+ generate: (input) => {
51378
+ context.progress.throwIfStopped();
51379
+ return generate(context.model, input, context.clientLLMGenerate, context.gateway);
51380
+ },
51376
51381
  userProvidedInstructions: context.systemPrompt
51377
- });
51382
+ }));
51378
51383
  context.recordUsage(response);
51379
51384
  context.logger.info("Act inference completed", {
51380
51385
  category: "action",
@@ -51391,7 +51396,7 @@ async function getActionFromLLM({ instruction, domElements, xpathMap, context })
51391
51396
  } : { response };
51392
51397
  }
51393
51398
  async function takeDeterministicAction({ action, variables, context }) {
51394
- context.ensureTimeRemaining();
51399
+ context.progress.throwIfStopped();
51395
51400
  const method = action.method?.trim();
51396
51401
  if (!method || method === "not-supported") {
51397
51402
  context.logger.error("Action has no supported method", {
@@ -51408,10 +51413,11 @@ async function takeDeterministicAction({ action, variables, context }) {
51408
51413
  const placeholderArgs = Array.isArray(action.arguments) ? [...action.arguments] : [];
51409
51414
  const resolvedArgs = substituteVariablesInArguments(action.arguments, variables) ?? [];
51410
51415
  try {
51411
- context.ensureTimeRemaining();
51412
- await performUnderstudyMethod(context.page, context.page.mainFrame(), method, action.selector, resolvedArgs, context.logger, context.domSettleTimeoutMs);
51416
+ context.progress.throwIfStopped();
51417
+ await performUnderstudyMethod(context.page, context.page.mainFrame(), method, action.selector, resolvedArgs, context.logger, context.progress, context.domSettleTimeoutMs);
51413
51418
  return successfulActionResult(action, method, action.selector, placeholderArgs);
51414
51419
  } catch (error) {
51420
+ context.progress.throwIfStopped();
51415
51421
  if (error instanceof TimeoutError) throw error;
51416
51422
  const message = error instanceof Error ? error.message : String(error);
51417
51423
  if (!context.selfHeal) return {
@@ -51437,8 +51443,8 @@ async function takeDeterministicAction({ action, variables, context }) {
51437
51443
  async function selfHealAction({ action, method, resolvedArgs, placeholderArgs, context }) {
51438
51444
  const actionInstruction = action.description ? action.description.toLowerCase().startsWith(method.toLowerCase()) ? action.description : `${method} ${action.description}` : method;
51439
51445
  try {
51440
- context.ensureTimeRemaining();
51441
- const { combinedTree, combinedXpathMap } = await context.page.captureSnapshot({});
51446
+ context.progress.throwIfStopped();
51447
+ const { combinedTree, combinedXpathMap } = await context.page.captureSnapshot({}, context.progress);
51442
51448
  const inferenceResult = await getActionFromLLM({
51443
51449
  instruction: buildActPrompt(actionInstruction, Object.values(SupportedUnderstudyAction), {}),
51444
51450
  domElements: combinedTree,
@@ -51452,10 +51458,11 @@ async function selfHealAction({ action, method, resolvedArgs, placeholderArgs, c
51452
51458
  actions: []
51453
51459
  };
51454
51460
  const selector = inferenceResult.action?.selector ?? action.selector;
51455
- context.ensureTimeRemaining();
51456
- await performUnderstudyMethod(context.page, context.page.mainFrame(), method, selector, resolvedArgs, context.logger, context.domSettleTimeoutMs);
51461
+ context.progress.throwIfStopped();
51462
+ await performUnderstudyMethod(context.page, context.page.mainFrame(), method, selector, resolvedArgs, context.logger, context.progress, context.domSettleTimeoutMs);
51457
51463
  return successfulActionResult(action, method, selector, placeholderArgs);
51458
51464
  } catch (error) {
51465
+ context.progress.throwIfStopped();
51459
51466
  if (error instanceof TimeoutError) throw error;
51460
51467
  return {
51461
51468
  success: false,
@@ -51555,9 +51562,14 @@ function transformUrlStringsToNumericIds(schema) {
51555
51562
  const [finalSchema, urlPaths] = transformSchema(schema, []);
51556
51563
  return [finalSchema, urlPaths];
51557
51564
  }
51558
- async function extract({ params, page, model, clientLLMGenerate, logger, systemPrompt = "", cache, gateway }) {
51565
+ async function extract(options) {
51566
+ return runWithProgress(options.progress ?? {
51567
+ name: "extract()",
51568
+ timeout: options.params.options?.timeout ?? 0
51569
+ }, (progress) => extractWithProgress(options, progress));
51570
+ }
51571
+ async function extractWithProgress({ params, page, model, clientLLMGenerate, logger, systemPrompt = "", cache, gateway }, progress) {
51559
51572
  const { instruction, options } = params;
51560
- const ensureTimeRemaining = createTimeoutGuard(options?.timeout, (ms) => new TimeoutError("extract()", ms));
51561
51573
  if (options?.screenshot) return (await runExtraction()).result;
51562
51574
  return await withCache({
51563
51575
  method: "extract",
@@ -51567,6 +51579,7 @@ async function extract({ params, page, model, clientLLMGenerate, logger, systemP
51567
51579
  bypass: shouldBypassCacheForLocatorScope(options),
51568
51580
  context: cache,
51569
51581
  logger,
51582
+ progress,
51570
51583
  onHit: (value) => ({
51571
51584
  data: json$1().parse(value),
51572
51585
  metadata: {
@@ -51577,21 +51590,17 @@ async function extract({ params, page, model, clientLLMGenerate, logger, systemP
51577
51590
  execute: () => runExtraction()
51578
51591
  });
51579
51592
  async function runExtraction() {
51580
- ensureTimeRemaining();
51593
+ progress.throwIfStopped();
51581
51594
  const { combinedTree, combinedUrlMap } = await page.captureSnapshot({
51582
51595
  focusLocator: options?.locator,
51583
51596
  ignoreLocators: options?.ignoreLocators
51584
- });
51585
- ensureTimeRemaining();
51586
- const screenshot = options?.screenshot ? await (async () => {
51587
- ensureTimeRemaining();
51588
- const image = await page.screenshot({
51589
- fullPage: false,
51590
- type: "png"
51591
- });
51592
- ensureTimeRemaining();
51593
- return image;
51594
- })() : void 0;
51597
+ }, progress);
51598
+ progress.throwIfStopped();
51599
+ const screenshot = options?.screenshot ? await page.screenshot({
51600
+ fullPage: false,
51601
+ type: "png"
51602
+ }, progress) : void 0;
51603
+ progress.throwIfStopped();
51595
51604
  logger.info(screenshot ? "Starting extraction using an accessibility snapshot and viewport screenshot" : "Starting extraction using an accessibility snapshot", {
51596
51605
  category: "extraction",
51597
51606
  instruction
@@ -51605,16 +51614,16 @@ async function extract({ params, page, model, clientLLMGenerate, logger, systemP
51605
51614
  data: bytesToBase64(screenshot),
51606
51615
  mimeType: "image/png"
51607
51616
  } : void 0;
51608
- ensureTimeRemaining();
51617
+ progress.throwIfStopped();
51609
51618
  const extractionResponse = await extract$1({
51610
51619
  instruction,
51611
51620
  domElements: combinedTree,
51612
51621
  schema: transformedSchema,
51613
- generate: (input) => generate(model, input, clientLLMGenerate, gateway),
51622
+ generate: (input) => progress.run("waiting for extract inference", () => generate(model, input, clientLLMGenerate, gateway)),
51614
51623
  userProvidedInstructions: systemPrompt,
51615
51624
  screenshot: screenshotContent
51616
51625
  });
51617
- ensureTimeRemaining();
51626
+ progress.throwIfStopped();
51618
51627
  const { metadata: { completed }, prompt_tokens, completion_tokens, reasoning_tokens, cached_input_tokens, inference_time_ms, ...rest } = extractionResponse;
51619
51628
  let output = rest;
51620
51629
  const idToUrl = combinedUrlMap ?? {};
@@ -51652,9 +51661,14 @@ async function extract({ params, page, model, clientLLMGenerate, logger, systemP
51652
51661
  //#endregion
51653
51662
  //#region services/observeService.ts
51654
51663
  var DEFAULT_OBSERVE_INSTRUCTION = "Find elements that can be used for any future actions in the page. These may be navigation links, related pages, section/subsection links, buttons, or other interactive elements. Be comprehensive: if there are multiple elements that may be relevant for future actions, return all of them.";
51655
- async function observe({ params, page, model, clientLLMGenerate, logger, systemPrompt = "", cache, gateway }) {
51664
+ async function observe(options) {
51665
+ return runWithProgress(options.progress ?? {
51666
+ name: "observe()",
51667
+ timeout: options.params.options?.timeout ?? 0
51668
+ }, (progress) => observeWithProgress(options, progress));
51669
+ }
51670
+ async function observeWithProgress({ params, page, model, clientLLMGenerate, logger, systemPrompt = "", cache, gateway }, progress) {
51656
51671
  const { instruction, options } = params;
51657
- const ensureTimeRemaining = createTimeoutGuard(options?.timeout, (ms) => new TimeoutError("observe()", ms));
51658
51672
  const effectiveInstruction = instruction ?? DEFAULT_OBSERVE_INSTRUCTION;
51659
51673
  logger.info("Starting observation", {
51660
51674
  category: "observation",
@@ -51668,6 +51682,7 @@ async function observe({ params, page, model, clientLLMGenerate, logger, systemP
51668
51682
  bypass: shouldBypassCacheForLocatorScope(options),
51669
51683
  context: cache,
51670
51684
  logger,
51685
+ progress,
51671
51686
  onHit: (value) => {
51672
51687
  const actions = normalizeCachedActions(value);
51673
51688
  if (actions.length === 0) throw new Error("Cached observe value contained no usable actions");
@@ -51682,22 +51697,22 @@ async function observe({ params, page, model, clientLLMGenerate, logger, systemP
51682
51697
  execute: () => runObservation()
51683
51698
  });
51684
51699
  async function runObservation() {
51685
- ensureTimeRemaining();
51700
+ progress.throwIfStopped();
51686
51701
  const { combinedTree, combinedXpathMap } = await page.captureSnapshot({
51687
51702
  focusLocator: options?.locator,
51688
51703
  ignoreLocators: options?.ignoreLocators
51689
- });
51690
- ensureTimeRemaining();
51704
+ }, progress);
51705
+ progress.throwIfStopped();
51691
51706
  logger.debug("Captured accessibility snapshot for observation", { category: "observation" });
51692
51707
  const observation = await observe$1({
51693
51708
  instruction: effectiveInstruction,
51694
51709
  domElements: combinedTree,
51695
- generate: (input) => generate(model, input, clientLLMGenerate, gateway),
51710
+ generate: (input) => progress.run("waiting for observe inference", () => generate(model, input, clientLLMGenerate, gateway)),
51696
51711
  userProvidedInstructions: systemPrompt,
51697
51712
  supportedActions: Object.values(SupportedUnderstudyAction),
51698
51713
  variables: options?.variables
51699
51714
  });
51700
- ensureTimeRemaining();
51715
+ progress.throwIfStopped();
51701
51716
  const xpathMap = combinedXpathMap ?? {};
51702
51717
  const actions = [];
51703
51718
  for (const element of observation.elements) {
@@ -51738,7 +51753,7 @@ async function observe({ params, page, model, clientLLMGenerate, logger, systemP
51738
51753
  arguments: resolvedArguments
51739
51754
  });
51740
51755
  }
51741
- ensureTimeRemaining();
51756
+ progress.throwIfStopped();
51742
51757
  logger.info("Observation completed", {
51743
51758
  category: "observation",
51744
51759
  promptTokens: observation.prompt_tokens,
@@ -54370,7 +54385,7 @@ var WebTracerProvider = class extends BasicTracerProvider {
54370
54385
  };
54371
54386
  var package_default = {
54372
54387
  name: "@browserbasehq/stagehand-extension",
54373
- version: "1.1.0-alpha-0336409092b85beeba38ea3cd4953f45eb96ed6b",
54388
+ version: "1.1.0-alpha-c9c8a41778b2000c9a9bdfc4b68e6c0c4866ab1a",
54374
54389
  "private": true,
54375
54390
  type: "module",
54376
54391
  scripts: {
@@ -56316,11 +56331,13 @@ var Page = class Page {
56316
56331
  await this.ensureCursorScript();
56317
56332
  this.cursorEnabled = true;
56318
56333
  }
56319
- async updateCursor(x, y) {
56334
+ async updateCursor(x, y, progress) {
56320
56335
  if (!this.cursorEnabled) return;
56321
56336
  try {
56322
- await this.mainFrameWrapper.evaluateInLocatorWorld(`globalThis.__stagehandLocatorScripts.moveCursorOverlay(${Math.round(x)}, ${Math.round(y)})`);
56323
- } catch {}
56337
+ await this.mainFrameWrapper.evaluateInLocatorWorld(`globalThis.__stagehandLocatorScripts.moveCursorOverlay(${Math.round(x)}, ${Math.round(y)})`, progress);
56338
+ } catch {
56339
+ progress?.throwIfStopped();
56340
+ }
56324
56341
  }
56325
56342
  async addInitScript(script, arg) {
56326
56343
  const source = await normalizeInitScriptSource(script, arg, "page.addInitScript");
@@ -57462,32 +57479,32 @@ var Page = class Page {
57462
57479
  button: "none"
57463
57480
  });
57464
57481
  }
57465
- async scroll(x, y, deltaX, deltaY) {
57466
- await this.updateCursor(x, y);
57467
- await this.mainSession.send("Input.dispatchMouseEvent", {
57482
+ async scroll(x, y, deltaX, deltaY, progress) {
57483
+ await runLocatorStep(progress, "updating cursor", () => this.updateCursor(x, y, progress));
57484
+ await runLocatorStep(progress, "scrolling page", () => this.mainSession.send("Input.dispatchMouseEvent", {
57468
57485
  type: "mouseMoved",
57469
57486
  x,
57470
57487
  y,
57471
57488
  button: "none"
57472
- });
57473
- await this.mainSession.send("Input.dispatchMouseEvent", {
57489
+ }));
57490
+ await runLocatorStep(progress, "scrolling page", () => this.mainSession.send("Input.dispatchMouseEvent", {
57474
57491
  type: "mouseWheel",
57475
57492
  x,
57476
57493
  y,
57477
57494
  button: "none",
57478
57495
  deltaX,
57479
57496
  deltaY
57480
- });
57497
+ }));
57481
57498
  }
57482
57499
  /**
57483
57500
  * Drag from (fromX, fromY) to (toX, toY) using mouse events.
57484
57501
  * Sends mouseMoved → mousePressed → mouseMoved (route or steps) → mouseReleased.
57485
57502
  */
57486
- async dragAndDrop(fromX, fromY, toX, toY, options) {
57503
+ async dragAndDrop(fromX, fromY, toX, toY, options, progress) {
57487
57504
  const button = options?.button ?? "left";
57488
57505
  const steps = Math.max(1, Math.floor(options?.steps ?? 1));
57489
57506
  const delay = Math.max(0, options?.delay ?? 0);
57490
- const sleep = (ms) => new Promise((r) => ms > 0 ? setTimeout(r, ms) : r());
57507
+ const sleep = (ms) => progress ? progress.delay(ms) : new Promise((r) => ms > 0 ? setTimeout(r, ms) : r());
57491
57508
  const buttonMask = (b) => {
57492
57509
  switch (b) {
57493
57510
  case "left": return 1;
@@ -57496,57 +57513,79 @@ var Page = class Page {
57496
57513
  default: return 1;
57497
57514
  }
57498
57515
  };
57499
- await this.updateCursor(fromX, fromY);
57500
- await this.mainSession.send("Input.dispatchMouseEvent", {
57516
+ await runLocatorStep(progress, "updating cursor", () => this.updateCursor(fromX, fromY, progress));
57517
+ await runLocatorStep(progress, "dragging mouse", () => this.mainSession.send("Input.dispatchMouseEvent", {
57501
57518
  type: "mouseMoved",
57502
57519
  x: fromX,
57503
57520
  y: fromY,
57504
57521
  button: "none"
57505
- });
57506
- await this.mainSession.send("Input.dispatchMouseEvent", {
57507
- type: "mousePressed",
57508
- x: fromX,
57509
- y: fromY,
57522
+ }));
57523
+ let pressed = false;
57524
+ let x = fromX;
57525
+ let y = fromY;
57526
+ const release = () => this.mainSession.send("Input.dispatchMouseEvent", {
57527
+ type: "mouseReleased",
57528
+ x,
57529
+ y,
57510
57530
  button,
57511
57531
  buttons: buttonMask(button),
57512
57532
  clickCount: 1
57513
57533
  });
57514
- const samePoint = (point, x, y) => point.x === x && point.y === y;
57515
- const route = options?.route ?? [];
57516
- let routeStart = 0;
57517
- let routeEnd = route.length;
57518
- while (routeStart < routeEnd && samePoint(route[routeStart], fromX, fromY)) routeStart++;
57519
- while (routeEnd > routeStart && samePoint(route[routeEnd - 1], toX, toY)) routeEnd--;
57520
- const movementPoints = route.length > 0 ? [...route.slice(routeStart, routeEnd), {
57521
- x: toX,
57522
- y: toY
57523
- }] : Array.from({ length: steps }, (_, index) => {
57524
- const t = (index + 1) / steps;
57525
- return {
57526
- x: fromX + (toX - fromX) * t,
57527
- y: fromY + (toY - fromY) * t
57528
- };
57529
- });
57530
- for (const { x, y } of movementPoints) {
57531
- await this.updateCursor(x, y);
57532
- await this.mainSession.send("Input.dispatchMouseEvent", {
57533
- type: "mouseMoved",
57534
- x,
57535
- y,
57536
- button,
57537
- buttons: buttonMask(button)
57534
+ try {
57535
+ progress?.throwIfStopped();
57536
+ await runLocatorStep(progress, "pressing mouse", () => {
57537
+ pressed = true;
57538
+ return this.mainSession.send("Input.dispatchMouseEvent", {
57539
+ type: "mousePressed",
57540
+ x: fromX,
57541
+ y: fromY,
57542
+ button,
57543
+ buttons: buttonMask(button),
57544
+ clickCount: 1
57545
+ });
57538
57546
  });
57539
- if (delay) await sleep(delay);
57547
+ const samePoint = (point, x, y) => point.x === x && point.y === y;
57548
+ const route = options?.route ?? [];
57549
+ let routeStart = 0;
57550
+ let routeEnd = route.length;
57551
+ while (routeStart < routeEnd && samePoint(route[routeStart], fromX, fromY)) routeStart++;
57552
+ while (routeEnd > routeStart && samePoint(route[routeEnd - 1], toX, toY)) routeEnd--;
57553
+ const movementPoints = route.length > 0 ? [...route.slice(routeStart, routeEnd), {
57554
+ x: toX,
57555
+ y: toY
57556
+ }] : Array.from({ length: steps }, (_, index) => {
57557
+ const t = (index + 1) / steps;
57558
+ return {
57559
+ x: fromX + (toX - fromX) * t,
57560
+ y: fromY + (toY - fromY) * t
57561
+ };
57562
+ });
57563
+ for (const point of movementPoints) {
57564
+ progress?.throwIfStopped();
57565
+ await runLocatorStep(progress, "updating cursor", () => this.updateCursor(point.x, point.y, progress));
57566
+ await runLocatorStep(progress, "dragging mouse", () => {
57567
+ x = point.x;
57568
+ y = point.y;
57569
+ return this.mainSession.send("Input.dispatchMouseEvent", {
57570
+ type: "mouseMoved",
57571
+ x,
57572
+ y,
57573
+ button,
57574
+ buttons: buttonMask(button)
57575
+ });
57576
+ });
57577
+ if (delay) await sleep(delay);
57578
+ }
57579
+ await runLocatorStep(progress, "updating cursor", () => this.updateCursor(toX, toY, progress));
57580
+ await runLocatorStep(progress, "releasing mouse", () => {
57581
+ pressed = false;
57582
+ return release();
57583
+ });
57584
+ } finally {
57585
+ if (pressed) if (progress) await progress.cleanup(release);
57586
+ else await release().catch(() => {});
57540
57587
  }
57541
- await this.updateCursor(toX, toY);
57542
- await this.mainSession.send("Input.dispatchMouseEvent", {
57543
- type: "mouseReleased",
57544
- x: toX,
57545
- y: toY,
57546
- button,
57547
- buttons: buttonMask(button),
57548
- clickCount: 1
57549
- });
57588
+ progress?.throwIfStopped();
57550
57589
  }
57551
57590
  /**
57552
57591
  * Type a string by dispatching keyDown/keyUp events per character.
@@ -57646,9 +57685,9 @@ var Page = class Page {
57646
57685
  * For printable characters, uses the text path on keyDown; for named keys, sets key/code/VK.
57647
57686
  * Supports key combinations with modifiers like "Cmd+A", "Ctrl+C", "Shift+Tab", etc.
57648
57687
  */
57649
- async keyPress(key, options) {
57688
+ async keyPress(key, options, progress) {
57650
57689
  const delay = Math.max(0, options?.delay ?? 0);
57651
- const sleep = (ms) => new Promise((r) => ms > 0 ? setTimeout(r, ms) : r());
57690
+ const sleep = (ms) => progress ? progress.delay(ms) : new Promise((r) => ms > 0 ? setTimeout(r, ms) : r());
57652
57691
  function split(keyString) {
57653
57692
  if (keyString === "+") return ["+"];
57654
57693
  const keys = [];
@@ -57663,13 +57702,19 @@ var Page = class Page {
57663
57702
  const tokens = split(key);
57664
57703
  const mainKey = tokens[tokens.length - 1];
57665
57704
  const modifierKeys = tokens.slice(0, -1);
57705
+ const heldKeys = /* @__PURE__ */ new Set();
57666
57706
  try {
57667
- for (const modKey of modifierKeys) await this.keyDown(modKey);
57668
- await this.keyDown(mainKey);
57707
+ for (const key of [...modifierKeys, mainKey]) await runLocatorStep(progress, "pressing key", () => {
57708
+ heldKeys.add(key);
57709
+ return this.keyDown(key);
57710
+ });
57669
57711
  if (delay) await sleep(delay);
57670
- await this.keyUp(mainKey);
57671
- for (let i = modifierKeys.length - 1; i >= 0; i--) await this.keyUp(modifierKeys[i]);
57712
+ for (const key of [mainKey, ...modifierKeys.toReversed()]) await runLocatorStep(progress, "releasing key", () => {
57713
+ heldKeys.delete(key);
57714
+ return this.keyUp(key);
57715
+ });
57672
57716
  } catch (error) {
57717
+ if (progress) await progress.cleanup(() => Promise.allSettled([...heldKeys].reverse().map((key) => this.keyUp(key))));
57673
57718
  this._pressedModifiers.clear();
57674
57719
  throw error;
57675
57720
  }
@@ -1,4 +1,4 @@
1
1
  """Generated from packages/protocol/package.json. Do not edit."""
2
2
 
3
3
  PROTOCOL_SEMVER_PATTERN = '^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\\+([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?$'
4
- STAGEHAND_PROTOCOL_VERSION = '2.1.0-alpha-0336409092b85beeba38ea3cd4953f45eb96ed6b'
4
+ STAGEHAND_PROTOCOL_VERSION = '2.1.0-alpha-c9c8a41778b2000c9a9bdfc4b68e6c0c4866ab1a'