@mastra/stagehand 0.1.0 → 0.2.0-alpha.0

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/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Stagehand } from '@browserbasehq/stagehand';
2
- import { ThreadManager, ThreadSession, ThreadManagerConfig, BrowserConfigBase, BrowserScope, CdpUrlProvider, MastraBrowser, BrowserToolError, BrowserState, BrowserTabState, ScreencastOptions, ScreencastStream, MouseEventParams, KeyboardEventParams } from '@mastra/core/browser';
2
+ import { ThreadManager, ThreadSession, ThreadManagerConfig, BrowserConfig, MastraBrowser, BrowserToolError, BrowserState, BrowserTabState, ScreencastOptions, ScreencastStream, MouseEventParams, KeyboardEventParams } from '@mastra/core/browser';
3
3
  import { Tool } from '@mastra/core/tools';
4
4
  import { z } from 'zod';
5
5
 
@@ -238,21 +238,22 @@ interface StagehandConfigExtensions {
238
238
  * Custom system prompt for AI operations (act, extract, observe)
239
239
  */
240
240
  systemPrompt?: string;
241
+ /**
242
+ * Whether to preserve the user data directory after the browser closes.
243
+ * By default, Stagehand may clean up temporary user data directories.
244
+ * Set to `true` to keep the profile data for future sessions.
245
+ *
246
+ * Only applicable when `profile` is provided.
247
+ *
248
+ * @default false
249
+ */
250
+ preserveUserDataDir?: boolean;
241
251
  }
242
252
  /**
243
- * Configuration for StagehandBrowser with compile-time enforcement of cdpUrl/scope compatibility.
244
- *
245
- * This type enforces that `cdpUrl` and `scope: 'thread'` cannot be used together:
246
- * - When `cdpUrl` is provided, `scope` must be `'shared'` or omitted
247
- * - When `scope: 'thread'` is used, `cdpUrl` must not be provided
253
+ * Configuration for StagehandBrowser.
254
+ * Extends the base BrowserConfig with Stagehand-specific options.
248
255
  */
249
- type StagehandBrowserConfig = (BrowserConfigBase & StagehandConfigExtensions & {
250
- cdpUrl?: undefined;
251
- scope?: BrowserScope;
252
- }) | (BrowserConfigBase & StagehandConfigExtensions & {
253
- cdpUrl: CdpUrlProvider;
254
- scope?: 'shared';
255
- });
256
+ type StagehandBrowserConfig = BrowserConfig & StagehandConfigExtensions;
256
257
  /**
257
258
  * Action returned from observe()
258
259
  */
