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.
- package/build/src/fast-cdp/fast-chat.js +45 -12
- package/build/src/main.js +1 -1
- package/package.json +1 -1
- package/scripts/cab +3 -1
|
@@ -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', '
|
|
65
|
-
const RESPONSE_WAIT_MAX_MS = Number(process.env.CAI_RESPONSE_WAIT_MAX_MS || '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (${
|
|
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 ??
|
|
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
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
|
-
|
|
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
|