@tontoko/fast-playwright-mcp 0.0.4

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.
Files changed (107) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +1047 -0
  3. package/cli.js +18 -0
  4. package/config.d.ts +124 -0
  5. package/index.d.ts +25 -0
  6. package/index.js +18 -0
  7. package/lib/actions.d.js +0 -0
  8. package/lib/batch/batch-executor.js +137 -0
  9. package/lib/browser-context-factory.js +252 -0
  10. package/lib/browser-server-backend.js +139 -0
  11. package/lib/config/constants.js +80 -0
  12. package/lib/config.js +405 -0
  13. package/lib/context.js +274 -0
  14. package/lib/diagnostics/common/diagnostic-base.js +63 -0
  15. package/lib/diagnostics/common/error-enrichment-utils.js +212 -0
  16. package/lib/diagnostics/common/index.js +56 -0
  17. package/lib/diagnostics/common/initialization-manager.js +210 -0
  18. package/lib/diagnostics/common/performance-tracker.js +132 -0
  19. package/lib/diagnostics/diagnostic-error.js +140 -0
  20. package/lib/diagnostics/diagnostic-level.js +123 -0
  21. package/lib/diagnostics/diagnostic-thresholds.js +347 -0
  22. package/lib/diagnostics/element-discovery.js +441 -0
  23. package/lib/diagnostics/enhanced-error-handler.js +376 -0
  24. package/lib/diagnostics/error-enrichment.js +157 -0
  25. package/lib/diagnostics/frame-reference-manager.js +179 -0
  26. package/lib/diagnostics/page-analyzer.js +639 -0
  27. package/lib/diagnostics/parallel-page-analyzer.js +129 -0
  28. package/lib/diagnostics/resource-manager.js +134 -0
  29. package/lib/diagnostics/smart-config.js +482 -0
  30. package/lib/diagnostics/smart-handle.js +118 -0
  31. package/lib/diagnostics/unified-system.js +717 -0
  32. package/lib/extension/cdp-relay.js +486 -0
  33. package/lib/extension/extension-context-factory.js +74 -0
  34. package/lib/extension/main.js +41 -0
  35. package/lib/file-utils.js +42 -0
  36. package/lib/generate-keys.js +75 -0
  37. package/lib/http-server.js +50 -0
  38. package/lib/in-process-client.js +64 -0
  39. package/lib/index.js +48 -0
  40. package/lib/javascript.js +90 -0
  41. package/lib/log.js +33 -0
  42. package/lib/loop/loop-claude.js +247 -0
  43. package/lib/loop/loop-open-ai.js +222 -0
  44. package/lib/loop/loop.js +174 -0
  45. package/lib/loop/main.js +46 -0
  46. package/lib/loopTools/context.js +76 -0
  47. package/lib/loopTools/main.js +65 -0
  48. package/lib/loopTools/perform.js +40 -0
  49. package/lib/loopTools/snapshot.js +37 -0
  50. package/lib/loopTools/tool.js +26 -0
  51. package/lib/manual-promise.js +125 -0
  52. package/lib/mcp/in-process-transport.js +91 -0
  53. package/lib/mcp/proxy-backend.js +127 -0
  54. package/lib/mcp/server.js +123 -0
  55. package/lib/mcp/transport.js +159 -0
  56. package/lib/package.js +28 -0
  57. package/lib/program.js +82 -0
  58. package/lib/response.js +493 -0
  59. package/lib/schemas/expectation.js +152 -0
  60. package/lib/session-log.js +210 -0
  61. package/lib/tab.js +417 -0
  62. package/lib/tools/base-tool-handler.js +141 -0
  63. package/lib/tools/batch-execute.js +150 -0
  64. package/lib/tools/common.js +65 -0
  65. package/lib/tools/console.js +60 -0
  66. package/lib/tools/diagnose/diagnose-analysis-runner.js +101 -0
  67. package/lib/tools/diagnose/diagnose-config-handler.js +130 -0
  68. package/lib/tools/diagnose/diagnose-report-builder.js +394 -0
  69. package/lib/tools/diagnose.js +147 -0
  70. package/lib/tools/dialogs.js +57 -0
  71. package/lib/tools/evaluate.js +67 -0
  72. package/lib/tools/files.js +53 -0
  73. package/lib/tools/find-elements.js +307 -0
  74. package/lib/tools/install.js +60 -0
  75. package/lib/tools/keyboard.js +93 -0
  76. package/lib/tools/mouse.js +110 -0
  77. package/lib/tools/navigate.js +82 -0
  78. package/lib/tools/network.js +50 -0
  79. package/lib/tools/pdf.js +46 -0
  80. package/lib/tools/screenshot.js +113 -0
  81. package/lib/tools/snapshot.js +158 -0
  82. package/lib/tools/tabs.js +97 -0
  83. package/lib/tools/tool.js +47 -0
  84. package/lib/tools/utils.js +131 -0
  85. package/lib/tools/wait.js +64 -0
  86. package/lib/tools.js +65 -0
  87. package/lib/types/batch.js +47 -0
  88. package/lib/types/diff.js +0 -0
  89. package/lib/types/performance.js +0 -0
  90. package/lib/types/threshold-base.js +0 -0
  91. package/lib/utils/array-utils.js +44 -0
  92. package/lib/utils/code-deduplication-utils.js +141 -0
  93. package/lib/utils/common-formatters.js +252 -0
  94. package/lib/utils/console-filter.js +64 -0
  95. package/lib/utils/diagnostic-report-utils.js +178 -0
  96. package/lib/utils/diff-formatter.js +126 -0
  97. package/lib/utils/disposable-manager.js +135 -0
  98. package/lib/utils/error-handler-middleware.js +77 -0
  99. package/lib/utils/image-processor.js +137 -0
  100. package/lib/utils/index.js +92 -0
  101. package/lib/utils/report-builder.js +189 -0
  102. package/lib/utils/request-logger.js +82 -0
  103. package/lib/utils/response-diff-detector.js +150 -0
  104. package/lib/utils/section-builder.js +62 -0
  105. package/lib/utils/tool-patterns.js +153 -0
  106. package/lib/utils.js +46 -0
  107. package/package.json +77 -0