@@ -348,13 +349,20 @@ declare class StagehandBrowser extends MastraBrowser {
348
349
  * Set up close event listener for a shared Stagehand instance.
349
350
  * Listens to both context and page close events for robust detection.
350
351
  */
351
- private setupCloseListenerForSharedScope;
352
352
  /**
353
- * Set up close event listener for a thread's Stagehand instance.
354
- * Uses CDP Target.targetDestroyed events to detect when all pages are gone.
353
+ * Set up a CDP-based close listener for a Stagehand instance.
354
+ *
355
+ * Tracks page targets via CDP `Target.targetCreated` / `Target.targetDestroyed`.
356
+ * When all page targets are gone the `onDisconnect` callback fires. This is more
357
+ * reliable than Playwright's `context.close` / `page.close` events which don't
358
+ * fire when Chrome is killed externally (SIGTERM/SIGKILL).
355
359
  */
356
- private setupCloseListenerForThread;
360
+ private setupCloseListener;
357
361
  protected doClose(): Promise<void>;
362
+ handleBrowserDisconnected(): void;
363
+ protected handleThreadBrowserDisconnected(threadId: string): void;
364
+ closeThreadSession(threadId: string): Promise<void>;
365
+ private patchExitType;
358
366
  /**
359
367
  * Check if the browser is still alive by verifying the context and pages exist.
360
368
  * Called by base class ensureReady() to detect externally closed browsers.
@@ -496,6 +504,17 @@ declare class StagehandBrowser extends MastraBrowser {
496
504
  injectKeyboardEvent(event: KeyboardEventParams, threadId?: string): Promise<void>;
497
505
  }
498
506
 
507
+ /**
508
+ * Extract the Chrome process PID from a Stagehand instance.
509
+ *
510
+ * Stagehand stores the chrome-launcher result in `state.chrome` after init.
511
+ * The PID is at `chrome.process?.pid ?? chrome.pid`. This isn't part of
512
+ * Stagehand's public API, so we access it via `as any`.
513
+ *
514
+ * Returns undefined if the PID can't be found (e.g. BROWSERBASE env, not yet init'd).
515
+ */
516
+ declare function getStagehandChromePid(stagehand: Stagehand): number | undefined;
517
+
499
518
  /**
500
519
  * Stagehand Tool Constants
501
520
  */
@@ -521,4 +540,4 @@ type StagehandToolName = (typeof STAGEHAND_TOOLS)[keyof typeof STAGEHAND_TOOLS];
521
540
  */
522
541
  declare function createStagehandTools(browser: StagehandBrowser): Record<string, Tool<any, any>>;
523
542
 
524
- export { type ActInput, type ActResult, type CloseInput, type ExtractInput, type ExtractResult, type ModelConfiguration, type NavigateInput, type ObserveInput, type ObserveResult, STAGEHAND_TOOLS, type StagehandAction, StagehandBrowser, type StagehandBrowserConfig, type StagehandToolName, type TabsInput, actInputSchema, closeInputSchema, createStagehandTools, extractInputSchema, navigateInputSchema, observeInputSchema, stagehandSchemas, tabsInputSchema };
543
+ export { type ActInput, type ActResult, type CloseInput, type ExtractInput, type ExtractResult, type ModelConfiguration, type NavigateInput, type ObserveInput, type ObserveResult, STAGEHAND_TOOLS, type StagehandAction, StagehandBrowser, type StagehandBrowserConfig, type StagehandToolName, type TabsInput, actInputSchema, closeInputSchema, createStagehandTools, extractInputSchema, getStagehandChromePid, navigateInputSchema, observeInputSchema, stagehandSchemas, tabsInputSchema };
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
1
2
  import { Stagehand } from '@browserbasehq/stagehand';
2
3
  import { MastraBrowser, DEFAULT_THREAD_ID, ScreencastStreamImpl, ThreadManager } from '@mastra/core/browser';
3
4
  import { createTool } from '@mastra/core/tools';
4
5
  import { z } from 'zod';
6
+ import { join } from 'path';
5
7
 
6
8
  // src/stagehand-browser.ts
7
9
  var StagehandThreadManager = class extends ThreadManager {
@@ -278,6 +280,30 @@ function createStagehandTools(browser) {
278
280
  [STAGEHAND_TOOLS.CLOSE]: createCloseTool(browser)
279
281
  };
280
282
  }
283
+ function patchProfileExitType(profilePath, logger) {
284
+ if (!profilePath) return;
285
+ const prefsPath = join(profilePath, "Default", "Preferences");
286
+ try {
287
+ if (!existsSync(prefsPath)) return;
288
+ const prefs = JSON.parse(readFileSync(prefsPath, "utf-8"));
289
+ if (prefs?.profile?.exit_type === "Normal") return;
290
+ prefs.profile = prefs.profile || {};
291
+ prefs.profile.exit_type = "Normal";
292
+ writeFileSync(prefsPath, JSON.stringify(prefs, null, 2), "utf-8");
293
+ logger?.debug?.(`Patched exit_type to Normal in ${prefsPath}`);
294
+ } catch {
295
+ }
296
+ }
297
+ function getStagehandChromePid(stagehand) {
298
+ try {
299
+ const state = stagehand.state;
300
+ if (state?.kind !== "LOCAL" || !state.chrome) return void 0;
301
+ const pid = state.chrome.process?.pid ?? state.chrome.pid;
302
+ return typeof pid === "number" && pid > 0 ? pid : void 0;
303
+ } catch {
304
+ return void 0;
305
+ }
306
+ }
281
307
 
282
308
  // src/stagehand-browser.ts
283
309
  var StagehandBrowser = class extends MastraBrowser {
@@ -301,7 +327,7 @@ var StagehandBrowser = class extends MastraBrowser {
301
327
  },
302
328
  // When a new browser is created for a thread, set up close listener
303
329
  onBrowserCreated: (stagehand, threadId) => {
304
- this.setupCloseListenerForThread(stagehand, threadId);
330
+ this.setupCloseListener(stagehand, () => this.handleThreadBrowserDisconnected(threadId), threadId);
305
331
  }
306
332
  });
