@mastra/stagehand 0.1.0-alpha.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/CHANGELOG.md +53 -0
- package/README.md +33 -16
- package/dist/index.cjs +139 -317
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -119
- package/dist/index.d.ts +72 -119
- package/dist/index.js +139 -318
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
package/dist/index.js
CHANGED
|
@@ -1,75 +1,33 @@
|
|
|
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 {
|
|
8
|
-
sharedStagehand = null;
|
|
9
10
|
sessions = /* @__PURE__ */ new Map();
|
|
10
11
|
createStagehand;
|
|
11
12
|
onBrowserCreated;
|
|
12
|
-
/** Map of thread ID to dedicated Stagehand instance (for 'thread' mode) */
|
|
13
|
-
threadStagehands = /* @__PURE__ */ new Map();
|
|
14
13
|
constructor(config) {
|
|
15
14
|
super(config);
|
|
16
15
|
this.createStagehand = config.createStagehand;
|
|
17
16
|
this.onBrowserCreated = config.onBrowserCreated;
|
|
18
17
|
}
|
|
19
|
-
/**
|
|
20
|
-
* Set the shared Stagehand instance (called after browser launch).
|
|
21
|
-
*/
|
|
22
|
-
setStagehand(instance) {
|
|
23
|
-
this.sharedStagehand = instance;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Clear the shared Stagehand instance (called when browser disconnects).
|
|
27
|
-
*/
|
|
28
|
-
clearStagehand() {
|
|
29
|
-
this.sharedStagehand = null;
|
|
30
|
-
}
|
|
31
18
|
/**
|
|
32
19
|
* Set the factory function for creating new Stagehand instances.
|
|
33
|
-
* Required for '
|
|
20
|
+
* Required for 'thread' scope mode.
|
|
34
21
|
*/
|
|
35
22
|
setCreateStagehand(factory) {
|
|
36
23
|
this.createStagehand = factory;
|
|
37
24
|
}
|
|
38
25
|
/**
|
|
39
|
-
* Get the
|
|
40
|
-
*/
|
|
41
|
-
getSharedStagehand() {
|
|
42
|
-
if (!this.sharedStagehand) {
|
|
43
|
-
throw new Error("Stagehand not initialized");
|
|
44
|
-
}
|
|
45
|
-
return this.sharedStagehand;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Get the Stagehand instance for a specific thread.
|
|
49
|
-
* In 'shared' mode, returns the shared instance.
|
|
50
|
-
* In 'thread' mode, returns the thread's dedicated instance.
|
|
51
|
-
*/
|
|
52
|
-
getStagehandForThread(threadId) {
|
|
53
|
-
if (this.scope === "thread") {
|
|
54
|
-
const session = this.sessions.get(threadId);
|
|
55
|
-
return session?.stagehand;
|
|
56
|
-
}
|
|
57
|
-
return this.sharedStagehand ?? void 0;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Get the Stagehand page for a thread.
|
|
61
|
-
* Returns the active page from the thread's Stagehand instance.
|
|
62
|
-
*/
|
|
63
|
-
getPageForThread(threadId) {
|
|
64
|
-
const stagehand = this.getStagehandForThread(threadId);
|
|
65
|
-
return stagehand?.context?.activePage();
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Get the shared manager - returns the active page or the Stagehand instance.
|
|
26
|
+
* Get the page for a specific thread, creating session if needed.
|
|
69
27
|
*/
|
|
70
|
-
|
|
71
|
-
const stagehand = this.
|
|
72
|
-
return stagehand
|
|
28
|
+
async getPageForThread(threadId) {
|
|
29
|
+
const stagehand = await this.getManagerForThread(threadId);
|
|
30
|
+
return stagehand?.context?.activePage() ?? null;
|
|
73
31
|
}
|
|
74
32
|
/**
|
|
75
33
|
* Create a new session for a thread.
|
|
@@ -88,7 +46,7 @@ var StagehandThreadManager = class extends ThreadManager {
|
|
|
88
46
|
this.logger?.debug?.(`Creating dedicated Stagehand instance for thread ${threadId}`);
|
|
89
47
|
const stagehand = await this.createStagehand();
|
|
90
48
|
session.stagehand = stagehand;
|
|
91
|
-
this.
|
|
49
|
+
this.threadManagers.set(threadId, stagehand);
|
|
92
50
|
if (savedState && savedState.tabs.length > 0) {
|
|
93
51
|
this.logger?.debug?.(`Restoring browser state for thread ${threadId}: ${savedState.tabs.length} tabs`);
|
|
94
52
|
await this.restoreBrowserState(stagehand, savedState);
|
|
@@ -127,18 +85,11 @@ var StagehandThreadManager = class extends ThreadManager {
|
|
|
127
85
|
}
|
|
128
86
|
}
|
|
129
87
|
/**
|
|
130
|
-
*
|
|
131
|
-
* For 'thread' mode, no switching needed - each thread has its own instance.
|
|
132
|
-
* For 'shared' mode, nothing to switch.
|
|
133
|
-
*/
|
|
134
|
-
async switchToSession(_session) {
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Get the manager for a specific session.
|
|
88
|
+
* Get the manager (Stagehand instance) for a specific session.
|
|
138
89
|
*/
|
|
139
90
|
getManagerForSession(session) {
|
|
140
91
|
if (this.scope === "thread" && session.stagehand) {
|
|
141
|
-
return session.stagehand
|
|
92
|
+
return session.stagehand;
|
|
142
93
|
}
|
|
143
94
|
return this.getSharedManager();
|
|
144
95
|
}
|
|
@@ -153,50 +104,14 @@ var StagehandThreadManager = class extends ThreadManager {
|
|
|
153
104
|
} catch (error) {
|
|
154
105
|
this.logger?.warn?.(`Failed to close Stagehand for thread ${session.threadId}: ${error}`);
|
|
155
106
|
}
|
|
156
|
-
this.threadStagehands.delete(session.threadId);
|
|
157
107
|
}
|
|
158
108
|
}
|
|
159
109
|
/**
|
|
160
|
-
*
|
|
161
|
-
|
|
162
|
-
async destroyAll() {
|
|
163
|
-
for (const [threadId, stagehand] of this.threadStagehands) {
|
|
164
|
-
try {
|
|
165
|
-
await stagehand.close();
|
|
166
|
-
} catch {
|
|
167
|
-
this.logger?.debug?.(`Failed to close Stagehand for thread: ${threadId}`);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
this.threadStagehands.clear();
|
|
171
|
-
this.sessions.clear();
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Check if any thread Stagehands are still running.
|
|
175
|
-
*/
|
|
176
|
-
hasActiveThreadStagehands() {
|
|
177
|
-
return this.threadStagehands.size > 0;
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Clear all session tracking without closing browsers.
|
|
181
|
-
* Used when browsers have been externally closed and we just need to reset state.
|
|
182
|
-
*/
|
|
183
|
-
clearAllSessions() {
|
|
184
|
-
this.threadStagehands.clear();
|
|
185
|
-
this.sessions.clear();
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Clear a specific thread's session without closing the browser.
|
|
189
|
-
* Used when a thread's browser has been externally closed.
|
|
190
|
-
* Preserves the browser state for potential restoration.
|
|
191
|
-
* @param threadId - The thread ID to clear
|
|
110
|
+
* Destroy all sessions (called during browser close).
|
|
111
|
+
* doDestroySession handles closing individual Stagehand instances.
|
|
192
112
|
*/
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (session?.browserState) {
|
|
196
|
-
this.savedBrowserStates.set(threadId, session.browserState);
|
|
197
|
-
}
|
|
198
|
-
this.threadStagehands.delete(threadId);
|
|
199
|
-
this.sessions.delete(threadId);
|
|
113
|
+
async destroyAllSessions() {
|
|
114
|
+
await super.destroyAllSessions();
|
|
200
115
|
}
|
|
201
116
|
};
|
|
202
117
|
var actInputSchema = z.object({
|
|
@@ -365,31 +280,44 @@ function createStagehandTools(browser) {
|
|
|
365
280
|
[STAGEHAND_TOOLS.CLOSE]: createCloseTool(browser)
|
|
366
281
|
};
|
|
367
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
|
+
}
|
|
368
307
|
|
|
369
308
|
// src/stagehand-browser.ts
|
|
370
|
-
var StagehandBrowser = class
|
|
309
|
+
var StagehandBrowser = class extends MastraBrowser {
|
|
371
310
|
id;
|
|
372
311
|
name = "StagehandBrowser";
|
|
373
312
|
provider = "browserbase/stagehand";
|
|
374
|
-
stagehand = null;
|
|
375
313
|
stagehandConfig;
|
|
376
|
-
/** Active screencast streams per thread (for reconnection on tab changes) */
|
|
377
|
-
activeScreencastStreams = /* @__PURE__ */ new Map();
|
|
378
314
|
/** Debounce timers per thread for tab change reconnection */
|
|
379
315
|
tabChangeDebounceTimers = /* @__PURE__ */ new Map();
|
|
380
|
-
/** Default key for shared scope */
|
|
381
|
-
static SHARED_STREAM_KEY = "__shared__";
|
|
382
316
|
constructor(config = {}) {
|
|
383
317
|
super(config);
|
|
384
318
|
this.id = `stagehand-${Date.now()}`;
|
|
385
319
|
this.stagehandConfig = config;
|
|
386
|
-
|
|
387
|
-
if (config.cdpUrl && effectiveScope === "thread") {
|
|
388
|
-
this.logger.warn?.(
|
|
389
|
-
'Browser scope "thread" is not supported when connecting via cdpUrl. Falling back to "shared" (shared browser connection).'
|
|
390
|
-
);
|
|
391
|
-
effectiveScope = "shared";
|
|
392
|
-
}
|
|
320
|
+
const effectiveScope = config.cdpUrl ? config.scope ?? "shared" : config.scope ?? "thread";
|
|
393
321
|
this.threadManager = new StagehandThreadManager({
|
|
394
322
|
scope: effectiveScope,
|
|
395
323
|
logger: this.logger,
|
|
@@ -399,19 +327,10 @@ var StagehandBrowser = class _StagehandBrowser extends MastraBrowser {
|
|
|
399
327
|
},
|
|
400
328
|
// When a new browser is created for a thread, set up close listener
|
|
401
329
|
onBrowserCreated: (stagehand, threadId) => {
|
|
402
|
-
this.
|
|
330
|
+
this.setupCloseListener(stagehand, () => this.handleThreadBrowserDisconnected(threadId), threadId);
|
|
403
331
|
}
|
|
404
332
|
});
|
|
405
333
|
}
|
|
406
|
-
/**
|
|
407
|
-
* Close a specific thread's browser session.
|
|
408
|
-
* For 'thread' scope, this closes only that thread's Stagehand instance.
|
|
409
|
-
* For 'shared' scope, this is a no-op (use close() to close the shared browser).
|
|
410
|
-
*/
|
|
411
|
-
async closeThreadSession(threadId) {
|
|
412
|
-
await this.threadManager.destroySession(threadId);
|
|
413
|
-
this.notifyBrowserClosed(threadId);
|
|
414
|
-
}
|
|
415
334
|
/**
|
|
416
335
|
* Ensure browser is ready and thread session exists.
|
|
417
336
|
* For 'thread' scope, this creates a dedicated Stagehand instance for the thread.
|
|
@@ -422,7 +341,7 @@ var StagehandBrowser = class _StagehandBrowser extends MastraBrowser {
|
|
|
422
341
|
const scope = this.getScope();
|
|
423
342
|
const threadId = this.getCurrentThread();
|
|
424
343
|
if (scope === "thread" && threadId && threadId !== DEFAULT_THREAD_ID) {
|
|
425
|
-
await this.
|
|
344
|
+
await this.getManagerForThread(threadId);
|
|
426
345
|
}
|
|
427
346
|
}
|
|
428
347
|
// ---------------------------------------------------------------------------
|
|
@@ -451,25 +370,34 @@ var StagehandBrowser = class _StagehandBrowser extends MastraBrowser {
|
|
|
451
370
|
stagehandOptions.projectId = config.projectId;
|
|
452
371
|
}
|
|
453
372
|
}
|
|
373
|
+
if (config.profile && !existsSync(config.profile)) {
|
|
374
|
+
mkdirSync(config.profile, { recursive: true });
|
|
375
|
+
}
|
|
454
376
|
if (config.cdpUrl && config.env !== "BROWSERBASE") {
|
|
455
377
|
const resolvedUrl = await this.resolveCdpUrl(config.cdpUrl);
|
|
456
378
|
const wsUrl = await this.resolveWebSocketUrl(resolvedUrl);
|
|
457
379
|
stagehandOptions.localBrowserLaunchOptions = {
|
|
458
380
|
cdpUrl: wsUrl,
|
|
459
381
|
headless: config.headless,
|
|
460
|
-
viewport: config.viewport
|
|
382
|
+
viewport: config.viewport,
|
|
383
|
+
userDataDir: config.profile,
|
|
384
|
+
executablePath: config.executablePath,
|
|
385
|
+
preserveUserDataDir: config.preserveUserDataDir
|
|
461
386
|
};
|
|
462
387
|
} else if (config.env !== "BROWSERBASE") {
|
|
463
388
|
stagehandOptions.localBrowserLaunchOptions = {
|
|
464
389
|
headless: config.headless,
|
|
465
|
-
viewport: config.viewport
|
|
390
|
+
viewport: config.viewport,
|
|
391
|
+
userDataDir: config.profile,
|
|
392
|
+
executablePath: config.executablePath,
|
|
393
|
+
preserveUserDataDir: config.preserveUserDataDir
|
|
466
394
|
};
|
|
467
395
|
}
|
|
468
396
|
return stagehandOptions;
|
|
469
397
|
}
|
|
470
398
|
/**
|
|
471
399
|
* Create a new Stagehand instance with the current config.
|
|
472
|
-
* Used by thread manager for '
|
|
400
|
+
* Used by thread manager for 'thread' scope.
|
|
473
401
|
*/
|
|
474
402
|
async createStagehandInstance() {
|
|
475
403
|
const stagehandOptions = await this.buildStagehandOptions();
|
|
@@ -483,104 +411,85 @@ var StagehandBrowser = class _StagehandBrowser extends MastraBrowser {
|
|
|
483
411
|
if (scope === "thread") {
|
|
484
412
|
return;
|
|
485
413
|
}
|
|
486
|
-
this.
|
|
487
|
-
this.threadManager.
|
|
488
|
-
this.setupCloseListener(this.
|
|
414
|
+
this.sharedManager = await this.createStagehandInstance();
|
|
415
|
+
this.threadManager.setSharedManager(this.sharedManager);
|
|
416
|
+
this.setupCloseListener(this.sharedManager, () => this.handleBrowserDisconnected());
|
|
489
417
|
}
|
|
490
418
|
/**
|
|
491
|
-
* Set up close event listener for a Stagehand instance.
|
|
419
|
+
* Set up close event listener for a shared Stagehand instance.
|
|
492
420
|
* Listens to both context and page close events for robust detection.
|
|
493
421
|
*/
|
|
494
|
-
setupCloseListener(stagehand) {
|
|
495
|
-
let disconnectHandled = false;
|
|
496
|
-
const handleDisconnect = () => {
|
|
497
|
-
if (disconnectHandled) return;
|
|
498
|
-
disconnectHandled = true;
|
|
499
|
-
this.handleBrowserDisconnected();
|
|
500
|
-
};
|
|
501
|
-
try {
|
|
502
|
-
const context = stagehand.context;
|
|
503
|
-
if (!context) return;
|
|
504
|
-
const contextWithEvents = context;
|
|
505
|
-
if (contextWithEvents?.on) {
|
|
506
|
-
contextWithEvents.on("close", handleDisconnect);
|
|
507
|
-
}
|
|
508
|
-
const pages = context.pages?.() ?? [];
|
|
509
|
-
for (const page of pages) {
|
|
510
|
-
const pageWithEvents = page;
|
|
511
|
-
if (pageWithEvents?.on) {
|
|
512
|
-
pageWithEvents.on("close", () => {
|
|
513
|
-
const remainingPages = context.pages?.() ?? [];
|
|
514
|
-
if (remainingPages.length === 0) {
|
|
515
|
-
handleDisconnect();
|
|
516
|
-
}
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
} catch {
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
422
|
/**
|
|
524
|
-
* Set up close
|
|
525
|
-
*
|
|
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).
|
|
526
429
|
*/
|
|
527
|
-
|
|
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
|
+
}
|
|
528
439
|
let disconnectHandled = false;
|
|
529
440
|
const handleDisconnect = () => {
|
|
530
441
|
if (disconnectHandled) return;
|
|
531
442
|
disconnectHandled = true;
|
|
532
|
-
|
|
443
|
+
onDisconnect();
|
|
533
444
|
};
|
|
534
445
|
try {
|
|
535
446
|
const stagehandAny = stagehand;
|
|
536
447
|
const conn = stagehandAny.ctx?.conn;
|
|
537
|
-
if (!conn?.on)
|
|
538
|
-
return;
|
|
539
|
-
}
|
|
448
|
+
if (!conn?.on) return;
|
|
540
449
|
const pageTargets = /* @__PURE__ */ new Set();
|
|
541
450
|
const context = stagehand.context;
|
|
542
451
|
if (context) {
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
const targetId = pageAny._targetId ?? pageAny.targetId;
|
|
547
|
-
if (targetId) {
|
|
548
|
-
pageTargets.add(targetId);
|
|
549
|
-
}
|
|
452
|
+
for (const page of context.pages?.() ?? []) {
|
|
453
|
+
const targetId = page._targetId ?? page.targetId;
|
|
454
|
+
if (targetId) pageTargets.add(targetId);
|
|
550
455
|
}
|
|
551
456
|
}
|
|
552
457
|
conn.on("Target.targetCreated", (params) => {
|
|
553
|
-
if (params.targetInfo.type === "page")
|
|
554
|
-
pageTargets.add(params.targetInfo.targetId);
|
|
555
|
-
}
|
|
458
|
+
if (params.targetInfo.type === "page") pageTargets.add(params.targetInfo.targetId);
|
|
556
459
|
});
|
|
557
460
|
conn.on("Target.targetDestroyed", (params) => {
|
|
558
461
|
if (pageTargets.has(params.targetId)) {
|
|
559
462
|
pageTargets.delete(params.targetId);
|
|
560
|
-
if (pageTargets.size === 0)
|
|
561
|
-
handleDisconnect();
|
|
562
|
-
}
|
|
463
|
+
if (pageTargets.size === 0) handleDisconnect();
|
|
563
464
|
}
|
|
564
465
|
});
|
|
565
466
|
} catch {
|
|
566
467
|
}
|
|
567
468
|
}
|
|
568
|
-
/**
|
|
569
|
-
* Handle browser disconnection for a specific thread.
|
|
570
|
-
* Called when a thread's browser is closed externally.
|
|
571
|
-
*/
|
|
572
|
-
handleThreadBrowserDisconnected(threadId) {
|
|
573
|
-
this.threadManager.clearSession(threadId);
|
|
574
|
-
this.logger.debug?.(`Cleared Stagehand session for thread: ${threadId}`);
|
|
575
|
-
this.notifyBrowserClosed(threadId);
|
|
576
|
-
}
|
|
577
469
|
async doClose() {
|
|
578
|
-
await this.threadManager.
|
|
579
|
-
if (this.
|
|
580
|
-
await this.
|
|
581
|
-
this.
|
|
470
|
+
await this.threadManager.destroyAllSessions();
|
|
471
|
+
if (this.sharedManager) {
|
|
472
|
+
await this.sharedManager.close();
|
|
473
|
+
this.sharedManager = null;
|
|
582
474
|
}
|
|
583
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);
|
|
584
493
|
}
|
|
585
494
|
/**
|
|
586
495
|
* Check if the browser is still alive by verifying the context and pages exist.
|
|
@@ -589,13 +498,13 @@ var StagehandBrowser = class _StagehandBrowser extends MastraBrowser {
|
|
|
589
498
|
async checkBrowserAlive() {
|
|
590
499
|
const scope = this.getScope();
|
|
591
500
|
if (scope === "thread") {
|
|
592
|
-
return this.threadManager.
|
|
501
|
+
return this.threadManager.hasActiveThreadManagers();
|
|
593
502
|
}
|
|
594
|
-
if (!this.
|
|
503
|
+
if (!this.sharedManager) {
|
|
595
504
|
return false;
|
|
596
505
|
}
|
|
597
506
|
try {
|
|
598
|
-
const context = this.
|
|
507
|
+
const context = this.sharedManager.context;
|
|
599
508
|
if (!context) {
|
|
600
509
|
return false;
|
|
601
510
|
}
|
|
@@ -605,7 +514,7 @@ var StagehandBrowser = class _StagehandBrowser extends MastraBrowser {
|
|
|
605
514
|
}
|
|
606
515
|
const url = pages[0]?.url();
|
|
607
516
|
if (url && url !== "about:blank") {
|
|
608
|
-
const state = this.getBrowserStateFromStagehand(this.
|
|
517
|
+
const state = this.getBrowserStateFromStagehand(this.sharedManager);
|
|
609
518
|
if (state) {
|
|
610
519
|
this.lastBrowserState = state;
|
|
611
520
|
}
|
|
@@ -619,24 +528,6 @@ var StagehandBrowser = class _StagehandBrowser extends MastraBrowser {
|
|
|
619
528
|
return false;
|
|
620
529
|
}
|
|
621
530
|
}
|
|
622
|
-
/**
|
|
623
|
-
* Handle browser disconnection by clearing internal state.
|
|
624
|
-
* For 'thread' scope, only notifies the specific thread's callbacks.
|
|
625
|
-
* For 'shared' scope, notifies all callbacks.
|
|
626
|
-
*/
|
|
627
|
-
handleBrowserDisconnected() {
|
|
628
|
-
const scope = this.threadManager.getScope();
|
|
629
|
-
const threadId = this.getCurrentThread();
|
|
630
|
-
if (scope === "thread" && threadId !== DEFAULT_THREAD_ID) {
|
|
631
|
-
this.threadManager.clearSession(threadId);
|
|
632
|
-
this.logger.debug?.(`Cleared Stagehand session for thread: ${threadId}`);
|
|
633
|
-
this.notifyBrowserClosed(threadId);
|
|
634
|
-
} else {
|
|
635
|
-
this.stagehand = null;
|
|
636
|
-
this.threadManager.clearStagehand();
|
|
637
|
-
super.handleBrowserDisconnected();
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
531
|
/**
|
|
641
532
|
* Create an error response from an exception.
|
|
642
533
|
* Extends base class to add Stagehand-specific error handling.
|
|
@@ -657,21 +548,21 @@ var StagehandBrowser = class _StagehandBrowser extends MastraBrowser {
|
|
|
657
548
|
// ---------------------------------------------------------------------------
|
|
658
549
|
/**
|
|
659
550
|
* Get the Stagehand instance for a thread, creating it if needed.
|
|
660
|
-
* For '
|
|
661
|
-
* For '
|
|
551
|
+
* For 'thread' scope, this creates a dedicated Stagehand instance.
|
|
552
|
+
* For 'shared' scope, returns the shared instance.
|
|
662
553
|
*/
|
|
663
|
-
async
|
|
554
|
+
async getManagerForThread(threadId) {
|
|
664
555
|
const scope = this.getScope();
|
|
665
556
|
if (scope === "shared") {
|
|
666
|
-
return this.
|
|
557
|
+
return this.sharedManager;
|
|
667
558
|
}
|
|
668
559
|
if (!threadId || threadId === DEFAULT_THREAD_ID) {
|
|
669
|
-
return this.
|
|
560
|
+
return this.sharedManager;
|
|
670
561
|
}
|
|
671
|
-
let stagehand = this.threadManager.
|
|
562
|
+
let stagehand = this.threadManager.getExistingManagerForThread(threadId);
|
|
672
563
|
if (!stagehand) {
|
|
673
564
|
await this.threadManager.getManagerForThread(threadId);
|
|
674
|
-
stagehand = this.threadManager.
|
|
565
|
+
stagehand = this.threadManager.getExistingManagerForThread(threadId);
|
|
675
566
|
}
|
|
676
567
|
return stagehand ?? null;
|
|
677
568
|
}
|
|
@@ -683,14 +574,14 @@ var StagehandBrowser = class _StagehandBrowser extends MastraBrowser {
|
|
|
683
574
|
*/
|
|
684
575
|
requireStagehand(explicitThreadId) {
|
|
685
576
|
const threadId = explicitThreadId ?? this.getCurrentThread();
|
|
686
|
-
const stagehand = this.threadManager.
|
|
577
|
+
const stagehand = this.threadManager.getExistingManagerForThread(threadId) ?? this.sharedManager;
|
|
687
578
|
if (!stagehand) {
|
|
688
579
|
throw new Error("Browser not launched");
|
|
689
580
|
}
|
|
690
581
|
return stagehand;
|
|
691
582
|
}
|
|
692
583
|
/**
|
|
693
|
-
* Get the current page from Stagehand v3, respecting thread
|
|
584
|
+
* Get the current page from Stagehand v3, respecting thread scope.
|
|
694
585
|
* @param explicitThreadId - Optional thread ID to use instead of getCurrentThread()
|
|
695
586
|
* Use this to avoid race conditions in concurrent tool calls.
|
|
696
587
|
*/
|
|
@@ -698,15 +589,15 @@ var StagehandBrowser = class _StagehandBrowser extends MastraBrowser {
|
|
|
698
589
|
const scope = this.getScope();
|
|
699
590
|
const threadId = explicitThreadId ?? this.getCurrentThread();
|
|
700
591
|
if (scope === "thread" && threadId && threadId !== DEFAULT_THREAD_ID) {
|
|
701
|
-
const stagehand = this.threadManager.
|
|
592
|
+
const stagehand = this.threadManager.getExistingManagerForThread(threadId);
|
|
702
593
|
if (stagehand?.context) {
|
|
703
594
|
return stagehand.context.activePage();
|
|
704
595
|
}
|
|
705
596
|
return null;
|
|
706
597
|
}
|
|
707
|
-
if (!this.
|
|
598
|
+
if (!this.sharedManager) return null;
|
|
708
599
|
try {
|
|
709
|
-
const context = this.
|
|
600
|
+
const context = this.sharedManager.context;
|
|
710
601
|
if (context) {
|
|
711
602
|
const activePage = context.activePage();
|
|
712
603
|
if (activePage) {
|
|
@@ -722,18 +613,10 @@ var StagehandBrowser = class _StagehandBrowser extends MastraBrowser {
|
|
|
722
613
|
return null;
|
|
723
614
|
}
|
|
724
615
|
/**
|
|
725
|
-
* Get the page for a
|
|
616
|
+
* Get the active page for a thread (implements abstract method from base class).
|
|
726
617
|
*/
|
|
727
|
-
async
|
|
728
|
-
|
|
729
|
-
if (scope === "shared") {
|
|
730
|
-
return this.getPage();
|
|
731
|
-
}
|
|
732
|
-
const stagehand = await this.getStagehandForThread(threadId);
|
|
733
|
-
if (stagehand?.context) {
|
|
734
|
-
return stagehand.context.activePage();
|
|
735
|
-
}
|
|
736
|
-
return null;
|
|
618
|
+
async getActivePage(threadId) {
|
|
619
|
+
return this.getPage(threadId);
|
|
737
620
|
}
|
|
738
621
|
/**
|
|
739
622
|
* Get a CDP session for a specific page.
|
|
@@ -984,7 +867,7 @@ var StagehandBrowser = class _StagehandBrowser extends MastraBrowser {
|
|
|
984
867
|
const effectiveThreadId = threadId ?? this.getCurrentThread();
|
|
985
868
|
const scope = this.threadManager.getScope();
|
|
986
869
|
if (scope === "thread" && effectiveThreadId) {
|
|
987
|
-
const stagehand = this.threadManager.
|
|
870
|
+
const stagehand = this.threadManager.getExistingManagerForThread(effectiveThreadId);
|
|
988
871
|
if (!stagehand?.context) {
|
|
989
872
|
return null;
|
|
990
873
|
}
|
|
@@ -1003,7 +886,7 @@ var StagehandBrowser = class _StagehandBrowser extends MastraBrowser {
|
|
|
1003
886
|
try {
|
|
1004
887
|
const url = page.url();
|
|
1005
888
|
if (url && url !== "about:blank") {
|
|
1006
|
-
const state = this.getBrowserStateFromStagehand(this.
|
|
889
|
+
const state = this.getBrowserStateFromStagehand(this.sharedManager);
|
|
1007
890
|
if (state) {
|
|
1008
891
|
this.lastBrowserState = state;
|
|
1009
892
|
}
|
|
@@ -1038,15 +921,24 @@ var StagehandBrowser = class _StagehandBrowser extends MastraBrowser {
|
|
|
1038
921
|
const scope = this.threadManager.getScope();
|
|
1039
922
|
const effectiveThreadId = threadId ?? this.getCurrentThread();
|
|
1040
923
|
if (scope === "thread" && effectiveThreadId) {
|
|
1041
|
-
const stagehand = this.threadManager.
|
|
924
|
+
const stagehand = this.threadManager.getExistingManagerForThread(effectiveThreadId);
|
|
1042
925
|
if (!stagehand) return null;
|
|
1043
926
|
return this.getBrowserStateFromStagehand(stagehand);
|
|
1044
927
|
}
|
|
1045
|
-
return this.getBrowserStateFromStagehand(this.
|
|
928
|
+
return this.getBrowserStateFromStagehand(this.sharedManager);
|
|
1046
929
|
} catch {
|
|
1047
930
|
return null;
|
|
1048
931
|
}
|
|
1049
932
|
}
|
|
933
|
+
/**
|
|
934
|
+
* Get browser state for a thread (implements abstract method from base class).
|
|
935
|
+
* Sync version that uses existing manager lookup without creating sessions.
|
|
936
|
+
*/
|
|
937
|
+
getBrowserStateForThread(threadId) {
|
|
938
|
+
const effectiveThreadId = threadId ?? this.getCurrentThread() ?? DEFAULT_THREAD_ID;
|
|
939
|
+
const stagehand = this.threadManager.getExistingManagerForThread(effectiveThreadId);
|
|
940
|
+
return this.getBrowserStateFromStagehand(stagehand);
|
|
941
|
+
}
|
|
1050
942
|
/**
|
|
1051
943
|
* Get browser state from a specific Stagehand instance.
|
|
1052
944
|
*/
|
|
@@ -1084,44 +976,15 @@ var StagehandBrowser = class _StagehandBrowser extends MastraBrowser {
|
|
|
1084
976
|
const state = await this.getBrowserState(threadId);
|
|
1085
977
|
return state?.activeTabIndex ?? 0;
|
|
1086
978
|
}
|
|
1087
|
-
/**
|
|
1088
|
-
* Update the browser state in the thread session.
|
|
1089
|
-
* Called on navigation, tab open/close to keep state fresh.
|
|
1090
|
-
*/
|
|
1091
|
-
updateSessionBrowserState(threadId) {
|
|
1092
|
-
try {
|
|
1093
|
-
const effectiveThreadId = threadId ?? this.getCurrentThread() ?? DEFAULT_THREAD_ID;
|
|
1094
|
-
const scope = this.threadManager.getScope();
|
|
1095
|
-
let stagehand = null;
|
|
1096
|
-
if (scope === "thread") {
|
|
1097
|
-
stagehand = this.threadManager.getStagehandForThread(effectiveThreadId);
|
|
1098
|
-
} else {
|
|
1099
|
-
stagehand = this.stagehand;
|
|
1100
|
-
}
|
|
1101
|
-
if (stagehand) {
|
|
1102
|
-
const state = this.getBrowserStateFromStagehand(stagehand);
|
|
1103
|
-
if (state) {
|
|
1104
|
-
this.threadManager.updateBrowserState(effectiveThreadId, state);
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
} catch {
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
979
|
// ---------------------------------------------------------------------------
|
|
1111
980
|
// Screencast (for Studio live view)
|
|
1112
981
|
// Uses Stagehand v3's native CDP access
|
|
1113
982
|
// ---------------------------------------------------------------------------
|
|
1114
|
-
/**
|
|
1115
|
-
* Get the stream key for a thread (or shared key for shared scope).
|
|
1116
|
-
*/
|
|
1117
|
-
getStreamKey(threadId) {
|
|
1118
|
-
return threadId || _StagehandBrowser.SHARED_STREAM_KEY;
|
|
1119
|
-
}
|
|
1120
983
|
async startScreencast(options) {
|
|
1121
984
|
const threadId = options?.threadId;
|
|
1122
985
|
const provider = {
|
|
1123
986
|
getCdpSession: async () => {
|
|
1124
|
-
const page = await this.getPageForThread(threadId
|
|
987
|
+
const page = await this.threadManager.getPageForThread(threadId);
|
|
1125
988
|
if (!page) {
|
|
1126
989
|
throw new Error("No page available for screencast");
|
|
1127
990
|
}
|
|
@@ -1155,7 +1018,7 @@ var StagehandBrowser = class _StagehandBrowser extends MastraBrowser {
|
|
|
1155
1018
|
* Uses CDP Target events since Stagehand doesn't expose page lifecycle events.
|
|
1156
1019
|
*/
|
|
1157
1020
|
async setupTabChangeDetection(threadId, stream) {
|
|
1158
|
-
const stagehand = await this.
|
|
1021
|
+
const stagehand = await this.getManagerForThread(threadId);
|
|
1159
1022
|
if (!stagehand?.context) return;
|
|
1160
1023
|
const connection = stagehand.context.conn;
|
|
1161
1024
|
if (!connection) {
|
|
@@ -1217,7 +1080,7 @@ var StagehandBrowser = class _StagehandBrowser extends MastraBrowser {
|
|
|
1217
1080
|
if (newPage && stagehand.context) {
|
|
1218
1081
|
stagehand.context.setActivePage(newPage);
|
|
1219
1082
|
}
|
|
1220
|
-
void this.
|
|
1083
|
+
void this.reconnectScreencastForThread(threadId, "manual tab tracked");
|
|
1221
1084
|
void setupPageNavigationListener();
|
|
1222
1085
|
} else {
|
|
1223
1086
|
this.logger.debug?.("Stagehand did not register the page (non-injectable URL)");
|
|
@@ -1300,48 +1163,6 @@ var StagehandBrowser = class _StagehandBrowser extends MastraBrowser {
|
|
|
1300
1163
|
this.logger.debug?.("Failed to set up tab change detection", error);
|
|
1301
1164
|
}
|
|
1302
1165
|
}
|
|
1303
|
-
/**
|
|
1304
|
-
* Reconnect the active screencast for a specific thread.
|
|
1305
|
-
*/
|
|
1306
|
-
async reconnectScreencastForThread(threadId, reason) {
|
|
1307
|
-
const streamKey = this.getStreamKey(threadId);
|
|
1308
|
-
const stream = this.activeScreencastStreams.get(streamKey);
|
|
1309
|
-
if (!stream || !stream.isActive()) {
|
|
1310
|
-
return;
|
|
1311
|
-
}
|
|
1312
|
-
if (!this.isBrowserRunning()) {
|
|
1313
|
-
this.logger.debug?.("Skipping screencast reconnect - browser not running");
|
|
1314
|
-
return;
|
|
1315
|
-
}
|
|
1316
|
-
const scope = this.getScope();
|
|
1317
|
-
if (scope === "thread" && threadId && !this.threadManager.getStagehandForThread(threadId)) {
|
|
1318
|
-
this.logger.debug?.(`Skipping screencast reconnect - no session for thread ${threadId}`);
|
|
1319
|
-
return;
|
|
1320
|
-
}
|
|
1321
|
-
this.logger.debug?.(`Reconnecting screencast: ${reason}`);
|
|
1322
|
-
try {
|
|
1323
|
-
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
1324
|
-
await stream.reconnect();
|
|
1325
|
-
const stagehand = await this.getStagehandForThread(threadId);
|
|
1326
|
-
const activePage = stagehand?.context?.activePage();
|
|
1327
|
-
if (activePage) {
|
|
1328
|
-
const url = activePage.url();
|
|
1329
|
-
if (url) {
|
|
1330
|
-
stream.emitUrl(url);
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
} catch (error) {
|
|
1334
|
-
this.logger.debug?.("Screencast reconnect failed", error);
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
/**
|
|
1338
|
-
* Reconnect the active screencast for the current thread.
|
|
1339
|
-
* Wrapper for reconnectScreencastForThread using getCurrentThread().
|
|
1340
|
-
*/
|
|
1341
|
-
async reconnectScreencast(reason) {
|
|
1342
|
-
const threadId = this.getCurrentThread();
|
|
1343
|
-
await this.reconnectScreencastForThread(threadId, reason);
|
|
1344
|
-
}
|
|
1345
1166
|
// NOTE: Manual tab switching in browser UI is not fully supported.
|
|
1346
1167
|
// Stagehand v3 does not track pages opened via browser UI (only pages created through its API).
|
|
1347
1168
|
// We've requested this feature from Browserbase - see Notion doc for details.
|
|
@@ -1350,7 +1171,7 @@ var StagehandBrowser = class _StagehandBrowser extends MastraBrowser {
|
|
|
1350
1171
|
// ---------------------------------------------------------------------------
|
|
1351
1172
|
async injectMouseEvent(event, threadId) {
|
|
1352
1173
|
const effectiveThreadId = threadId ?? this.getCurrentThread();
|
|
1353
|
-
const page = await this.getPageForThread(effectiveThreadId
|
|
1174
|
+
const page = await this.threadManager.getPageForThread(effectiveThreadId);
|
|
1354
1175
|
const cdpSession = this.getCdpSessionForPage(page);
|
|
1355
1176
|
if (!cdpSession) {
|
|
1356
1177
|
throw new Error("No CDP session available");
|
|
@@ -1376,7 +1197,7 @@ var StagehandBrowser = class _StagehandBrowser extends MastraBrowser {
|
|
|
1376
1197
|
}
|
|
1377
1198
|
async injectKeyboardEvent(event, threadId) {
|
|
1378
1199
|
const effectiveThreadId = threadId ?? this.getCurrentThread();
|
|
1379
|
-
const page = await this.getPageForThread(effectiveThreadId
|
|
1200
|
+
const page = await this.threadManager.getPageForThread(effectiveThreadId);
|
|
1380
1201
|
const cdpSession = this.getCdpSessionForPage(page);
|
|
1381
1202
|
if (!cdpSession) {
|
|
1382
1203
|
throw new Error("No CDP session available");
|
|
@@ -1392,6 +1213,6 @@ var StagehandBrowser = class _StagehandBrowser extends MastraBrowser {
|
|
|
1392
1213
|
}
|
|
1393
1214
|
};
|
|
1394
1215
|
|
|
1395
|
-
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 };
|
|
1396
1217
|
//# sourceMappingURL=index.js.map
|
|
1397
1218
|
//# sourceMappingURL=index.js.map
|