package/lib/tab.js ADDED
@@ -0,0 +1,417 @@
1
+ import { createRequire } from "node:module";
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __toESM = (mod, isNodeMode, target) => {
8
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
9
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
+ for (let key of __getOwnPropNames(mod))
11
+ if (!__hasOwnProp.call(to, key))
12
+ __defProp(to, key, {
13
+ get: () => mod[key],
14
+ enumerable: true
15
+ });
16
+ return to;
17
+ };
18
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
19
+
20
+ // src/tab.ts
21
+ import { EventEmitter } from "node:events";
22
+ import debug from "debug";
23
+ import { TIMEOUTS } from "./config/constants.js";
24
+ import { logUnhandledError } from "./log.js";
25
+ import { ManualPromise } from "./manual-promise.js";
26
+ import { callOnPageNoTrace, waitForCompletion } from "./tools/utils.js";
27
+ var REF_VALUE_REGEX = /\[ref=([^\]]+)\]/;
28
+ var TabEvents = {
29
+ modalState: "modalState"
30
+ };
31
+ var snapshotDebug = debug("pw:mcp:snapshot");
32
+
33
+ class Tab extends EventEmitter {
34
+ context;
35
+ page;
36
+ _lastTitle = "about:blank";
37
+ _consoleMessages = [];
38
+ _recentConsoleMessages = [];
39
+ _requests = new Map;
40
+ _onPageClose;
41
+ _modalStates = [];
42
+ _downloads = [];
43
+ _navigationState = {
44
+ isNavigating: false,
45
+ lastNavigationStart: 0
46
+ };
47
+ constructor(context, page, onPageClose) {
48
+ super();
49
+ this.context = context;
50
+ this.page = page;
51
+ this._onPageClose = onPageClose;
52
+ page.on("console", (event) => this._handleConsoleMessage(messageToConsoleMessage(event)));
53
+ page.on("pageerror", (error) => this._handleConsoleMessage(pageErrorToConsoleMessage(error)));
54
+ page.on("request", (request) => this._requests.set(request, null));
55
+ page.on("response", (response) => this._requests.set(response.request(), response));
56
+ page.on("close", () => this._onClose());
57
+ page.on("filechooser", (chooser) => {
58
+ this.setModalState({
59
+ type: "fileChooser",
60
+ description: "File chooser",
61
+ fileChooser: chooser
62
+ });
63
+ });
64
+ page.on("dialog", (dialog) => this._dialogShown(dialog));
65
+ page.on("download", (download) => {
66
+ this._downloadStarted(download).catch(() => {});
67
+ });
68
+ page.on("framenavigated", (frame) => {
69
+ if (frame === page.mainFrame()) {
70
+ this._handleNavigationStart();
71
+ }
72
+ });
73
+ page.on("load", () => {
74
+ this._handleNavigationComplete();
75
+ });
76
+ page.on("domcontentloaded", () => {
77
+ this._navigationState.isNavigating = true;
78
+ });
79
+ page.setDefaultNavigationTimeout(60000);
80
+ page.setDefaultTimeout(TIMEOUTS.DEFAULT_PAGE_TIMEOUT);
81
+ page[tabSymbol] = this;
82
+ }
83
+ static forPage(page) {
84
+ return page[tabSymbol];
85
+ }
86
+ modalStates() {
87
+ return this._modalStates;
88
+ }
89
+ setModalState(modalState) {
90
+ this._modalStates.push(modalState);
91
+ this.emit(TabEvents.modalState, modalState);
92
+ }
93
+ clearModalState(modalState) {
94
+ this._modalStates = this._modalStates.filter((state) => state !== modalState);
95
+ }
96
+ modalStatesMarkdown() {
97
+ return renderModalStates(this.context, this.modalStates());
98
+ }
99
+ _dialogShown(dialog) {
100
+ this.setModalState({
101
+ type: "dialog",
102
+ description: `"${dialog.type()}" dialog with message "${dialog.message()}"`,
103
+ dialog
104
+ });
105
+ }
106
+ async _downloadStarted(download) {
107
+ const entry = {
108
+ download,
109
+ finished: false,
110
+ outputFile: await this.context.outputFile(download.suggestedFilename())
111
+ };
112
+ this._downloads.push(entry);
113
+ await download.saveAs(entry.outputFile);
114
+ entry.finished = true;
115
+ }
116
+ _clearCollectedArtifacts() {
117
+ this._consoleMessages.length = 0;
118
+ this._recentConsoleMessages.length = 0;
119
+ this._requests.clear();
120
+ }
121
+ _handleConsoleMessage(message) {
122
+ this._consoleMessages.push(message);
123
+ this._recentConsoleMessages.push(message);
124
+ }
125
+ _onClose() {
126
+ this._clearCollectedArtifacts();
127
+ this._onPageClose(this);
128
+ }
129
+ async updateTitle() {
130
+ await this._raceAgainstModalStates(async () => {
131
+ this._lastTitle = await callOnPageNoTrace(this.page, (page) => page.title());
132
+ });
133
+ }
134
+ lastTitle() {
135
+ return this._lastTitle;
136
+ }
137
+ isCurrentTab() {
138
+ return this === this.context.currentTab();
139
+ }
140
+ async waitForLoadState(state, options) {
141
+ await callOnPageNoTrace(this.page, (page) => page.waitForLoadState(state, options).catch(logUnhandledError));
142
+ }
143
+ _handleNavigationStart() {
144
+ this._navigationState.isNavigating = true;
145
+ this._navigationState.lastNavigationStart = Date.now();
146
+ this._navigationState.navigationPromise = this._createNavigationPromise();
147
+ }
148
+ _handleNavigationComplete() {
149
+ this._navigationState.isNavigating = false;
150
+ }
151
+ _createNavigationPromise() {
152
+ return new Promise((resolve) => {
153
+ const checkComplete = () => {
154
+ if (!this._navigationState.isNavigating) {
155
+ resolve();
156
+ return;
157
+ }
158
+ if (Date.now() - this._navigationState.lastNavigationStart > getNavigationTimeouts().navigationTimeout) {
159
+ this._navigationState.isNavigating = false;
160
+ resolve();
161
+ return;
162
+ }
163
+ setTimeout(checkComplete, getNavigationTimeouts().checkInterval);
164
+ };
165
+ setTimeout(checkComplete, getNavigationTimeouts().checkInterval);
166
+ });
167
+ }
168
+ isNavigating() {
169
+ const isStale = Date.now() - this._navigationState.lastNavigationStart > getNavigationTimeouts().staleTimeout;
170
+ if (isStale && this._navigationState.isNavigating) {
171
+ this._navigationState.isNavigating = false;
172
+ }
173
+ return this._navigationState.isNavigating;
174
+ }
175
+ async waitForNavigationComplete() {
176
+ if (this._navigationState.navigationPromise) {
177
+ await this._navigationState.navigationPromise;
178
+ }
179
+ }
180
+ async navigate(url) {
181
+ this._clearCollectedArtifacts();
182
+ const downloadEvent = callOnPageNoTrace(this.page, (page) => page.waitForEvent("download").catch(logUnhandledError));
183
+ try {
184
+ await this.page.goto(url, { waitUntil: "domcontentloaded" });
185
+ } catch (_e) {
186
+ const e = _e;
187
+ const mightBeDownload = e.message.includes("net::ERR_ABORTED") || e.message.includes("Download is starting");
188
+ if (!mightBeDownload) {
189
+ throw e;
190
+ }
191
+ const download = await Promise.race([
192
+ downloadEvent,
193
+ new Promise((resolve) => setTimeout(resolve, TIMEOUTS.LONG_DELAY))
194
+ ]);
195
+ if (!download) {
196
+ throw e;
197
+ }
198
+ await new Promise((resolve) => setTimeout(resolve, TIMEOUTS.SHORT_DELAY));
199
+ return;
200
+ }
201
+ await this.waitForLoadState("load", {
202
+ timeout: TIMEOUTS.LOAD_STATE_TIMEOUT
203
+ });
204
+ }
205
+ consoleMessages() {
206
+ return this._consoleMessages;
207
+ }
208
+ requests() {
209
+ return this._requests;
210
+ }
211
+ async captureSnapshot() {
212
+ return await this._captureSnapshotInternal();
213
+ }
214
+ async capturePartialSnapshot(selector, maxLength) {
215
+ return await this._captureSnapshotInternal(selector, maxLength);
216
+ }
217
+ async _captureSnapshotInternal(selector, maxLength) {
218
+ let tabSnapshot;
219
+ const modalStates = await this._raceAgainstModalStates(async () => {
220
+ let snapshot;
221
+ if (selector) {
222
+ const fullSnapshot = await this.page._snapshotForAI();
223
+ snapshot = this._extractPartialSnapshot(fullSnapshot, selector);
224
+ } else {
225
+ snapshot = await this.page._snapshotForAI();
226
+ }
227
+ if (maxLength && snapshot.length > maxLength) {
228
+ snapshot = this._truncateAtWordBoundary(snapshot, maxLength);
229
+ }
230
+ tabSnapshot = {
231
+ url: this.page.url(),
232
+ title: await this.page.title(),
233
+ ariaSnapshot: snapshot,
234
+ modalStates: [],
235
+ consoleMessages: [],
236
+ downloads: this._downloads
237
+ };
238
+ });
239
+ if (tabSnapshot) {
240
+ tabSnapshot.consoleMessages = this._recentConsoleMessages;
241
+ this._recentConsoleMessages = [];
242
+ }
243
+ return tabSnapshot ?? {
244
+ url: this.page.url(),
245
+ title: "",
246
+ ariaSnapshot: "",
247
+ modalStates,
248
+ consoleMessages: [],
249
+ downloads: []
250
+ };
251
+ }
252
+ _extractPartialSnapshot(fullSnapshot, selector) {
253
+ const lines = fullSnapshot.split(`
254
+ `);
255
+ const selectorToRole = {
256
+ main: "main",
257
+ header: "banner",
258
+ footer: "contentinfo",
259
+ nav: "navigation",
260
+ aside: "complementary",
261
+ section: "region",
262
+ article: "article"
263
+ };
264
+ const expectedRole = selectorToRole[selector] ?? selector;
265
+ let capturing = false;
266
+ let captureIndent = -1;
267
+ const capturedLines = [];
268
+ for (const line of lines) {
269
+ const indent = line.length - line.trimStart().length;
270
+ const trimmedLine = line.trim();
271
+ const rolePattern = new RegExp(`^- ${expectedRole}\\s*(?:\\[[^\\]]*\\])*\\s*:?`);
272
+ if (!capturing && rolePattern.test(trimmedLine)) {
273
+ capturing = true;
274
+ captureIndent = indent;
275
+ capturedLines.push(line);
276
+ continue;
277
+ }
278
+ if (capturing) {
279
+ if (indent > captureIndent) {
280
+ capturedLines.push(line);
281
+ } else {
282
+ break;
283
+ }
284
+ }
285
+ }
286
+ if (capturedLines.length > 0) {
287
+ const minIndent = capturedLines[0].length - capturedLines[0].trimStart().length;
288
+ const normalizedLines = capturedLines.map((line) => {
289
+ if (line.trim() === "") {
290
+ return line;
291
+ }
292
+ const currentIndent = line.length - line.trimStart().length;
293
+ const newIndent = Math.max(0, currentIndent - minIndent);
294
+ return " ".repeat(newIndent) + line.trimStart();
295
+ });
296
+ return normalizedLines.join(`
297
+ `);
298
+ }
299
+ snapshotDebug('Selector "%s" not found in snapshot, returning full snapshot', selector);
300
+ return fullSnapshot;
301
+ }
302
+ _truncateAtWordBoundary(text, maxLength) {
303
+ if (text.length <= maxLength) {
304
+ return text;
305
+ }
306
+ let truncateIndex = maxLength;
307
+ if (text[maxLength] && text[maxLength] !== " " && text[maxLength] !== `
308
+ `) {
309
+ for (let i = maxLength - 1;i >= 0; i--) {
310
+ if (text[i] === " " || text[i] === `
311
+ `) {
312
+ truncateIndex = i;
313
+ break;
314
+ }
315
+ }
316
+ if (maxLength - truncateIndex > 20) {
317
+ truncateIndex = maxLength;
318
+ }
319
+ }
320
+ let result = text.substring(0, truncateIndex).trim();
321
+ if (result.length > maxLength) {
322
+ result = result.substring(0, maxLength);
323
+ }
324
+ return result;
325
+ }
326
+ _javaScriptBlocked() {
327
+ return this._modalStates.some((state) => state.type === "dialog");
328
+ }
329
+ async _raceAgainstModalStates(action) {
330
+ if (this.modalStates().length) {
331
+ return this.modalStates();
332
+ }
333
+ const promise = new ManualPromise;
334
+ const listener = (modalState) => promise.resolve([modalState]);
335
+ this.once(TabEvents.modalState, listener);
336
+ return await Promise.race([
337
+ action().then(() => {
338
+ this.off(TabEvents.modalState, listener);
339
+ return [];
340
+ }),
341
+ promise
342
+ ]);
343
+ }
344
+ async waitForCompletion(callback) {
345
+ await this._raceAgainstModalStates(() => waitForCompletion(this, callback));
346
+ }
347
+ async refLocator(params) {
348
+ return (await this.refLocators([params]))[0];
349
+ }
350
+ async refLocators(params) {
351
+ const snapshot = await this.page._snapshotForAI();
352
+ return params.map((param) => {
353
+ if (!snapshot.includes(`[ref=${param.ref}]`)) {
354
+ const availableRefs = this._getAvailableRefs(snapshot);
355
+ throw new Error(`Ref ${param.ref} not found. Available refs: [${availableRefs.join(", ")}]. Element: ${param.element}. Consider capturing a new snapshot if the page has changed.`);
356
+ }
357
+ return this.page.locator(`aria-ref=${param.ref}`).describe(param.element);
358
+ });
359
+ }
360
+ async waitForTimeout(time) {
361
+ if (this._javaScriptBlocked()) {
362
+ await new Promise((f) => setTimeout(f, time));
363
+ return;
364
+ }
365
+ await callOnPageNoTrace(this.page, (page) => {
366
+ return page.evaluate((timeout) => new Promise((f) => setTimeout(f, timeout)), time);
367
+ });
368
+ }
369
+ _getAvailableRefs(snapshot) {
370
+ const refMatches = snapshot.match(/\[ref=([^\]]+)\]/g);
371
+ if (!refMatches) {
372
+ return [];
373
+ }
374
+ return refMatches.map((match) => {
375
+ const refValue = REF_VALUE_REGEX.exec(match);
376
+ return refValue ? refValue[1] : "";
377
+ }).filter(Boolean);
378
+ }
379
+ }
380
+ function messageToConsoleMessage(message) {
381
+ return {
382
+ type: message.type(),
383
+ text: message.text(),
384
+ toString: () => `[${message.type().toUpperCase()}] ${message.text()} @ ${message.location().url}:${message.location().lineNumber}`
385
+ };
386
+ }
387
+ function pageErrorToConsoleMessage(error) {
388
+ return {
389
+ type: undefined,
390
+ text: error.message,
391
+ toString: () => error.stack ?? error.message
392
+ };
393
+ }
394
+ function renderModalStates(context, modalStates) {
395
+ const result = ["### Modal state"];
396
+ if (modalStates.length === 0) {
397
+ result.push("- There is no modal state present");
398
+ }
399
+ for (const state of modalStates) {
400
+ const tool = context.tools.filter((t) => ("clearsModalState" in t)).find((t) => t.clearsModalState === state.type);
401
+ result.push(`- [${state.description}]: can be handled by the "${tool?.schema.name}" tool`);
402
+ }
403
+ return result;
404
+ }
405
+ var tabSymbol = Symbol("tabSymbol");
406
+ function getNavigationTimeouts() {
407
+ return {
408
+ navigationTimeout: TIMEOUTS.DEFAULT_PAGE_TIMEOUT,
409
+ checkInterval: 100,
410
+ staleTimeout: 1e4
411
+ };
412
+ }
413
+ export {
414
+ renderModalStates,
415
+ TabEvents,
416
+ Tab
417
+ };
@@ -0,0 +1,141 @@
1
+ import { createRequire } from "node:module";
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __toESM = (mod, isNodeMode, target) => {
8
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
9
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
+ for (let key of __getOwnPropNames(mod))
11
+ if (!__hasOwnProp.call(to, key))
12
+ __defProp(to, key, {
13
+ get: () => mod[key],
14
+ enumerable: true
15
+ });
16
+ return to;
17
+ };
18
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
19
+
20
+ // src/tools/base-tool-handler.ts
21
+ import { z } from "zod";
22
+ import { expectationSchema } from "../schemas/expectation.js";
23
+ import { getErrorMessage } from "../utils/common-formatters.js";
24
+ var baseElementSchema = z.object({
25
+ element: z.string().optional().describe("Human-readable element description used to obtain permission to interact with the element"),
26
+ ref: z.string().optional().describe("Exact target element reference from the page snapshot")
27
+ });
28
+ var baseExpectationSchema = z.object({
29
+ expectation: expectationSchema
30
+ });
31
+
32
+ class BaseToolHandler {
33
+ toolName;
34
+ constructor(toolName) {
35
+ this.toolName = toolName;
36
+ }
37
+ async handle(tab, params, response) {
38
+ try {
39
+ this.handleExpectation(params, response);
40
+ await this.executeToolLogic(tab, params, response);
41
+ } catch (error) {
42
+ this.handleToolError(error, response, params);
43
+ throw error;
44
+ }
45
+ }
46
+ handleExpectation(params, response) {
47
+ if (params.expectation) {
48
+ this.applyExpectationToResponse(params.expectation, response);
49
+ }
50
+ }
51
+ applyExpectationToResponse(expectation, response) {
52
+ const exp = expectation;
53
+ if (exp.includeSnapshot !== false) {
54
+ response.setIncludeSnapshot();
55
+ }
56
+ }
57
+ handleToolError(error, response, params) {
58
+ const errorMessage = getErrorMessage(error);
59
+ response.addError(`Error in ${this.toolName}: ${errorMessage}`);
60
+ if ("element" in params && params.element) {
61
+ response.addResult(`Element context: ${params.element}`);
62
+ }
63
+ if ("ref" in params && params.ref) {
64
+ response.addResult(`Reference: ${params.ref}`);
65
+ }
66
+ }
67
+ addCodeComment(response, operation, context) {
68
+ const comment = context ? `// ${operation} - ${context}` : `// ${operation}`;
69
+ response.addCode(comment);
70
+ }
71
+ async waitForCompletion(tab, operation, operationName) {
72
+ try {
73
+ await tab.waitForCompletion(operation);
74
+ } catch (error) {
75
+ const operation_name = operationName ?? "operation";
76
+ throw new Error(`${this.toolName} ${operation_name} failed: ${getErrorMessage(error)}`);
77
+ }
78
+ }
79
+ }
80
+
81
+ class BaseElementToolHandler extends BaseToolHandler {
82
+ async resolveElementLocator(tab, params) {
83
+ if (params.ref && params.element) {
84
+ return await tab.refLocator({
85
+ ref: params.ref,
86
+ element: params.element
87
+ });
88
+ }
89
+ }
90
+ validateElementParams(params) {
91
+ if (params.ref && !params.element) {
92
+ throw new Error("Element description is required when ref is provided");
93
+ }
94
+ if (params.element && !params.ref) {
95
+ throw new Error("Element ref is required when element description is provided");
96
+ }
97
+ }
98
+ handleToolError(error, response, params) {
99
+ super.handleToolError(error, response, params);
100
+ if (params.element && params.ref) {
101
+ response.addResult("Suggestion: Verify element selector is still valid");
102
+ response.addResult("Suggestion: Check if element is visible and interactable");
103
+ }
104
+ }
105
+ }
106
+ function createToolWithBaseHandler(config, handlerClass) {
107
+ const handler = new handlerClass(config.name);
108
+ return {
109
+ capability: config.capability ?? "core",
110
+ schema: {
111
+ name: config.name,
112
+ title: config.title,
113
+ description: config.description,
114
+ inputSchema: config.inputSchema,
115
+ type: config.type ?? "readOnly"
116
+ },
117
+ handle: async (tab, params, response) => {
118
+ await handler.handle(tab, params, response);
119
+ }
120
+ };
121
+ }
122
+ var mouseCoordinateSchema = baseExpectationSchema.extend({
123
+ x: z.number().describe("X coordinate"),
124
+ y: z.number().describe("Y coordinate")
125
+ });
126
+ var navigationSchema = baseExpectationSchema.extend({
127
+ url: z.string().describe("The URL to navigate to")
128
+ });
129
+ var keyboardSchema = baseExpectationSchema.extend({
130
+ key: z.string().describe("Key to press")
131
+ });
132
+ export {
133
+ navigationSchema,
134
+ mouseCoordinateSchema,
135
+ keyboardSchema,
136
+ createToolWithBaseHandler,
137
+ baseExpectationSchema,
138
+ baseElementSchema,
139
+ BaseToolHandler,
140
+ BaseElementToolHandler
141
+ };
@@ -0,0 +1,150 @@
1
+ import { createRequire } from "node:module";
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __toESM = (mod, isNodeMode, target) => {
8
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
9
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
+ for (let key of __getOwnPropNames(mod))
11
+ if (!__hasOwnProp.call(to, key))
12
+ __defProp(to, key, {
13
+ get: () => mod[key],
14
+ enumerable: true
15
+ });
16
+ return to;
17
+ };
18
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
19
+
20
+ // src/tools/batch-execute.ts
21
+ import { batchExecuteSchema } from "../types/batch.js";
22
+ import { createBatchErrorHandler } from "../utils/error-handler-middleware.js";
23
+ import { defineTool } from "./tool.js";
24
+ var batchExecuteTool = defineTool({
25
+ capability: "core",
26
+ schema: {
27
+ name: "browser_batch_execute",
28
+ title: "Batch Execute Browser Actions",
29
+ description: `Execute multiple browser actions in sequence with optimized response handling.RECOMMENDED:Use this tool instead of individual actions when performing multiple operations to significantly reduce token usage and improve performance.BY DEFAULT use for:form filling(multiple type→click),multi-step navigation,any workflow with 2+ known steps.Saves 90% tokens vs individual calls.globalExpectation:{includeSnapshot:false,snapshotOptions:{selector:"#app"},diffOptions:{enabled:true}}.Per-step override:steps[].expectation.Example:[{tool:"browser_navigate",arguments:{url:"https://example.com"}},{tool:"browser_type",arguments:{element:"username",ref:"#user",text:"john"}},{tool:"browser_click",arguments:{element:"submit",ref:"#btn"}}].`,
30
+ inputSchema: batchExecuteSchema,
31
+ type: "destructive"
32
+ },
33
+ handle: async (context, params, response) => {
34
+ try {
35
+ const batchExecutor = getBatchExecutorOrError(context, response);
36
+ if (!batchExecutor) {
37
+ return;
38
+ }
39
+ const result = await batchExecutor.execute(params);
40
+ processExecutionResult(result, response);
41
+ } catch (error) {
42
+ const errorHandler = createBatchErrorHandler("BatchExecute");
43
+ const enrichedError = errorHandler(error);
44
+ response.addError(enrichedError.message);
45
+ }
46
+ }
47
+ });
48
+ function getBatchExecutorOrError(context, response) {
49
+ const batchExecutor = context.getBatchExecutor();
50
+ if (!batchExecutor) {
51
+ response.addError("Batch executor not available. Please ensure the browser context is properly initialized.");
52
+ return null;
53
+ }
54
+ return batchExecutor;
55
+ }
56
+ function processExecutionResult(result, response) {
57
+ response.addResult(formatBatchResult(result));
58
+ if (result.steps.length > 0) {
59
+ addStepDetails(result, response);
60
+ }
61
+ addFinalStateIfNeeded(result, response);
62
+ handleExecutionErrors(result, response);
63
+ }
64
+ function addStepDetails(result, response) {
65
+ response.addResult("");
66
+ response.addResult("### Step Details");
67
+ for (const stepResult of result.steps) {
68
+ addStepResult(stepResult, response);
69
+ }
70
+ }
71
+ function addStepResult(stepResult, response) {
72
+ const status = stepResult.success ? "✅" : "❌";
73
+ const duration = `${stepResult.executionTimeMs}ms`;
74
+ response.addResult(`${status} Step ${stepResult.stepIndex + 1}: ${stepResult.toolName} (${duration})`);
75
+ if (stepResult.success && stepResult.result) {
76
+ addSuccessfulStepContent(stepResult, response);
77
+ } else if (!stepResult.success && stepResult.error) {
78
+ response.addResult(` Error: ${stepResult.error}`);
79
+ }
80
+ }
81
+ function addSuccessfulStepContent(stepResult, response) {
82
+ const stepContent = stepResult.result;
83
+ const textContent = stepContent.content?.[0]?.text;
84
+ if (typeof textContent === "string") {
85
+ const lines = textContent.split(`
86
+ `).slice(0, 3);
87
+ response.addResult(` ${lines.join(`
88
+ `)}`);
89
+ if (textContent.split(`
90
+ `).length > 3) {
91
+ response.addResult(" ...");
92
+ }
93
+ }
94
+ }
95
+ function addFinalStateIfNeeded(result, response) {
96
+ const successfulStepsWithContent = getSuccessfulStepsWithContent(result);
97
+ if (successfulStepsWithContent.length > 0 && result.stopReason === "completed") {
98
+ response.addResult("");
99
+ response.addResult("### Final State");
100
+ const lastStep = successfulStepsWithContent.at(-1);
101
+ const finalContent = extractFinalStepContent(lastStep);
102
+ if (finalContent) {
103
+ response.addResult(finalContent);
104
+ }
105
+ }
106
+ }
107
+ function getSuccessfulStepsWithContent(result) {
108
+ return result.steps.filter((s) => s.success && s.result && typeof s.result === "object" && ("content" in s.result) && Array.isArray(s.result.content) && s.result.content[0]?.text && !(("isError" in s.result) && s.result.isError));
109
+ }
110
+ function extractFinalStepContent(lastStep) {
111
+ if (lastStep?.result && typeof lastStep.result === "object" && "content" in lastStep.result && Array.isArray(lastStep.result.content) && lastStep.result.content[0]?.text) {
112
+ return lastStep.result.content[0].text;
113
+ }
114
+ return null;
115
+ }
116
+ function handleExecutionErrors(result, response) {
117
+ if (result.stopReason === "error" || result.failedSteps > 0) {
118
+ const errorMessage = result.stopReason === "error" ? "stopped due to error" : "completed with failures";
119
+ response.addError(`Batch execution ${errorMessage}`);
120
+ }
121
+ }
122
+ function formatBatchResult(result) {
123
+ const lines = [];
124
+ lines.push("### Batch Execution Summary");
125
+ lines.push(`- Status: ${getStatusDisplay(result.stopReason)}`);
126
+ lines.push(`- Total Steps: ${result.totalSteps}`);
127
+ lines.push(`- Successful: ${result.successfulSteps}`);
128
+ lines.push(`- Failed: ${result.failedSteps}`);
129
+ lines.push(`- Total Time: ${result.totalExecutionTimeMs}ms`);
130
+ if (result.stopReason === "error") {
131
+ lines.push("- Note: Execution stopped early due to error");
132
+ }
133
+ return lines.join(`
134
+ `);
135
+ }
136
+ function getStatusDisplay(stopReason) {
137
+ switch (stopReason) {
138
+ case "completed":
139
+ return "✅ Completed";
140
+ case "error":
141
+ return "❌ Stopped on Error";
142
+ case "stopped":
143
+ return "⏹️ Stopped";
144
+ default:
145
+ return "❓ Unknown";
146
+ }
147
+ }
148
+ export {
149
+ batchExecuteTool
150
+ };