@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.cjs
CHANGED
|
@@ -1,77 +1,35 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var fs = require('fs');
|
|
3
4
|
var stagehand = require('@browserbasehq/stagehand');
|
|
4
5
|
var browser = require('@mastra/core/browser');
|
|
5
6
|
var tools = require('@mastra/core/tools');
|
|
6
7
|
var zod = require('zod');
|
|
8
|
+
var path = require('path');
|
|
7
9
|
|
|
8
10
|
// src/stagehand-browser.ts
|
|
9
11
|
var StagehandThreadManager = class extends browser.ThreadManager {
|
|
10
|
-
sharedStagehand = null;
|
|
11
12
|
sessions = /* @__PURE__ */ new Map();
|
|
12
13
|
createStagehand;
|
|
13
14
|
onBrowserCreated;
|
|
14
|
-
/** Map of thread ID to dedicated Stagehand instance (for 'thread' mode) */
|
|
15
|
-
threadStagehands = /* @__PURE__ */ new Map();
|
|
16
15
|
constructor(config) {
|
|
17
16
|
super(config);
|
|
18
17
|
this.createStagehand = config.createStagehand;
|
|
19
18
|
this.onBrowserCreated = config.onBrowserCreated;
|
|
20
19
|
}
|
|
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
20
|
/**
|
|
34
21
|
* Set the factory function for creating new Stagehand instances.
|
|
35
|
-
* Required for '
|
|
22
|
+
* Required for 'thread' scope mode.
|
|
36
23
|
*/
|
|
37
24
|
setCreateStagehand(factory) {
|
|
38
25
|
this.createStagehand = factory;
|
|
39
26
|
}
|
|
40
27
|
/**
|
|
41
|
-
* Get the
|
|
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.
|
|
28
|
+
* Get the page for a specific thread, creating session if needed.
|
|
71
29
|
*/
|
|
72
|
-
|
|
73
|
-
const stagehand = this.
|
|
74
|
-
return stagehand
|
|
30
|
+
async getPageForThread(threadId) {
|
|
31
|
+
const stagehand = await this.getManagerForThread(threadId);
|
|
32
|
+
return stagehand?.context?.activePage() ?? null;
|
|
75
33
|
}
|
|
76
34
|
/**
|
|
77
35
|
* Create a new session for a thread.
|
|
@@ -90,7 +48,7 @@ var StagehandThreadManager = class extends browser.ThreadManager {
|
|
|
90
48
|
this.logger?.debug?.(`Creating dedicated Stagehand instance for thread ${threadId}`);
|
|
91
49
|
const stagehand = await this.createStagehand();
|
|
92
50
|
session.stagehand = stagehand;
|
|
93
|
-
this.
|
|
51
|
+
this.threadManagers.set(threadId, stagehand);
|
|
94
52
|
if (savedState && savedState.tabs.length > 0) {
|
|
95
53
|
this.logger?.debug?.(`Restoring browser state for thread ${threadId}: ${savedState.tabs.length} tabs`);
|
|
96
54
|
await this.restoreBrowserState(stagehand, savedState);
|
|
@@ -129,18 +87,11 @@ var StagehandThreadManager = class extends browser.ThreadManager {
|
|
|
129
87
|
}
|
|
130
88
|
}
|
|
131
89
|
/**
|
|
132
|
-
*
|
|
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.
|
|
90
|
+
* Get the manager (Stagehand instance) for a specific session.
|
|
140
91
|
*/
|
|
141
92
|
getManagerForSession(session) {
|
|
142
93
|
if (this.scope === "thread" && session.stagehand) {
|
|
143
|
-
return session.stagehand
|
|
94
|
+
return session.stagehand;
|
|
144
95
|
}
|
|
145
96
|
return this.getSharedManager();
|
|
146
97
|
}
|
|
@@ -155,50 +106,14 @@ var StagehandThreadManager = class extends browser.ThreadManager {
|
|
|
155
106
|
} catch (error) {
|
|
156
107
|
this.logger?.warn?.(`Failed to close Stagehand for thread ${session.threadId}: ${error}`);
|
|
157
108
|
}
|
|
158
|
-
this.threadStagehands.delete(session.threadId);
|
|
159
109
|
}
|
|
160
110
|
}
|
|
161
111
|
/**
|
|
162
|
-
*
|
|
163
|
-
|
|
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
|
|
112
|
+
* Destroy all sessions (called during browser close).
|
|
113
|
+
* doDestroySession handles closing individual Stagehand instances.
|
|
194
114
|
*/
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (session?.browserState) {
|
|
198
|
-
this.savedBrowserStates.set(threadId, session.browserState);
|
|
199
|
-
}
|
|
200
|
-
this.threadStagehands.delete(threadId);
|
|
201
|
-
this.sessions.delete(threadId);
|
|
115
|
+
async destroyAllSessions() {
|
|
116
|
+
await super.destroyAllSessions();
|
|
202
117
|
}
|
|
203
118
|
};
|
|
204
119
|
var actInputSchema = zod.z.object({
|
|
@@ -367,31 +282,44 @@ function createStagehandTools(browser) {
|
|
|
367
282
|
[STAGEHAND_TOOLS.CLOSE]: createCloseTool(browser)
|
|
368
283
|
};
|
|
369
284
|
}
|
|
285
|
+
function patchProfileExitType(profilePath, logger) {
|
|
286
|
+
if (!profilePath) return;
|
|
287
|
+
const prefsPath = path.join(profilePath, "Default", "Preferences");
|
|
288
|
+
try {
|
|
289
|
+
if (!fs.existsSync(prefsPath)) return;
|
|
290
|
+
const prefs = JSON.parse(fs.readFileSync(prefsPath, "utf-8"));
|
|
291
|
+
if (prefs?.profile?.exit_type === "Normal") return;
|
|
292
|
+
prefs.profile = prefs.profile || {};
|
|
293
|
+
prefs.profile.exit_type = "Normal";
|
|
294
|
+
fs.writeFileSync(prefsPath, JSON.stringify(prefs, null, 2), "utf-8");
|
|
295
|
+
logger?.debug?.(`Patched exit_type to Normal in ${prefsPath}`);
|
|
296
|
+
} catch {
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function getStagehandChromePid(stagehand) {
|
|
300
|
+
try {
|
|
301
|
+
const state = stagehand.state;
|
|
302
|
+
if (state?.kind !== "LOCAL" || !state.chrome) return void 0;
|
|
303
|
+
const pid = state.chrome.process?.pid ?? state.chrome.pid;
|
|
304
|
+
return typeof pid === "number" && pid > 0 ? pid : void 0;
|
|
305
|
+
} catch {
|
|
306
|
+
return void 0;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
370
309
|
|
|
371
310
|
// src/stagehand-browser.ts
|
|
372
|
-
var StagehandBrowser = class
|
|
311
|
+
var StagehandBrowser = class extends browser.MastraBrowser {
|
|
373
312
|
id;
|
|
374
313
|
name = "StagehandBrowser";
|
|
375
314
|
provider = "browserbase/stagehand";
|
|
376
|
-
stagehand = null;
|
|
377
315
|
stagehandConfig;
|
|
378
|
-
/** Active screencast streams per thread (for reconnection on tab changes) */
|
|
379
|
-
activeScreencastStreams = /* @__PURE__ */ new Map();
|
|
380
316
|
/** Debounce timers per thread for tab change reconnection */
|
|
381
317
|
tabChangeDebounceTimers = /* @__PURE__ */ new Map();
|
|
382
|
-
/** Default key for shared scope */
|
|
383
|
-
static SHARED_STREAM_KEY = "__shared__";
|
|
384
318
|
constructor(config = {}) {
|
|
385
319
|
super(config);
|
|
386
320
|
this.id = `stagehand-${Date.now()}`;
|
|
387
321
|
this.stagehandConfig = config;
|
|
388
|
-
|
|
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
|
-
}
|
|
322
|
+
const effectiveScope = config.cdpUrl ? config.scope ?? "shared" : config.scope ?? "thread";
|
|
395
323
|
this.threadManager = new StagehandThreadManager({
|
|
396
324
|
scope: effectiveScope,
|
|
397
325
|
logger: this.logger,
|
|
@@ -401,19 +329,10 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
|
|
|
401
329
|
},
|
|
402
330
|
// When a new browser is created for a thread, set up close listener
|
|
403
331
|
onBrowserCreated: (stagehand, threadId) => {
|
|
404
|
-
this.
|
|
332
|
+
this.setupCloseListener(stagehand, () => this.handleThreadBrowserDisconnected(threadId), threadId);
|
|
405
333
|
}
|
|
406
334
|
});
|
|
407
335
|
}
|
|
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
336
|
/**
|
|
418
337
|
* Ensure browser is ready and thread session exists.
|
|
419
338
|
* For 'thread' scope, this creates a dedicated Stagehand instance for the thread.
|
|
@@ -424,7 +343,7 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
|
|
|
424
343
|
const scope = this.getScope();
|
|
425
344
|
const threadId = this.getCurrentThread();
|
|
426
345
|
if (scope === "thread" && threadId && threadId !== browser.DEFAULT_THREAD_ID) {
|
|
427
|
-
await this.
|
|
346
|
+
await this.getManagerForThread(threadId);
|
|
428
347
|
}
|
|
429
348
|
}
|
|
430
349
|
// ---------------------------------------------------------------------------
|
|
@@ -453,25 +372,34 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
|
|
|
453
372
|
stagehandOptions.projectId = config.projectId;
|
|
454
373
|
}
|
|
455
374
|
}
|
|
375
|
+
if (config.profile && !fs.existsSync(config.profile)) {
|
|
376
|
+
fs.mkdirSync(config.profile, { recursive: true });
|
|
377
|
+
}
|
|
456
378
|
if (config.cdpUrl && config.env !== "BROWSERBASE") {
|
|
457
379
|
const resolvedUrl = await this.resolveCdpUrl(config.cdpUrl);
|
|
458
380
|
const wsUrl = await this.resolveWebSocketUrl(resolvedUrl);
|
|
459
381
|
stagehandOptions.localBrowserLaunchOptions = {
|
|
460
382
|
cdpUrl: wsUrl,
|
|
461
383
|
headless: config.headless,
|
|
462
|
-
viewport: config.viewport
|
|
384
|
+
viewport: config.viewport,
|
|
385
|
+
userDataDir: config.profile,
|
|
386
|
+
executablePath: config.executablePath,
|
|
387
|
+
preserveUserDataDir: config.preserveUserDataDir
|
|
463
388
|
};
|
|
464
389
|
} else if (config.env !== "BROWSERBASE") {
|
|
465
390
|
stagehandOptions.localBrowserLaunchOptions = {
|
|
466
391
|
headless: config.headless,
|
|
467
|
-
viewport: config.viewport
|
|
392
|
+
viewport: config.viewport,
|
|
393
|
+
userDataDir: config.profile,
|
|
394
|
+
executablePath: config.executablePath,
|
|
395
|
+
preserveUserDataDir: config.preserveUserDataDir
|
|
468
396
|
};
|
|
469
397
|
}
|
|
470
398
|
return stagehandOptions;
|
|
471
399
|
}
|
|
472
400
|
/**
|
|
473
401
|
* Create a new Stagehand instance with the current config.
|
|
474
|
-
* Used by thread manager for '
|
|
402
|
+
* Used by thread manager for 'thread' scope.
|
|
475
403
|
*/
|
|
476
404
|
async createStagehandInstance() {
|
|
477
405
|
const stagehandOptions = await this.buildStagehandOptions();
|
|
@@ -485,104 +413,85 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
|
|
|
485
413
|
if (scope === "thread") {
|
|
486
414
|
return;
|
|
487
415
|
}
|
|
488
|
-
this.
|
|
489
|
-
this.threadManager.
|
|
490
|
-
this.setupCloseListener(this.
|
|
416
|
+
this.sharedManager = await this.createStagehandInstance();
|
|
417
|
+
this.threadManager.setSharedManager(this.sharedManager);
|
|
418
|
+
this.setupCloseListener(this.sharedManager, () => this.handleBrowserDisconnected());
|
|
491
419
|
}
|
|
492
420
|
/**
|
|
493
|
-
* Set up close event listener for a Stagehand instance.
|
|
421
|
+
* Set up close event listener for a shared Stagehand instance.
|
|
494
422
|
* Listens to both context and page close events for robust detection.
|
|
495
423
|
*/
|
|
496
|
-
setupCloseListener(stagehand) {
|
|
497
|
-
let disconnectHandled = false;
|
|
498
|
-
const handleDisconnect = () => {
|
|
499
|
-
if (disconnectHandled) return;
|
|
500
|
-
disconnectHandled = true;
|
|
501
|
-
this.handleBrowserDisconnected();
|
|
502
|
-
};
|
|
503
|
-
try {
|
|
504
|
-
const context = stagehand.context;
|
|
505
|
-
if (!context) return;
|
|
506
|
-
const contextWithEvents = context;
|
|
507
|
-
if (contextWithEvents?.on) {
|
|
508
|
-
contextWithEvents.on("close", handleDisconnect);
|
|
509
|
-
}
|
|
510
|
-
const pages = context.pages?.() ?? [];
|
|
511
|
-
for (const page of pages) {
|
|
512
|
-
const pageWithEvents = page;
|
|
513
|
-
if (pageWithEvents?.on) {
|
|
514
|
-
pageWithEvents.on("close", () => {
|
|
515
|
-
const remainingPages = context.pages?.() ?? [];
|
|
516
|
-
if (remainingPages.length === 0) {
|
|
517
|
-
handleDisconnect();
|
|
518
|
-
}
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
} catch {
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
424
|
/**
|
|
526
|
-
* Set up close
|
|
527
|
-
*
|
|
425
|
+
* Set up a CDP-based close listener for a Stagehand instance.
|
|
426
|
+
*
|
|
427
|
+
* Tracks page targets via CDP `Target.targetCreated` / `Target.targetDestroyed`.
|
|
428
|
+
* When all page targets are gone the `onDisconnect` callback fires. This is more
|
|
429
|
+
* reliable than Playwright's `context.close` / `page.close` events which don't
|
|
430
|
+
* fire when Chrome is killed externally (SIGTERM/SIGKILL).
|
|
528
431
|
*/
|
|
529
|
-
|
|
432
|
+
setupCloseListener(stagehand, onDisconnect, threadId) {
|
|
433
|
+
const chromePid = getStagehandChromePid(stagehand);
|
|
434
|
+
if (chromePid != null) {
|
|
435
|
+
if (threadId) {
|
|
436
|
+
this.threadBrowserPids.set(threadId, chromePid);
|
|
437
|
+
} else {
|
|
438
|
+
this.sharedBrowserPid = chromePid;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
530
441
|
let disconnectHandled = false;
|
|
531
442
|
const handleDisconnect = () => {
|
|
532
443
|
if (disconnectHandled) return;
|
|
533
444
|
disconnectHandled = true;
|
|
534
|
-
|
|
445
|
+
onDisconnect();
|
|
535
446
|
};
|
|
536
447
|
try {
|
|
537
448
|
const stagehandAny = stagehand;
|
|
538
449
|
const conn = stagehandAny.ctx?.conn;
|
|
539
|
-
if (!conn?.on)
|
|
540
|
-
return;
|
|
541
|
-
}
|
|
450
|
+
if (!conn?.on) return;
|
|
542
451
|
const pageTargets = /* @__PURE__ */ new Set();
|
|
543
452
|
const context = stagehand.context;
|
|
544
453
|
if (context) {
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
const targetId = pageAny._targetId ?? pageAny.targetId;
|
|
549
|
-
if (targetId) {
|
|
550
|
-
pageTargets.add(targetId);
|
|
551
|
-
}
|
|
454
|
+
for (const page of context.pages?.() ?? []) {
|
|
455
|
+
const targetId = page._targetId ?? page.targetId;
|
|
456
|
+
if (targetId) pageTargets.add(targetId);
|
|
552
457
|
}
|
|
553
458
|
}
|
|
554
459
|
conn.on("Target.targetCreated", (params) => {
|
|
555
|
-
if (params.targetInfo.type === "page")
|
|
556
|
-
pageTargets.add(params.targetInfo.targetId);
|
|
557
|
-
}
|
|
460
|
+
if (params.targetInfo.type === "page") pageTargets.add(params.targetInfo.targetId);
|
|
558
461
|
});
|
|
559
462
|
conn.on("Target.targetDestroyed", (params) => {
|
|
560
463
|
if (pageTargets.has(params.targetId)) {
|
|
561
464
|
pageTargets.delete(params.targetId);
|
|
562
|
-
if (pageTargets.size === 0)
|
|
563
|
-
handleDisconnect();
|
|
564
|
-
}
|
|
465
|
+
if (pageTargets.size === 0) handleDisconnect();
|
|
565
466
|
}
|
|
566
467
|
});
|
|
567
468
|
} catch {
|
|
568
469
|
}
|
|
569
470
|
}
|
|
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
471
|
async doClose() {
|
|
580
|
-
await this.threadManager.
|
|
581
|
-
if (this.
|
|
582
|
-
await this.
|
|
583
|
-
this.
|
|
472
|
+
await this.threadManager.destroyAllSessions();
|
|
473
|
+
if (this.sharedManager) {
|
|
474
|
+
await this.sharedManager.close();
|
|
475
|
+
this.sharedManager = null;
|
|
584
476
|
}
|
|
585
477
|
this.setCurrentThread(void 0);
|
|
478
|
+
this.patchExitType();
|
|
479
|
+
}
|
|
480
|
+
handleBrowserDisconnected() {
|
|
481
|
+
super.handleBrowserDisconnected();
|
|
482
|
+
this.patchExitType();
|
|
483
|
+
}
|
|
484
|
+
handleThreadBrowserDisconnected(threadId) {
|
|
485
|
+
super.handleThreadBrowserDisconnected(threadId);
|
|
486
|
+
this.patchExitType();
|
|
487
|
+
}
|
|
488
|
+
async closeThreadSession(threadId) {
|
|
489
|
+
await super.closeThreadSession(threadId);
|
|
490
|
+
this.patchExitType();
|
|
491
|
+
}
|
|
492
|
+
patchExitType() {
|
|
493
|
+
if (!this.config.profile) return;
|
|
494
|
+
patchProfileExitType(this.config.profile, this.logger);
|
|
586
495
|
}
|
|
587
496
|
/**
|
|
588
497
|
* Check if the browser is still alive by verifying the context and pages exist.
|
|
@@ -591,13 +500,13 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
|
|
|
591
500
|
async checkBrowserAlive() {
|
|
592
501
|
const scope = this.getScope();
|
|
593
502
|
if (scope === "thread") {
|
|
594
|
-
return this.threadManager.
|
|
503
|
+
return this.threadManager.hasActiveThreadManagers();
|
|
595
504
|
}
|
|
596
|
-
if (!this.
|
|
505
|
+
if (!this.sharedManager) {
|
|
597
506
|
return false;
|
|
598
507
|
}
|
|
599
508
|
try {
|
|
600
|
-
const context = this.
|
|
509
|
+
const context = this.sharedManager.context;
|
|
601
510
|
if (!context) {
|
|
602
511
|
return false;
|
|
603
512
|
}
|
|
@@ -607,7 +516,7 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
|
|
|
607
516
|
}
|
|
608
517
|
const url = pages[0]?.url();
|
|
609
518
|
if (url && url !== "about:blank") {
|
|
610
|
-
const state = this.getBrowserStateFromStagehand(this.
|
|
519
|
+
const state = this.getBrowserStateFromStagehand(this.sharedManager);
|
|
611
520
|
if (state) {
|
|
612
521
|
this.lastBrowserState = state;
|
|
613
522
|
}
|
|
@@ -621,24 +530,6 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
|
|
|
621
530
|
return false;
|
|
622
531
|
}
|
|
623
532
|
}
|
|
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
533
|
/**
|
|
643
534
|
* Create an error response from an exception.
|
|
644
535
|
* Extends base class to add Stagehand-specific error handling.
|
|
@@ -659,21 +550,21 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
|
|
|
659
550
|
// ---------------------------------------------------------------------------
|
|
660
551
|
/**
|
|
661
552
|
* Get the Stagehand instance for a thread, creating it if needed.
|
|
662
|
-
* For '
|
|
663
|
-
* For '
|
|
553
|
+
* For 'thread' scope, this creates a dedicated Stagehand instance.
|
|
554
|
+
* For 'shared' scope, returns the shared instance.
|
|
664
555
|
*/
|
|
665
|
-
async
|
|
556
|
+
async getManagerForThread(threadId) {
|
|
666
557
|
const scope = this.getScope();
|
|
667
558
|
if (scope === "shared") {
|
|
668
|
-
return this.
|
|
559
|
+
return this.sharedManager;
|
|
669
560
|
}
|
|
670
561
|
if (!threadId || threadId === browser.DEFAULT_THREAD_ID) {
|
|
671
|
-
return this.
|
|
562
|
+
return this.sharedManager;
|
|
672
563
|
}
|
|
673
|
-
let stagehand = this.threadManager.
|
|
564
|
+
let stagehand = this.threadManager.getExistingManagerForThread(threadId);
|
|
674
565
|
if (!stagehand) {
|
|
675
566
|
await this.threadManager.getManagerForThread(threadId);
|
|
676
|
-
stagehand = this.threadManager.
|
|
567
|
+
stagehand = this.threadManager.getExistingManagerForThread(threadId);
|
|
677
568
|
}
|
|
678
569
|
return stagehand ?? null;
|
|
679
570
|
}
|
|
@@ -685,14 +576,14 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
|
|
|
685
576
|
*/
|
|
686
577
|
requireStagehand(explicitThreadId) {
|
|
687
578
|
const threadId = explicitThreadId ?? this.getCurrentThread();
|
|
688
|
-
const stagehand = this.threadManager.
|
|
579
|
+
const stagehand = this.threadManager.getExistingManagerForThread(threadId) ?? this.sharedManager;
|
|
689
580
|
if (!stagehand) {
|
|
690
581
|
throw new Error("Browser not launched");
|
|
691
582
|
}
|
|
692
583
|
return stagehand;
|
|
693
584
|
}
|
|
694
585
|
/**
|
|
695
|
-
* Get the current page from Stagehand v3, respecting thread
|
|
586
|
+
* Get the current page from Stagehand v3, respecting thread scope.
|
|
696
587
|
* @param explicitThreadId - Optional thread ID to use instead of getCurrentThread()
|
|
697
588
|
* Use this to avoid race conditions in concurrent tool calls.
|
|
698
589
|
*/
|
|
@@ -700,15 +591,15 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
|
|
|
700
591
|
const scope = this.getScope();
|
|
701
592
|
const threadId = explicitThreadId ?? this.getCurrentThread();
|
|
702
593
|
if (scope === "thread" && threadId && threadId !== browser.DEFAULT_THREAD_ID) {
|
|
703
|
-
const stagehand = this.threadManager.
|
|
594
|
+
const stagehand = this.threadManager.getExistingManagerForThread(threadId);
|
|
704
595
|
if (stagehand?.context) {
|
|
705
596
|
return stagehand.context.activePage();
|
|
706
597
|
}
|
|
707
598
|
return null;
|
|
708
599
|
}
|
|
709
|
-
if (!this.
|
|
600
|
+
if (!this.sharedManager) return null;
|
|
710
601
|
try {
|
|
711
|
-
const context = this.
|
|
602
|
+
const context = this.sharedManager.context;
|
|
712
603
|
if (context) {
|
|
713
604
|
const activePage = context.activePage();
|
|
714
605
|
if (activePage) {
|
|
@@ -724,18 +615,10 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
|
|
|
724
615
|
return null;
|
|
725
616
|
}
|
|
726
617
|
/**
|
|
727
|
-
* Get the page for a
|
|
618
|
+
* Get the active page for a thread (implements abstract method from base class).
|
|
728
619
|
*/
|
|
729
|
-
async
|
|
730
|
-
|
|
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;
|
|
620
|
+
async getActivePage(threadId) {
|
|
621
|
+
return this.getPage(threadId);
|
|
739
622
|
}
|
|
740
623
|
/**
|
|
741
624
|
* Get a CDP session for a specific page.
|
|
@@ -986,7 +869,7 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
|
|
|
986
869
|
const effectiveThreadId = threadId ?? this.getCurrentThread();
|
|
987
870
|
const scope = this.threadManager.getScope();
|
|
988
871
|
if (scope === "thread" && effectiveThreadId) {
|
|
989
|
-
const stagehand = this.threadManager.
|
|
872
|
+
const stagehand = this.threadManager.getExistingManagerForThread(effectiveThreadId);
|
|
990
873
|
if (!stagehand?.context) {
|
|
991
874
|
return null;
|
|
992
875
|
}
|
|
@@ -1005,7 +888,7 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
|
|
|
1005
888
|
try {
|
|
1006
889
|
const url = page.url();
|
|
1007
890
|
if (url && url !== "about:blank") {
|
|
1008
|
-
const state = this.getBrowserStateFromStagehand(this.
|
|
891
|
+
const state = this.getBrowserStateFromStagehand(this.sharedManager);
|
|
1009
892
|
if (state) {
|
|
1010
893
|
this.lastBrowserState = state;
|
|
1011
894
|
}
|
|
@@ -1040,15 +923,24 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
|
|
|
1040
923
|
const scope = this.threadManager.getScope();
|
|
1041
924
|
const effectiveThreadId = threadId ?? this.getCurrentThread();
|
|
1042
925
|
if (scope === "thread" && effectiveThreadId) {
|
|
1043
|
-
const stagehand = this.threadManager.
|
|
926
|
+
const stagehand = this.threadManager.getExistingManagerForThread(effectiveThreadId);
|
|
1044
927
|
if (!stagehand) return null;
|
|
1045
928
|
return this.getBrowserStateFromStagehand(stagehand);
|
|
1046
929
|
}
|
|
1047
|
-
return this.getBrowserStateFromStagehand(this.
|
|
930
|
+
return this.getBrowserStateFromStagehand(this.sharedManager);
|
|
1048
931
|
} catch {
|
|
1049
932
|
return null;
|
|
1050
933
|
}
|
|
1051
934
|
}
|
|
935
|
+
/**
|
|
936
|
+
* Get browser state for a thread (implements abstract method from base class).
|
|
937
|
+
* Sync version that uses existing manager lookup without creating sessions.
|
|
938
|
+
*/
|
|
939
|
+
getBrowserStateForThread(threadId) {
|
|
940
|
+
const effectiveThreadId = threadId ?? this.getCurrentThread() ?? browser.DEFAULT_THREAD_ID;
|
|
941
|
+
const stagehand = this.threadManager.getExistingManagerForThread(effectiveThreadId);
|
|
942
|
+
return this.getBrowserStateFromStagehand(stagehand);
|
|
943
|
+
}
|
|
1052
944
|
/**
|
|
1053
945
|
* Get browser state from a specific Stagehand instance.
|
|
1054
946
|
*/
|
|
@@ -1086,44 +978,15 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
|
|
|
1086
978
|
const state = await this.getBrowserState(threadId);
|
|
1087
979
|
return state?.activeTabIndex ?? 0;
|
|
1088
980
|
}
|
|
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
981
|
// ---------------------------------------------------------------------------
|
|
1113
982
|
// Screencast (for Studio live view)
|
|
1114
983
|
// Uses Stagehand v3's native CDP access
|
|
1115
984
|
// ---------------------------------------------------------------------------
|
|
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
985
|
async startScreencast(options) {
|
|
1123
986
|
const threadId = options?.threadId;
|
|
1124
987
|
const provider = {
|
|
1125
988
|
getCdpSession: async () => {
|
|
1126
|
-
const page = await this.getPageForThread(threadId
|
|
989
|
+
const page = await this.threadManager.getPageForThread(threadId);
|
|
1127
990
|
if (!page) {
|
|
1128
991
|
throw new Error("No page available for screencast");
|
|
1129
992
|
}
|
|
@@ -1157,7 +1020,7 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
|
|
|
1157
1020
|
* Uses CDP Target events since Stagehand doesn't expose page lifecycle events.
|
|
1158
1021
|
*/
|
|
1159
1022
|
async setupTabChangeDetection(threadId, stream) {
|
|
1160
|
-
const stagehand = await this.
|
|
1023
|
+
const stagehand = await this.getManagerForThread(threadId);
|
|
1161
1024
|
if (!stagehand?.context) return;
|
|
1162
1025
|
const connection = stagehand.context.conn;
|
|
1163
1026
|
if (!connection) {
|
|
@@ -1219,7 +1082,7 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
|
|
|
1219
1082
|
if (newPage && stagehand.context) {
|
|
1220
1083
|
stagehand.context.setActivePage(newPage);
|
|
1221
1084
|
}
|
|
1222
|
-
void this.
|
|
1085
|
+
void this.reconnectScreencastForThread(threadId, "manual tab tracked");
|
|
1223
1086
|
void setupPageNavigationListener();
|
|
1224
1087
|
} else {
|
|
1225
1088
|
this.logger.debug?.("Stagehand did not register the page (non-injectable URL)");
|
|
@@ -1302,48 +1165,6 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
|
|
|
1302
1165
|
this.logger.debug?.("Failed to set up tab change detection", error);
|
|
1303
1166
|
}
|
|
1304
1167
|
}
|
|
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
1168
|
// NOTE: Manual tab switching in browser UI is not fully supported.
|
|
1348
1169
|
// Stagehand v3 does not track pages opened via browser UI (only pages created through its API).
|
|
1349
1170
|
// We've requested this feature from Browserbase - see Notion doc for details.
|
|
@@ -1352,7 +1173,7 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
|
|
|
1352
1173
|
// ---------------------------------------------------------------------------
|
|
1353
1174
|
async injectMouseEvent(event, threadId) {
|
|
1354
1175
|
const effectiveThreadId = threadId ?? this.getCurrentThread();
|
|
1355
|
-
const page = await this.getPageForThread(effectiveThreadId
|
|
1176
|
+
const page = await this.threadManager.getPageForThread(effectiveThreadId);
|
|
1356
1177
|
const cdpSession = this.getCdpSessionForPage(page);
|
|
1357
1178
|
if (!cdpSession) {
|
|
1358
1179
|
throw new Error("No CDP session available");
|
|
@@ -1378,7 +1199,7 @@ var StagehandBrowser = class _StagehandBrowser extends browser.MastraBrowser {
|
|
|
1378
1199
|
}
|
|
1379
1200
|
async injectKeyboardEvent(event, threadId) {
|
|
1380
1201
|
const effectiveThreadId = threadId ?? this.getCurrentThread();
|
|
1381
|
-
const page = await this.getPageForThread(effectiveThreadId
|
|
1202
|
+
const page = await this.threadManager.getPageForThread(effectiveThreadId);
|
|
1382
1203
|
const cdpSession = this.getCdpSessionForPage(page);
|
|
1383
1204
|
if (!cdpSession) {
|
|
1384
1205
|
throw new Error("No CDP session available");
|
|
@@ -1400,6 +1221,7 @@ exports.actInputSchema = actInputSchema;
|
|
|
1400
1221
|
exports.closeInputSchema = closeInputSchema;
|
|
1401
1222
|
exports.createStagehandTools = createStagehandTools;
|
|
1402
1223
|
exports.extractInputSchema = extractInputSchema;
|
|
1224
|
+
exports.getStagehandChromePid = getStagehandChromePid;
|
|
1403
1225
|
exports.navigateInputSchema = navigateInputSchema;
|
|
1404
1226
|
exports.observeInputSchema = observeInputSchema;
|
|
1405
1227
|
exports.stagehandSchemas = stagehandSchemas;
|