307
333
  }
@@ -344,18 +370,27 @@ var StagehandBrowser = class extends MastraBrowser {
344
370
  stagehandOptions.projectId = config.projectId;
345
371
  }
346
372
  }
373
+ if (config.profile && !existsSync(config.profile)) {
374
+ mkdirSync(config.profile, { recursive: true });
375
+ }
347
376
  if (config.cdpUrl && config.env !== "BROWSERBASE") {
348
377
  const resolvedUrl = await this.resolveCdpUrl(config.cdpUrl);
349
378
  const wsUrl = await this.resolveWebSocketUrl(resolvedUrl);
350
379
  stagehandOptions.localBrowserLaunchOptions = {
351
380
  cdpUrl: wsUrl,
352
381
  headless: config.headless,
353
- viewport: config.viewport
382
+ viewport: config.viewport,
383
+ userDataDir: config.profile,
384
+ executablePath: config.executablePath,
385
+ preserveUserDataDir: config.preserveUserDataDir
354
386
  };
355
387
  } else if (config.env !== "BROWSERBASE") {
356
388
  stagehandOptions.localBrowserLaunchOptions = {
357
389
  headless: config.headless,
358
- viewport: config.viewport
390
+ viewport: config.viewport,
391
+ userDataDir: config.profile,
392
+ executablePath: config.executablePath,
393
+ preserveUserDataDir: config.preserveUserDataDir
359
394
  };
360
395
  }
361
396
  return stagehandOptions;
@@ -378,81 +413,54 @@ var StagehandBrowser = class extends MastraBrowser {
378
413
  }
379
414
  this.sharedManager = await this.createStagehandInstance();
380
415
  this.threadManager.setSharedManager(this.sharedManager);
381
- this.setupCloseListenerForSharedScope(this.sharedManager);
416
+ this.setupCloseListener(this.sharedManager, () => this.handleBrowserDisconnected());
382
417
  }
383
418
  /**
384
419
  * Set up close event listener for a shared Stagehand instance.
385
420
  * Listens to both context and page close events for robust detection.
386
421
  */
387
- setupCloseListenerForSharedScope(stagehand) {
388
- let disconnectHandled = false;
389
- const handleDisconnect = () => {
390
- if (disconnectHandled) return;
391
- disconnectHandled = true;
392
- this.handleBrowserDisconnected();
393
- };
394
- try {
395
- const context = stagehand.context;
396
- if (!context) return;
397
- const contextWithEvents = context;
398
- if (contextWithEvents?.on) {
399
- contextWithEvents.on("close", handleDisconnect);
400
- }
401
- const pages = context.pages?.() ?? [];
402
- for (const page of pages) {
403
- const pageWithEvents = page;
404
- if (pageWithEvents?.on) {
405
- pageWithEvents.on("close", () => {
406
- const remainingPages = context.pages?.() ?? [];
407
- if (remainingPages.length === 0) {
408
- handleDisconnect();
409
- }
410
- });
411
- }
412
- }
413
- } catch {
414
- }
415
- }
416
422
  /**
417
- * Set up close event listener for a thread's Stagehand instance.
418
- * Uses CDP Target.targetDestroyed events to detect when all pages are gone.
423
+ * Set up a CDP-based close listener for a Stagehand instance.
424
+ *
425
+ * Tracks page targets via CDP `Target.targetCreated` / `Target.targetDestroyed`.
426
+ * When all page targets are gone the `onDisconnect` callback fires. This is more
427
+ * reliable than Playwright's `context.close` / `page.close` events which don't
428
+ * fire when Chrome is killed externally (SIGTERM/SIGKILL).
419
429
  */
