chrometools-mcp 3.2.10 ā 3.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +169 -0
- package/README.md +14 -5
- package/angular-tools.js +9 -3
- package/bridge/bridge-client.js +62 -7
- package/bridge/bridge-service.js +80 -2
- package/browser/page-manager.js +83 -0
- package/extension/background.js +117 -0
- package/extension/content.js +3 -1
- package/extension/manifest.json +2 -1
- package/index.js +284 -48
- package/models/TextInputModel.js +56 -5
- package/models/index.js +20 -6
- package/nul +0 -0
- package/package.json +1 -1
- package/pom/apom-tree-converter.js +308 -39
- package/server/tool-definitions.js +3 -1
- package/server/tool-schemas.js +5 -0
- package/utils/hints-generator.js +46 -4
- package/utils/post-click-diagnostics.js +146 -47
|
@@ -4,51 +4,74 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { consoleLogs, networkRequests } from '../browser/page-manager.js';
|
|
7
|
+
import { getNetworkRequestsFromBridge, isBridgeConnected } from '../bridge/bridge-client.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
|
-
* Wait for
|
|
10
|
+
* Wait for network requests to complete
|
|
11
|
+
* Tracks all requests (GET, POST, PUT, PATCH, DELETE) that started within detection window
|
|
10
12
|
* @param {number} beforeActionTimestamp - Timestamp before action to track new requests
|
|
11
|
-
* @param {number}
|
|
12
|
-
* @param {number} maxWaitMs - Maximum time to wait for requests (default:
|
|
13
|
+
* @param {number} detectionWindowMs - Time window to detect requests (default: 200ms)
|
|
14
|
+
* @param {number} maxWaitMs - Maximum time to wait for requests (default: 10000ms)
|
|
13
15
|
* @returns {Promise<{pendingFound: boolean, waitedMs: number, completedRequests: number, totalRequests: number}>}
|
|
14
16
|
*/
|
|
15
|
-
export async function waitForPendingRequests(beforeActionTimestamp,
|
|
17
|
+
export async function waitForPendingRequests(beforeActionTimestamp, detectionWindowMs = 200, maxWaitMs = 10000) {
|
|
16
18
|
const startTime = Date.now();
|
|
17
19
|
|
|
18
|
-
// Step 1: Wait
|
|
19
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
20
|
+
// Step 1: Wait for detection window to let requests start
|
|
21
|
+
await new Promise(resolve => setTimeout(resolve, detectionWindowMs));
|
|
20
22
|
|
|
21
|
-
// Step 2:
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
// Step 2: Find all requests (GET, POST, PUT, PATCH, DELETE) that started within detection window
|
|
24
|
+
const cutoffStart = new Date(beforeActionTimestamp).toISOString();
|
|
25
|
+
const cutoffEnd = new Date(beforeActionTimestamp + detectionWindowMs).toISOString();
|
|
26
|
+
|
|
27
|
+
const trackedRequests = networkRequests.filter(req => {
|
|
28
|
+
// Track all HTTP methods
|
|
29
|
+
if (!['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
// Only requests in detection window [T0, T0+200ms]
|
|
33
|
+
return req.timestamp >= cutoffStart && req.timestamp <= cutoffEnd;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// If no requests found, return immediately
|
|
37
|
+
if (trackedRequests.length === 0) {
|
|
38
|
+
return {
|
|
39
|
+
pendingFound: false,
|
|
40
|
+
waitedMs: Date.now() - startTime,
|
|
41
|
+
completedRequests: 0,
|
|
42
|
+
stillPending: 0,
|
|
43
|
+
pendingRequests: [],
|
|
44
|
+
totalRequests: 0,
|
|
45
|
+
trackedRequests: []
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Step 3: Wait for these specific requests to complete
|
|
50
|
+
const trackedRequestIds = new Set(trackedRequests.map(req => req.requestId));
|
|
26
51
|
|
|
27
|
-
// Step 3: Check for pending requests (from post-action requests)
|
|
28
52
|
const checkPending = () => {
|
|
29
|
-
return
|
|
53
|
+
return networkRequests.filter(req =>
|
|
54
|
+
trackedRequestIds.has(req.requestId) && req.status === 'pending'
|
|
55
|
+
);
|
|
30
56
|
};
|
|
31
57
|
|
|
32
58
|
let pending = checkPending();
|
|
33
|
-
let allPostActionRequests = getPostActionRequests();
|
|
34
59
|
const initialPendingCount = pending.length;
|
|
35
60
|
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
await new Promise(resolve => setTimeout(resolve, 100)); // Check every 100ms
|
|
41
|
-
pending = checkPending();
|
|
42
|
-
allPostActionRequests = getPostActionRequests(); // Update total count
|
|
43
|
-
}
|
|
61
|
+
// Wait for requests to complete (with configurable timeout)
|
|
62
|
+
while (pending.length > 0 && (Date.now() - startTime) < maxWaitMs) {
|
|
63
|
+
await new Promise(resolve => setTimeout(resolve, 100)); // Check every 100ms
|
|
64
|
+
pending = checkPending();
|
|
44
65
|
}
|
|
45
66
|
|
|
46
|
-
|
|
67
|
+
// Collect final results for tracked requests
|
|
68
|
+
const finalRequests = networkRequests.filter(req => trackedRequestIds.has(req.requestId));
|
|
47
69
|
const completedRequests = finalRequests.filter(req => req.status === 'completed' || (typeof req.status === 'number'));
|
|
48
|
-
const
|
|
70
|
+
const stillPendingRequests = pending.map(req => ({
|
|
49
71
|
url: req.url,
|
|
50
72
|
method: req.method,
|
|
51
|
-
timestamp: req.timestamp
|
|
73
|
+
timestamp: req.timestamp,
|
|
74
|
+
status: 'pending' // Still waiting after timeout
|
|
52
75
|
}));
|
|
53
76
|
|
|
54
77
|
return {
|
|
@@ -56,8 +79,14 @@ export async function waitForPendingRequests(beforeActionTimestamp, initialWaitM
|
|
|
56
79
|
waitedMs: Date.now() - startTime,
|
|
57
80
|
completedRequests: completedRequests.length,
|
|
58
81
|
stillPending: pending.length,
|
|
59
|
-
pendingRequests:
|
|
60
|
-
totalRequests: finalRequests.length
|
|
82
|
+
pendingRequests: stillPendingRequests,
|
|
83
|
+
totalRequests: finalRequests.length,
|
|
84
|
+
trackedRequests: finalRequests.map(req => ({
|
|
85
|
+
method: req.method,
|
|
86
|
+
url: req.url,
|
|
87
|
+
status: req.status,
|
|
88
|
+
statusText: req.statusText
|
|
89
|
+
}))
|
|
61
90
|
};
|
|
62
91
|
}
|
|
63
92
|
|
|
@@ -133,19 +162,54 @@ export function collectErrors(sinceTimestamp = null, maxConsoleErrors = 15, maxN
|
|
|
133
162
|
* Full post-action diagnostics: wait for requests and collect errors
|
|
134
163
|
* @param {Page} page - Puppeteer page instance
|
|
135
164
|
* @param {number} beforeActionTimestamp - Timestamp before action (to filter errors)
|
|
165
|
+
* @param {Object} options - Options for diagnostics
|
|
166
|
+
* @param {boolean} options.skipNetworkWait - Skip waiting for network requests (default: false)
|
|
167
|
+
* @param {number} options.networkWaitTimeout - Custom timeout for network wait in ms (default: 10000)
|
|
168
|
+
* @param {string} options.urlBeforeAction - URL before action (to detect navigation/form submit)
|
|
136
169
|
* @returns {Promise<Object>} Diagnostics result with errors and network info
|
|
137
170
|
*/
|
|
138
|
-
export async function runPostClickDiagnostics(page, beforeActionTimestamp) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
171
|
+
export async function runPostClickDiagnostics(page, beforeActionTimestamp, options = {}) {
|
|
172
|
+
const { skipNetworkWait = false, networkWaitTimeout = 10000, urlBeforeAction = null } = options;
|
|
173
|
+
|
|
174
|
+
// Wait for network requests (all methods within 200ms detection window)
|
|
175
|
+
// Default maxWait = 10s for click, configurable via networkWaitTimeout parameter
|
|
176
|
+
const networkInfo = skipNetworkWait
|
|
177
|
+
? { pendingFound: false, waitedMs: 0, completedRequests: 0, stillPending: 0, pendingRequests: [], totalRequests: 0, trackedRequests: [], allRecentRequests: [] }
|
|
178
|
+
: await waitForPendingRequests(beforeActionTimestamp, 200, networkWaitTimeout);
|
|
142
179
|
|
|
143
180
|
// Small delay to let pending requests update their error status
|
|
144
181
|
// (handles case where request completes with error right after maxWait expires)
|
|
145
182
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
146
183
|
|
|
184
|
+
// Check for page navigation (indicates form submit in non-SPA apps)
|
|
185
|
+
const currentUrl = page.url();
|
|
186
|
+
let navigationDetected = null;
|
|
187
|
+
if (urlBeforeAction && currentUrl !== urlBeforeAction) {
|
|
188
|
+
navigationDetected = {
|
|
189
|
+
from: urlBeforeAction,
|
|
190
|
+
to: currentUrl,
|
|
191
|
+
likelyFormSubmit: true // Page URL changed after click - likely form POST with redirect
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Fetch network requests from Bridge (Extension webRequest API)
|
|
196
|
+
// These persist across page navigations, unlike CDP requests
|
|
197
|
+
let bridgeRequests = [];
|
|
198
|
+
if (isBridgeConnected()) {
|
|
199
|
+
try {
|
|
200
|
+
const allBridgeRequests = await getNetworkRequestsFromBridge({ timeout: 2000 });
|
|
201
|
+
// Filter to requests after beforeActionTimestamp
|
|
202
|
+
const cutoffTime = beforeActionTimestamp - 1000; // 1s buffer
|
|
203
|
+
bridgeRequests = allBridgeRequests.filter(req =>
|
|
204
|
+
req.timestamp >= cutoffTime
|
|
205
|
+
);
|
|
206
|
+
} catch (e) {
|
|
207
|
+
// Bridge not available, continue without
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
147
211
|
// Check for chrome error page (ERR_CONNECTION_REFUSED, etc.)
|
|
148
|
-
const url =
|
|
212
|
+
const url = currentUrl;
|
|
149
213
|
let chromeErrorInfo = null;
|
|
150
214
|
if (url.startsWith('chrome-error://')) {
|
|
151
215
|
chromeErrorInfo = await page.evaluate(() => {
|
|
@@ -169,8 +233,19 @@ export async function runPostClickDiagnostics(page, beforeActionTimestamp) {
|
|
|
169
233
|
stillPending: networkInfo.stillPending,
|
|
170
234
|
pendingRequests: networkInfo.pendingRequests,
|
|
171
235
|
totalRequests: networkInfo.totalRequests,
|
|
172
|
-
waitedMs: networkInfo.waitedMs
|
|
236
|
+
waitedMs: networkInfo.waitedMs,
|
|
237
|
+
trackedRequests: networkInfo.trackedRequests || [],
|
|
238
|
+
allRecentRequests: networkInfo.allRecentRequests || [],
|
|
239
|
+
// Bridge requests (from Extension webRequest API) - persist across page navigations
|
|
240
|
+
bridgeRequests: bridgeRequests.map(req => ({
|
|
241
|
+
method: req.method,
|
|
242
|
+
url: req.url,
|
|
243
|
+
type: req.type,
|
|
244
|
+
status: req.status,
|
|
245
|
+
timestamp: req.timestamp
|
|
246
|
+
}))
|
|
173
247
|
},
|
|
248
|
+
navigation: navigationDetected,
|
|
174
249
|
chromeError: chromeErrorInfo,
|
|
175
250
|
errors: {
|
|
176
251
|
consoleErrors: errors.consoleErrors,
|
|
@@ -201,25 +276,49 @@ export function formatDiagnosticsForAI(diagnostics) {
|
|
|
201
276
|
output += `\n ā Backend likely not running or unreachable`;
|
|
202
277
|
}
|
|
203
278
|
|
|
204
|
-
//
|
|
279
|
+
// Page navigation detection (form submit in non-SPA apps)
|
|
280
|
+
if (diagnostics.navigation) {
|
|
281
|
+
output += `\n\nš Page navigation detected (form submit):`;
|
|
282
|
+
output += `\n From: ${diagnostics.navigation.from}`;
|
|
283
|
+
output += `\n To: ${diagnostics.navigation.to}`;
|
|
284
|
+
output += `\n ā This indicates a successful form POST with page reload`;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Network activity - show all tracked requests (GET/POST/PUT/PATCH/DELETE)
|
|
205
288
|
const netActivity = diagnostics.networkActivity;
|
|
206
|
-
|
|
207
|
-
const errorCount = diagnostics.errors.networkErrors.length;
|
|
208
|
-
const successCount = netActivity.completedRequests - errorCount;
|
|
289
|
+
const trackedRequests = netActivity.trackedRequests || [];
|
|
209
290
|
|
|
210
|
-
|
|
291
|
+
// Show requests detected within 200ms after action
|
|
292
|
+
if (trackedRequests.length > 0) {
|
|
293
|
+
output += `\n\nš” Network requests (${trackedRequests.length}):`;
|
|
294
|
+
trackedRequests.forEach((req, idx) => {
|
|
295
|
+
const statusIcon = req.status === 'pending' ? 'ā³' :
|
|
296
|
+
(req.status === 'completed' || (typeof req.status === 'number' && req.status < 400) ? 'ā' : 'ā');
|
|
297
|
+
const statusText = req.statusText || req.status || 'pending';
|
|
298
|
+
output += `\n ${idx + 1}. ${statusIcon} ${req.method} ${req.url}`;
|
|
299
|
+
output += `\n ā Status: ${statusText}`;
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Show if some requests are still pending after timeout
|
|
211
303
|
if (netActivity.stillPending > 0) {
|
|
212
|
-
output += `\n
|
|
213
|
-
output += `\n ā±ļø Timeout: Stopped waiting after ${netActivity.waitedMs}ms`;
|
|
214
|
-
output += `\n ā ${netActivity.stillPending} request(s) still running - status unknown`;
|
|
215
|
-
output += `\n ā May complete successfully or fail - cannot determine outcome`;
|
|
216
|
-
} else if (errorCount > 0) {
|
|
217
|
-
output += `\nā ļø Network: ${successCount} OK, ${errorCount} failed (${netActivity.waitedMs}ms)`;
|
|
218
|
-
} else {
|
|
219
|
-
output += `\nā Network: ${netActivity.completedRequests} completed (${netActivity.waitedMs}ms)`;
|
|
304
|
+
output += `\n\nā³ ${netActivity.stillPending} request(s) still pending after ${Math.round(netActivity.waitedMs)}ms timeout`;
|
|
220
305
|
}
|
|
221
306
|
} else {
|
|
222
|
-
output += '\n
|
|
307
|
+
output += '\n\nš” No network requests detected within 200ms';
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Bridge requests (from Extension - persist across page reloads)
|
|
311
|
+
const bridgeRequests = netActivity.bridgeRequests || [];
|
|
312
|
+
if (bridgeRequests.length > 0) {
|
|
313
|
+
output += `\n\nš” Browser-level requests (via Extension):`;
|
|
314
|
+
bridgeRequests.forEach((req, idx) => {
|
|
315
|
+
const statusIcon = req.status === 'pending' ? 'ā³' :
|
|
316
|
+
(req.status === 'completed' || (typeof req.status === 'number' && req.status < 400) ? 'ā' : 'ā');
|
|
317
|
+
output += `\n ${idx + 1}. ${statusIcon} ${req.method} ${req.url}`;
|
|
318
|
+
if (req.status !== 'pending') {
|
|
319
|
+
output += ` ā ${req.status}`;
|
|
320
|
+
}
|
|
321
|
+
});
|
|
223
322
|
}
|
|
224
323
|
|
|
225
324
|
// Errors
|