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