chrome-ai-bridge 2.3.4 → 2.3.5

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.
@@ -92,7 +92,13 @@ export class RelayServer extends EventEmitter {
92
92
  ws.on('message', (data) => {
93
93
  this.handleMessage(data.toString());
94
94
  });
95
+ // Guard: only update state if this socket is still the current one.
96
+ // Prevents a stale socket's close event from corrupting a newer connection.
95
97
  ws.on('close', () => {
98
+ if (this.ws !== ws) {
99
+ debugLog('[RelayServer] Stale socket closed (ignored — already replaced)');
100
+ return;
101
+ }
96
102
  debugLog('[RelayServer] Extension disconnected');
97
103
  this.stopKeepAlive();
98
104
  this.rejectPendingRequests(new Error('RELAY_DISCONNECTED: Extension socket closed before request completion'));
@@ -330,10 +336,18 @@ export class RelayServer extends EventEmitter {
330
336
  * Stop server
331
337
  */
332
338
  async stop() {
339
+ this.stopKeepAlive();
333
340
  if (this.ws) {
334
- this.ws.close();
341
+ try {
342
+ this.ws.close();
343
+ }
344
+ catch {
345
+ // ignore close errors
346
+ }
335
347
  this.ws = null;
336
348
  }
349
+ this.ready = false;
350
+ this.tabId = null;
337
351
  this.rejectPendingRequests(new Error('RELAY_STOPPED: Relay stopped before request completion'));
338
352
  if (this.discoveryServer) {
339
353
  this.discoveryServer.close();
@@ -343,6 +357,7 @@ export class RelayServer extends EventEmitter {
343
357
  if (this.wss) {
344
358
  return new Promise((resolve) => {
345
359
  this.wss.close(() => {
360
+ this.wss = null;
346
361
  debugLog('[RelayServer] Server stopped');
347
362
  resolve();
348
363
  });
@@ -166,22 +166,52 @@ async function rotateHistoryIfNeeded() {
166
166
  }
167
167
  /**
168
168
  * キャッシュされたGeminiクライアントをクリア(リトライ用)
169
+ * @deprecated Use resetConnection('gemini') instead
169
170
  */
170
- export function clearGeminiClient() {
171
- const client = getClientFromAgent('gemini');
172
- const relay = getRelayFromAgent('gemini');
171
+ export async function clearGeminiClient() {
172
+ await resetConnection('gemini');
173
+ }
174
+ /**
175
+ * 指定 kind の接続を協調的にクリーンアップする。
176
+ * RelayServer・CdpClient・SessionManager・CDP リスナーを一括リセット。
177
+ * 接続失敗時のリトライ前に呼ぶことで「スティッキーな障害状態」を防ぐ。
178
+ */
179
+ export async function resetConnection(kind) {
180
+ const label = kind === 'chatgpt' ? 'ChatGPT' : 'Gemini';
181
+ console.error(`[fast-cdp] resetConnection(${kind}) — coordinated cleanup start`);
182
+ // 1. CdpClient: all CDP event listeners removed
183
+ const client = getClientFromAgent(kind);
173
184
  if (client) {
174
- console.error('[Gemini] Cached client cleared');
185
+ try {
186
+ client.removeAllCdpListeners();
187
+ }
188
+ catch {
189
+ // ignore
190
+ }
191
+ console.error(`[${label}] CdpClient listeners removed`);
175
192
  }
193
+ // 2. RelayServer: stop + reference clear (await to ensure port is released)
194
+ const relay = getRelayFromAgent(kind);
176
195
  if (relay) {
177
196
  try {
178
- relay.stop();
197
+ await relay.stop();
179
198
  }
180
199
  catch {
181
200
  // ignore stop errors
182
201
  }
202
+ console.error(`[${label}] RelayServer stopped`);
183
203
  }
184
- setClientForAgent('gemini', null, null);
204
+ // 3. Agent connection reference clear
205
+ setClientForAgent(kind, null, null);
206
+ console.error(`[${label}] Agent connection references cleared`);
207
+ // 4. Session info clear (await to prevent write race on retry)
208
+ try {
209
+ await clearAgentSession(kind);
210
+ }
211
+ catch {
212
+ // ignore session clear errors
213
+ }
214
+ console.error(`[fast-cdp] resetConnection(${kind}) — cleanup complete`);
185
215
  }
186
216
  /**
187
217
  * 全接続をクリーンアップ(プロセス終了時用)
@@ -367,7 +397,9 @@ async function createConnection(kind) {
367
397
  logWarn('fast-chat', `${kind} existing tab not found`, {
368
398
  error: error instanceof Error ? error.message : String(error),
369
399
  });
370
- console.error(`[fast-cdp] ${kind} existing tab not found, will create new tab`);
400
+ console.error(`[fast-cdp] ${kind} existing tab not found, resetting before new tab`);
401
+ // 再利用失敗 → stale 参照をクリアしてから新規タブへ
402
+ await resetConnection(kind);
371
403
  }
372
404
  }
373
405
  // 新しいタブを作成
@@ -422,7 +454,9 @@ async function createConnection(kind) {
422
454
  });
423
455
  console.error(`[fast-cdp] ${kind} new tab attempt ${attempt + 1} failed:`, lastError.message);
424
456
  if (attempt < 1) {
425
- console.error(`[fast-cdp] Retrying in 1s...`);
457
+ // リトライ前に協調クリーンアップして stale 状態を排除
458
+ console.error(`[fast-cdp] Resetting ${kind} connection before retry...`);
459
+ await resetConnection(kind);
426
460
  await new Promise(r => setTimeout(r, 1000));
427
461
  }
428
462
  }
@@ -446,22 +480,10 @@ export async function getClient(kind) {
446
480
  console.error(`[fast-cdp] Reusing healthy ${kind} connection`);
447
481
  return existing;
448
482
  }
449
- // 接続が切れている → 古いRelayServerをクリーンアップ
483
+ // 接続が切れている → 協調クリーンアップして再接続
450
484
  logConnectionState(kind, 'reconnecting');
451
- console.error(`[fast-cdp] ${kind} connection lost, reconnecting...`);
452
- // 古いRelayServerを停止
453
- const oldRelay = getRelayFromAgent(kind);
454
- if (oldRelay) {
455
- logInfo('fast-chat', `Stopping stale ${kind} RelayServer`);
456
- console.error(`[fast-cdp] Stopping stale ${kind} RelayServer`);
457
- await oldRelay.stop().catch((err) => {
458
- logWarn('fast-chat', `Failed to stop stale ${kind} RelayServer`, {
459
- error: err instanceof Error ? err.message : String(err),
460
- });
461
- });
462
- }
463
- // キャッシュをクリア
464
- setClientForAgent(kind, null, null);
485
+ console.error(`[fast-cdp] ${kind} connection lost, performing coordinated reset...`);
486
+ await resetConnection(kind);
465
487
  }
466
488
  // 新しい接続を作成
467
489
  return await createConnection(kind);
@@ -2205,9 +2227,8 @@ async function askGeminiFastInternal(question, debug) {
2205
2227
  const stuckCheckResult = await checkGeminiStuckState(client);
2206
2228
  if (stuckCheckResult.isStuck) {
2207
2229
  console.error(`[Gemini] Existing chat appears stuck (stop button detected for ${stuckCheckResult.waitedMs}ms). Clearing session and retrying.`);
2208
- // セッションとクライアントをクリア
2209
- await clearAgentSession('gemini');
2210
- clearGeminiClient();
2230
+ // 協調クリーンアップ(RelayServer + Client + Session を一括リセット)
2231
+ await resetConnection('gemini');
2211
2232
  // エラーを投げて、呼び出し元でリトライを促す
2212
2233
  throw new Error('GEMINI_STUCK_EXISTING_CHAT: Previous chat appears stuck (stop button visible). Session cleared, please retry.');
2213
2234
  }
@@ -2475,9 +2496,8 @@ async function askGeminiFastInternal(question, debug) {
2475
2496
  // 30秒以上停止ボタンが検出され続けている場合、セッションをクリアして新規チャットに切り替え
2476
2497
  if (stopButtonConsecutiveCount >= forceNewChatThreshold) {
2477
2498
  console.error(`[Gemini] Stop button detected for ${forceNewChatThreshold * 0.5}s - clearing session and forcing new chat`);
2478
- // セッションとクライアントをクリアして新規チャットを強制
2479
- await clearAgentSession('gemini');
2480
- clearGeminiClient();
2499
+ // 協調クリーンアップ(RelayServer + Client + Session を一括リセット)
2500
+ await resetConnection('gemini');
2481
2501
  // エラーを投げて再試行を促す
2482
2502
  throw new Error('GEMINI_STUCK_STOP_BUTTON: Previous response appears stuck. Session cleared, please retry.');
2483
2503
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-ai-bridge",
3
- "version": "2.3.4",
3
+ "version": "2.3.5",
4
4
  "description": "MCP server bridging Chrome extension and AI assistants (ChatGPT, Gemini). Extension-only mode - no Puppeteer.",
5
5
  "type": "module",
6
6
  "bin": "./scripts/cli.mjs",