chrome-ai-bridge 2.5.1 → 2.5.2

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.
@@ -61,8 +61,8 @@ function envWithFallback(newName, oldName, defaultVal) {
61
61
  }
62
62
  const CONNECT_REUSE_TIMEOUT_MS = Number(envWithFallback('CAI_CONNECT_REUSE_TIMEOUT_MS', 'MCP_CONNECT_REUSE_TIMEOUT_MS', '12000'));
63
63
  const CONNECT_NEWTAB_TIMEOUT_MS = Number(envWithFallback('CAI_CONNECT_NEWTAB_TIMEOUT_MS', 'MCP_CONNECT_NEWTAB_TIMEOUT_MS', '20000'));
64
- const TOOL_BUDGET_MS = Number(envWithFallback('CAI_TOOL_BUDGET_MS', 'CAI_MCP_TOOL_BUDGET_MS', '50000'));
65
- const RESPONSE_WAIT_MAX_MS = Number(process.env.CAI_RESPONSE_WAIT_MAX_MS || '40000');
64
+ const TOOL_BUDGET_MS = Number(envWithFallback('CAI_TOOL_BUDGET_MS', 'CAI_MCP_TOOL_BUDGET_MS', '300000'));
65
+ const RESPONSE_WAIT_MAX_MS = Number(process.env.CAI_RESPONSE_WAIT_MAX_MS || '300000');
66
66
  const BUDGET_RESERVE_MS = Number(envWithFallback('CAI_BUDGET_RESERVE_MS', 'CAI_MCP_BUDGET_RESERVE_MS', '3000'));
