chrome-devtools-mcp-for-extension 0.18.4 → 0.18.6

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.
@@ -10,6 +10,32 @@ import { ToolCategories } from './categories.js';
10
10
  import { defineTool } from './ToolDefinition.js';
11
11
  import { GEMINI_CONFIG } from '../config.js';
12
12
  import { isLoginRequired } from '../login-helper.js';
13
+ /**
14
+ * Navigate with retry logic for handling ERR_ABORTED and other network errors
15
+ */
16
+ async function navigateWithRetry(page, url, options = { waitUntil: 'networkidle2', maxRetries: 3 }) {
17
+ const { waitUntil, maxRetries = 3 } = options;
18
+ let lastError = null;
19
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
20
+ try {
21
+ await page.goto(url, { waitUntil, timeout: 30000 });
22
+ return; // Success
23
+ }
24
+ catch (error) {
25
+ lastError = error instanceof Error ? error : new Error(String(error));
26
+ // Check if it's a retryable error
27
+ const isRetryable = lastError.message.includes('ERR_ABORTED') ||
28
+ lastError.message.includes('ERR_CONNECTION_RESET') ||
29
+ lastError.message.includes('net::ERR_');
30
+ if (!isRetryable || attempt === maxRetries) {
31
+ throw lastError;
32
+ }
33
+ // Wait before retry (exponential backoff)
34
+ await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
35
+ }
36
+ }
37
+ throw lastError;
38
+ }
13
39
  /**
14
40
  * Path to store chat session data
15
41
  */
@@ -146,7 +172,7 @@ export const askGeminiWeb = defineTool({
146
172
  const page = context.getSelectedPage();
147
173
  try {
148
174
  response.appendResponseLine('Geminiに接続中...');
149
- await page.goto(GEMINI_CONFIG.DEFAULT_URL, { waitUntil: 'networkidle2' });
175
+ await navigateWithRetry(page, GEMINI_CONFIG.DEFAULT_URL, { waitUntil: 'networkidle2' });
150
176
  const needsLogin = await isLoginRequired(page);
151
177
  if (needsLogin) {
152
178
  response.appendResponseLine('\n❌ Geminiへのログインが必要です');
@@ -163,7 +189,7 @@ export const askGeminiWeb = defineTool({
163
189
  const sortedSessions = [...projectSessions].sort((a, b) => new Date(b.lastUsed).getTime() - new Date(a.lastUsed).getTime());
164
190
  const latestSession = sortedSessions[0];
165
191
  response.appendResponseLine(`既存のチャットを使用: ${latestSession.url}`);
166
- await page.goto(latestSession.url, { waitUntil: 'networkidle2' });
192
+ await navigateWithRetry(page, latestSession.url, { waitUntil: 'networkidle2' });
167
193
  sessionChatId = latestSession.chatId;
168
194
  await new Promise((resolve) => setTimeout(resolve, 1000));
169
195
  }
@@ -176,7 +202,7 @@ export const askGeminiWeb = defineTool({
176
202
  }
177
203
  if (isNewChat) {
178
204
  response.appendResponseLine('新規チャットを作成中...');
179
- await page.goto(GEMINI_CONFIG.BASE_URL + 'app', { waitUntil: 'networkidle2' });
205
+ await navigateWithRetry(page, GEMINI_CONFIG.BASE_URL + 'app', { waitUntil: 'networkidle2' });
180
206
  }
181
207
  response.appendResponseLine('質問を送信中...');
182
208
  // Input text using the textbox element
@@ -255,17 +281,34 @@ export const askGeminiWeb = defineTool({
255
281
  b.textContent?.includes('Stop') ||
256
282
  b.getAttribute('aria-label')?.includes('Stop'));
257
283
  // Check for "プロンプトを送信" button - this indicates response is complete
258
- const sendButton = buttons.find(b => b.textContent?.includes('プロンプトを送信') ||
259
- b.getAttribute('aria-label')?.includes('プロンプトを送信') ||
260
- b.getAttribute('aria-label')?.includes('Send message'));
261
- // Check for status text
284
+ // Must be enabled (not disabled) to indicate completion
285
+ const sendButton = buttons.find(b => {
286
+ const hasLabel = b.textContent?.includes('プロンプトを送信') ||
287
+ b.getAttribute('aria-label')?.includes('プロンプトを送信') ||
288
+ b.getAttribute('aria-label')?.includes('Send message');
289
+ return hasLabel && !b.disabled;
290
+ });
291
+ // Check for status text and thinking indicators
262
292
  const bodyText = document.body.innerText;
263
293
  const isTyping = bodyText.includes('Gemini が入力中です') ||
264
294
  bodyText.includes('Gemini is typing');
265
- const isComplete = bodyText.includes('Gemini が回答しました') ||
295
+ // Check for thinking/analyzing indicators (Gemini shows these during processing)
296
+ const isThinking = bodyText.includes('Analyzing') ||
297
+ bodyText.includes('分析中') ||
298
+ bodyText.includes('Crafting') ||
299
+ bodyText.includes('作成中') ||
300
+ bodyText.includes('Thinking') ||
301
+ bodyText.includes('思考中') ||
302
+ bodyText.includes('Researching') ||
303
+ bodyText.includes('調査中');
304
+ // Check for loading spinners or progress indicators
305
+ const hasSpinner = document.querySelector('[role="progressbar"]') !== null ||
306
+ document.querySelector('.loading') !== null ||
307
+ document.querySelector('[aria-busy="true"]') !== null;
308
+ const isComplete = (bodyText.includes('Gemini が回答しました') ||
266
309
  bodyText.includes('Gemini has responded') ||
267
- !!sendButton; // "プロンプトを送信" button indicates completion
268
- const isGenerating = !!stopButton || isTyping;
310
+ !!sendButton) && !isThinking && !hasSpinner;
311
+ const isGenerating = !!stopButton || isTyping || isThinking || hasSpinner;
269
312
  // Get the response content from model-response elements
270
313
  const modelResponses = Array.from(document.querySelectorAll('model-response'));
271
314
  let responseContent = '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-devtools-mcp-for-extension",
3
- "version": "0.18.4",
3
+ "version": "0.18.6",
4
4
  "description": "MCP server for Chrome extension development with Web Store automation. Fork of chrome-devtools-mcp with extension-specific tools.",
5
5
  "type": "module",
6
6
  "bin": "./scripts/cli.mjs",