420
- setupCloseListenerForThread(stagehand, threadId) {
430
+ setupCloseListener(stagehand, onDisconnect, threadId) {
431
+ const chromePid = getStagehandChromePid(stagehand);
432
+ if (chromePid != null) {
433
+ if (threadId) {
434
+ this.threadBrowserPids.set(threadId, chromePid);
435
+ } else {
436
+ this.sharedBrowserPid = chromePid;
437
+ }
438
+ }
421
439
  let disconnectHandled = false;
422
440
  const handleDisconnect = () => {
423
441
  if (disconnectHandled) return;
424
442
  disconnectHandled = true;
425
- this.handleThreadBrowserDisconnected(threadId);
443
+ onDisconnect();
426
444
  };
427
445
  try {
428
446
  const stagehandAny = stagehand;
429
447
  const conn = stagehandAny.ctx?.conn;
430
- if (!conn?.on) {
431
- return;
432
- }
448
+ if (!conn?.on) return;
433
449
  const pageTargets = /* @__PURE__ */ new Set();
434
450
  const context = stagehand.context;
435
451
  if (context) {
436
- const pages = context.pages?.() ?? [];
437
- for (const page of pages) {
438
- const pageAny = page;
439
- const targetId = pageAny._targetId ?? pageAny.targetId;
440
- if (targetId) {
441
- pageTargets.add(targetId);
442
- }
452
+ for (const page of context.pages?.() ?? []) {
453
+ const targetId = page._targetId ?? page.targetId;
454
+ if (targetId) pageTargets.add(targetId);
443
455
  }
444
456
  }
445
457
  conn.on("Target.targetCreated", (params) => {
446
- if (params.targetInfo.type === "page") {
447
- pageTargets.add(params.targetInfo.targetId);
448
- }
458
+ if (params.targetInfo.type === "page") pageTargets.add(params.targetInfo.targetId);
449
459
  });
450
460
  conn.on("Target.targetDestroyed", (params) => {
451
461
  if (pageTargets.has(params.targetId)) {
452
462
  pageTargets.delete(params.targetId);
453
- if (pageTargets.size === 0) {
454
- handleDisconnect();
455
- }
463
+ if (pageTargets.size === 0) handleDisconnect();
456
464
  }
457
465
  });
458
466
  } catch {
@@ -465,6 +473,23 @@ var StagehandBrowser = class extends MastraBrowser {
465
473
  this.sharedManager = null;
466
474
  }
467
475
  this.setCurrentThread(void 0);
476
+ this.patchExitType();
477
+ }
478
+ handleBrowserDisconnected() {
479
+ super.handleBrowserDisconnected();
480
+ this.patchExitType();
481
+ }
482
+ handleThreadBrowserDisconnected(threadId) {
483
+ super.handleThreadBrowserDisconnected(threadId);
484
+ this.patchExitType();
485
+ }
486
+ async closeThreadSession(threadId) {
487
+ await super.closeThreadSession(threadId);
488
+ this.patchExitType();
489
+ }
490
+ patchExitType() {
491
+ if (!this.config.profile) return;
492
+ patchProfileExitType(this.config.profile, this.logger);
468
493
  }
469
494
  /**
470
495
  * Check if the browser is still alive by verifying the context and pages exist.
@@ -1188,6 +1213,6 @@ var StagehandBrowser = class extends MastraBrowser {
1188
1213
  }
1189
1214
  };
1190
1215
 
1191
- export { STAGEHAND_TOOLS, StagehandBrowser, actInputSchema, closeInputSchema, createStagehandTools, extractInputSchema, navigateInputSchema, observeInputSchema, stagehandSchemas, tabsInputSchema };
1216
+ export { STAGEHAND_TOOLS, StagehandBrowser, actInputSchema, closeInputSchema, createStagehandTools, extractInputSchema, getStagehandChromePid, navigateInputSchema, observeInputSchema, stagehandSchemas, tabsInputSchema };
1192
1217
  //# sourceMappingURL=index.js.map
1193
1218
  //# sourceMappingURL=index.js.map