67
67
  function getRemainingBudgetMs(startMs, overrideBudgetMs) {
68
68
  return (overrideBudgetMs ?? TOOL_BUDGET_MS) - (nowMs() - startMs) - BUDGET_RESERVE_MS;
@@ -1037,21 +1037,31 @@ async function askChatGPTFastInternal(question, debug, budgetMs) {
1037
1037
  // 60秒 caller deadline を超えないよう、残り予算内で待機する。
1038
1038
  const maxWaitMs = getResponseWaitBudgetMs(t0, RESPONSE_WAIT_MAX_MS, 'chatgpt-response', budgetMs);
1039
1039
  const pollIntervalMs = 1000;
1040
+ const IDLE_TIMEOUT_MS = 30000; // ストップボタン消失後、30秒間無活動でタイムアウト
1040
1041
  const startWait = Date.now();
1042
+ let lastActivityAt = Date.now(); // 最後にストップボタンorテキスト成長を検出した時刻
1041
1043
  let lastLoggedState = '';
1042
1044
  let sawStopButton = false; // 生成中状態を検出したかどうか
1043
1045
  let streamingText = ''; // ストリーミング中に取得したテキスト(完了後に折りたたまれる対策)
1044
1046
  let textStableCount = 0; // テキスト長が安定した回数(2-poll confirmation)
1045
1047
  let lastTextLength = -1; // 前回のテキスト長
1046
- while (Date.now() - startWait < maxWaitMs) {
1048
+ let stopButtonGoneCount = 0; // ストップボタンが連続不在のポール数(Thinkingフェーズ切替誤判定防止)
1049
+ let textGrowingCount = 0; // テキストが成長中のポール数(成長中はフォールバック抑止)
1050
+ // ストップボタンが見えている間は無制限に待つ(maxWaitMs は絶対安全上限)
1051
+ while (Date.now() - startWait < maxWaitMs && Date.now() - lastActivityAt < IDLE_TIMEOUT_MS) {
1047
1052
  const state = await client.evaluate(`
1048
1053
  (() => {
1049
1054
  ${DOM_UTILS_CODE}
1050
1055
 
1051
1056
  // 停止ボタン検出(フォールバックセレクター付き)
1052
1057
  const stopBtn = document.querySelector('button[data-testid="stop-button"]') ||
1058
+ document.querySelector('button[aria-label="ストリーミングの停止"]') ||
1059
+ document.querySelector('button[aria-label="Stop streaming"]') ||
1053
1060
  document.querySelector('button[aria-label*="停止"]') ||
1054
- document.querySelector('button[aria-label*="Stop"]');
1061
+ document.querySelector('button[aria-label*="Stop"]') ||
1062
+ [...document.querySelectorAll('button')].find(b =>
1063
+ b.querySelector('rect') && (b.textContent || '').trim() === ''
1064
+ );
1055
1065
  const buttons = __collectDeep(['button', '[role="button"]']).nodes;
1056
1066
  // 送信ボタン検出(フォールバックセレクター付き)
1057
1067
  // 注意: 応答完了後は音声ボタンに置き換わり、送信ボタンがDOMから消える
@@ -1089,7 +1099,7 @@ async function askChatGPTFastInternal(question, debug, budgetMs) {
1089
1099
  // 「今すぐ回答」「Skip thinking」ボタンがある場合はThinking進行中
1090
1100
  const hasSkipThinkingButton = bodyText.includes('今すぐ回答') ||
1091
1101
  bodyText.includes('Skip thinking');
1092
- const isStillGenerating = (hasGeneratingText && !hasThinkingComplete) || hasSkipThinkingButton;
1102
+ const isStillGenerating = Boolean(stopBtn) || (hasGeneratingText && !hasThinkingComplete) || hasSkipThinkingButton;
1093
1103
 
1094
1104
  // 最後のアシスタントメッセージに実際のテキストがあるかチェック
1095
1105
  // 旧UIセレクター + 新UI(article)の両方を試す
@@ -1302,14 +1312,16 @@ async function askChatGPTFastInternal(question, debug, budgetMs) {
1302
1312
  })()
1303
1313
  `);
1304
1314
  // stopボタンを検出したらフラグを立てる(生成が始まった証拠)
1315
+ // ストップボタンが見えている間はアクティブとみなし、タイムアウトを延長し続ける
1305
1316
  if (state.hasStopButton) {
1306
1317
  sawStopButton = true;
1318
+ lastActivityAt = Date.now();
1307
1319
  }
1308
1320
  // 状態が変化した場合のみログ出力
1309
1321
  const currentState = JSON.stringify(state);
1310
1322
  if (currentState !== lastLoggedState) {
1311
1323
  const elapsed = Math.round((Date.now() - startWait) / 1000);
1312
- console.error(`[ChatGPT] State @${elapsed}s: stop=${state.hasStopButton}, send=${state.sendButtonFound}(disabled=${state.sendButtonDisabled}), assistant=${state.assistantMsgCount}, inputHasText=${state.inputBoxHasText}, sawStop=${sawStopButton}, generating=${state.isStillGenerating}, skipThink=${state.hasSkipThinkingButton}, hasText=${state.hasResponseText}`);
1324
+ console.error(`[ChatGPT] State @${elapsed}s: stop=${state.hasStopButton}, send=${state.sendButtonFound}(disabled=${state.sendButtonDisabled}), assistant=${state.assistantMsgCount}, inputHasText=${state.inputBoxHasText}, sawStop=${sawStopButton}, generating=${state.isStillGenerating}, skipThink=${state.hasSkipThinkingButton}, hasText=${state.hasResponseText}, textGrow=${textGrowingCount}`);
1313
1325
  lastLoggedState = currentState;
1314
1326
  }
1315
1327
  // 応答完了条件(Thinkingモード対応版):
@@ -1321,16 +1333,31 @@ async function askChatGPTFastInternal(question, debug, budgetMs) {
1321
1333
  const currentTextLen = state.debug_lastAssistantInnerTextLen;
1322
1334
  if (currentTextLen === lastTextLength && currentTextLen > 0) {
1323
1335
  textStableCount++;
1336
+ textGrowingCount = 0;
1337
+ }
1338
+ else if (currentTextLen > lastTextLength) {
1339
+ textStableCount = 0;
1340
+ textGrowingCount++;
1341
+ lastTextLength = currentTextLen;
1342
+ lastActivityAt = Date.now();
1324
1343
  }
1325
1344
  else {
1326
1345
  textStableCount = 0;
1346
+ textGrowingCount = 0;
1327
1347
  lastTextLength = currentTextLen;
1328
1348
  }
1329
- if (sawStopButton && !state.hasStopButton && !state.inputBoxHasText &&
1349
+ // ストップボタン不在の連続カウント(Thinkingフェーズ切替時の一瞬消失を除外)
1350
+ if (sawStopButton && !state.hasStopButton) {
1351
+ stopButtonGoneCount++;
1352
+ }
1353
+ else {
1354
+ stopButtonGoneCount = 0;
1355
+ }
1356
+ if (sawStopButton && stopButtonGoneCount >= 3 && !state.inputBoxHasText &&
1330
1357
  state.assistantMsgCount > initialAssistantCount) {
1331
1358
  // 2-poll confirmation: テキスト長が2回連続安定してから完了とする
1332
1359
  if (textStableCount >= 2) {
1333
- console.error(`[ChatGPT] Response complete - stop gone, text stable for ${textStableCount} polls (len=${currentTextLen})`);
1360
+ console.error(`[ChatGPT] Response complete - stop gone for ${stopButtonGoneCount} polls, text stable for ${textStableCount} polls (len=${currentTextLen})`);
1334
1361
  streamingText = await client.evaluate(`
1335
1362
  (() => {
1336
1363
  const msgs = document.querySelectorAll('[data-message-author-role="assistant"]');
@@ -1352,7 +1379,7 @@ async function askChatGPTFastInternal(question, debug, budgetMs) {
1352
1379
  break;
1353
1380
  }
1354
1381
  // まだ安定していない — 次のポールまで待機
1355
- console.error(`[ChatGPT] Stop button gone but text not stable yet (len=${currentTextLen}, stableCount=${textStableCount})`);
1382
+ console.error(`[ChatGPT] Stop button gone (${stopButtonGoneCount} polls) but text not stable yet (len=${currentTextLen}, stableCount=${textStableCount})`);
1356
1383
  await new Promise(r => setTimeout(r, pollIntervalMs));
1357
1384
  continue;
1358
1385
  }
@@ -1360,7 +1387,8 @@ async function askChatGPTFastInternal(question, debug, budgetMs) {
1360
1387
  // (stopボタンを見逃した場合の救済)
1361
1388
  const elapsed = Date.now() - startWait;
1362
1389
  if (elapsed > 5000 && !state.hasStopButton && !state.inputBoxHasText &&
1363
- state.assistantMsgCount > initialAssistantCount && !state.isStillGenerating) {
1390
+ state.assistantMsgCount > initialAssistantCount && !state.isStillGenerating &&
1391
+ textGrowingCount === 0) {
1364
1392
  if (textStableCount >= 2) {
1365
1393
  console.error(`[ChatGPT] Response complete - fallback after 5s, text stable (len=${currentTextLen}, stableCount=${textStableCount})`);
1366
1394
  break;
@@ -1409,9 +1437,14 @@ async function askChatGPTFastInternal(question, debug, budgetMs) {
1409
1437
  };
1410
1438
  })()
1411
1439
  `);
1412
- console.error(`[ChatGPT] Timeout - final state: ${JSON.stringify(finalState)}`);
1440
+ const elapsedMs = Date.now() - startWait;
1441
+ const idleMs = Date.now() - lastActivityAt;
1442
+ const reason = idleMs >= IDLE_TIMEOUT_MS
1443
+ ? `idle for ${Math.round(idleMs / 1000)}s (no stop button or text growth)`
1444
+ : `absolute ceiling ${maxWaitMs}ms reached`;
1445
+ console.error(`[ChatGPT] Timeout - ${reason}, elapsed=${Math.round(elapsedMs / 1000)}s, final state: ${JSON.stringify(finalState)}`);
1413
1446
  await resetConnection('chatgpt');
1414
- throw new Error(`Timed out waiting for ChatGPT response (${maxWaitMs}ms). Final state: ${JSON.stringify(finalState)}`);
1447
+ throw new Error(`Timed out waiting for ChatGPT response (${Math.round(elapsedMs / 1000)}s, ${reason}). Final state: ${JSON.stringify(finalState)}`);
1415
1448
  }
1416
1449
  // ChatGPT 5.2 Thinking モデル対応:
1417
1450
  // 回答が「思考」として折りたたまれている場合は展開してからテキストを取得
package/build/src/main.js CHANGED
@@ -202,7 +202,7 @@ const httpServer = http.createServer(async (req, res) => {
202
202
  return;
203
203
  }
204
204
  const { target, question, debug: debugFlag, budgetMs: requestBudgetMs } = parsed;
205
- const effectiveBudgetMs = requestBudgetMs ?? 120000;
205
+ const effectiveBudgetMs = requestBudgetMs ?? 300000;
206
206
  if (!target || !question) {
207
207
  res.writeHead(400, { 'Content-Type': 'application/json' }).end(JSON.stringify({ success: false, error: 'Missing required fields: target, question' }));
208
208
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-ai-bridge",
3
- "version": "2.5.1",
3
+ "version": "2.5.2",
4
4
  "description": "CLI tool for querying ChatGPT and Gemini via Chrome extension. No Puppeteer required.",
5
5
  "type": "module",
6
6
  "bin": {
package/scripts/cab CHANGED
@@ -11,7 +11,9 @@ set -euo pipefail
11
11
  CAB_PORT="${CAI_IPC_PORT:-9321}"
12
12
  CAB_HOST="127.0.0.1"
13
13
  CAB_BASE="http://${CAB_HOST}:${CAB_PORT}"
14
- CAB_DIR="$(cd "$(dirname "$0")" && pwd)"
14
+ # Resolve symlinks to get the real script path (macOS compatible)
15
+ _CAB_SELF="$(realpath "$0" 2>/dev/null || python3 -c "import os,sys; print(os.path.realpath(sys.argv[1]))" "$0")"
16
+ CAB_DIR="$(cd "$(dirname "$_CAB_SELF")" && pwd)"
15
17
  CAB_LOG_DIR="${HOME}/.cache/chrome-ai-bridge"
16
18
  CAB_LOG="${CAB_LOG_DIR}/cab-daemon.log"
17
19
  CAB_STARTUP_TIMEOUT=30