@roxybrowser/playwright 2.0.2-beta.1 → 2.0.2-beta.12
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/README.md +30 -7
- package/dist/bin/roxybrowser-mcp.js +3 -0
- package/dist/bin/roxybrowser-mcp.js.map +1 -1
- package/dist/browser.d.ts +33 -5
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +134 -15
- package/dist/browser.js.map +1 -1
- package/dist/browserType.d.ts +23 -1
- package/dist/browserType.d.ts.map +1 -1
- package/dist/browserType.js +33 -5
- package/dist/browserType.js.map +1 -1
- package/dist/human/bubbleCursor.d.ts +2 -0
- package/dist/human/bubbleCursor.d.ts.map +1 -0
- package/dist/human/bubbleCursor.js +126 -0
- package/dist/human/bubbleCursor.js.map +1 -0
- package/dist/human/controller.d.ts +1 -0
- package/dist/human/controller.d.ts.map +1 -1
- package/dist/human/controller.js +13 -12
- package/dist/human/controller.js.map +1 -1
- package/dist/human/profile.d.ts.map +1 -1
- package/dist/human/profile.js +0 -4
- package/dist/human/profile.js.map +1 -1
- package/dist/human/types.d.ts +0 -1
- package/dist/human/types.d.ts.map +1 -1
- package/dist/locator.js +1 -1
- package/dist/locator.js.map +1 -1
- package/dist/mcp/backend/connect.d.ts +1 -0
- package/dist/mcp/backend/connect.d.ts.map +1 -1
- package/dist/mcp/backend/connect.js +4 -2
- package/dist/mcp/backend/connect.js.map +1 -1
- package/dist/mcp/backend/context.d.ts +3 -0
- package/dist/mcp/backend/context.d.ts.map +1 -1
- package/dist/mcp/backend/context.js +9 -1
- package/dist/mcp/backend/context.js.map +1 -1
- package/dist/mcp/backend/keyboard.d.ts +28 -0
- package/dist/mcp/backend/keyboard.d.ts.map +1 -1
- package/dist/mcp/backend/keyboard.js +9 -3
- package/dist/mcp/backend/keyboard.js.map +1 -1
- package/dist/mcp/backend/network.d.ts.map +1 -1
- package/dist/mcp/backend/network.js +30 -4
- package/dist/mcp/backend/network.js.map +1 -1
- package/dist/mcp/backend/response.d.ts +2 -0
- package/dist/mcp/backend/response.d.ts.map +1 -1
- package/dist/mcp/backend/response.js +53 -7
- package/dist/mcp/backend/response.js.map +1 -1
- package/dist/mcp/connectedBrowser.d.ts.map +1 -1
- package/dist/mcp/connectedBrowser.js +492 -43
- package/dist/mcp/connectedBrowser.js.map +1 -1
- package/dist/mcp/output.d.ts +5 -0
- package/dist/mcp/output.d.ts.map +1 -1
- package/dist/mcp/output.js +17 -1
- package/dist/mcp/output.js.map +1 -1
- package/dist/mcp/runtime.d.ts +10 -1
- package/dist/mcp/runtime.d.ts.map +1 -1
- package/dist/mcp/runtime.js +82 -13
- package/dist/mcp/runtime.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +3 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools/mouse.d.ts.map +1 -1
- package/dist/mcp/tools/mouse.js +5 -2
- package/dist/mcp/tools/mouse.js.map +1 -1
- package/dist/mcp/transports/inMemory.d.ts.map +1 -1
- package/dist/mcp/transports/inMemory.js +1 -0
- package/dist/mcp/transports/inMemory.js.map +1 -1
- package/dist/mcp/types.d.ts +12 -1
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/page.d.ts +2 -0
- package/dist/page.d.ts.map +1 -1
- package/dist/page.js +12 -0
- package/dist/page.js.map +1 -1
- package/dist/protocol/adapter.d.ts +1 -0
- package/dist/protocol/adapter.d.ts.map +1 -1
- package/dist/protocol/bidi/backend.d.ts +1 -1
- package/dist/protocol/bidi/backend.d.ts.map +1 -1
- package/dist/protocol/bidi/backend.js +6 -2
- package/dist/protocol/bidi/backend.js.map +1 -1
- package/dist/protocol/cdp/backend.d.ts.map +1 -1
- package/dist/protocol/cdp/backend.js +122 -8
- package/dist/protocol/cdp/backend.js.map +1 -1
- package/dist/roxybrowser.bundle.js +560 -68
- package/dist/roxybrowser.bundle.js.map +1 -1
- package/dist/types/api.d.ts +23 -4
- package/dist/types/api.d.ts.map +1 -1
- package/dist/types/options.d.ts +0 -1
- package/dist/types/options.d.ts.map +1 -1
- package/package.json +2 -1
|
@@ -5124,6 +5124,7 @@ var RoxyPage = class RoxyPage {
|
|
|
5124
5124
|
closed = false;
|
|
5125
5125
|
closePromise = null;
|
|
5126
5126
|
closeReason;
|
|
5127
|
+
ownedContext;
|
|
5127
5128
|
defaultTimeoutMs = DEFAULT_EVENT_TIMEOUT_MS;
|
|
5128
5129
|
defaultNavigationTimeoutMs = DEFAULT_EVENT_TIMEOUT_MS;
|
|
5129
5130
|
currentViewportSize = null;
|
|
@@ -6111,6 +6112,10 @@ waiting for navigation${navigationTargetDescription} until "${waitUntil}"\n=====
|
|
|
6111
6112
|
}
|
|
6112
6113
|
this.closeReason = options.reason;
|
|
6113
6114
|
this.closePromise = (async () => {
|
|
6115
|
+
if (this.ownedContext) {
|
|
6116
|
+
await this.ownedContext.close().catch(() => {});
|
|
6117
|
+
return;
|
|
6118
|
+
}
|
|
6114
6119
|
await this.dismissActiveDialogsForClose();
|
|
6115
6120
|
await this.finalizeVideoRecording();
|
|
6116
6121
|
await this.adapter.close(options).catch(() => {});
|
|
@@ -6122,6 +6127,9 @@ waiting for navigation${navigationTargetDescription} until "${waitUntil}"\n=====
|
|
|
6122
6127
|
this.closePromise = null;
|
|
6123
6128
|
}
|
|
6124
6129
|
}
|
|
6130
|
+
setOwnedContext(context) {
|
|
6131
|
+
this.ownedContext = context;
|
|
6132
|
+
}
|
|
6125
6133
|
defaultTimeout() {
|
|
6126
6134
|
return this.defaultTimeoutMs;
|
|
6127
6135
|
}
|
|
@@ -10650,12 +10658,74 @@ var RoxyBrowser = class {
|
|
|
10650
10658
|
session;
|
|
10651
10659
|
adapter;
|
|
10652
10660
|
humanDefaults;
|
|
10653
|
-
|
|
10654
|
-
|
|
10661
|
+
_browserName;
|
|
10662
|
+
_browserType;
|
|
10663
|
+
_version;
|
|
10664
|
+
_contexts = [];
|
|
10665
|
+
_listeners = /* @__PURE__ */ new Map();
|
|
10666
|
+
_connected = true;
|
|
10667
|
+
constructor(session, adapter, humanDefaults, _browserName, _browserType, _version) {
|
|
10655
10668
|
this.session = session;
|
|
10656
10669
|
this.adapter = adapter;
|
|
10657
10670
|
this.humanDefaults = humanDefaults;
|
|
10658
|
-
this.
|
|
10671
|
+
this._browserName = _browserName;
|
|
10672
|
+
this._browserType = _browserType;
|
|
10673
|
+
this._version = _version;
|
|
10674
|
+
}
|
|
10675
|
+
on(event, listener) {
|
|
10676
|
+
return this._addListenerInternal(event, listener);
|
|
10677
|
+
}
|
|
10678
|
+
once(event, listener) {
|
|
10679
|
+
const wrapped = (payload) => {
|
|
10680
|
+
this._removeListenerInternal(event, listener);
|
|
10681
|
+
listener(payload);
|
|
10682
|
+
};
|
|
10683
|
+
this._ensureListenerSet(event).add({
|
|
10684
|
+
original: listener,
|
|
10685
|
+
wrapped
|
|
10686
|
+
});
|
|
10687
|
+
return this;
|
|
10688
|
+
}
|
|
10689
|
+
addListener(event, listener) {
|
|
10690
|
+
return this._addListenerInternal(event, listener);
|
|
10691
|
+
}
|
|
10692
|
+
removeListener(event, listener) {
|
|
10693
|
+
return this._removeListenerInternal(event, listener);
|
|
10694
|
+
}
|
|
10695
|
+
off(event, listener) {
|
|
10696
|
+
return this._removeListenerInternal(event, listener);
|
|
10697
|
+
}
|
|
10698
|
+
prependListener(event, listener) {
|
|
10699
|
+
const entry = {
|
|
10700
|
+
original: listener,
|
|
10701
|
+
wrapped: listener
|
|
10702
|
+
};
|
|
10703
|
+
const existing = this._listeners.get(event);
|
|
10704
|
+
if (!existing) {
|
|
10705
|
+
const s = new Set([entry]);
|
|
10706
|
+
this._listeners.set(event, s);
|
|
10707
|
+
} else {
|
|
10708
|
+
const s = new Set([entry, ...Array.from(existing)]);
|
|
10709
|
+
this._listeners.set(event, s);
|
|
10710
|
+
}
|
|
10711
|
+
return this;
|
|
10712
|
+
}
|
|
10713
|
+
removeAllListeners(type) {
|
|
10714
|
+
if (type === void 0) this._listeners.clear();
|
|
10715
|
+
else this._listeners.delete(type);
|
|
10716
|
+
return this;
|
|
10717
|
+
}
|
|
10718
|
+
browserType() {
|
|
10719
|
+
return this._browserType;
|
|
10720
|
+
}
|
|
10721
|
+
contexts() {
|
|
10722
|
+
return [...this._contexts];
|
|
10723
|
+
}
|
|
10724
|
+
isConnected() {
|
|
10725
|
+
return this._connected;
|
|
10726
|
+
}
|
|
10727
|
+
version() {
|
|
10728
|
+
return this._version;
|
|
10659
10729
|
}
|
|
10660
10730
|
async newContext(options = {}) {
|
|
10661
10731
|
const normalizedOptions = {
|
|
@@ -10666,26 +10736,66 @@ var RoxyBrowser = class {
|
|
|
10666
10736
|
...options.recordVideo.dir ? { dir: resolve(options.recordVideo.dir) } : {}
|
|
10667
10737
|
} } : {}
|
|
10668
10738
|
};
|
|
10669
|
-
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
10739
|
+
const context = new RoxyBrowserContext(await this.session.newContext(normalizedOptions), resolveHumanizationOptions(normalizedOptions.human, this.humanDefaults), normalizedOptions, this._browserName);
|
|
10740
|
+
this._contexts.push(context);
|
|
10741
|
+
context.on("close", () => {
|
|
10742
|
+
const index = this._contexts.indexOf(context);
|
|
10743
|
+
if (index !== -1) this._contexts.splice(index, 1);
|
|
10744
|
+
});
|
|
10745
|
+
this._emit("context", context);
|
|
10746
|
+
return context;
|
|
10747
|
+
}
|
|
10748
|
+
async newPage(options) {
|
|
10749
|
+
const context = await this.newContext(options);
|
|
10750
|
+
const page = await context.newPage();
|
|
10751
|
+
const roxyPage = page;
|
|
10752
|
+
if (typeof roxyPage.setOwnedContext === "function") roxyPage.setOwnedContext(context);
|
|
10753
|
+
else page.once("close", () => context.close().catch(() => {}));
|
|
10754
|
+
return page;
|
|
10673
10755
|
}
|
|
10674
|
-
async close() {
|
|
10756
|
+
async close(options) {
|
|
10757
|
+
if (!this._connected) return;
|
|
10758
|
+
this._connected = false;
|
|
10675
10759
|
try {
|
|
10676
10760
|
await withCloseTimeout$1(this.session.close(), BROWSER_SESSION_CLOSE_TIMEOUT_MS);
|
|
10677
10761
|
} finally {
|
|
10678
10762
|
await this.adapter.close();
|
|
10763
|
+
this._emit("disconnected", this);
|
|
10679
10764
|
}
|
|
10680
10765
|
}
|
|
10766
|
+
_addListenerInternal(event, listener) {
|
|
10767
|
+
this._ensureListenerSet(event).add({
|
|
10768
|
+
original: listener,
|
|
10769
|
+
wrapped: listener
|
|
10770
|
+
});
|
|
10771
|
+
return this;
|
|
10772
|
+
}
|
|
10773
|
+
_removeListenerInternal(event, listener) {
|
|
10774
|
+
const entries = this._listeners.get(event);
|
|
10775
|
+
if (!entries) return this;
|
|
10776
|
+
for (const entry of Array.from(entries)) if (entry.original === listener) entries.delete(entry);
|
|
10777
|
+
if (entries.size === 0) this._listeners.delete(event);
|
|
10778
|
+
return this;
|
|
10779
|
+
}
|
|
10780
|
+
_ensureListenerSet(event) {
|
|
10781
|
+
const existing = this._listeners.get(event);
|
|
10782
|
+
if (existing) return existing;
|
|
10783
|
+
const created = /* @__PURE__ */ new Set();
|
|
10784
|
+
this._listeners.set(event, created);
|
|
10785
|
+
return created;
|
|
10786
|
+
}
|
|
10787
|
+
_emit(event, payload) {
|
|
10788
|
+
const entries = this._listeners.get(event);
|
|
10789
|
+
if (!entries?.size) return false;
|
|
10790
|
+
for (const entry of Array.from(entries)) entry.wrapped(payload);
|
|
10791
|
+
return true;
|
|
10792
|
+
}
|
|
10681
10793
|
};
|
|
10682
10794
|
async function withCloseTimeout$1(promise, timeoutMs) {
|
|
10683
10795
|
let timer;
|
|
10684
10796
|
try {
|
|
10685
10797
|
return await Promise.race([promise, new Promise((_, reject) => {
|
|
10686
|
-
timer = setTimeout(() => {
|
|
10687
|
-
reject(/* @__PURE__ */ new Error(`Timed out closing browser session after ${timeoutMs}ms.`));
|
|
10688
|
-
}, timeoutMs);
|
|
10798
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Timed out closing browser session after ${timeoutMs}ms.`)), timeoutMs);
|
|
10689
10799
|
})]);
|
|
10690
10800
|
} finally {
|
|
10691
10801
|
if (timer) clearTimeout(timer);
|
|
@@ -16005,7 +16115,7 @@ var BidiPageAdapter = class BidiPageAdapter {
|
|
|
16005
16115
|
resultOwnership: "none"
|
|
16006
16116
|
}));
|
|
16007
16117
|
if (response.type === "exception") throw new Error(response.exceptionDetails.text || "BiDi evaluation failed.");
|
|
16008
|
-
return parseSerializedEvaluationResult(extractBiDiValue(response.result));
|
|
16118
|
+
return parseSerializedEvaluationResult(extractBiDiValue$1(response.result));
|
|
16009
16119
|
}
|
|
16010
16120
|
async evaluateFunction(expression, arg) {
|
|
16011
16121
|
const serializedArg = arg === void 0 ? "" : serializeForEvaluation$1(arg);
|
|
@@ -17249,11 +17359,11 @@ var BidiElementHandleAdapter = class BidiElementHandleAdapter {
|
|
|
17249
17359
|
return this.page.selectOptionReference(this.reference(), values, options);
|
|
17250
17360
|
}
|
|
17251
17361
|
};
|
|
17252
|
-
function extractBiDiValue(value) {
|
|
17253
|
-
if (value.type === "array" && Array.isArray(value.value)) return value.value.map((entry) => extractBiDiValue(entry));
|
|
17362
|
+
function extractBiDiValue$1(value) {
|
|
17363
|
+
if (value.type === "array" && Array.isArray(value.value)) return value.value.map((entry) => extractBiDiValue$1(entry));
|
|
17254
17364
|
if (value.type === "object" && Array.isArray(value.value)) {
|
|
17255
17365
|
const obj = {};
|
|
17256
|
-
for (const [key, val] of value.value) obj[key] = extractBiDiValue(val);
|
|
17366
|
+
for (const [key, val] of value.value) obj[key] = extractBiDiValue$1(val);
|
|
17257
17367
|
return obj;
|
|
17258
17368
|
}
|
|
17259
17369
|
return value.value;
|
|
@@ -17645,9 +17755,9 @@ function buildFirefoxLaunchArgs(options, userDataDir, port) {
|
|
|
17645
17755
|
...options.args ?? []
|
|
17646
17756
|
];
|
|
17647
17757
|
}
|
|
17648
|
-
function resolveFirefoxExecutableCandidates(options, platform = currentPlatform$1(), fileExistsFn = fileExists$1) {
|
|
17758
|
+
function resolveFirefoxExecutableCandidates(options, platform = currentPlatform$1(), playwrightFirefoxExecutablePath, fileExistsFn = fileExists$1) {
|
|
17649
17759
|
if (options.executablePath) return [options.executablePath];
|
|
17650
|
-
return filterExistingFirefoxExecutableCandidates(defaultFirefoxExecutableCandidates(platform), platform, fileExistsFn);
|
|
17760
|
+
return filterExistingFirefoxExecutableCandidates([...playwrightFirefoxExecutablePath ? [playwrightFirefoxExecutablePath] : [], ...defaultFirefoxExecutableCandidates(platform)], platform, fileExistsFn);
|
|
17651
17761
|
}
|
|
17652
17762
|
function defaultFirefoxExecutableCandidates(platform) {
|
|
17653
17763
|
const candidates = [];
|
|
@@ -53298,11 +53408,34 @@ var RoxyBrowserType = class {
|
|
|
53298
53408
|
protocol: options.protocol ?? (this.browserName === "firefox" ? "bidi" : "cdp")
|
|
53299
53409
|
});
|
|
53300
53410
|
}
|
|
53301
|
-
|
|
53411
|
+
/**
|
|
53412
|
+
* Connect to an already-running browser over a WebSocket endpoint.
|
|
53413
|
+
*
|
|
53414
|
+
* ⚠️ DIVERGENCE FROM PLAYWRIGHT — DO NOT "FIX" THIS BACK TO CDP-ONLY:
|
|
53415
|
+
*
|
|
53416
|
+
* In upstream Playwright, `BrowserType.connect()` is a CDP-only operation: it
|
|
53417
|
+
* connects to a running Chromium over the DevTools Protocol. Firefox/WebKit are
|
|
53418
|
+
* not supported here (Playwright routes BiDi through `connectOverCDP`-style
|
|
53419
|
+
* helpers or its own internal plumbing, not this method).
|
|
53420
|
+
*
|
|
53421
|
+
* Roxy intentionally changes that semantics. This method dispatches on
|
|
53422
|
+
* `browserName` so a single entry point serves both browser families:
|
|
53423
|
+
* - chromium → CDP (ws:// DevTools endpoint)
|
|
53424
|
+
* - firefox → BiDi (ws:// WebDriver BiDi endpoint)
|
|
53425
|
+
*
|
|
53426
|
+
* This is deliberate, not a bug. If you are an agent "normalizing" this code to
|
|
53427
|
+
* match Playwright upstream, STOP — the dual-protocol dispatch is the feature.
|
|
53428
|
+
* To force CDP specifically, call `connectOverCDP()` instead, which remains
|
|
53429
|
+
* chromium-only and CDP-only by design (see the guard below).
|
|
53430
|
+
*
|
|
53431
|
+
* `options.protocol` may still be passed to override the per-browser default.
|
|
53432
|
+
*/
|
|
53433
|
+
async connect(endpointURL, options) {
|
|
53302
53434
|
return this.connectBrowser({
|
|
53303
|
-
|
|
53304
|
-
|
|
53305
|
-
|
|
53435
|
+
browserName: this.browserName,
|
|
53436
|
+
protocol: this.browserName === "chromium" ? "cdp" : "bidi",
|
|
53437
|
+
wsEndpoint: endpointURL,
|
|
53438
|
+
...options
|
|
53306
53439
|
});
|
|
53307
53440
|
}
|
|
53308
53441
|
async connectOverCDP(progressOrEndpointURL, endpointURLOrOptions, maybeOptions = {}) {
|
|
@@ -53334,7 +53467,9 @@ var RoxyBrowserType = class {
|
|
|
53334
53467
|
protocol
|
|
53335
53468
|
});
|
|
53336
53469
|
await adapter.connect();
|
|
53337
|
-
|
|
53470
|
+
const session = await adapter.browser();
|
|
53471
|
+
const versionStr = await session.version();
|
|
53472
|
+
return new RoxyBrowser(session, adapter, resolveHumanizationOptions(options.human), options.browserName ?? this.browserName, this, versionStr);
|
|
53338
53473
|
}
|
|
53339
53474
|
};
|
|
53340
53475
|
var chromium = new RoxyBrowserType("chromium", {
|
|
@@ -73317,6 +73452,7 @@ var Response$1 = class {
|
|
|
73317
73452
|
includeSnapshot = "none";
|
|
73318
73453
|
fullSnapshot;
|
|
73319
73454
|
isClose = false;
|
|
73455
|
+
rawResults = false;
|
|
73320
73456
|
constructor(context, toolName, toolArgs) {
|
|
73321
73457
|
this.context = context;
|
|
73322
73458
|
this.toolName = toolName;
|
|
@@ -73340,6 +73476,9 @@ var Response$1 = class {
|
|
|
73340
73476
|
setClose() {
|
|
73341
73477
|
this.isClose = true;
|
|
73342
73478
|
}
|
|
73479
|
+
setRawResults() {
|
|
73480
|
+
this.rawResults = true;
|
|
73481
|
+
}
|
|
73343
73482
|
setIncludeSnapshot() {
|
|
73344
73483
|
this.includeSnapshot = this.context.config.snapshot?.mode ?? "full";
|
|
73345
73484
|
}
|
|
@@ -73355,6 +73494,21 @@ var Response$1 = class {
|
|
|
73355
73494
|
async serialize() {
|
|
73356
73495
|
const sections = [];
|
|
73357
73496
|
if (this.errors.length) sections.push("### Error", ...this.errors);
|
|
73497
|
+
if (this.rawResults) {
|
|
73498
|
+
if (this.results.length) sections.push(...this.results);
|
|
73499
|
+
return {
|
|
73500
|
+
content: [{
|
|
73501
|
+
type: "text",
|
|
73502
|
+
text: sections.join("\n")
|
|
73503
|
+
}, ...this.images.map((image) => ({
|
|
73504
|
+
type: "image",
|
|
73505
|
+
data: image.data,
|
|
73506
|
+
mimeType: image.mimeType
|
|
73507
|
+
}))],
|
|
73508
|
+
...this.isClose ? { isClose: true } : {},
|
|
73509
|
+
...this.errors.length ? { isError: true } : {}
|
|
73510
|
+
};
|
|
73511
|
+
}
|
|
73358
73512
|
if (this.results.length) {
|
|
73359
73513
|
if (sections.length) sections.push("");
|
|
73360
73514
|
sections.push("### Result", ...this.results);
|
|
@@ -73364,16 +73518,23 @@ var Response$1 = class {
|
|
|
73364
73518
|
sections.push("### Code", "```js", ...this.code, "```");
|
|
73365
73519
|
}
|
|
73366
73520
|
if (this.includeSnapshot === "full") {
|
|
73367
|
-
|
|
73368
|
-
|
|
73369
|
-
|
|
73521
|
+
if (!await this.context.runtime.hasDialog()) {
|
|
73522
|
+
const snapshot = await reconcileSnapshotWithTabs(this.context, await this.context.runtime.snapshot());
|
|
73523
|
+
if (sections.length) sections.push("");
|
|
73524
|
+
sections.push(formatSnapshot(snapshot));
|
|
73525
|
+
}
|
|
73370
73526
|
}
|
|
73371
73527
|
if (this.fullSnapshot) {
|
|
73372
|
-
|
|
73528
|
+
let snapshot = await reconcileSnapshotWithTabs(this.context, await this.context.runtime.snapshot({
|
|
73373
73529
|
...this.fullSnapshot.target !== void 0 ? { target: this.fullSnapshot.target } : {},
|
|
73374
73530
|
...this.fullSnapshot.depth !== void 0 ? { depth: this.fullSnapshot.depth } : {},
|
|
73375
73531
|
...this.fullSnapshot.boxes !== void 0 ? { boxes: this.fullSnapshot.boxes } : {}
|
|
73376
|
-
});
|
|
73532
|
+
}));
|
|
73533
|
+
if (!this.fullSnapshot.filename && snapshot.retryable && snapshot.text.trim().length === 0 && snapshot.url && snapshot.url !== "about:blank") snapshot = await reconcileSnapshotWithTabs(this.context, await this.context.runtime.snapshot({
|
|
73534
|
+
...this.fullSnapshot.target !== void 0 ? { target: this.fullSnapshot.target } : {},
|
|
73535
|
+
...this.fullSnapshot.depth !== void 0 ? { depth: this.fullSnapshot.depth } : {},
|
|
73536
|
+
...this.fullSnapshot.boxes !== void 0 ? { boxes: this.fullSnapshot.boxes } : {}
|
|
73537
|
+
}));
|
|
73377
73538
|
if (this.fullSnapshot.filename) {
|
|
73378
73539
|
const resolvedFilename = await this.context.resolveOutputFile(this.fullSnapshot.filename);
|
|
73379
73540
|
await writeFile(resolvedFilename, snapshot.text);
|
|
@@ -73400,6 +73561,15 @@ var Response$1 = class {
|
|
|
73400
73561
|
};
|
|
73401
73562
|
}
|
|
73402
73563
|
};
|
|
73564
|
+
async function reconcileSnapshotWithTabs(context, snapshot) {
|
|
73565
|
+
const activeTab = (await context.runtime.listTabs()).find((tab) => tab.active);
|
|
73566
|
+
if (!activeTab) return snapshot;
|
|
73567
|
+
return {
|
|
73568
|
+
...snapshot,
|
|
73569
|
+
title: activeTab.title || snapshot.title,
|
|
73570
|
+
url: activeTab.url || snapshot.url
|
|
73571
|
+
};
|
|
73572
|
+
}
|
|
73403
73573
|
//#endregion
|
|
73404
73574
|
//#region src/mcp/backend/tool.ts
|
|
73405
73575
|
function defineTool$1(tool) {
|
|
@@ -73498,7 +73668,8 @@ var connect_default = [defineTool$1({
|
|
|
73498
73668
|
description: "Attach to an existing browser and seed the active tab snapshot.",
|
|
73499
73669
|
inputSchema: object({
|
|
73500
73670
|
endpoint: string().min(1),
|
|
73501
|
-
browser: _enum(["chrome", "firefox"]).default("chrome")
|
|
73671
|
+
browser: _enum(["chrome", "firefox"]).default("chrome"),
|
|
73672
|
+
sessionId: string().min(1).optional()
|
|
73502
73673
|
}),
|
|
73503
73674
|
type: "action"
|
|
73504
73675
|
},
|
|
@@ -73507,7 +73678,8 @@ var connect_default = [defineTool$1({
|
|
|
73507
73678
|
const result = await context.runtime.connect({
|
|
73508
73679
|
protocol,
|
|
73509
73680
|
endpoint: params.endpoint,
|
|
73510
|
-
browser: params.browser === "chrome" ? "chromium" : params.browser
|
|
73681
|
+
browser: params.browser === "chrome" ? "chromium" : params.browser,
|
|
73682
|
+
...params.sessionId ? { sessionId: params.sessionId } : {}
|
|
73511
73683
|
});
|
|
73512
73684
|
response.addTextResult(formatConnectResult({
|
|
73513
73685
|
...result,
|
|
@@ -73952,7 +74124,17 @@ var networkRequest = defineTool$1({
|
|
|
73952
74124
|
response.addError(`Request #${args.index} not found. Use browser_network_requests to see available indexes.`);
|
|
73953
74125
|
return;
|
|
73954
74126
|
}
|
|
73955
|
-
|
|
74127
|
+
if (args.part) {
|
|
74128
|
+
response.setRawResults();
|
|
74129
|
+
const partText = args.part === "response-body" ? await context.runtime.fetchResponseBody(args.index) ?? "" : renderRequestPart(request, args.part);
|
|
74130
|
+
if (args.filename) {
|
|
74131
|
+
const resolvedFilename = await context.resolveOutputFile(args.filename);
|
|
74132
|
+
await writeFile(resolvedFilename, partText);
|
|
74133
|
+
response.addTextResult(`Saved network request to "${resolvedFilename}".`);
|
|
74134
|
+
} else response.addTextResult(partText);
|
|
74135
|
+
return;
|
|
74136
|
+
}
|
|
74137
|
+
const text = renderRequestDetails(request);
|
|
73956
74138
|
if (args.filename) {
|
|
73957
74139
|
const resolvedFilename = await context.resolveOutputFile(args.filename);
|
|
73958
74140
|
await writeFile(resolvedFilename, text);
|
|
@@ -73986,8 +74168,10 @@ function renderRequestDetails(request) {
|
|
|
73986
74168
|
if (request.mimeType) lines.push(` mimeType: ${request.mimeType}`);
|
|
73987
74169
|
appendHeaders(lines, "Request headers", request.requestHeaders);
|
|
73988
74170
|
if (request.responseHeaders) appendHeaders(lines, "Response headers", request.responseHeaders);
|
|
73989
|
-
|
|
73990
|
-
if (request.
|
|
74171
|
+
const hints = [];
|
|
74172
|
+
if (request.requestBody) hints.push(`Call browser_network_request with part="request-body" to read the request body.`);
|
|
74173
|
+
if (canHaveResponseBody(request)) hints.push(`Call browser_network_request with part="response-body" to read the response body.`);
|
|
74174
|
+
if (hints.length) lines.push("", ...hints);
|
|
73991
74175
|
return lines.join("\n");
|
|
73992
74176
|
}
|
|
73993
74177
|
function renderRequestPart(request, part) {
|
|
@@ -74006,6 +74190,10 @@ function appendHeaders(lines, title, headers) {
|
|
|
74006
74190
|
function renderHeaders(headers) {
|
|
74007
74191
|
return Object.entries(headers).map(([key, value]) => `${key}: ${value}`).join("\n");
|
|
74008
74192
|
}
|
|
74193
|
+
function canHaveResponseBody(request) {
|
|
74194
|
+
if (request.failureText || request.status === void 0) return false;
|
|
74195
|
+
return request.status !== 204 && request.status !== 304 && !(request.status >= 100 && request.status < 200);
|
|
74196
|
+
}
|
|
74009
74197
|
var network_default = [networkRequests, networkRequest];
|
|
74010
74198
|
var runCode_default = [defineTool$1({
|
|
74011
74199
|
capability: "devtools",
|
|
@@ -74233,6 +74421,16 @@ var allTools = [...mouse_default, ...form_default];
|
|
|
74233
74421
|
function delay$1(ms) {
|
|
74234
74422
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
74235
74423
|
}
|
|
74424
|
+
async function withBiDiTimeout(promise, timeoutMs) {
|
|
74425
|
+
let timer;
|
|
74426
|
+
try {
|
|
74427
|
+
return await Promise.race([promise, new Promise((_, reject) => {
|
|
74428
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Timed out after ${timeoutMs}ms.`)), timeoutMs);
|
|
74429
|
+
})]);
|
|
74430
|
+
} finally {
|
|
74431
|
+
if (timer) clearTimeout(timer);
|
|
74432
|
+
}
|
|
74433
|
+
}
|
|
74236
74434
|
var chromeRemoteInterface = "default" in import_chrome_remote_interface ? import_chrome_remote_interface.default : import_chrome_remote_interface;
|
|
74237
74435
|
function buildConnectionFromWsEndpoint(browserWsEndpoint) {
|
|
74238
74436
|
const parsed = new URL(browserWsEndpoint);
|
|
@@ -74666,13 +74864,13 @@ var CDP_KEY_MAP = {
|
|
|
74666
74864
|
async function evaluateBiDi(client, contextId, functionSource, arg) {
|
|
74667
74865
|
const expression = arg === void 0 ? `(${functionSource})()` : `(${functionSource})(${JSON.stringify(arg)})`;
|
|
74668
74866
|
const response = await client.scriptEvaluate({
|
|
74669
|
-
expression,
|
|
74867
|
+
expression: wrapWithSerializedEvaluationResult(expression),
|
|
74670
74868
|
target: { context: contextId },
|
|
74671
74869
|
awaitPromise: true,
|
|
74672
74870
|
resultOwnership: "none"
|
|
74673
74871
|
});
|
|
74674
74872
|
if (response.type === "exception") throw new Error(response.exceptionDetails?.text || "BiDi runtime evaluation failed.");
|
|
74675
|
-
return response.result
|
|
74873
|
+
return parseSerializedEvaluationResult(extractBiDiValue(response.result));
|
|
74676
74874
|
}
|
|
74677
74875
|
async function evaluateBiDiRef(client, contextId, functionSource, arg) {
|
|
74678
74876
|
const expression = arg === void 0 ? `(${functionSource})()` : `(${functionSource})(${JSON.stringify(arg)})`;
|
|
@@ -74694,6 +74892,16 @@ async function evaluateBiDiRef(client, contextId, functionSource, arg) {
|
|
|
74694
74892
|
...response.result?.value?.handle !== void 0 ? { handle: response.result.value.handle } : {}
|
|
74695
74893
|
};
|
|
74696
74894
|
}
|
|
74895
|
+
function extractBiDiValue(value) {
|
|
74896
|
+
if (!value) return;
|
|
74897
|
+
if (value.type === "array" && Array.isArray(value.value)) return value.value.map((entry) => extractBiDiValue(entry));
|
|
74898
|
+
if (value.type === "object" && Array.isArray(value.value)) {
|
|
74899
|
+
const obj = {};
|
|
74900
|
+
for (const [key, val] of value.value) obj[key] = extractBiDiValue(val);
|
|
74901
|
+
return obj;
|
|
74902
|
+
}
|
|
74903
|
+
return value.value;
|
|
74904
|
+
}
|
|
74697
74905
|
function toAriaSnapshotPayload(request = {}) {
|
|
74698
74906
|
return {
|
|
74699
74907
|
options: normalizeAriaSnapshotOptions({
|
|
@@ -74733,6 +74941,7 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
74733
74941
|
pageConsoleStates = /* @__PURE__ */ new Map();
|
|
74734
74942
|
pageNetworkStates = /* @__PURE__ */ new Map();
|
|
74735
74943
|
pageDialogStates = /* @__PURE__ */ new Map();
|
|
74944
|
+
dialogWaiters = /* @__PURE__ */ new Map();
|
|
74736
74945
|
activeTabId;
|
|
74737
74946
|
versionString = "Chromium/unknown";
|
|
74738
74947
|
constructor(browserClient, connection) {
|
|
@@ -74748,7 +74957,7 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
74748
74957
|
});
|
|
74749
74958
|
const session = new CdpConnectedBrowserSession(await chromeRemoteInterface({ target: connection.browserWsEndpoint }), connection);
|
|
74750
74959
|
session.versionString = version.Browser;
|
|
74751
|
-
|
|
74960
|
+
await session.refreshTabs();
|
|
74752
74961
|
await session.getActivePageClient().catch(() => void 0);
|
|
74753
74962
|
return session;
|
|
74754
74963
|
}
|
|
@@ -74800,6 +75009,7 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
74800
75009
|
async click(target, options) {
|
|
74801
75010
|
const pageClient = await this.getActivePageClient();
|
|
74802
75011
|
const contextId = await this.getActiveUtilityContextId(pageClient);
|
|
75012
|
+
const tabId = await this.getActiveTabId();
|
|
74803
75013
|
const point = await evaluateCdp(pageClient, "nodeToken" in target ? ACTION_POINT_EVALUATE_SOURCE : ACTION_POINT_BY_SELECTOR_SOURCE, "nodeToken" in target ? { nodeToken: target.nodeToken } : { selector: target.selector }, contextId);
|
|
74804
75014
|
if (!point.ok || point.x === void 0 || point.y === void 0) {
|
|
74805
75015
|
const isSelector = "selector" in target;
|
|
@@ -74833,7 +75043,7 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
74833
75043
|
modifiers: modifiersMask
|
|
74834
75044
|
});
|
|
74835
75045
|
await delay$1(options.clickHoldMs);
|
|
74836
|
-
|
|
75046
|
+
const releasePromise = pageClient.Input.dispatchMouseEvent({
|
|
74837
75047
|
type: "mouseReleased",
|
|
74838
75048
|
x: point.x,
|
|
74839
75049
|
y: point.y,
|
|
@@ -74841,6 +75051,7 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
74841
75051
|
clickCount,
|
|
74842
75052
|
modifiers: modifiersMask
|
|
74843
75053
|
});
|
|
75054
|
+
await Promise.race([releasePromise, this.waitForDialog(tabId, options.clickHoldMs + 1e3)]);
|
|
74844
75055
|
}
|
|
74845
75056
|
}
|
|
74846
75057
|
async drag(start, end, options) {
|
|
@@ -75096,24 +75307,72 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
75096
75307
|
}
|
|
75097
75308
|
}
|
|
75098
75309
|
async handleDialog(accept, promptText) {
|
|
75099
|
-
const tabId =
|
|
75310
|
+
const tabId = this.dialogTabId();
|
|
75100
75311
|
if (!this.pageDialogStates.has(tabId)) throw new McpToolError("no_dialog", "No dialog visible.");
|
|
75101
|
-
const pageClient = await this.getActivePageClient();
|
|
75312
|
+
const pageClient = this.pageClients.get(tabId) ?? await this.getActivePageClient();
|
|
75102
75313
|
this.pageDialogStates.delete(tabId);
|
|
75103
75314
|
await pageClient.Page.handleJavaScriptDialog({
|
|
75104
75315
|
accept,
|
|
75105
75316
|
...promptText !== void 0 ? { promptText } : {}
|
|
75106
75317
|
});
|
|
75107
75318
|
}
|
|
75319
|
+
async hasDialog() {
|
|
75320
|
+
return this.pageDialogStates.size > 0;
|
|
75321
|
+
}
|
|
75322
|
+
waitForDialog(tabId, timeoutMs) {
|
|
75323
|
+
if (this.pageDialogStates.has(tabId)) return Promise.resolve();
|
|
75324
|
+
return new Promise((resolve) => {
|
|
75325
|
+
const waiter = { resolve: () => {
|
|
75326
|
+
if (waiter.timer) clearTimeout(waiter.timer);
|
|
75327
|
+
this.removeDialogWaiter(tabId, waiter);
|
|
75328
|
+
resolve();
|
|
75329
|
+
} };
|
|
75330
|
+
waiter.timer = setTimeout(() => waiter.resolve(), timeoutMs);
|
|
75331
|
+
const waiters = this.dialogWaiters.get(tabId) ?? /* @__PURE__ */ new Set();
|
|
75332
|
+
waiters.add(waiter);
|
|
75333
|
+
this.dialogWaiters.set(tabId, waiters);
|
|
75334
|
+
});
|
|
75335
|
+
}
|
|
75336
|
+
resolveDialogWaiters(tabId) {
|
|
75337
|
+
const waiters = this.dialogWaiters.get(tabId);
|
|
75338
|
+
if (!waiters) return;
|
|
75339
|
+
this.dialogWaiters.delete(tabId);
|
|
75340
|
+
for (const waiter of waiters) waiter.resolve();
|
|
75341
|
+
}
|
|
75342
|
+
removeDialogWaiter(tabId, waiter) {
|
|
75343
|
+
const waiters = this.dialogWaiters.get(tabId);
|
|
75344
|
+
if (!waiters) return;
|
|
75345
|
+
waiters.delete(waiter);
|
|
75346
|
+
if (waiters.size === 0) this.dialogWaiters.delete(tabId);
|
|
75347
|
+
}
|
|
75108
75348
|
async networkRequests() {
|
|
75109
75349
|
const tabId = await this.getActiveTabId();
|
|
75350
|
+
await this.hydratePerformanceResourceRequests(tabId);
|
|
75110
75351
|
return this.ensureNetworkState(tabId).requests.map(cloneNetworkRequest);
|
|
75111
75352
|
}
|
|
75112
75353
|
async networkRequest(index) {
|
|
75113
75354
|
const tabId = await this.getActiveTabId();
|
|
75355
|
+
await this.hydratePerformanceResourceRequests(tabId);
|
|
75114
75356
|
const request = this.ensureNetworkState(tabId).requests[index - 1];
|
|
75115
75357
|
return request ? cloneNetworkRequest(request) : void 0;
|
|
75116
75358
|
}
|
|
75359
|
+
async fetchResponseBody(index) {
|
|
75360
|
+
const tabId = await this.getActiveTabId();
|
|
75361
|
+
const state = this.ensureNetworkState(tabId);
|
|
75362
|
+
const request = state.requests[index - 1];
|
|
75363
|
+
if (!request || !request.requestId) return request?.responseBody;
|
|
75364
|
+
if (!canReadResponseBody(request)) return;
|
|
75365
|
+
if (request.responseBody !== void 0) return request.responseBody;
|
|
75366
|
+
await waitForLoadingDone(state, request.requestId, 5e3).catch(() => void 0);
|
|
75367
|
+
if (request.responseBody !== void 0) return request.responseBody;
|
|
75368
|
+
if (state.bodyRead.has(request.requestId)) return;
|
|
75369
|
+
state.bodyRead.add(request.requestId);
|
|
75370
|
+
const clientNetwork = (this.pageClients.get(tabId) ?? await this.getActivePageClient()).Network;
|
|
75371
|
+
if (!clientNetwork) return;
|
|
75372
|
+
const body = await clientNetwork.getResponseBody({ requestId: request.requestId }).catch(() => void 0);
|
|
75373
|
+
if (body) request.responseBody = body.base64Encoded ? Buffer.from(body.body, "base64").toString("utf8") : body.body;
|
|
75374
|
+
return request.responseBody;
|
|
75375
|
+
}
|
|
75117
75376
|
async runCodeUnsafe(code) {
|
|
75118
75377
|
const pageClient = await this.getActivePageClient();
|
|
75119
75378
|
const contextId = await this.getActiveUtilityContextId(pageClient);
|
|
@@ -75173,6 +75432,12 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
75173
75432
|
if (!activeTab) throw new McpToolError("no_active_tab", "No active tab is available.");
|
|
75174
75433
|
return activeTab.id;
|
|
75175
75434
|
}
|
|
75435
|
+
dialogTabId() {
|
|
75436
|
+
if (this.activeTabId && this.pageDialogStates.has(this.activeTabId)) return this.activeTabId;
|
|
75437
|
+
const tabId = this.pageDialogStates.keys().next().value;
|
|
75438
|
+
if (!tabId) throw new McpToolError("no_dialog", "No dialog visible.");
|
|
75439
|
+
return tabId;
|
|
75440
|
+
}
|
|
75176
75441
|
async getActivePageClient() {
|
|
75177
75442
|
const activeTab = (await this.refreshTabs()).find((tab) => tab.active);
|
|
75178
75443
|
if (!activeTab) throw new McpToolError("no_active_tab", "No active tab is available.");
|
|
@@ -75254,6 +75519,7 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
75254
75519
|
...event.defaultPrompt !== void 0 ? { defaultPrompt: event.defaultPrompt } : {},
|
|
75255
75520
|
...event.url !== void 0 ? { url: event.url } : {}
|
|
75256
75521
|
});
|
|
75522
|
+
this.resolveDialogWaiters(tabId);
|
|
75257
75523
|
});
|
|
75258
75524
|
this.installNetworkCollection(tabId, client);
|
|
75259
75525
|
}
|
|
@@ -75292,20 +75558,29 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
75292
75558
|
});
|
|
75293
75559
|
client.Network.loadingFinished(async (event) => {
|
|
75294
75560
|
const request = state.byRequestId.get(event.requestId);
|
|
75295
|
-
if (!request)
|
|
75561
|
+
if (!request) {
|
|
75562
|
+
resolveLoadingDone(state, event.requestId, true);
|
|
75563
|
+
return;
|
|
75564
|
+
}
|
|
75296
75565
|
const startedAt = state.startedAt.get(event.requestId);
|
|
75297
75566
|
if (startedAt !== void 0 && event.timestamp !== void 0) request.durationMs = Math.round(event.timestamp * 1e3 - startedAt);
|
|
75298
|
-
if (canReadResponseBody(request)) {
|
|
75567
|
+
if (canReadResponseBody(request) && !state.bodyRead.has(event.requestId)) {
|
|
75568
|
+
state.bodyRead.add(event.requestId);
|
|
75299
75569
|
const body = await client.Network?.getResponseBody({ requestId: event.requestId }).catch(() => void 0);
|
|
75300
75570
|
if (body) request.responseBody = body.base64Encoded ? Buffer.from(body.body, "base64").toString("utf8") : body.body;
|
|
75301
75571
|
}
|
|
75572
|
+
resolveLoadingDone(state, event.requestId, true);
|
|
75302
75573
|
});
|
|
75303
75574
|
client.Network.loadingFailed((event) => {
|
|
75304
75575
|
const request = state.byRequestId.get(event.requestId);
|
|
75305
|
-
if (!request)
|
|
75576
|
+
if (!request) {
|
|
75577
|
+
resolveLoadingDone(state, event.requestId, false);
|
|
75578
|
+
return;
|
|
75579
|
+
}
|
|
75306
75580
|
request.failureText = event.errorText ?? "Unknown error";
|
|
75307
75581
|
const startedAt = state.startedAt.get(event.requestId);
|
|
75308
75582
|
if (startedAt !== void 0 && event.timestamp !== void 0) request.durationMs = Math.round(event.timestamp * 1e3 - startedAt);
|
|
75583
|
+
resolveLoadingDone(state, event.requestId, false);
|
|
75309
75584
|
});
|
|
75310
75585
|
}
|
|
75311
75586
|
ensureConsoleState(tabId) {
|
|
@@ -75327,7 +75602,10 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
75327
75602
|
state = {
|
|
75328
75603
|
requests: [],
|
|
75329
75604
|
byRequestId: /* @__PURE__ */ new Map(),
|
|
75330
|
-
startedAt: /* @__PURE__ */ new Map()
|
|
75605
|
+
startedAt: /* @__PURE__ */ new Map(),
|
|
75606
|
+
hydratedPerformanceResources: false,
|
|
75607
|
+
loadingDone: /* @__PURE__ */ new Map(),
|
|
75608
|
+
bodyRead: /* @__PURE__ */ new Set()
|
|
75331
75609
|
};
|
|
75332
75610
|
this.pageNetworkStates.set(tabId, state);
|
|
75333
75611
|
}
|
|
@@ -75343,10 +75621,79 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
75343
75621
|
this.pageNetworkStates.set(tabId, {
|
|
75344
75622
|
requests: [],
|
|
75345
75623
|
byRequestId: /* @__PURE__ */ new Map(),
|
|
75346
|
-
startedAt: /* @__PURE__ */ new Map()
|
|
75624
|
+
startedAt: /* @__PURE__ */ new Map(),
|
|
75625
|
+
hydratedPerformanceResources: false,
|
|
75626
|
+
loadingDone: /* @__PURE__ */ new Map(),
|
|
75627
|
+
bodyRead: /* @__PURE__ */ new Set()
|
|
75347
75628
|
});
|
|
75348
75629
|
this.pageDialogStates.delete(tabId);
|
|
75349
75630
|
}
|
|
75631
|
+
async hydratePerformanceResourceRequests(tabId) {
|
|
75632
|
+
const state = this.ensureNetworkState(tabId);
|
|
75633
|
+
if (state.hydratedPerformanceResources) return;
|
|
75634
|
+
state.hydratedPerformanceResources = true;
|
|
75635
|
+
const pageClient = await this.getActivePageClient();
|
|
75636
|
+
const contextId = await this.getActiveUtilityContextId(pageClient);
|
|
75637
|
+
const documentRequest = await evaluateCdp(pageClient, String.raw`() => {
|
|
75638
|
+
const navigation = performance.getEntriesByType("navigation")[0];
|
|
75639
|
+
return {
|
|
75640
|
+
url: String(location.href || ""),
|
|
75641
|
+
duration: navigation ? Math.round(Number(navigation.duration || 0)) : undefined
|
|
75642
|
+
};
|
|
75643
|
+
}`, void 0, contextId).catch(() => void 0);
|
|
75644
|
+
if (documentRequest?.url && !Array.from(state.byRequestId.values()).some((request) => request.url === documentRequest.url)) {
|
|
75645
|
+
const requestId = `performance:navigation:${documentRequest.url}`;
|
|
75646
|
+
const request = {
|
|
75647
|
+
index: state.requests.length + 1,
|
|
75648
|
+
requestId,
|
|
75649
|
+
method: "GET",
|
|
75650
|
+
url: documentRequest.url,
|
|
75651
|
+
resourceType: "document",
|
|
75652
|
+
requestHeaders: {},
|
|
75653
|
+
status: 200,
|
|
75654
|
+
statusText: "OK",
|
|
75655
|
+
...documentRequest.duration !== void 0 ? { durationMs: documentRequest.duration } : {}
|
|
75656
|
+
};
|
|
75657
|
+
state.requests.push(request);
|
|
75658
|
+
state.byRequestId.set(requestId, request);
|
|
75659
|
+
}
|
|
75660
|
+
const resources = await evaluateCdp(pageClient, String.raw`() => performance.getEntriesByType("resource").map((entry) => ({
|
|
75661
|
+
name: String(entry.name || ""),
|
|
75662
|
+
initiatorType: String(entry.initiatorType || "other"),
|
|
75663
|
+
duration: Math.round(Number(entry.duration || 0)),
|
|
75664
|
+
responseStatus: typeof entry.responseStatus === "number" ? entry.responseStatus : undefined
|
|
75665
|
+
}))`, void 0, contextId).catch(() => []);
|
|
75666
|
+
for (const resource of resources) {
|
|
75667
|
+
if (!resource.name || Array.from(state.byRequestId.values()).some((request) => request.url === resource.name)) continue;
|
|
75668
|
+
const status = resource.responseStatus && resource.responseStatus > 0 ? resource.responseStatus : await this.probeResourceStatus(pageClient, contextId, resource.name);
|
|
75669
|
+
const requestId = `performance:${resource.name}`;
|
|
75670
|
+
const request = {
|
|
75671
|
+
index: state.requests.length + 1,
|
|
75672
|
+
requestId,
|
|
75673
|
+
method: "GET",
|
|
75674
|
+
url: resource.name,
|
|
75675
|
+
resourceType: normalizeResourceType(resource.initiatorType),
|
|
75676
|
+
requestHeaders: {},
|
|
75677
|
+
...status !== void 0 ? {
|
|
75678
|
+
status,
|
|
75679
|
+
statusText: statusTextForStatus(status)
|
|
75680
|
+
} : {},
|
|
75681
|
+
...resource.duration !== void 0 ? { durationMs: resource.duration } : {}
|
|
75682
|
+
};
|
|
75683
|
+
state.requests.push(request);
|
|
75684
|
+
state.byRequestId.set(requestId, request);
|
|
75685
|
+
}
|
|
75686
|
+
}
|
|
75687
|
+
async probeResourceStatus(pageClient, contextId, url) {
|
|
75688
|
+
return evaluateCdp(pageClient, String.raw`async (url) => {
|
|
75689
|
+
try {
|
|
75690
|
+
const response = await fetch(url, { method: "HEAD", cache: "no-store" });
|
|
75691
|
+
return response.status;
|
|
75692
|
+
} catch {
|
|
75693
|
+
return undefined;
|
|
75694
|
+
}
|
|
75695
|
+
}`, url, contextId).catch(() => void 0);
|
|
75696
|
+
}
|
|
75350
75697
|
addConsoleMessage(tabId, message) {
|
|
75351
75698
|
const state = this.ensureConsoleState(tabId);
|
|
75352
75699
|
if (!shouldIncludeConsoleMessage(message.type)) return;
|
|
@@ -75480,6 +75827,45 @@ function canReadResponseBody(request) {
|
|
|
75480
75827
|
if (request.failureText || request.status === void 0) return false;
|
|
75481
75828
|
return request.status !== 204 && request.status !== 304 && !(request.status >= 100 && request.status < 200);
|
|
75482
75829
|
}
|
|
75830
|
+
function loadingDoneEntry(state, requestId) {
|
|
75831
|
+
let entry = state.loadingDone.get(requestId);
|
|
75832
|
+
if (!entry) {
|
|
75833
|
+
let resolve;
|
|
75834
|
+
let reject;
|
|
75835
|
+
entry = {
|
|
75836
|
+
promise: new Promise((res, rej) => {
|
|
75837
|
+
resolve = res;
|
|
75838
|
+
reject = rej;
|
|
75839
|
+
}),
|
|
75840
|
+
resolve,
|
|
75841
|
+
reject
|
|
75842
|
+
};
|
|
75843
|
+
state.loadingDone.set(requestId, entry);
|
|
75844
|
+
}
|
|
75845
|
+
return entry;
|
|
75846
|
+
}
|
|
75847
|
+
function resolveLoadingDone(state, requestId, success) {
|
|
75848
|
+
const entry = state.loadingDone.get(requestId);
|
|
75849
|
+
if (!entry) return;
|
|
75850
|
+
state.loadingDone.delete(requestId);
|
|
75851
|
+
if (success) entry.resolve();
|
|
75852
|
+
else entry.reject(/* @__PURE__ */ new Error("Request failed before the response body was available."));
|
|
75853
|
+
}
|
|
75854
|
+
async function waitForLoadingDone(state, requestId, timeoutMs) {
|
|
75855
|
+
const entry = loadingDoneEntry(state, requestId);
|
|
75856
|
+
await Promise.race([entry.promise, new Promise((resolve) => setTimeout(resolve, timeoutMs))]);
|
|
75857
|
+
}
|
|
75858
|
+
function statusTextForStatus(status) {
|
|
75859
|
+
if (status === 200) return "OK";
|
|
75860
|
+
if (status === 204) return "No Content";
|
|
75861
|
+
if (status === 304) return "Not Modified";
|
|
75862
|
+
if (status === 400) return "Bad Request";
|
|
75863
|
+
if (status === 401) return "Unauthorized";
|
|
75864
|
+
if (status === 403) return "Forbidden";
|
|
75865
|
+
if (status === 404) return "Not Found";
|
|
75866
|
+
if (status === 500) return "Internal Server Error";
|
|
75867
|
+
return "";
|
|
75868
|
+
}
|
|
75483
75869
|
function cloneNetworkRequest(request) {
|
|
75484
75870
|
return {
|
|
75485
75871
|
...request,
|
|
@@ -75558,6 +75944,7 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
75558
75944
|
pageConsoleStates = /* @__PURE__ */ new Map();
|
|
75559
75945
|
pageNetworkStates = /* @__PURE__ */ new Map();
|
|
75560
75946
|
pageDialogStates = /* @__PURE__ */ new Map();
|
|
75947
|
+
dialogWaiters = /* @__PURE__ */ new Map();
|
|
75561
75948
|
bidiListeners = /* @__PURE__ */ new Map();
|
|
75562
75949
|
responseDataCollector;
|
|
75563
75950
|
activeTabId;
|
|
@@ -75571,12 +75958,12 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
75571
75958
|
if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") throw new McpToolError("unsupported_protocol_input", `BiDi endpoint must be a ws(s) URL. Received "${parsed.protocol}".`);
|
|
75572
75959
|
const client = await getBidiClientFactory()({
|
|
75573
75960
|
browserName: "firefox",
|
|
75574
|
-
webSocketUrl: normalizeFirefoxBidiEndpoint(args.endpoint)
|
|
75961
|
+
webSocketUrl: normalizeFirefoxBidiEndpoint(args.endpoint, args.sessionId)
|
|
75575
75962
|
});
|
|
75576
75963
|
const session = new BidiConnectedBrowserSession(client);
|
|
75577
|
-
session.ownsSession = await ensureMcpBiDiSession(client, args.endpoint);
|
|
75964
|
+
session.ownsSession = await ensureMcpBiDiSession(client, args.endpoint, args.sessionId);
|
|
75578
75965
|
await session.initialize();
|
|
75579
|
-
|
|
75966
|
+
await session.refreshTabs();
|
|
75580
75967
|
return session;
|
|
75581
75968
|
}
|
|
75582
75969
|
async version() {
|
|
@@ -75619,10 +76006,13 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
75619
76006
|
}
|
|
75620
76007
|
async snapshot(request = {}) {
|
|
75621
76008
|
const tabId = await this.getActiveTabId();
|
|
75622
|
-
return
|
|
75623
|
-
|
|
75624
|
-
|
|
75625
|
-
|
|
76009
|
+
return {
|
|
76010
|
+
...toBrowserSnapshot(await retryUntilReady(() => evaluateBiDi(this.client, tabId, PLAYWRIGHT_ARIA_SNAPSHOT_EVALUATE_SOURCE, toAriaSnapshotPayload(request))), request, {
|
|
76011
|
+
console: this.consoleSummary(tabId),
|
|
76012
|
+
consoleLink: await this.takeConsoleLink(tabId)
|
|
76013
|
+
}),
|
|
76014
|
+
retryable: true
|
|
76015
|
+
};
|
|
75626
76016
|
}
|
|
75627
76017
|
async consoleMessages(level = "info", all = false) {
|
|
75628
76018
|
const activeTabId = await this.getActiveTabId();
|
|
@@ -75725,10 +76115,12 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
75725
76115
|
parameters: { pointerType: "mouse" },
|
|
75726
76116
|
actions: pointerActions
|
|
75727
76117
|
});
|
|
75728
|
-
|
|
76118
|
+
const performPromise = this.client.inputPerformActions({
|
|
75729
76119
|
context: tabId,
|
|
75730
76120
|
actions
|
|
75731
76121
|
});
|
|
76122
|
+
await Promise.race([performPromise, this.waitForDialog(tabId, options.clickHoldMs + 5e3)]);
|
|
76123
|
+
performPromise.catch(() => {});
|
|
75732
76124
|
await this.client.inputReleaseActions({ context: tabId }).catch(() => {});
|
|
75733
76125
|
}
|
|
75734
76126
|
async drag(start, end, options) {
|
|
@@ -76029,14 +76421,17 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
76029
76421
|
}
|
|
76030
76422
|
}
|
|
76031
76423
|
async handleDialog(accept, promptText) {
|
|
76032
|
-
const tabId =
|
|
76424
|
+
const tabId = this.dialogTabId();
|
|
76033
76425
|
if (!this.pageDialogStates.has(tabId)) throw new McpToolError("no_dialog", "No dialog visible.");
|
|
76034
76426
|
this.pageDialogStates.delete(tabId);
|
|
76035
|
-
await this.client.browsingContextHandleUserPrompt({
|
|
76427
|
+
await withBiDiTimeout(this.client.browsingContextHandleUserPrompt({
|
|
76036
76428
|
context: tabId,
|
|
76037
76429
|
accept,
|
|
76038
76430
|
...promptText !== void 0 ? { userText: promptText } : {}
|
|
76039
|
-
});
|
|
76431
|
+
}), 5e3);
|
|
76432
|
+
}
|
|
76433
|
+
async hasDialog() {
|
|
76434
|
+
return this.pageDialogStates.size > 0;
|
|
76040
76435
|
}
|
|
76041
76436
|
async networkRequests() {
|
|
76042
76437
|
const tabId = await this.getActiveTabId();
|
|
@@ -76047,6 +76442,15 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
76047
76442
|
const request = this.ensureNetworkState(tabId).requests[index - 1];
|
|
76048
76443
|
return request ? cloneNetworkRequest(request) : void 0;
|
|
76049
76444
|
}
|
|
76445
|
+
async fetchResponseBody(index) {
|
|
76446
|
+
const tabId = await this.getActiveTabId();
|
|
76447
|
+
const request = this.ensureNetworkState(tabId).requests[index - 1];
|
|
76448
|
+
if (!request || !request.requestId) return request?.responseBody;
|
|
76449
|
+
if (request.responseBody !== void 0) return request.responseBody;
|
|
76450
|
+
const body = await this.getResponseBody(request.requestId).catch(() => void 0);
|
|
76451
|
+
if (body !== void 0) request.responseBody = body;
|
|
76452
|
+
return request.responseBody;
|
|
76453
|
+
}
|
|
76050
76454
|
async runCodeUnsafe(code) {
|
|
76051
76455
|
return this.evaluate(`async () => {
|
|
76052
76456
|
const fn = eval(${JSON.stringify(`(${code})`)});
|
|
@@ -76062,6 +76466,7 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
76062
76466
|
}`);
|
|
76063
76467
|
}
|
|
76064
76468
|
async initialize() {
|
|
76469
|
+
this.attachBiDiListeners();
|
|
76065
76470
|
await this.client.sessionSubscribe({ events: [
|
|
76066
76471
|
"browsingContext.userPromptOpened",
|
|
76067
76472
|
"log.entryAdded",
|
|
@@ -76075,7 +76480,6 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
76075
76480
|
maxEncodedDataSize: 1e7
|
|
76076
76481
|
}).catch(() => void 0);
|
|
76077
76482
|
this.responseDataCollector = collectorResult?.collector;
|
|
76078
|
-
this.attachBiDiListeners();
|
|
76079
76483
|
}
|
|
76080
76484
|
attachBiDiListeners() {
|
|
76081
76485
|
this.attachBiDiListener("log.entryAdded", (payload) => this.handleLogEntry(payload));
|
|
@@ -76110,6 +76514,45 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
76110
76514
|
targetArg(target) {
|
|
76111
76515
|
return "nodeToken" in target ? { nodeToken: target.nodeToken } : { selector: target.selector };
|
|
76112
76516
|
}
|
|
76517
|
+
dialogTabId() {
|
|
76518
|
+
if (this.activeTabId && this.pageDialogStates.has(this.activeTabId)) return this.activeTabId;
|
|
76519
|
+
const tabId = this.pageDialogStates.keys().next().value;
|
|
76520
|
+
if (!tabId) throw new McpToolError("no_dialog", "No dialog visible.");
|
|
76521
|
+
return tabId;
|
|
76522
|
+
}
|
|
76523
|
+
waitForDialog(tabId, timeoutMs) {
|
|
76524
|
+
if (this.pageDialogStates.has(tabId)) return Promise.resolve();
|
|
76525
|
+
return new Promise((resolve, reject) => {
|
|
76526
|
+
const waiter = {
|
|
76527
|
+
resolve: () => {
|
|
76528
|
+
if (waiter.timer) clearTimeout(waiter.timer);
|
|
76529
|
+
this.removeDialogWaiter(tabId, waiter);
|
|
76530
|
+
resolve();
|
|
76531
|
+
},
|
|
76532
|
+
reject: (error) => {
|
|
76533
|
+
if (waiter.timer) clearTimeout(waiter.timer);
|
|
76534
|
+
this.removeDialogWaiter(tabId, waiter);
|
|
76535
|
+
reject(error);
|
|
76536
|
+
}
|
|
76537
|
+
};
|
|
76538
|
+
waiter.timer = setTimeout(() => waiter.reject?.(/* @__PURE__ */ new Error("Timed out waiting for dialog.")), timeoutMs);
|
|
76539
|
+
const waiters = this.dialogWaiters.get(tabId) ?? /* @__PURE__ */ new Set();
|
|
76540
|
+
waiters.add(waiter);
|
|
76541
|
+
this.dialogWaiters.set(tabId, waiters);
|
|
76542
|
+
});
|
|
76543
|
+
}
|
|
76544
|
+
resolveDialogWaiters(tabId) {
|
|
76545
|
+
const waiters = this.dialogWaiters.get(tabId);
|
|
76546
|
+
if (!waiters) return;
|
|
76547
|
+
this.dialogWaiters.delete(tabId);
|
|
76548
|
+
for (const waiter of waiters) waiter.resolve();
|
|
76549
|
+
}
|
|
76550
|
+
removeDialogWaiter(tabId, waiter) {
|
|
76551
|
+
const waiters = this.dialogWaiters.get(tabId);
|
|
76552
|
+
if (!waiters) return;
|
|
76553
|
+
waiters.delete(waiter);
|
|
76554
|
+
if (waiters.size === 0) this.dialogWaiters.delete(tabId);
|
|
76555
|
+
}
|
|
76113
76556
|
async actionPoint(tabId, target) {
|
|
76114
76557
|
const source = "nodeToken" in target ? ACTION_POINT_EVALUATE_SOURCE : ACTION_POINT_BY_SELECTOR_SOURCE;
|
|
76115
76558
|
const point = await evaluateBiDi(this.client, tabId, source, this.targetArg(target));
|
|
@@ -76166,6 +76609,7 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
76166
76609
|
type: event.type ?? "alert",
|
|
76167
76610
|
...event.defaultValue !== void 0 ? { defaultPrompt: event.defaultValue } : {}
|
|
76168
76611
|
});
|
|
76612
|
+
this.resolveDialogWaiters(event.context);
|
|
76169
76613
|
}
|
|
76170
76614
|
handleBeforeRequestSent(payload) {
|
|
76171
76615
|
const event = parseBidiNetworkEvent(payload);
|
|
@@ -76268,7 +76712,10 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
76268
76712
|
state = {
|
|
76269
76713
|
requests: [],
|
|
76270
76714
|
byRequestId: /* @__PURE__ */ new Map(),
|
|
76271
|
-
startedAt: /* @__PURE__ */ new Map()
|
|
76715
|
+
startedAt: /* @__PURE__ */ new Map(),
|
|
76716
|
+
hydratedPerformanceResources: false,
|
|
76717
|
+
loadingDone: /* @__PURE__ */ new Map(),
|
|
76718
|
+
bodyRead: /* @__PURE__ */ new Set()
|
|
76272
76719
|
};
|
|
76273
76720
|
this.pageNetworkStates.set(tabId, state);
|
|
76274
76721
|
}
|
|
@@ -76284,7 +76731,10 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
76284
76731
|
this.pageNetworkStates.set(tabId, {
|
|
76285
76732
|
requests: [],
|
|
76286
76733
|
byRequestId: /* @__PURE__ */ new Map(),
|
|
76287
|
-
startedAt: /* @__PURE__ */ new Map()
|
|
76734
|
+
startedAt: /* @__PURE__ */ new Map(),
|
|
76735
|
+
hydratedPerformanceResources: false,
|
|
76736
|
+
loadingDone: /* @__PURE__ */ new Map(),
|
|
76737
|
+
bodyRead: /* @__PURE__ */ new Set()
|
|
76288
76738
|
});
|
|
76289
76739
|
this.pageDialogStates.delete(tabId);
|
|
76290
76740
|
}
|
|
@@ -76322,14 +76772,18 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
76322
76772
|
return `${path.relative(process.cwd(), state.logFile)}${fromLine === state.logLine ? `#L${fromLine}` : `#L${fromLine}-L${state.logLine}`}`;
|
|
76323
76773
|
}
|
|
76324
76774
|
};
|
|
76325
|
-
function normalizeFirefoxBidiEndpoint(endpoint) {
|
|
76775
|
+
function normalizeFirefoxBidiEndpoint(endpoint, sessionId) {
|
|
76326
76776
|
const url = new URL(endpoint);
|
|
76777
|
+
if (sessionId) {
|
|
76778
|
+
url.pathname = `/session/${sessionId}`;
|
|
76779
|
+
return url.toString();
|
|
76780
|
+
}
|
|
76327
76781
|
if (url.pathname === "/" || url.pathname === "") url.pathname = "/session";
|
|
76328
76782
|
return url.toString();
|
|
76329
76783
|
}
|
|
76330
|
-
async function ensureMcpBiDiSession(client, endpoint) {
|
|
76784
|
+
async function ensureMcpBiDiSession(client, endpoint, sessionId) {
|
|
76331
76785
|
await client.sessionStatus({});
|
|
76332
|
-
if (isSessionSpecificFirefoxBidiEndpoint(endpoint)) return false;
|
|
76786
|
+
if (sessionId || isSessionSpecificFirefoxBidiEndpoint(endpoint)) return false;
|
|
76333
76787
|
try {
|
|
76334
76788
|
await client.browsingContextGetTree({});
|
|
76335
76789
|
return false;
|
|
@@ -76414,6 +76868,7 @@ var McpRuntime = class {
|
|
|
76414
76868
|
this.invalidateSnapshot();
|
|
76415
76869
|
this.pendingFileUploadTarget = void 0;
|
|
76416
76870
|
this.tabs = await session.newTab(url);
|
|
76871
|
+
if (this.snapshotMode === "none") return { tabs: this.tabs };
|
|
76417
76872
|
const snapshot = this.tabs.some((tab) => tab.active) ? await this.snapshot() : void 0;
|
|
76418
76873
|
return snapshot ? {
|
|
76419
76874
|
tabs: this.tabs,
|
|
@@ -76427,6 +76882,7 @@ var McpRuntime = class {
|
|
|
76427
76882
|
this.invalidateSnapshot();
|
|
76428
76883
|
this.pendingFileUploadTarget = void 0;
|
|
76429
76884
|
this.tabs = await session.selectTab(tab.id);
|
|
76885
|
+
if (this.snapshotMode === "none") return { tabs: this.tabs };
|
|
76430
76886
|
const snapshot = await this.snapshot();
|
|
76431
76887
|
return {
|
|
76432
76888
|
tabs: this.tabs,
|
|
@@ -76440,6 +76896,7 @@ var McpRuntime = class {
|
|
|
76440
76896
|
this.invalidateSnapshot();
|
|
76441
76897
|
this.pendingFileUploadTarget = void 0;
|
|
76442
76898
|
this.tabs = await session.closeTab(tab.id);
|
|
76899
|
+
if (this.snapshotMode === "none") return { tabs: this.tabs };
|
|
76443
76900
|
const snapshot = this.tabs.some((candidate) => candidate.active) ? await this.snapshot() : void 0;
|
|
76444
76901
|
return snapshot ? {
|
|
76445
76902
|
tabs: this.tabs,
|
|
@@ -76448,26 +76905,49 @@ var McpRuntime = class {
|
|
|
76448
76905
|
}
|
|
76449
76906
|
async snapshot(args = {}) {
|
|
76450
76907
|
const session = this.requireConnected();
|
|
76451
|
-
this.tabs = await session.listTabs();
|
|
76452
|
-
const activeTab = this.requireActiveTab();
|
|
76453
76908
|
const requestKey = this.snapshotRequestKey(args);
|
|
76454
76909
|
const request = {
|
|
76455
76910
|
...args.boxes !== void 0 ? { boxes: args.boxes } : {},
|
|
76456
76911
|
...args.depth !== void 0 ? { depth: args.depth } : {},
|
|
76457
76912
|
...args.target ? { target: this.resolveSnapshotTarget(args.target) } : {}
|
|
76458
76913
|
};
|
|
76459
|
-
const snapshot = await
|
|
76914
|
+
const { activeTab, currentActiveTab, snapshot } = await this.captureStableSnapshot(session, request);
|
|
76460
76915
|
this.snapshotCache = {
|
|
76461
|
-
tabId:
|
|
76916
|
+
tabId: currentActiveTab.id,
|
|
76462
76917
|
requestKey,
|
|
76463
76918
|
text: snapshot.text,
|
|
76464
76919
|
refs: { ...snapshot.refs },
|
|
76465
|
-
title: snapshot.title,
|
|
76466
|
-
url: snapshot.url,
|
|
76920
|
+
title: currentActiveTab.title || snapshot.title,
|
|
76921
|
+
url: currentActiveTab.url || snapshot.url,
|
|
76467
76922
|
...snapshot.console ? { console: { ...snapshot.console } } : {},
|
|
76468
76923
|
...snapshot.consoleLink ? { consoleLink: snapshot.consoleLink } : {}
|
|
76469
76924
|
};
|
|
76470
|
-
return
|
|
76925
|
+
return {
|
|
76926
|
+
...snapshot,
|
|
76927
|
+
title: currentActiveTab.title || snapshot.title,
|
|
76928
|
+
url: currentActiveTab.url || snapshot.url
|
|
76929
|
+
};
|
|
76930
|
+
}
|
|
76931
|
+
async captureStableSnapshot(session, request) {
|
|
76932
|
+
let lastAttempt;
|
|
76933
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
76934
|
+
this.tabs = await session.listTabs();
|
|
76935
|
+
const activeTab = this.requireActiveTab();
|
|
76936
|
+
const snapshot = await session.snapshot(request);
|
|
76937
|
+
const refreshedTabs = await session.listTabs();
|
|
76938
|
+
this.tabs = refreshedTabs;
|
|
76939
|
+
const currentActiveTab = refreshedTabs.find((tab) => tab.active) ?? refreshedTabs.find((tab) => tab.id === activeTab.id) ?? activeTab;
|
|
76940
|
+
const captured = {
|
|
76941
|
+
activeTab,
|
|
76942
|
+
currentActiveTab,
|
|
76943
|
+
snapshot
|
|
76944
|
+
};
|
|
76945
|
+
lastAttempt = captured;
|
|
76946
|
+
if (!snapshot.retryable || snapshot.text.trim().length > 0 || currentActiveTab.url === "about:blank") return captured;
|
|
76947
|
+
await delay(150 * (attempt + 1));
|
|
76948
|
+
}
|
|
76949
|
+
if (!lastAttempt) throw new McpToolError("action_failed", "Unable to capture page snapshot.");
|
|
76950
|
+
return lastAttempt;
|
|
76471
76951
|
}
|
|
76472
76952
|
async click(target, opts) {
|
|
76473
76953
|
const session = this.requireConnected();
|
|
@@ -76486,6 +76966,7 @@ var McpRuntime = class {
|
|
|
76486
76966
|
this.invalidateSnapshot();
|
|
76487
76967
|
this.pendingFileUploadTarget = opensFileChooser ? resolved : void 0;
|
|
76488
76968
|
if (this.snapshotMode === "none") return;
|
|
76969
|
+
if (await session.hasDialog()) return;
|
|
76489
76970
|
return this.snapshot();
|
|
76490
76971
|
}
|
|
76491
76972
|
async hover(target) {
|
|
@@ -76495,13 +76976,16 @@ var McpRuntime = class {
|
|
|
76495
76976
|
this.invalidateSnapshot();
|
|
76496
76977
|
this.pendingFileUploadTarget = void 0;
|
|
76497
76978
|
if (this.snapshotMode === "none") return;
|
|
76979
|
+
if (await session.hasDialog()) return;
|
|
76498
76980
|
return this.snapshot();
|
|
76499
76981
|
}
|
|
76500
76982
|
async navigate(url) {
|
|
76501
|
-
|
|
76983
|
+
const session = this.requireConnected();
|
|
76984
|
+
await session.navigate(normalizeNavigationUrl(url));
|
|
76502
76985
|
this.invalidateSnapshot();
|
|
76503
76986
|
this.pendingFileUploadTarget = void 0;
|
|
76504
76987
|
if (this.snapshotMode === "none") return;
|
|
76988
|
+
if (await session.hasDialog()) return;
|
|
76505
76989
|
return this.snapshot();
|
|
76506
76990
|
}
|
|
76507
76991
|
async type(ref, text, opts) {
|
|
@@ -76650,6 +77134,9 @@ var McpRuntime = class {
|
|
|
76650
77134
|
async networkRequest(index) {
|
|
76651
77135
|
return this.requireConnected().networkRequest(index);
|
|
76652
77136
|
}
|
|
77137
|
+
async fetchResponseBody(index) {
|
|
77138
|
+
return this.requireConnected().fetchResponseBody(index);
|
|
77139
|
+
}
|
|
76653
77140
|
async runCodeUnsafe(code) {
|
|
76654
77141
|
const result = await this.requireConnected().runCodeUnsafe(code);
|
|
76655
77142
|
this.invalidateSnapshot();
|
|
@@ -76686,6 +77173,10 @@ var McpRuntime = class {
|
|
|
76686
77173
|
hasPendingFileUploadTarget() {
|
|
76687
77174
|
return !!this.pendingFileUploadTarget;
|
|
76688
77175
|
}
|
|
77176
|
+
async hasDialog() {
|
|
77177
|
+
if (!this.connection) return false;
|
|
77178
|
+
return this.connection.session.hasDialog();
|
|
77179
|
+
}
|
|
76689
77180
|
async close() {
|
|
76690
77181
|
this.invalidateSnapshot();
|
|
76691
77182
|
this.pendingFileUploadTarget = void 0;
|
|
@@ -78306,6 +78797,7 @@ async function createRoxyBrowserMcpInMemory(options = {}) {
|
|
|
78306
78797
|
await bundle.server.connect(serverTransport);
|
|
78307
78798
|
return {
|
|
78308
78799
|
server: bundle.server,
|
|
78800
|
+
runtimeManager: bundle.runtimeManager,
|
|
78309
78801
|
serverTransport,
|
|
78310
78802
|
clientTransport,
|
|
78311
78803
|
close: async () => {
|