chrome-ai-bridge 2.4.0 → 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/README.md +28 -40
- package/build/extension/README.md +10 -10
- package/build/extension/background.mjs +94 -26
- package/build/extension/manifest.json +2 -2
- package/build/extension/relay-server.ts +16 -2
- package/build/extension/ui/connect.html +1 -1
- package/build/extension/ui/connect.js +46 -10
- package/build/src/cli.js +6 -1
- package/build/src/config.js +2 -4
- package/build/src/extension/relay-server.js +20 -2
- package/build/src/fast-cdp/agent-context.js +2 -2
- package/build/src/fast-cdp/{mcp-logger.js → debug-logger.js} +11 -11
- package/build/src/fast-cdp/extension-raw.js +51 -5
- package/build/src/fast-cdp/fast-chat.js +166 -101
- package/build/src/logger.js +3 -3
- package/build/src/main.js +104 -568
- package/build/src/plugin-api.js +1 -1
- package/build/src/runtime-scope.js +1 -1
- package/build/src/tools/ai-helpers.js +72 -17
- package/build/src/tools/chatgpt-gemini-web.js +1 -1
- package/build/src/tools/chatgpt-web.js +7 -7
- package/build/src/tools/gemini-web.js +10 -22
- package/build/src/tools/optional-tools.js +8 -5
- package/package.json +17 -18
- package/scripts/cab +202 -0
- package/scripts/cli.mjs +1 -1
- package/build/src/McpResponse.js +0 -60
- package/build/src/stdio-http-proxy.js +0 -157
|
@@ -2,7 +2,7 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { connectViaExtensionRaw } from './extension-raw.js';
|
|
4
4
|
import { CdpClient } from './cdp-client.js';
|
|
5
|
-
import { logConnectionState, logInfo, logError, logWarn } from './
|
|
5
|
+
import { logConnectionState, logInfo, logError, logWarn } from './debug-logger.js';
|
|
6
6
|
import { DOM_UTILS_CODE } from './utils/index.js';
|
|
7
7
|
import { getDriver } from './drivers/index.js';
|
|
8
8
|
import { NetworkInterceptor } from './network-interceptor.js';
|
|
@@ -49,18 +49,29 @@ function setClientForAgent(kind, client, relay) {
|
|
|
49
49
|
conn.geminiRelay = relay;
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
// Env var deprecation helpers
|
|
53
|
+
function envWithFallback(newName, oldName, defaultVal) {
|
|
54
|
+
if (process.env[newName])
|
|
55
|
+
return process.env[newName];
|
|
56
|
+
if (process.env[oldName]) {
|
|
57
|
+
console.error(`[deprecation] ${oldName} is deprecated, use ${newName} instead`);
|
|
58
|
+
return process.env[oldName];
|
|
59
|
+
}
|
|
60
|
+
return defaultVal;
|
|
59
61
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
const CONNECT_REUSE_TIMEOUT_MS = Number(envWithFallback('CAI_CONNECT_REUSE_TIMEOUT_MS', 'MCP_CONNECT_REUSE_TIMEOUT_MS', '12000'));
|
|
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', '300000'));
|
|
65
|
+
const RESPONSE_WAIT_MAX_MS = Number(process.env.CAI_RESPONSE_WAIT_MAX_MS || '300000');
|
|
66
|
+
const BUDGET_RESERVE_MS = Number(envWithFallback('CAI_BUDGET_RESERVE_MS', 'CAI_MCP_BUDGET_RESERVE_MS', '3000'));
|
|
67
|
+
function getRemainingBudgetMs(startMs, overrideBudgetMs) {
|
|
68
|
+
return (overrideBudgetMs ?? TOOL_BUDGET_MS) - (nowMs() - startMs) - BUDGET_RESERVE_MS;
|
|
69
|
+
}
|
|
70
|
+
function getResponseWaitBudgetMs(startMs, ceilingMs, stage, overrideBudgetMs) {
|
|
71
|
+
const effectiveBudget = overrideBudgetMs ?? TOOL_BUDGET_MS;
|
|
72
|
+
const remaining = getRemainingBudgetMs(startMs, overrideBudgetMs);
|
|
62
73
|
if (remaining <= 1000) {
|
|
63
|
-
throw new Error(`
|
|
74
|
+
throw new Error(`TOOL_BUDGET_EXCEEDED: stage=${stage} budgetMs=${effectiveBudget} reserveMs=${BUDGET_RESERVE_MS}`);
|
|
64
75
|
}
|
|
65
76
|
return Math.max(1000, Math.min(ceilingMs, remaining));
|
|
66
77
|
}
|
|
@@ -72,6 +83,14 @@ function nowMs() {
|
|
|
72
83
|
* 軽量なevaluateコマンドで接続が生きているかチェック
|
|
73
84
|
*/
|
|
74
85
|
async function isConnectionHealthy(client, kind) {
|
|
86
|
+
// Fast-path: if relay is already disconnected, skip the expensive evaluate call
|
|
87
|
+
if (kind) {
|
|
88
|
+
const relay = getRelayFromAgent(kind);
|
|
89
|
+
if (relay && !relay.isReady()) {
|
|
90
|
+
logConnectionState(kind, 'unhealthy', { elapsed: 0, error: 'relay not ready (fast-path)' });
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
75
94
|
const startTime = Date.now();
|
|
76
95
|
try {
|
|
77
96
|
// 4秒タイムアウトで簡単なコマンドを実行(2秒では不十分な場合があった)
|
|
@@ -228,7 +247,7 @@ export async function resetConnection(kind) {
|
|
|
228
247
|
}
|
|
229
248
|
/**
|
|
230
249
|
* 全接続をクリーンアップ(プロセス終了時用)
|
|
231
|
-
*
|
|
250
|
+
* サーバー終了時にゾンビプロセスを防ぐために使用
|
|
232
251
|
*/
|
|
233
252
|
export async function cleanupAllConnections() {
|
|
234
253
|
// Snapshot entries before clearing to avoid mutation during iteration
|
|
@@ -505,7 +524,17 @@ async function navigate(client, url) {
|
|
|
505
524
|
await client.send('Page.navigate', { url });
|
|
506
525
|
await client.waitForFunction(`document.readyState === 'complete'`, 30000);
|
|
507
526
|
}
|
|
508
|
-
|
|
527
|
+
/** Strip conversation-specific paths (/c/<id>, /app/<id>) to prevent chat pollution on reuse */
|
|
528
|
+
function getBaseUrl(kind, url) {
|
|
529
|
+
if (kind === 'chatgpt') {
|
|
530
|
+
return url.replace(/\/c\/[a-zA-Z0-9_-]+.*$/, '/');
|
|
531
|
+
}
|
|
532
|
+
if (kind === 'gemini') {
|
|
533
|
+
return url.replace(/\/app\/[a-zA-Z0-9_-]+.*$/, '/');
|
|
534
|
+
}
|
|
535
|
+
return url;
|
|
536
|
+
}
|
|
537
|
+
async function askChatGPTFastInternal(question, debug, budgetMs) {
|
|
509
538
|
const t0 = nowMs();
|
|
510
539
|
const timings = {};
|
|
511
540
|
logInfo('chatgpt', 'askChatGPTFast started', { questionLength: question.length });
|
|
@@ -523,16 +552,14 @@ async function askChatGPTFastInternal(question, debug) {
|
|
|
523
552
|
// SPA描画安定化のため追加待機
|
|
524
553
|
await new Promise(r => setTimeout(r, 500));
|
|
525
554
|
console.error('[ChatGPT] Waited 500ms for SPA rendering');
|
|
526
|
-
// 既存チャット(/c/を含むURL
|
|
555
|
+
// 既存チャット(/c/を含むURL)の場合、新規チャットへ遷移
|
|
556
|
+
// 同じ会話に質問を投入すると前回のコンテキストが応答に影響するため
|
|
527
557
|
const currentUrl = await client.evaluate('location.href');
|
|
528
558
|
if (currentUrl.includes('/c/')) {
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
catch {
|
|
534
|
-
console.error('[ChatGPT] No existing messages found, continuing as new chat');
|
|
535
|
-
}
|
|
559
|
+
console.error('[ChatGPT] Existing conversation detected, navigating to new chat...');
|
|
560
|
+
await navigate(client, 'https://chatgpt.com/');
|
|
561
|
+
await new Promise(r => setTimeout(r, 500));
|
|
562
|
+
console.error('[ChatGPT] New chat page loaded');
|
|
536
563
|
}
|
|
537
564
|
// 入力欄が表示されるまで待機してから取得
|
|
538
565
|
const tWaitInput = nowMs();
|
|
@@ -1008,21 +1035,33 @@ async function askChatGPTFastInternal(question, debug) {
|
|
|
1008
1035
|
const tWaitResp = nowMs();
|
|
1009
1036
|
console.error('[ChatGPT] Waiting for response (using stop button detection)...');
|
|
1010
1037
|
// 60秒 caller deadline を超えないよう、残り予算内で待機する。
|
|
1011
|
-
const maxWaitMs = getResponseWaitBudgetMs(t0, RESPONSE_WAIT_MAX_MS, 'chatgpt-response');
|
|
1038
|
+
const maxWaitMs = getResponseWaitBudgetMs(t0, RESPONSE_WAIT_MAX_MS, 'chatgpt-response', budgetMs);
|
|
1012
1039
|
const pollIntervalMs = 1000;
|
|
1040
|
+
const IDLE_TIMEOUT_MS = 30000; // ストップボタン消失後、30秒間無活動でタイムアウト
|
|
1013
1041
|
const startWait = Date.now();
|
|
1042
|
+
let lastActivityAt = Date.now(); // 最後にストップボタンorテキスト成長を検出した時刻
|
|
1014
1043
|
let lastLoggedState = '';
|
|
1015
1044
|
let sawStopButton = false; // 生成中状態を検出したかどうか
|
|
1016
1045
|
let streamingText = ''; // ストリーミング中に取得したテキスト(完了後に折りたたまれる対策)
|
|
1017
|
-
|
|
1046
|
+
let textStableCount = 0; // テキスト長が安定した回数(2-poll confirmation)
|
|
1047
|
+
let lastTextLength = -1; // 前回のテキスト長
|
|
1048
|
+
let stopButtonGoneCount = 0; // ストップボタンが連続不在のポール数(Thinkingフェーズ切替誤判定防止)
|
|
1049
|
+
let textGrowingCount = 0; // テキストが成長中のポール数(成長中はフォールバック抑止)
|
|
1050
|
+
// ストップボタンが見えている間は無制限に待つ(maxWaitMs は絶対安全上限)
|
|
1051
|
+
while (Date.now() - startWait < maxWaitMs && Date.now() - lastActivityAt < IDLE_TIMEOUT_MS) {
|
|
1018
1052
|
const state = await client.evaluate(`
|
|
1019
1053
|
(() => {
|
|
1020
1054
|
${DOM_UTILS_CODE}
|
|
1021
1055
|
|
|
1022
1056
|
// 停止ボタン検出(フォールバックセレクター付き)
|
|
1023
1057
|
const stopBtn = document.querySelector('button[data-testid="stop-button"]') ||
|
|
1058
|
+
document.querySelector('button[aria-label="ストリーミングの停止"]') ||
|
|
1059
|
+
document.querySelector('button[aria-label="Stop streaming"]') ||
|
|
1024
1060
|
document.querySelector('button[aria-label*="停止"]') ||
|
|
1025
|
-
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
|
+
);
|
|
1026
1065
|
const buttons = __collectDeep(['button', '[role="button"]']).nodes;
|
|
1027
1066
|
// 送信ボタン検出(フォールバックセレクター付き)
|
|
1028
1067
|
// 注意: 応答完了後は音声ボタンに置き換わり、送信ボタンがDOMから消える
|
|
@@ -1060,7 +1099,7 @@ async function askChatGPTFastInternal(question, debug) {
|
|
|
1060
1099
|
// 「今すぐ回答」「Skip thinking」ボタンがある場合はThinking進行中
|
|
1061
1100
|
const hasSkipThinkingButton = bodyText.includes('今すぐ回答') ||
|
|
1062
1101
|
bodyText.includes('Skip thinking');
|
|
1063
|
-
const isStillGenerating = (hasGeneratingText && !hasThinkingComplete) || hasSkipThinkingButton;
|
|
1102
|
+
const isStillGenerating = Boolean(stopBtn) || (hasGeneratingText && !hasThinkingComplete) || hasSkipThinkingButton;
|
|
1064
1103
|
|
|
1065
1104
|
// 最後のアシスタントメッセージに実際のテキストがあるかチェック
|
|
1066
1105
|
// 旧UIセレクター + 新UI(article)の両方を試す
|
|
@@ -1273,14 +1312,16 @@ async function askChatGPTFastInternal(question, debug) {
|
|
|
1273
1312
|
})()
|
|
1274
1313
|
`);
|
|
1275
1314
|
// stopボタンを検出したらフラグを立てる(生成が始まった証拠)
|
|
1315
|
+
// ストップボタンが見えている間はアクティブとみなし、タイムアウトを延長し続ける
|
|
1276
1316
|
if (state.hasStopButton) {
|
|
1277
1317
|
sawStopButton = true;
|
|
1318
|
+
lastActivityAt = Date.now();
|
|
1278
1319
|
}
|
|
1279
1320
|
// 状態が変化した場合のみログ出力
|
|
1280
1321
|
const currentState = JSON.stringify(state);
|
|
1281
1322
|
if (currentState !== lastLoggedState) {
|
|
1282
1323
|
const elapsed = Math.round((Date.now() - startWait) / 1000);
|
|
1283
|
-
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}`);
|
|
1284
1325
|
lastLoggedState = currentState;
|
|
1285
1326
|
}
|
|
1286
1327
|
// 応答完了条件(Thinkingモード対応版):
|
|
@@ -1288,46 +1329,81 @@ async function askChatGPTFastInternal(question, debug) {
|
|
|
1288
1329
|
// 2. AND 入力欄が空
|
|
1289
1330
|
// 3. AND 新しいアシスタントメッセージが増えた
|
|
1290
1331
|
// 注: hasResponseText は CDP でテキスト取得できない場合があるため必須条件から外す
|
|
1291
|
-
|
|
1332
|
+
// テキスト安定性チェック(全完了条件で共通使用)
|
|
1333
|
+
const currentTextLen = state.debug_lastAssistantInnerTextLen;
|
|
1334
|
+
if (currentTextLen === lastTextLength && currentTextLen > 0) {
|
|
1335
|
+
textStableCount++;
|
|
1336
|
+
textGrowingCount = 0;
|
|
1337
|
+
}
|
|
1338
|
+
else if (currentTextLen > lastTextLength) {
|
|
1339
|
+
textStableCount = 0;
|
|
1340
|
+
textGrowingCount++;
|
|
1341
|
+
lastTextLength = currentTextLen;
|
|
1342
|
+
lastActivityAt = Date.now();
|
|
1343
|
+
}
|
|
1344
|
+
else {
|
|
1345
|
+
textStableCount = 0;
|
|
1346
|
+
textGrowingCount = 0;
|
|
1347
|
+
lastTextLength = currentTextLen;
|
|
1348
|
+
}
|
|
1349
|
+
// ストップボタン不在の連続カウント(Thinkingフェーズ切替時の一瞬消失を除外)
|
|
1350
|
+
if (sawStopButton && !state.hasStopButton) {
|
|
1351
|
+
stopButtonGoneCount++;
|
|
1352
|
+
}
|
|
1353
|
+
else {
|
|
1354
|
+
stopButtonGoneCount = 0;
|
|
1355
|
+
}
|
|
1356
|
+
if (sawStopButton && stopButtonGoneCount >= 3 && !state.inputBoxHasText &&
|
|
1292
1357
|
state.assistantMsgCount > initialAssistantCount) {
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1358
|
+
// 2-poll confirmation: テキスト長が2回連続安定してから完了とする
|
|
1359
|
+
if (textStableCount >= 2) {
|
|
1360
|
+
console.error(`[ChatGPT] Response complete - stop gone for ${stopButtonGoneCount} polls, text stable for ${textStableCount} polls (len=${currentTextLen})`);
|
|
1361
|
+
streamingText = await client.evaluate(`
|
|
1362
|
+
(() => {
|
|
1363
|
+
const msgs = document.querySelectorAll('[data-message-author-role="assistant"]');
|
|
1364
|
+
if (msgs.length === 0) return '';
|
|
1365
|
+
const last = msgs[msgs.length - 1];
|
|
1366
|
+
const md = last.querySelector('.markdown');
|
|
1367
|
+
if (md) {
|
|
1368
|
+
const t = (md.innerText || md.textContent || '').trim();
|
|
1369
|
+
if (t.length > 0) return t;
|
|
1370
|
+
}
|
|
1371
|
+
const rt = last.querySelector('.result-thinking');
|
|
1372
|
+
if (rt) {
|
|
1373
|
+
const t = (rt.innerText || rt.textContent || '').trim();
|
|
1374
|
+
if (t.length > 0) return t;
|
|
1375
|
+
}
|
|
1376
|
+
return (last.innerText || last.textContent || '').trim();
|
|
1377
|
+
})()
|
|
1378
|
+
`);
|
|
1379
|
+
break;
|
|
1311
1380
|
}
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1381
|
+
// まだ安定していない — 次のポールまで待機
|
|
1382
|
+
console.error(`[ChatGPT] Stop button gone (${stopButtonGoneCount} polls) but text not stable yet (len=${currentTextLen}, stableCount=${textStableCount})`);
|
|
1383
|
+
await new Promise(r => setTimeout(r, pollIntervalMs));
|
|
1384
|
+
continue;
|
|
1316
1385
|
}
|
|
1317
1386
|
// フォールバック: 5秒以上待って、stopボタンなし、入力欄空、新しいアシスタントメッセージが増えた
|
|
1318
1387
|
// (stopボタンを見逃した場合の救済)
|
|
1319
1388
|
const elapsed = Date.now() - startWait;
|
|
1320
1389
|
if (elapsed > 5000 && !state.hasStopButton && !state.inputBoxHasText &&
|
|
1321
|
-
state.assistantMsgCount > initialAssistantCount && !state.isStillGenerating
|
|
1322
|
-
|
|
1323
|
-
|
|
1390
|
+
state.assistantMsgCount > initialAssistantCount && !state.isStillGenerating &&
|
|
1391
|
+
textGrowingCount === 0) {
|
|
1392
|
+
if (textStableCount >= 2) {
|
|
1393
|
+
console.error(`[ChatGPT] Response complete - fallback after 5s, text stable (len=${currentTextLen}, stableCount=${textStableCount})`);
|
|
1394
|
+
break;
|
|
1395
|
+
}
|
|
1396
|
+
console.error(`[ChatGPT] Fallback conditions met but text not stable yet (len=${currentTextLen}, stableCount=${textStableCount})`);
|
|
1324
1397
|
}
|
|
1325
1398
|
// Thinkingモード専用フォールバック: stopボタンなしでも、生成完了していれば完了
|
|
1326
1399
|
// 重要: 「今すぐ回答」ボタンがある間は、まだThinking中なので待機を継続
|
|
1327
1400
|
if (elapsed > 10000 && !state.isStillGenerating && !state.hasSkipThinkingButton &&
|
|
1328
1401
|
state.assistantMsgCount > initialAssistantCount && !state.inputBoxHasText) {
|
|
1329
|
-
|
|
1330
|
-
|
|
1402
|
+
if (textStableCount >= 2) {
|
|
1403
|
+
console.error(`[ChatGPT] Response complete - Thinking mode fallback after 10s, text stable (len=${currentTextLen}, stableCount=${textStableCount})`);
|
|
1404
|
+
break;
|
|
1405
|
+
}
|
|
1406
|
+
console.error(`[ChatGPT] Thinking fallback conditions met but text not stable yet (len=${currentTextLen}, stableCount=${textStableCount})`);
|
|
1331
1407
|
}
|
|
1332
1408
|
await new Promise(r => setTimeout(r, pollIntervalMs));
|
|
1333
1409
|
}
|
|
@@ -1361,9 +1437,14 @@ async function askChatGPTFastInternal(question, debug) {
|
|
|
1361
1437
|
};
|
|
1362
1438
|
})()
|
|
1363
1439
|
`);
|
|
1364
|
-
|
|
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)}`);
|
|
1365
1446
|
await resetConnection('chatgpt');
|
|
1366
|
-
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)}`);
|
|
1367
1448
|
}
|
|
1368
1449
|
// ChatGPT 5.2 Thinking モデル対応:
|
|
1369
1450
|
// 回答が「思考」として折りたたまれている場合は展開してからテキストを取得
|
|
@@ -1401,7 +1482,7 @@ async function askChatGPTFastInternal(question, debug) {
|
|
|
1401
1482
|
// 回答完了後、DOM安定化のための追加待機
|
|
1402
1483
|
// ChatGPT Thinkingモードでは、停止ボタン消失後も最終回答がレンダリングされるまで遅延がある
|
|
1403
1484
|
// 回答テキストが存在するまでポーリングで待機
|
|
1404
|
-
const maxWaitForText = getResponseWaitBudgetMs(t0, 15000, 'chatgpt-finalize');
|
|
1485
|
+
const maxWaitForText = getResponseWaitBudgetMs(t0, 15000, 'chatgpt-finalize', budgetMs);
|
|
1405
1486
|
const pollInterval = 200;
|
|
1406
1487
|
const waitStart = Date.now();
|
|
1407
1488
|
let hasResponseText = false;
|
|
@@ -1941,7 +2022,7 @@ async function askChatGPTFastInternal(question, debug) {
|
|
|
1941
2022
|
console.error(`[ChatGPT] Response extracted: ${finalAnswer.slice(0, 100)}...`);
|
|
1942
2023
|
const finalUrl = await client.evaluate('location.href');
|
|
1943
2024
|
if (finalUrl && finalUrl.includes('chatgpt.com')) {
|
|
1944
|
-
await saveAgentSession('chatgpt', finalUrl);
|
|
2025
|
+
await saveAgentSession('chatgpt', getBaseUrl('chatgpt', finalUrl));
|
|
1945
2026
|
}
|
|
1946
2027
|
timings.waitResponseMs = nowMs() - tWaitResp;
|
|
1947
2028
|
timings.totalMs = nowMs() - t0;
|
|
@@ -2053,7 +2134,7 @@ async function askChatGPTFastInternal(question, debug) {
|
|
|
2053
2134
|
* Driver経由でChatGPTに質問(実験的)
|
|
2054
2135
|
* 環境変数 CAI_USE_DRIVERS=1 で有効化
|
|
2055
2136
|
*/
|
|
2056
|
-
async function askChatGPTViaDriver(question, debug) {
|
|
2137
|
+
async function askChatGPTViaDriver(question, debug, budgetMs) {
|
|
2057
2138
|
const t0 = nowMs();
|
|
2058
2139
|
const timings = {};
|
|
2059
2140
|
// 接続
|
|
@@ -2082,7 +2163,7 @@ async function askChatGPTViaDriver(question, debug) {
|
|
|
2082
2163
|
timings.sendMs = nowMs() - tSend;
|
|
2083
2164
|
// 応答待機
|
|
2084
2165
|
const tWaitResp = nowMs();
|
|
2085
|
-
const driverWaitBudgetMs = getResponseWaitBudgetMs(t0, RESPONSE_WAIT_MAX_MS, 'chatgpt-driver-response');
|
|
2166
|
+
const driverWaitBudgetMs = getResponseWaitBudgetMs(t0, RESPONSE_WAIT_MAX_MS, 'chatgpt-driver-response', budgetMs);
|
|
2086
2167
|
await driver.waitForResponse({ maxWaitMs: driverWaitBudgetMs });
|
|
2087
2168
|
timings.waitResponseMs = nowMs() - tWaitResp;
|
|
2088
2169
|
// 応答抽出
|
|
@@ -2096,7 +2177,7 @@ async function askChatGPTViaDriver(question, debug) {
|
|
|
2096
2177
|
// セッション保存
|
|
2097
2178
|
const finalUrl = await driver.getCurrentUrl();
|
|
2098
2179
|
if (finalUrl.includes('chatgpt.com')) {
|
|
2099
|
-
await saveAgentSession('chatgpt', finalUrl);
|
|
2180
|
+
await saveAgentSession('chatgpt', getBaseUrl('chatgpt', finalUrl));
|
|
2100
2181
|
}
|
|
2101
2182
|
timings.totalMs = nowMs() - t0;
|
|
2102
2183
|
// 履歴保存
|
|
@@ -2120,7 +2201,7 @@ async function askChatGPTViaDriver(question, debug) {
|
|
|
2120
2201
|
/**
|
|
2121
2202
|
* Driver経由でGeminiに質問(実験的)
|
|
2122
2203
|
*/
|
|
2123
|
-
async function askGeminiViaDriver(question, debug) {
|
|
2204
|
+
async function askGeminiViaDriver(question, debug, budgetMs) {
|
|
2124
2205
|
const t0 = nowMs();
|
|
2125
2206
|
const timings = {};
|
|
2126
2207
|
// 接続
|
|
@@ -2150,7 +2231,7 @@ async function askGeminiViaDriver(question, debug) {
|
|
|
2150
2231
|
timings.sendMs = nowMs() - tSend;
|
|
2151
2232
|
// 応答待機
|
|
2152
2233
|
const tWaitResp = nowMs();
|
|
2153
|
-
const driverWaitBudgetMs = getResponseWaitBudgetMs(t0, RESPONSE_WAIT_MAX_MS, 'gemini-driver-response');
|
|
2234
|
+
const driverWaitBudgetMs = getResponseWaitBudgetMs(t0, RESPONSE_WAIT_MAX_MS, 'gemini-driver-response', budgetMs);
|
|
2154
2235
|
await driver.waitForResponse({ maxWaitMs: driverWaitBudgetMs });
|
|
2155
2236
|
timings.waitResponseMs = nowMs() - tWaitResp;
|
|
2156
2237
|
// 応答抽出
|
|
@@ -2164,7 +2245,7 @@ async function askGeminiViaDriver(question, debug) {
|
|
|
2164
2245
|
// セッション保存
|
|
2165
2246
|
const finalUrl = await driver.getCurrentUrl();
|
|
2166
2247
|
if (finalUrl.includes('gemini.google.com')) {
|
|
2167
|
-
await saveAgentSession('gemini', finalUrl);
|
|
2248
|
+
await saveAgentSession('gemini', getBaseUrl('gemini', finalUrl));
|
|
2168
2249
|
}
|
|
2169
2250
|
timings.totalMs = nowMs() - t0;
|
|
2170
2251
|
// 履歴保存
|
|
@@ -2190,24 +2271,24 @@ const USE_DRIVERS = process.env.CAI_USE_DRIVERS === '1';
|
|
|
2190
2271
|
/**
|
|
2191
2272
|
* ChatGPTに質問して回答を取得(後方互換用)
|
|
2192
2273
|
*/
|
|
2193
|
-
export async function askChatGPTFast(question, debug) {
|
|
2274
|
+
export async function askChatGPTFast(question, debug, budgetMs) {
|
|
2194
2275
|
if (USE_DRIVERS) {
|
|
2195
|
-
const result = await askChatGPTViaDriver(question, debug);
|
|
2276
|
+
const result = await askChatGPTViaDriver(question, debug, budgetMs);
|
|
2196
2277
|
return result.answer;
|
|
2197
2278
|
}
|
|
2198
|
-
const result = await askChatGPTFastInternal(question, debug);
|
|
2279
|
+
const result = await askChatGPTFastInternal(question, debug, budgetMs);
|
|
2199
2280
|
return result.answer;
|
|
2200
2281
|
}
|
|
2201
2282
|
/**
|
|
2202
2283
|
* ChatGPTに質問して回答とタイミング情報を取得
|
|
2203
2284
|
*/
|
|
2204
|
-
export async function askChatGPTFastWithTimings(question, debug) {
|
|
2285
|
+
export async function askChatGPTFastWithTimings(question, debug, budgetMs) {
|
|
2205
2286
|
if (USE_DRIVERS) {
|
|
2206
|
-
return askChatGPTViaDriver(question, debug);
|
|
2287
|
+
return askChatGPTViaDriver(question, debug, budgetMs);
|
|
2207
2288
|
}
|
|
2208
|
-
return askChatGPTFastInternal(question, debug);
|
|
2289
|
+
return askChatGPTFastInternal(question, debug, budgetMs);
|
|
2209
2290
|
}
|
|
2210
|
-
async function askGeminiFastInternal(question, debug) {
|
|
2291
|
+
async function askGeminiFastInternal(question, debug, budgetMs) {
|
|
2211
2292
|
const t0 = nowMs();
|
|
2212
2293
|
const timings = {};
|
|
2213
2294
|
const client = await getClient('gemini');
|
|
@@ -2234,31 +2315,15 @@ async function askGeminiFastInternal(question, debug) {
|
|
|
2234
2315
|
// SPA描画安定化のため追加待機
|
|
2235
2316
|
await new Promise(r => setTimeout(r, 500));
|
|
2236
2317
|
console.error('[Gemini] Waited 500ms for SPA rendering');
|
|
2237
|
-
// 既存チャット(URLにチャットID
|
|
2318
|
+
// 既存チャット(URLにチャットIDが含まれる)の場合、新規チャットへ遷移
|
|
2319
|
+
// 同じ会話に質問を投入すると前回のコンテキストが応答に影響するため
|
|
2238
2320
|
const geminiCurrentUrl = await client.evaluate('location.href');
|
|
2239
|
-
// 既存チャットのURLパターン: /app/xxxxx (チャットID)
|
|
2240
2321
|
const isExistingGeminiChat = /\/app\/[a-zA-Z0-9]+/.test(geminiCurrentUrl);
|
|
2241
2322
|
if (isExistingGeminiChat) {
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
const stuckCheckResult = await checkGeminiStuckState(client);
|
|
2247
|
-
if (stuckCheckResult.isStuck) {
|
|
2248
|
-
console.error(`[Gemini] Existing chat appears stuck (stop button detected for ${stuckCheckResult.waitedMs}ms). Clearing session and retrying.`);
|
|
2249
|
-
// 協調クリーンアップ(RelayServer + Client + Session を一括リセット)
|
|
2250
|
-
await resetConnection('gemini');
|
|
2251
|
-
// エラーを投げて、呼び出し元でリトライを促す
|
|
2252
|
-
throw new Error('GEMINI_STUCK_EXISTING_CHAT: Previous chat appears stuck (stop button visible). Session cleared, please retry.');
|
|
2253
|
-
}
|
|
2254
|
-
}
|
|
2255
|
-
catch (error) {
|
|
2256
|
-
// GEMINI_STUCK_* エラーは再スロー(リトライ用)
|
|
2257
|
-
if (error instanceof Error && error.message.includes('GEMINI_STUCK_')) {
|
|
2258
|
-
throw error;
|
|
2259
|
-
}
|
|
2260
|
-
console.error('[Gemini] No existing messages found, continuing as new chat');
|
|
2261
|
-
}
|
|
2323
|
+
console.error('[Gemini] Existing conversation detected, navigating to new chat...');
|
|
2324
|
+
await navigate(client, 'https://gemini.google.com/');
|
|
2325
|
+
await new Promise(r => setTimeout(r, 500));
|
|
2326
|
+
console.error('[Gemini] New chat page loaded');
|
|
2262
2327
|
}
|
|
2263
2328
|
const tWaitInput = nowMs();
|
|
2264
2329
|
await client.waitForFunction(`!!document.querySelector('[role="textbox"], div[contenteditable="true"], textarea') || !!document.querySelector('a[href*="accounts.google.com"]')`, 15000);
|
|
@@ -2680,7 +2745,7 @@ async function askGeminiFastInternal(question, debug) {
|
|
|
2680
2745
|
const tWaitResp = nowMs();
|
|
2681
2746
|
console.error('[Gemini] Waiting for response completion (polling with diagnostics)...');
|
|
2682
2747
|
// ChatGPT側と同様のポーリングループで応答完了を検出
|
|
2683
|
-
const maxWaitMs = getResponseWaitBudgetMs(t0, RESPONSE_WAIT_MAX_MS, 'gemini-response');
|
|
2748
|
+
const maxWaitMs = getResponseWaitBudgetMs(t0, RESPONSE_WAIT_MAX_MS, 'gemini-response', budgetMs);
|
|
2684
2749
|
const pollIntervalMs = 1000;
|
|
2685
2750
|
const startWait = Date.now();
|
|
2686
2751
|
let lastLoggedState = '';
|
|
@@ -2953,7 +3018,7 @@ async function askGeminiFastInternal(question, debug) {
|
|
|
2953
3018
|
console.error(`[Gemini] Response extracted: ${normalized.slice(0, 100)}...`);
|
|
2954
3019
|
const finalUrl = await client.evaluate('location.href');
|
|
2955
3020
|
if (finalUrl && finalUrl.includes('gemini.google.com')) {
|
|
2956
|
-
await saveAgentSession('gemini', finalUrl);
|
|
3021
|
+
await saveAgentSession('gemini', getBaseUrl('gemini', finalUrl));
|
|
2957
3022
|
}
|
|
2958
3023
|
timings.waitResponseMs = nowMs() - tWaitResp;
|
|
2959
3024
|
timings.totalMs = nowMs() - t0;
|
|
@@ -3054,22 +3119,22 @@ async function askGeminiFastInternal(question, debug) {
|
|
|
3054
3119
|
/**
|
|
3055
3120
|
* Geminiに質問して回答を取得(後方互換用)
|
|
3056
3121
|
*/
|
|
3057
|
-
export async function askGeminiFast(question, debug) {
|
|
3122
|
+
export async function askGeminiFast(question, debug, budgetMs) {
|
|
3058
3123
|
if (USE_DRIVERS) {
|
|
3059
|
-
const result = await askGeminiViaDriver(question, debug);
|
|
3124
|
+
const result = await askGeminiViaDriver(question, debug, budgetMs);
|
|
3060
3125
|
return result.answer;
|
|
3061
3126
|
}
|
|
3062
|
-
const result = await askGeminiFastInternal(question, debug);
|
|
3127
|
+
const result = await askGeminiFastInternal(question, debug, budgetMs);
|
|
3063
3128
|
return result.answer;
|
|
3064
3129
|
}
|
|
3065
3130
|
/**
|
|
3066
3131
|
* Geminiに質問して回答とタイミング情報を取得
|
|
3067
3132
|
*/
|
|
3068
|
-
export async function askGeminiFastWithTimings(question, debug) {
|
|
3133
|
+
export async function askGeminiFastWithTimings(question, debug, budgetMs) {
|
|
3069
3134
|
if (USE_DRIVERS) {
|
|
3070
|
-
return askGeminiViaDriver(question, debug);
|
|
3135
|
+
return askGeminiViaDriver(question, debug, budgetMs);
|
|
3071
3136
|
}
|
|
3072
|
-
return askGeminiFastInternal(question, debug);
|
|
3137
|
+
return askGeminiFastInternal(question, debug, budgetMs);
|
|
3073
3138
|
}
|
|
3074
3139
|
export async function takeCdpSnapshot(kind, options) {
|
|
3075
3140
|
const result = {
|
package/build/src/logger.js
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import fs from 'node:fs';
|
|
7
7
|
import debug from 'debug';
|
|
8
|
-
const
|
|
8
|
+
const cabDebugNamespace = 'cab:log';
|
|
9
9
|
const namespacesToEnable = [
|
|
10
|
-
|
|
10
|
+
cabDebugNamespace,
|
|
11
11
|
...(process.env['DEBUG'] ? [process.env['DEBUG']] : []),
|
|
12
12
|
];
|
|
13
13
|
export function saveLogsToFile(fileName) {
|
|
@@ -24,4 +24,4 @@ export function saveLogsToFile(fileName) {
|
|
|
24
24
|
});
|
|
25
25
|
return logFile;
|
|
26
26
|
}
|
|
27
|
-
export const logger = debug(
|
|
27
|
+
export const logger = debug(cabDebugNamespace);
|