@mastra/stagehand 0.1.0-alpha.0 → 0.1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # @mastra/stagehand
2
2
 
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add AI-powered browser automation with Stagehand SDK integration ([#14938](https://github.com/mastra-ai/mastra/pull/14938))
8
+
9
+ **New Features:**
10
+ - AI-powered browser automation using Stagehand's act/extract/observe primitives
11
+ - Native Browserbase integration for cloud browser sessions
12
+ - Real-time screencast streaming via WebSocket
13
+ - Mouse and keyboard input injection
14
+ - Thread-scoped browser isolation (`scope: 'thread'`)
15
+ - State persistence and restoration across sessions
16
+
17
+ **Configuration:**
18
+
19
+ ```typescript
20
+ import { StagehandBrowser } from '@mastra/stagehand';
21
+
22
+ // Local browser
23
+ const browser = new StagehandBrowser({
24
+ headless: true,
25
+ scope: 'thread',
26
+ });
27
+
28
+ // Browserbase cloud
29
+ const browser = new StagehandBrowser({
30
+ env: 'BROWSERBASE',
31
+ apiKey: process.env.BROWSERBASE_API_KEY,
32
+ projectId: process.env.BROWSERBASE_PROJECT_ID,
33
+ });
34
+
35
+ const agent = mastra.getAgent('my-agent', { browser });
36
+ ```
37
+
38
+ ### Patch Changes
39
+
40
+ - Updated dependencies [[`cb15509`](https://github.com/mastra-ai/mastra/commit/cb15509b58f6a83e11b765c945082afc027db972), [`81e4259`](https://github.com/mastra-ai/mastra/commit/81e425939b4ceeb4f586e9b6d89c3b1c1f2d2fe7), [`951b8a1`](https://github.com/mastra-ai/mastra/commit/951b8a1b5ef7e1474c59dc4f2b9fc1a8b1e508b6), [`80c5668`](https://github.com/mastra-ai/mastra/commit/80c5668e365470d3a96d3e953868fd7a643ff67c), [`3d478c1`](https://github.com/mastra-ai/mastra/commit/3d478c1e13f17b80f330ac49d7aa42ef929b93ff), [`2b4ea10`](https://github.com/mastra-ai/mastra/commit/2b4ea10b053e4ea1ab232d536933a4a3c4cba999), [`a0544f0`](https://github.com/mastra-ai/mastra/commit/a0544f0a1e6bd52ac12676228967c1938e43648d), [`6039f17`](https://github.com/mastra-ai/mastra/commit/6039f176f9c457304825ff1df8c83b8e457376c0), [`06b928d`](https://github.com/mastra-ai/mastra/commit/06b928dfc2f5630d023467476cc5919dfa858d0a), [`6a8d984`](https://github.com/mastra-ai/mastra/commit/6a8d9841f2933456ee1598099f488d742b600054), [`c8c86aa`](https://github.com/mastra-ai/mastra/commit/c8c86aa1458017fbd1c0776fdc0c520d129df8a6)]:
41
+ - @mastra/core@1.22.0
42
+
3
43
  ## 0.1.0-alpha.0
4
44
 
5
45
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -7,71 +7,27 @@ var zod = require('zod');
7
7
 
8
8
  // src/stagehand-browser.ts
9
9
  var StagehandThreadManager = class extends browser.ThreadManager {
10
- sharedStagehand = null;
11
10
  sessions = /* @__PURE__ */ new Map();
12
11
  createStagehand;
13
12
  onBrowserCreated;
14
- /** Map of thread ID to dedicated Stagehand instance (for 'thread' mode) */
15
- threadStagehands = /* @__PURE__ */ new Map();
16
13
  constructor(config) {
17
14
  super(config);
18
15
  this.createStagehand = config.createStagehand;
19
16
  this.onBrowserCreated = config.onBrowserCreated;
20
17
  }
21
- /**
22
- * Set the shared Stagehand instance (called after browser launch).
23
- */
24
- setStagehand(instance) {
25
- this.sharedStagehand = instance;
26
- }
27
- /**
28
- * Clear the shared Stagehand instance (called when browser disconnects).
29
- */
30
- clearStagehand() {
31
- this.sharedStagehand = null;
32
- }
33
18
  /**
34
19
  * Set the factory function for creating new Stagehand instances.
35
- * Required for 'browser' scope mode.
20
+ * Required for 'thread' scope mode.
36
21
  */
37
22
  setCreateStagehand(factory) {
38
23
  this.createStagehand = factory;
39
24
  }
40
25
  /**
41
- * Get the shared Stagehand instance.
42
- */
43
- getSharedStagehand() {
44
- if (!this.sharedStagehand) {
45
- throw new Error("Stagehand not initialized");
46
- }
47
- return this.sharedStagehand;
48
- }
49
- /**
50
- * Get the Stagehand instance for a specific thread.
51
- * In 'shared' mode, returns the shared instance.
52
- * In 'thread' mode, returns the thread's dedicated instance.
53
- */
54
- getStagehandForThread(threadId) {
55
- if (this.scope === "thread") {
56
- const session = this.sessions.get(threadId);
57
- return session?.stagehand;
58
- }
59
- return this.sharedStagehand ?? void 0;
60
- }
61
- /**
62
- * Get the Stagehand page for a thread.
63
- * Returns the active page from the thread's Stagehand instance.
64
- */
65
- getPageForThread(threadId) {
66
- const stagehand = this.getStagehandForThread(threadId);
67
- return stagehand?.context?.activePage();
68
- }
69
- /**
70
- * Get the shared manager - returns the active page or the Stagehand instance.
26
+ * Get the page for a specific thread, creating session if needed.
71
27
  */
72
- getSharedManager() {
73
- const stagehand = this.getSharedStagehand();
74
- return stagehand.context.activePage() ?? stagehand;
28
+ async getPageForThread(threadId) {
29
+ const stagehand = await this.getManagerForThread(threadId);
30
+ return stagehand?.context?.activePage() ?? null;
75
31
  }
76
32
  /**
77
33
  * Create a new session for a thread.
@@ -90,7 +46,7 @@ var StagehandThreadManager = class extends browser.ThreadManager {
90
46
  this.logger?.debug?.(`Creating dedicated Stagehand instance for thread ${threadId}`);
91
47
  const stagehand = await this.createStagehand();
92
48
  session.stagehand = stagehand;
93
- this.threadStagehands.set(threadId, stagehand);
49
+ this.threadManagers.set(threadId, stagehand);
94
50
  if (savedState && savedState.tabs.length > 0) {
95
51
  this.logger?.debug?.(`Restoring browser state for thread ${threadId}: ${savedState.tabs.length} tabs`);
96
52
  await this.restoreBrowserState(stagehand, savedState);
@@ -129,18 +85,11 @@ var StagehandThreadManager = class extends browser.ThreadManager {
129
85
  }
130
86
  }
131
87
  /**
132
- * Switch to an existing session.
133
- * For 'thread' mode, no switching needed - each thread has its own instance.
134
- * For 'shared' mode, nothing to switch.
135
- */
136
- async switchToSession(_session) {
137
- }
138
- /**
139
- * Get the manager for a specific session.
88
+ * Get the manager (Stagehand instance) for a specific session.
140
89
  */
141
90
  getManagerForSession(session) {
142
91
  if (this.scope === "thread" && session.stagehand) {
143
- return session.stagehand.context.activePage() ?? session.stagehand;
92
+ return session.stagehand;
144
93
  }
145
94
  return this.getSharedManager();
146
95
  }
@@ -155,50 +104,14 @@ var StagehandThreadManager = class extends browser.ThreadManager {
155
104
  } catch (error) {
156
105
  this.logger?.warn?.(`Failed to close Stagehand for thread ${session.threadId}: ${error}`);
157
106
  }
158
- this.threadStagehands.delete(session.threadId);
159
107
  }
160
108
  }
161
109
  /**
162
- * Clean up all thread sessions.
110
+ * Destroy all sessions (called during browser close).
111
+ * doDestroySession handles closing individual Stagehand instances.
163
112
  */
164
- async destroyAll() {
165
- for (const [threadId, stagehand] of this.threadStagehands) {
166
- try {
167
- await stagehand.close();
168
- } catch {
169
- this.logger?.debug?.(`Failed to close Stagehand for thread: ${threadId}`);
170
- }
171
- }
172
- this.threadStagehands.clear();
173
- this.sessions.clear();
174
- }
175
- /**
176
- * Check if any thread Stagehands are still running.
177
- */
178
- hasActiveThreadStagehands() {
179
- return this.threadStagehands.size > 0;
180
- }
181
- /**
182
- * Clear all session tracking without closing browsers.
183
- * Used when browsers have been externally closed and we just need to reset state.
184
- */
185
- clearAllSessions() {
186
- this.threadStagehands.clear();
187
- this.sessions.clear();
188
- }
189
- /**
190
- * Clear a specific thread's session without closing the browser.
191
- * Used when a thread's browser has been externally closed.
192
- * Preserves the browser state for potential restoration.
193
- * @param threadId - The thread ID to clear
194
- */
195
- clearSession(threadId) {
196
- const session = this.sessions.get(threadId);
197
- if (session?.browserState) {
198
- this.savedBrowserStates.set(threadId, session.browserState);
199
- }
200
- this.threadStagehands.delete(threadId);
201
- this.sessions.delete(threadId);
113
+ async destroyAllSessions() {
114
+ await super.destroyAllSessions();
202
115
  }
203
116
  };
204
117
  var actInputSchema = zod.z.object({
@@ -369,29 +282,18 @@ function createStagehandTools(browser) {
369
282
  }
370
283
 
371
284
  // src/stagehand-browser.ts
372
- var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
285
+ var StagehandBrowser = class extends browser.MastraBrowser {
373
286
  id;
374
287
  name = "StagehandBrowser";
375
288
  provider = "browserbase/stagehand";
376
- stagehand = null;
377
289
  stagehandConfig;
378
- /** Active screencast streams per thread (for reconnection on tab changes) */
379
- activeScreencastStreams = /* @__PURE__ */ new Map();
380
290
  /** Debounce timers per thread for tab change reconnection */
381
291
  tabChangeDebounceTimers = /* @__PURE__ */ new Map();
382
- /** Default key for shared scope */
383
- static SHARED_STREAM_KEY = "__shared__";
384
292
  constructor(config = {}) {
385
293
  super(config);
386
294
  this.id = `stagehand-${Date.now()}`;
387
295
  this.stagehandConfig = config;
388
- let effectiveScope = config.scope ?? "thread";
389
- if (config.cdpUrl && effectiveScope === "thread") {
390
- this.logger.warn?.(
391
- 'Browser scope "thread" is not supported when connecting via cdpUrl. Falling back to "shared" (shared browser connection).'
392
- );
393
- effectiveScope = "shared";
394
- }
296
+ const effectiveScope = config.cdpUrl ? config.scope ?? "shared" : config.scope ?? "thread";
395
297
  this.threadManager = new StagehandThreadManager({
396
298
  scope: effectiveScope,
397
299
  logger: this.logger,
@@ -405,15 +307,6 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
405
307
  }
406
308
  });
407
309
  }
408
- /**
409
- * Close a specific thread's browser session.
410
- * For 'thread' scope, this closes only that thread's Stagehand instance.
411
- * For 'shared' scope, this is a no-op (use close() to close the shared browser).
412
- */
413
- async closeThreadSession(threadId) {
414
- await this.threadManager.destroySession(threadId);
415
- this.notifyBrowserClosed(threadId);
416
- }
417
310
  /**
418
311
  * Ensure browser is ready and thread session exists.
419
312
  * For 'thread' scope, this creates a dedicated Stagehand instance for the thread.
@@ -424,7 +317,7 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
424
317
  const scope = this.getScope();
425
318
  const threadId = this.getCurrentThread();
426
319
  if (scope === "thread" && threadId && threadId !== browser.DEFAULT_THREAD_ID) {
427
- await this.getStagehandForThread(threadId);
320
+ await this.getManagerForThread(threadId);
428
321
  }
429
322
  }
430
323
  // ---------------------------------------------------------------------------
@@ -471,7 +364,7 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
471
364
  }
472
365
  /**
473
366
  * Create a new Stagehand instance with the current config.
474
- * Used by thread manager for 'browser' isolation mode.
367
+ * Used by thread manager for 'thread' scope.
475
368
  */
476
369
  async createStagehandInstance() {
477
370
  const stagehandOptions = await this.buildStagehandOptions();
@@ -485,15 +378,15 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
485
378
  if (scope === "thread") {
486
379
  return;
487
380
  }
488
- this.stagehand = await this.createStagehandInstance();
489
- this.threadManager.setStagehand(this.stagehand);
490
- this.setupCloseListener(this.stagehand);
381
+ this.sharedManager = await this.createStagehandInstance();
382
+ this.threadManager.setSharedManager(this.sharedManager);
383
+ this.setupCloseListenerForSharedScope(this.sharedManager);
491
384
  }
492
385
  /**
493
- * Set up close event listener for a Stagehand instance.
386
+ * Set up close event listener for a shared Stagehand instance.
494
387
  * Listens to both context and page close events for robust detection.
495
388
  */
496
- setupCloseListener(stagehand) {
389
+ setupCloseListenerForSharedScope(stagehand) {
497
390
  let disconnectHandled = false;
498
391
  const handleDisconnect = () => {
499
392
  if (disconnectHandled) return;
@@ -567,20 +460,11 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
567
460
  } catch {
568
461
  }
569
462
  }
570
- /**
571
- * Handle browser disconnection for a specific thread.
572
- * Called when a thread's browser is closed externally.
573
- */
574
- handleThreadBrowserDisconnected(threadId) {
575
- this.threadManager.clearSession(threadId);
576
- this.logger.debug?.(`Cleared Stagehand session for thread: ${threadId}`);
577
- this.notifyBrowserClosed(threadId);
578
- }
579
463
  async doClose() {
580
- await this.threadManager.destroyAll();
581
- if (this.stagehand) {
582
- await this.stagehand.close();
583
- this.stagehand = null;
464
+ await this.threadManager.destroyAllSessions();
465
+ if (this.sharedManager) {
466
+ await this.sharedManager.close();
467
+ this.sharedManager = null;
584
468
  }
585
469
  this.setCurrentThread(void 0);
586
470
  }
@@ -591,13 +475,13 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
591
475
  async checkBrowserAlive() {
592
476
  const scope = this.getScope();
593
477
  if (scope === "thread") {
594
- return this.threadManager.hasActiveThreadStagehands();
478
+ return this.threadManager.hasActiveThreadManagers();
595
479
  }
596
- if (!this.stagehand) {
480
+ if (!this.sharedManager) {
597
481
  return false;
598
482
  }
599
483
  try {
600
- const context = this.stagehand.context;
484
+ const context = this.sharedManager.context;
601
485
  if (!context) {
602
486
  return false;
603
487
  }
@@ -607,7 +491,7 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
607
491
  }
608
492
  const url = pages[0]?.url();
609
493
  if (url && url !== "about:blank") {
610
- const state = this.getBrowserStateFromStagehand(this.stagehand);
494
+ const state = this.getBrowserStateFromStagehand(this.sharedManager);
611
495
  if (state) {
612
496
  this.lastBrowserState = state;
613
497
  }
@@ -621,24 +505,6 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
621
505
  return false;
622
506
  }
623
507
  }
624
- /**
625
- * Handle browser disconnection by clearing internal state.
626
- * For 'thread' scope, only notifies the specific thread's callbacks.
627
- * For 'shared' scope, notifies all callbacks.
628
- */
629
- handleBrowserDisconnected() {
630
- const scope = this.threadManager.getScope();
631
- const threadId = this.getCurrentThread();
632
- if (scope === "thread" && threadId !== browser.DEFAULT_THREAD_ID) {
633
- this.threadManager.clearSession(threadId);
634
- this.logger.debug?.(`Cleared Stagehand session for thread: ${threadId}`);
635
- this.notifyBrowserClosed(threadId);
636
- } else {
637
- this.stagehand = null;
638
- this.threadManager.clearStagehand();
639
- super.handleBrowserDisconnected();
640
- }
641
- }
642
508
  /**
643
509
  * Create an error response from an exception.
644
510
  * Extends base class to add Stagehand-specific error handling.
@@ -659,21 +525,21 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
659
525
  // ---------------------------------------------------------------------------
660
526
  /**
661
527
  * Get the Stagehand instance for a thread, creating it if needed.
662
- * For 'browser' isolation, this creates a dedicated Stagehand instance.
663
- * For 'none' isolation, returns the shared instance.
528
+ * For 'thread' scope, this creates a dedicated Stagehand instance.
529
+ * For 'shared' scope, returns the shared instance.
664
530
  */
665
- async getStagehandForThread(threadId) {
531
+ async getManagerForThread(threadId) {
666
532
  const scope = this.getScope();
667
533
  if (scope === "shared") {
668
- return this.stagehand;
534
+ return this.sharedManager;
669
535
  }
670
536
  if (!threadId || threadId === browser.DEFAULT_THREAD_ID) {
671
- return this.stagehand;
537
+ return this.sharedManager;
672
538
  }
673
- let stagehand = this.threadManager.getStagehandForThread(threadId);
539
+ let stagehand = this.threadManager.getExistingManagerForThread(threadId);
674
540
  if (!stagehand) {
675
541
  await this.threadManager.getManagerForThread(threadId);
676
- stagehand = this.threadManager.getStagehandForThread(threadId);
542
+ stagehand = this.threadManager.getExistingManagerForThread(threadId);
677
543
  }
678
544
  return stagehand ?? null;
679
545
  }
@@ -685,14 +551,14 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
685
551
  */
686
552
  requireStagehand(explicitThreadId) {
687
553
  const threadId = explicitThreadId ?? this.getCurrentThread();
688
- const stagehand = this.threadManager.getStagehandForThread(threadId ?? "") ?? this.stagehand;
554
+ const stagehand = this.threadManager.getExistingManagerForThread(threadId) ?? this.sharedManager;
689
555
  if (!stagehand) {
690
556
  throw new Error("Browser not launched");
691
557
  }
692
558
  return stagehand;
693
559
  }
694
560
  /**
695
- * Get the current page from Stagehand v3, respecting thread isolation.
561
+ * Get the current page from Stagehand v3, respecting thread scope.
696
562
  * @param explicitThreadId - Optional thread ID to use instead of getCurrentThread()
697
563
  * Use this to avoid race conditions in concurrent tool calls.
698
564
  */
@@ -700,15 +566,15 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
700
566
  const scope = this.getScope();
701
567
  const threadId = explicitThreadId ?? this.getCurrentThread();
702
568
  if (scope === "thread" && threadId && threadId !== browser.DEFAULT_THREAD_ID) {
703
- const stagehand = this.threadManager.getStagehandForThread(threadId);
569
+ const stagehand = this.threadManager.getExistingManagerForThread(threadId);
704
570
  if (stagehand?.context) {
705
571
  return stagehand.context.activePage();
706
572
  }
707
573
  return null;
708
574
  }
709
- if (!this.stagehand) return null;
575
+ if (!this.sharedManager) return null;
710
576
  try {
711
- const context = this.stagehand.context;
577
+ const context = this.sharedManager.context;
712
578
  if (context) {
713
579
  const activePage = context.activePage();
714
580
  if (activePage) {
@@ -724,18 +590,10 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
724
590
  return null;
725
591
  }
726
592
  /**
727
- * Get the page for a specific thread, creating session if needed.
593
+ * Get the active page for a thread (implements abstract method from base class).
728
594
  */
729
- async getPageForThread(threadId) {
730
- const scope = this.threadManager.getScope();
731
- if (scope === "shared") {
732
- return this.getPage();
733
- }
734
- const stagehand = await this.getStagehandForThread(threadId);
735
- if (stagehand?.context) {
736
- return stagehand.context.activePage();
737
- }
738
- return null;
595
+ async getActivePage(threadId) {
596
+ return this.getPage(threadId);
739
597
  }
740
598
  /**
741
599
  * Get a CDP session for a specific page.
@@ -986,7 +844,7 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
986
844
  const effectiveThreadId = threadId ?? this.getCurrentThread();
987
845
  const scope = this.threadManager.getScope();
988
846
  if (scope === "thread" && effectiveThreadId) {
989
- const stagehand = this.threadManager.getStagehandForThread(effectiveThreadId);
847
+ const stagehand = this.threadManager.getExistingManagerForThread(effectiveThreadId);
990
848
  if (!stagehand?.context) {
991
849
  return null;
992
850
  }
@@ -1005,7 +863,7 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
1005
863
  try {
1006
864
  const url = page.url();
1007
865
  if (url && url !== "about:blank") {
1008
- const state = this.getBrowserStateFromStagehand(this.stagehand);
866
+ const state = this.getBrowserStateFromStagehand(this.sharedManager);
1009
867
  if (state) {
1010
868
  this.lastBrowserState = state;
1011
869
  }
@@ -1040,15 +898,24 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
1040
898
  const scope = this.threadManager.getScope();
1041
899
  const effectiveThreadId = threadId ?? this.getCurrentThread();
1042
900
  if (scope === "thread" && effectiveThreadId) {
1043
- const stagehand = this.threadManager.getStagehandForThread(effectiveThreadId);
901
+ const stagehand = this.threadManager.getExistingManagerForThread(effectiveThreadId);
1044
902
  if (!stagehand) return null;
1045
903
  return this.getBrowserStateFromStagehand(stagehand);
1046
904
  }
1047
- return this.getBrowserStateFromStagehand(this.stagehand);
905
+ return this.getBrowserStateFromStagehand(this.sharedManager);
1048
906
  } catch {
1049
907
  return null;
1050
908
  }
1051
909
  }
910
+ /**
911
+ * Get browser state for a thread (implements abstract method from base class).
912
+ * Sync version that uses existing manager lookup without creating sessions.
913
+ */
914
+ getBrowserStateForThread(threadId) {
915
+ const effectiveThreadId = threadId ?? this.getCurrentThread() ?? browser.DEFAULT_THREAD_ID;
916
+ const stagehand = this.threadManager.getExistingManagerForThread(effectiveThreadId);
917
+ return this.getBrowserStateFromStagehand(stagehand);
918
+ }
1052
919
  /**
1053
920
  * Get browser state from a specific Stagehand instance.
1054
921
  */
@@ -1086,44 +953,15 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
1086
953
  const state = await this.getBrowserState(threadId);
1087
954
  return state?.activeTabIndex ?? 0;
1088
955
  }
1089
- /**
1090
- * Update the browser state in the thread session.
1091
- * Called on navigation, tab open/close to keep state fresh.
1092
- */
1093
- updateSessionBrowserState(threadId) {
1094
- try {
1095
- const effectiveThreadId = threadId ?? this.getCurrentThread() ?? browser.DEFAULT_THREAD_ID;
1096
- const scope = this.threadManager.getScope();
1097
- let stagehand = null;
1098
- if (scope === "thread") {
1099
- stagehand = this.threadManager.getStagehandForThread(effectiveThreadId);
1100
- } else {
1101
- stagehand = this.stagehand;
1102
- }
1103
- if (stagehand) {
1104
- const state = this.getBrowserStateFromStagehand(stagehand);
1105
- if (state) {
1106
- this.threadManager.updateBrowserState(effectiveThreadId, state);
1107
- }
1108
- }
1109
- } catch {
1110
- }
1111
- }
1112
956
  // ---------------------------------------------------------------------------
1113
957
  // Screencast (for Studio live view)
1114
958
  // Uses Stagehand v3's native CDP access
1115
959
  // ---------------------------------------------------------------------------
1116
- /**
1117
- * Get the stream key for a thread (or shared key for shared scope).
1118
- */
1119
- getStreamKey(threadId) {
1120
- return threadId || _StagehandBrowser.SHARED_STREAM_KEY;
1121
- }
1122
960
  async startScreencast(options) {
1123
961
  const threadId = options?.threadId;
1124
962
  const provider = {
1125
963
  getCdpSession: async () => {
1126
- const page = await this.getPageForThread(threadId ?? "");
964
+ const page = await this.threadManager.getPageForThread(threadId);
1127
965
  if (!page) {
1128
966
  throw new Error("No page available for screencast");
1129
967
  }
@@ -1157,7 +995,7 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
1157
995
  * Uses CDP Target events since Stagehand doesn't expose page lifecycle events.
1158
996
  */
1159
997
  async setupTabChangeDetection(threadId, stream) {
1160
- const stagehand = await this.getStagehandForThread(threadId);
998
+ const stagehand = await this.getManagerForThread(threadId);
1161
999
  if (!stagehand?.context) return;
1162
1000
  const connection = stagehand.context.conn;
1163
1001
  if (!connection) {
@@ -1219,7 +1057,7 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
1219
1057
  if (newPage && stagehand.context) {
1220
1058
  stagehand.context.setActivePage(newPage);
1221
1059
  }
1222
- void this.reconnectScreencast("manual tab tracked");
1060
+ void this.reconnectScreencastForThread(threadId, "manual tab tracked");
1223
1061
  void setupPageNavigationListener();
1224
1062
  } else {
1225
1063
  this.logger.debug?.("Stagehand did not register the page (non-injectable URL)");
@@ -1302,48 +1140,6 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
1302
1140
  this.logger.debug?.("Failed to set up tab change detection", error);
1303
1141
  }
1304
1142
  }
1305
- /**
1306
- * Reconnect the active screencast for a specific thread.
1307
- */
1308
- async reconnectScreencastForThread(threadId, reason) {
1309
- const streamKey = this.getStreamKey(threadId);
1310
- const stream = this.activeScreencastStreams.get(streamKey);
1311
- if (!stream || !stream.isActive()) {
1312
- return;
1313
- }
1314
- if (!this.isBrowserRunning()) {
1315
- this.logger.debug?.("Skipping screencast reconnect - browser not running");
1316
- return;
1317
- }
1318
- const scope = this.getScope();
1319
- if (scope === "thread" && threadId && !this.threadManager.getStagehandForThread(threadId)) {
1320
- this.logger.debug?.(`Skipping screencast reconnect - no session for thread ${threadId}`);
1321
- return;
1322
- }
1323
- this.logger.debug?.(`Reconnecting screencast: ${reason}`);
1324
- try {
1325
- await new Promise((resolve) => setTimeout(resolve, 150));
1326
- await stream.reconnect();
1327
- const stagehand = await this.getStagehandForThread(threadId);
1328
- const activePage = stagehand?.context?.activePage();
1329
- if (activePage) {
1330
- const url = activePage.url();
1331
- if (url) {
1332
- stream.emitUrl(url);
1333
- }
1334
- }
1335
- } catch (error) {
1336
- this.logger.debug?.("Screencast reconnect failed", error);
1337
- }
1338
- }
1339
- /**
1340
- * Reconnect the active screencast for the current thread.
1341
- * Wrapper for reconnectScreencastForThread using getCurrentThread().
1342
- */
1343
- async reconnectScreencast(reason) {
1344
- const threadId = this.getCurrentThread();
1345
- await this.reconnectScreencastForThread(threadId, reason);
1346
- }
1347
1143
  // NOTE: Manual tab switching in browser UI is not fully supported.
1348
1144
  // Stagehand v3 does not track pages opened via browser UI (only pages created through its API).
1349
1145
  // We've requested this feature from Browserbase - see Notion doc for details.
@@ -1352,7 +1148,7 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
1352
1148
  // ---------------------------------------------------------------------------
1353
1149
  async injectMouseEvent(event, threadId) {
1354
1150
  const effectiveThreadId = threadId ?? this.getCurrentThread();
1355
- const page = await this.getPageForThread(effectiveThreadId ?? "");
1151
+ const page = await this.threadManager.getPageForThread(effectiveThreadId);
1356
1152
  const cdpSession = this.getCdpSessionForPage(page);
1357
1153
  if (!cdpSession) {
1358
1154
  throw new Error("No CDP session available");
@@ -1378,7 +1174,7 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
1378
1174
  }
1379
1175
  async injectKeyboardEvent(event, threadId) {
1380
1176
  const effectiveThreadId = threadId ?? this.getCurrentThread();
1381
- const page = await this.getPageForThread(effectiveThreadId ?? "");
1177
+ const page = await this.threadManager.getPageForThread(effectiveThreadId);
1382
1178
  const cdpSession = this.getCdpSessionForPage(page);
1383
1179
  if (!cdpSession) {
1384
1180
  throw new Error("No CDP session available");