@steipete/oracle 0.9.0 → 0.11.0
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/LICENSE +1 -1
- package/README.md +107 -49
- package/dist/bin/oracle-cli.js +551 -410
- package/dist/bin/oracle-mcp.js +2 -2
- package/dist/bin/oracle.js +165 -279
- package/dist/scripts/agent-send.js +31 -31
- package/dist/scripts/check.js +6 -6
- package/dist/scripts/debug/extract-chatgpt-response.js +10 -10
- package/dist/scripts/docs-list.js +30 -30
- package/dist/scripts/git-policy.js +25 -23
- package/dist/scripts/run-cli.js +8 -8
- package/dist/scripts/runner.js +203 -195
- package/dist/scripts/test-browser.js +21 -18
- package/dist/scripts/test-remote-chrome.js +20 -20
- package/dist/src/bridge/connection.js +18 -18
- package/dist/src/bridge/userConfigFile.js +7 -7
- package/dist/src/browser/actions/archiveConversation.js +224 -0
- package/dist/src/browser/actions/assistantResponse.js +175 -101
- package/dist/src/browser/actions/attachmentDataTransfer.js +49 -47
- package/dist/src/browser/actions/attachments.js +246 -150
- package/dist/src/browser/actions/deepResearch.js +662 -0
- package/dist/src/browser/actions/domEvents.js +2 -2
- package/dist/src/browser/actions/modelSelection.js +342 -119
- package/dist/src/browser/actions/navigation.js +183 -137
- package/dist/src/browser/actions/projectSources.js +491 -0
- package/dist/src/browser/actions/promptComposer.js +152 -91
- package/dist/src/browser/actions/remoteFileTransfer.js +10 -10
- package/dist/src/browser/actions/thinkingStatus.js +391 -0
- package/dist/src/browser/actions/thinkingTime.js +207 -110
- package/dist/src/browser/artifacts.js +150 -0
- package/dist/src/browser/attachRunning.js +31 -0
- package/dist/src/browser/chatgptImages.js +315 -0
- package/dist/src/browser/chromeLifecycle.js +276 -63
- package/dist/src/browser/config.js +59 -16
- package/dist/src/browser/constants.js +25 -12
- package/dist/src/browser/controlPlan.js +81 -0
- package/dist/src/browser/cookies.js +19 -19
- package/dist/src/browser/detect.js +250 -77
- package/dist/src/browser/domDebug.js +50 -1
- package/dist/src/browser/index.js +1559 -692
- package/dist/src/browser/liveTabs.js +434 -0
- package/dist/src/browser/modelStrategy.js +1 -1
- package/dist/src/browser/pageActions.js +5 -5
- package/dist/src/browser/policies.js +16 -13
- package/dist/src/browser/profileState.js +127 -42
- package/dist/src/browser/projectSourcesRunner.js +366 -0
- package/dist/src/browser/prompt.js +72 -42
- package/dist/src/browser/promptSummary.js +5 -5
- package/dist/src/browser/providerDomFlow.js +1 -1
- package/dist/src/browser/providers/chatgptDomProvider.js +9 -9
- package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +51 -42
- package/dist/src/browser/providers/index.js +2 -2
- package/dist/src/browser/reattach.js +178 -73
- package/dist/src/browser/reattachHelpers.js +32 -27
- package/dist/src/browser/sessionRunner.js +89 -25
- package/dist/src/browser/tabLeaseRegistry.js +182 -0
- package/dist/src/browser/utils.js +9 -9
- package/dist/src/browserMode.js +1 -1
- package/dist/src/cli/bridge/claudeConfig.js +24 -20
- package/dist/src/cli/bridge/client.js +28 -20
- package/dist/src/cli/bridge/codexConfig.js +16 -16
- package/dist/src/cli/bridge/doctor.js +47 -39
- package/dist/src/cli/bridge/host.js +58 -56
- package/dist/src/cli/browserConfig.js +102 -48
- package/dist/src/cli/browserDefaults.js +51 -26
- package/dist/src/cli/browserTabs.js +228 -0
- package/dist/src/cli/bundleWarnings.js +1 -1
- package/dist/src/cli/clipboard.js +11 -2
- package/dist/src/cli/detach.js +2 -2
- package/dist/src/cli/dryRun.js +62 -26
- package/dist/src/cli/duplicatePromptGuard.js +12 -4
- package/dist/src/cli/engine.js +9 -9
- package/dist/src/cli/errorUtils.js +1 -1
- package/dist/src/cli/fileSize.js +3 -3
- package/dist/src/cli/format.js +2 -2
- package/dist/src/cli/help.js +28 -28
- package/dist/src/cli/hiddenAliases.js +3 -3
- package/dist/src/cli/markdownBundle.js +7 -7
- package/dist/src/cli/markdownRenderer.js +15 -15
- package/dist/src/cli/notifier.js +77 -67
- package/dist/src/cli/options.js +131 -106
- package/dist/src/cli/oscUtils.js +1 -1
- package/dist/src/cli/projectSources.js +116 -0
- package/dist/src/cli/promptRequirement.js +2 -2
- package/dist/src/cli/renderOutput.js +1 -1
- package/dist/src/cli/rootAlias.js +1 -1
- package/dist/src/cli/runOptions.js +32 -28
- package/dist/src/cli/sessionCommand.js +82 -21
- package/dist/src/cli/sessionDisplay.js +213 -87
- package/dist/src/cli/sessionLineage.js +6 -2
- package/dist/src/cli/sessionRunner.js +149 -95
- package/dist/src/cli/sessionTable.js +26 -23
- package/dist/src/cli/stdin.js +22 -0
- package/dist/src/cli/tagline.js +121 -124
- package/dist/src/cli/tui/index.js +139 -128
- package/dist/src/cli/writeOutputPath.js +5 -5
- package/dist/src/config.js +7 -7
- package/dist/src/gemini-web/browserSessionManager.js +19 -15
- package/dist/src/gemini-web/client.js +76 -70
- package/dist/src/gemini-web/executionMode.js +6 -8
- package/dist/src/gemini-web/executor.js +98 -93
- package/dist/src/gemini-web/index.js +1 -1
- package/dist/src/mcp/consultPresets.js +19 -0
- package/dist/src/mcp/server.js +18 -12
- package/dist/src/mcp/tools/consult.js +246 -67
- package/dist/src/mcp/tools/projectSources.js +123 -0
- package/dist/src/mcp/tools/sessionResources.js +12 -12
- package/dist/src/mcp/tools/sessions.js +26 -17
- package/dist/src/mcp/types.js +12 -5
- package/dist/src/mcp/utils.js +21 -8
- package/dist/src/oracle/background.js +15 -15
- package/dist/src/oracle/claude.js +53 -25
- package/dist/src/oracle/client.js +50 -41
- package/dist/src/oracle/config.js +96 -66
- package/dist/src/oracle/errors.js +38 -38
- package/dist/src/oracle/files.js +55 -46
- package/dist/src/oracle/finishLine.js +10 -8
- package/dist/src/oracle/format.js +3 -3
- package/dist/src/oracle/gemini.js +37 -33
- package/dist/src/oracle/logging.js +7 -7
- package/dist/src/oracle/markdown.js +28 -28
- package/dist/src/oracle/modelResolver.js +16 -16
- package/dist/src/oracle/multiModelRunner.js +12 -12
- package/dist/src/oracle/oscProgress.js +8 -8
- package/dist/src/oracle/promptAssembly.js +6 -3
- package/dist/src/oracle/request.js +16 -13
- package/dist/src/oracle/run.js +160 -135
- package/dist/src/oracle/runUtils.js +8 -5
- package/dist/src/oracle/tokenEstimate.js +6 -6
- package/dist/src/oracle/tokenStats.js +5 -5
- package/dist/src/oracle/tokenStringifier.js +5 -5
- package/dist/src/oracle.js +12 -12
- package/dist/src/oracleHome.js +3 -3
- package/dist/src/projectSources/plan.js +27 -0
- package/dist/src/projectSources/url.js +23 -0
- package/dist/src/remote/client.js +25 -25
- package/dist/src/remote/health.js +20 -20
- package/dist/src/remote/remoteServiceConfig.js +9 -9
- package/dist/src/remote/server.js +129 -118
- package/dist/src/sessionManager.js +78 -75
- package/dist/src/sessionStore.js +3 -3
- package/dist/src/version.js +10 -10
- package/dist/vendor/oracle-notifier/README.md +2 -0
- package/package.json +67 -62
- package/vendor/oracle-notifier/README.md +2 -0
- package/dist/markdansi/types/index.js +0 -4
- package/dist/oracle/bin/oracle-cli.js +0 -472
- package/dist/oracle/src/browser/actions/assistantResponse.js +0 -471
- package/dist/oracle/src/browser/actions/attachments.js +0 -82
- package/dist/oracle/src/browser/actions/modelSelection.js +0 -190
- package/dist/oracle/src/browser/actions/navigation.js +0 -75
- package/dist/oracle/src/browser/actions/promptComposer.js +0 -167
- package/dist/oracle/src/browser/chromeLifecycle.js +0 -104
- package/dist/oracle/src/browser/config.js +0 -33
- package/dist/oracle/src/browser/constants.js +0 -40
- package/dist/oracle/src/browser/cookies.js +0 -210
- package/dist/oracle/src/browser/domDebug.js +0 -36
- package/dist/oracle/src/browser/index.js +0 -331
- package/dist/oracle/src/browser/pageActions.js +0 -5
- package/dist/oracle/src/browser/prompt.js +0 -88
- package/dist/oracle/src/browser/promptSummary.js +0 -20
- package/dist/oracle/src/browser/sessionRunner.js +0 -80
- package/dist/oracle/src/browser/utils.js +0 -62
- package/dist/oracle/src/browserMode.js +0 -1
- package/dist/oracle/src/cli/browserConfig.js +0 -44
- package/dist/oracle/src/cli/dryRun.js +0 -59
- package/dist/oracle/src/cli/engine.js +0 -17
- package/dist/oracle/src/cli/errorUtils.js +0 -9
- package/dist/oracle/src/cli/help.js +0 -70
- package/dist/oracle/src/cli/markdownRenderer.js +0 -15
- package/dist/oracle/src/cli/options.js +0 -103
- package/dist/oracle/src/cli/promptRequirement.js +0 -14
- package/dist/oracle/src/cli/rootAlias.js +0 -30
- package/dist/oracle/src/cli/sessionCommand.js +0 -77
- package/dist/oracle/src/cli/sessionDisplay.js +0 -270
- package/dist/oracle/src/cli/sessionRunner.js +0 -94
- package/dist/oracle/src/heartbeat.js +0 -43
- package/dist/oracle/src/oracle/client.js +0 -48
- package/dist/oracle/src/oracle/config.js +0 -29
- package/dist/oracle/src/oracle/errors.js +0 -101
- package/dist/oracle/src/oracle/files.js +0 -220
- package/dist/oracle/src/oracle/format.js +0 -33
- package/dist/oracle/src/oracle/fsAdapter.js +0 -7
- package/dist/oracle/src/oracle/oscProgress.js +0 -60
- package/dist/oracle/src/oracle/request.js +0 -48
- package/dist/oracle/src/oracle/run.js +0 -444
- package/dist/oracle/src/oracle/tokenStats.js +0 -39
- package/dist/oracle/src/oracle/types.js +0 -1
- package/dist/oracle/src/oracle.js +0 -9
- package/dist/oracle/src/sessionManager.js +0 -205
- package/dist/oracle/src/version.js +0 -39
- package/dist/scripts/chrome/browser-tools.js +0 -295
- package/dist/src/browser/profileSync.js +0 -141
- /package/dist/{oracle/src/browser → src/projectSources}/types.js +0 -0
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
import { CLOUDFLARE_SCRIPT_SELECTOR, CLOUDFLARE_TITLE, INPUT_SELECTORS
|
|
2
|
-
import { delay } from
|
|
3
|
-
import { logDomFailure } from
|
|
4
|
-
import { BrowserAutomationError } from
|
|
1
|
+
import { CLOUDFLARE_SCRIPT_SELECTOR, CLOUDFLARE_TITLE, INPUT_SELECTORS } from "../constants.js";
|
|
2
|
+
import { delay } from "../utils.js";
|
|
3
|
+
import { logDomFailure } from "../domDebug.js";
|
|
4
|
+
import { BrowserAutomationError } from "../../oracle/errors.js";
|
|
5
5
|
export function installJavaScriptDialogAutoDismissal(Page, logger) {
|
|
6
6
|
const pageAny = Page;
|
|
7
|
-
if (typeof pageAny.on !==
|
|
7
|
+
if (typeof pageAny.on !== "function" || typeof pageAny.handleJavaScriptDialog !== "function") {
|
|
8
8
|
return () => { };
|
|
9
9
|
}
|
|
10
10
|
const handler = async (params) => {
|
|
11
|
-
const type = typeof params?.type ===
|
|
12
|
-
const message = typeof params?.message ===
|
|
13
|
-
logger(`[nav] dismissing JS dialog (${type})${message ? `: ${message.slice(0, 140)}` :
|
|
11
|
+
const type = typeof params?.type === "string" ? params.type : "unknown";
|
|
12
|
+
const message = typeof params?.message === "string" ? params.message : "";
|
|
13
|
+
logger(`[nav] dismissing JS dialog (${type})${message ? `: ${message.slice(0, 140)}` : ""}`);
|
|
14
14
|
try {
|
|
15
|
-
await pageAny.handleJavaScriptDialog?.({ accept: true, promptText:
|
|
15
|
+
await pageAny.handleJavaScriptDialog?.({ accept: true, promptText: "" });
|
|
16
16
|
}
|
|
17
17
|
catch (error) {
|
|
18
18
|
const msg = error instanceof Error ? error.message : String(error);
|
|
19
19
|
logger(`[nav] failed to dismiss JS dialog: ${msg}`);
|
|
20
20
|
}
|
|
21
21
|
};
|
|
22
|
-
pageAny.on(
|
|
22
|
+
pageAny.on("javascriptDialogOpening", handler);
|
|
23
23
|
return () => {
|
|
24
24
|
try {
|
|
25
|
-
pageAny.off?.(
|
|
25
|
+
pageAny.off?.("javascriptDialogOpening", handler);
|
|
26
26
|
}
|
|
27
27
|
catch {
|
|
28
28
|
try {
|
|
29
|
-
pageAny.removeListener?.(
|
|
29
|
+
pageAny.removeListener?.("javascriptDialogOpening", handler);
|
|
30
30
|
}
|
|
31
31
|
catch {
|
|
32
32
|
// ignore
|
|
@@ -92,13 +92,13 @@ async function dismissBlockingUi(Runtime, logger) {
|
|
|
92
92
|
}).catch(() => null);
|
|
93
93
|
const value = outcome?.result?.value;
|
|
94
94
|
if (value?.dismissed) {
|
|
95
|
-
logger(`[nav] dismissed blocking UI (${value.action ??
|
|
95
|
+
logger(`[nav] dismissed blocking UI (${value.action ?? "unknown"})`);
|
|
96
96
|
return true;
|
|
97
97
|
}
|
|
98
98
|
return false;
|
|
99
99
|
}
|
|
100
100
|
export async function navigateToPromptReadyWithFallback(Page, Runtime, options, deps = {}) {
|
|
101
|
-
const { url, fallbackUrl, timeoutMs, fallbackTimeoutMs, headless, logger
|
|
101
|
+
const { url, fallbackUrl, timeoutMs, fallbackTimeoutMs, headless, logger } = options;
|
|
102
102
|
const navigate = deps.navigateToChatGPT ?? navigateToChatGPT;
|
|
103
103
|
const ensureBlocked = deps.ensureNotBlocked ?? ensureNotBlocked;
|
|
104
104
|
const ensureReady = deps.ensurePromptReady ?? ensurePromptReady;
|
|
@@ -115,6 +115,8 @@ export async function navigateToPromptReadyWithFallback(Page, Runtime, options,
|
|
|
115
115
|
}
|
|
116
116
|
const fallbackTimeout = fallbackTimeoutMs ?? Math.max(timeoutMs * 2, 120_000);
|
|
117
117
|
logger(`Prompt not ready after ${Math.round(timeoutMs / 1000)}s on ${url}; retrying ${fallbackUrl} with ${Math.round(fallbackTimeout / 1000)}s timeout.`);
|
|
118
|
+
await navigate(Page, Runtime, "about:blank", logger);
|
|
119
|
+
await delay(250);
|
|
118
120
|
await navigate(Page, Runtime, fallbackUrl, logger);
|
|
119
121
|
await ensureBlocked(Runtime, headless, logger);
|
|
120
122
|
await dismissBlockingUi(Runtime, logger).catch(() => false);
|
|
@@ -125,10 +127,15 @@ export async function navigateToPromptReadyWithFallback(Page, Runtime, options,
|
|
|
125
127
|
export async function ensureNotBlocked(Runtime, headless, logger) {
|
|
126
128
|
if (await isCloudflareInterstitial(Runtime)) {
|
|
127
129
|
const message = headless
|
|
128
|
-
?
|
|
129
|
-
:
|
|
130
|
-
logger(
|
|
131
|
-
throw new BrowserAutomationError(message, { stage:
|
|
130
|
+
? "Cloudflare challenge detected in headless mode. Re-run with --headful so you can solve the challenge."
|
|
131
|
+
: "Cloudflare challenge detected. Complete the “Just a moment…” check in the open browser, then rerun.";
|
|
132
|
+
logger("Cloudflare anti-bot page detected");
|
|
133
|
+
throw new BrowserAutomationError(message, { stage: "cloudflare-challenge", headless });
|
|
134
|
+
}
|
|
135
|
+
if (await isChatGptAccountSecurityBlock(Runtime)) {
|
|
136
|
+
const message = "ChatGPT account security block detected. Open chatgpt.com in Chrome, secure the account, then rerun Oracle.";
|
|
137
|
+
logger("ChatGPT account security block detected");
|
|
138
|
+
throw new BrowserAutomationError(message, { stage: "chatgpt-account-blocked" });
|
|
132
139
|
}
|
|
133
140
|
}
|
|
134
141
|
const LOGIN_CHECK_TIMEOUT_MS = 5_000;
|
|
@@ -157,98 +164,92 @@ export async function ensureLoggedIn(Runtime, logger, options = {}) {
|
|
|
157
164
|
});
|
|
158
165
|
const retryProbe = normalizeLoginProbe(retryOutcome.result?.value);
|
|
159
166
|
if (retryProbe.ok) {
|
|
160
|
-
logger(
|
|
167
|
+
logger("Login restored via Welcome back account picker");
|
|
161
168
|
return;
|
|
162
169
|
}
|
|
163
170
|
logger(`Login retry after Welcome back failed (status=${retryProbe.status}, domLoginCta=${Boolean(retryProbe.domLoginCta)})`);
|
|
164
171
|
}
|
|
165
|
-
logger(`Login probe failed (status=${probe.status}, domLoginCta=${Boolean(probe.domLoginCta)}, onAuthPage=${Boolean(probe.onAuthPage)}, url=${probe.pageUrl ??
|
|
166
|
-
const domLabel = probe.domLoginCta ?
|
|
172
|
+
logger(`Login probe failed (status=${probe.status}, domLoginCta=${Boolean(probe.domLoginCta)}, onAuthPage=${Boolean(probe.onAuthPage)}, url=${probe.pageUrl ?? "n/a"}, error=${probe.error ?? "none"})`);
|
|
173
|
+
const domLabel = probe.domLoginCta ? " Login button detected on page." : "";
|
|
167
174
|
const cookieHint = options.remoteSession
|
|
168
|
-
?
|
|
175
|
+
? "The remote Chrome session is not signed into ChatGPT. Sign in there, then rerun."
|
|
169
176
|
: (options.appliedCookies ?? 0) === 0
|
|
170
|
-
?
|
|
171
|
-
:
|
|
177
|
+
? "No ChatGPT cookies were applied; sign in to chatgpt.com in Chrome or pass inline cookies (--browser-inline-cookies[(-file)] / ORACLE_BROWSER_COOKIES_JSON)."
|
|
178
|
+
: "ChatGPT login appears missing; open chatgpt.com in Chrome to refresh the session or provide inline cookies (--browser-inline-cookies[(-file)] / ORACLE_BROWSER_COOKIES_JSON).";
|
|
172
179
|
throw new Error(`ChatGPT session not detected.${domLabel} ${cookieHint}`);
|
|
173
180
|
}
|
|
174
181
|
async function attemptWelcomeBackLogin(Runtime, logger) {
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const TIMEOUT_MS = 30000;
|
|
179
|
-
const getLabel = (node) =>
|
|
180
|
-
(node?.textContent || node?.getAttribute?.('aria-label') || '').trim();
|
|
181
|
-
const isAccount = (label) =>
|
|
182
|
-
Boolean(label) &&
|
|
183
|
-
label.includes('@') &&
|
|
184
|
-
!/log in|sign up|create account|another account/i.test(label);
|
|
185
|
-
const findAccount = () => {
|
|
186
|
-
const candidates = Array.from(document.querySelectorAll('[role="button"],button,a'));
|
|
187
|
-
return candidates.find((node) => isAccount(getLabel(node))) || null;
|
|
188
|
-
};
|
|
189
|
-
const clickAccount = () => {
|
|
190
|
-
const account = findAccount();
|
|
191
|
-
if (!account) return null;
|
|
182
|
+
const deadline = Date.now() + 30_000;
|
|
183
|
+
while (Date.now() < deadline) {
|
|
184
|
+
let outcome;
|
|
192
185
|
try {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
return new Promise((resolve) => {
|
|
208
|
-
const timer = setTimeout(() => {
|
|
209
|
-
observer.disconnect();
|
|
210
|
-
resolve({ clicked: false, reason: 'timeout' });
|
|
211
|
-
}, TIMEOUT_MS);
|
|
212
|
-
const observer = new MutationObserver(() => {
|
|
213
|
-
const result = clickAccount();
|
|
214
|
-
if (result) {
|
|
215
|
-
clearTimeout(timer);
|
|
216
|
-
observer.disconnect();
|
|
217
|
-
resolve(result);
|
|
186
|
+
outcome = await Runtime.evaluate({
|
|
187
|
+
expression: `(() => {
|
|
188
|
+
// Learned: "Welcome back" shows as a modal with account chips; click the email chip.
|
|
189
|
+
const getLabel = (node) =>
|
|
190
|
+
(node?.textContent || node?.getAttribute?.('aria-label') || '').trim();
|
|
191
|
+
const isAccount = (label) =>
|
|
192
|
+
Boolean(label) &&
|
|
193
|
+
label.includes('@') &&
|
|
194
|
+
!/log in|sign up|create account|another account/i.test(label);
|
|
195
|
+
const candidates = Array.from(document.querySelectorAll('[role="button"],button,a'));
|
|
196
|
+
const account = candidates.find((node) => isAccount(getLabel(node))) || null;
|
|
197
|
+
if (!account) {
|
|
198
|
+
return { clicked: false, reason: 'not-found' };
|
|
218
199
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
200
|
+
const label = getLabel(account);
|
|
201
|
+
setTimeout(() => {
|
|
202
|
+
try {
|
|
203
|
+
account.click();
|
|
204
|
+
} catch {
|
|
205
|
+
// ignore; caller will re-probe login state
|
|
206
|
+
}
|
|
207
|
+
}, 0);
|
|
208
|
+
return { clicked: true, label };
|
|
209
|
+
})()`,
|
|
210
|
+
awaitPromise: false,
|
|
211
|
+
returnByValue: true,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
216
|
+
if (/navigated or closed|context was destroyed|target closed/i.test(message)) {
|
|
217
|
+
logger("Welcome back account click triggered navigation.");
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
logger(`Welcome back auto-select probe failed: ${message}`);
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
if (outcome.exceptionDetails) {
|
|
224
|
+
const details = outcome.exceptionDetails;
|
|
225
|
+
const description = (details.exception &&
|
|
226
|
+
typeof details.exception.description === "string" &&
|
|
227
|
+
details.exception.description) ||
|
|
228
|
+
details.text ||
|
|
229
|
+
"unknown error";
|
|
230
|
+
logger(`Welcome back auto-select probe failed: ${description}`);
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
const result = outcome.result?.value;
|
|
234
|
+
if (!result) {
|
|
235
|
+
logger("Welcome back auto-select probe returned no result.");
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
if (!("clicked" in result) && !("reason" in result)) {
|
|
239
|
+
logger("Welcome back auto-select probe returned an unexpected result.");
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
if (result.clicked) {
|
|
243
|
+
logger(`Welcome back modal detected; selected account ${result.label ?? "(unknown)"}`);
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
if (result.reason && result.reason !== "not-found") {
|
|
247
|
+
logger(`Welcome back modal present but auto-select failed (${result.reason}).`);
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
await delay(500);
|
|
251
251
|
}
|
|
252
|
+
logger("Welcome back modal not detected after login probe failure.");
|
|
252
253
|
return false;
|
|
253
254
|
}
|
|
254
255
|
export async function ensurePromptReady(Runtime, timeoutMs, logger) {
|
|
@@ -257,15 +258,15 @@ export async function ensurePromptReady(Runtime, timeoutMs, logger) {
|
|
|
257
258
|
const authUrl = await currentUrl(Runtime);
|
|
258
259
|
if (authUrl && isAuthLoginUrl(authUrl)) {
|
|
259
260
|
// Learned: auth.openai.com/login can appear after cookies are copied; allow manual login window.
|
|
260
|
-
logger(
|
|
261
|
+
logger("Auth login page detected; waiting for manual login to complete...");
|
|
261
262
|
const extended = Math.min(Math.max(timeoutMs, 60_000), 20 * 60_000);
|
|
262
263
|
const loggedIn = await waitForPrompt(Runtime, extended);
|
|
263
264
|
if (loggedIn) {
|
|
264
265
|
return;
|
|
265
266
|
}
|
|
266
267
|
}
|
|
267
|
-
await logDomFailure(Runtime, logger,
|
|
268
|
-
throw new Error(
|
|
268
|
+
await logDomFailure(Runtime, logger, "prompt-textarea");
|
|
269
|
+
throw new Error("Prompt textarea did not appear before timeout");
|
|
269
270
|
}
|
|
270
271
|
}
|
|
271
272
|
async function waitForDocumentReady(Runtime, timeoutMs) {
|
|
@@ -275,24 +276,24 @@ async function waitForDocumentReady(Runtime, timeoutMs) {
|
|
|
275
276
|
expression: `document.readyState`,
|
|
276
277
|
returnByValue: true,
|
|
277
278
|
});
|
|
278
|
-
if (result?.value ===
|
|
279
|
+
if (result?.value === "complete" || result?.value === "interactive") {
|
|
279
280
|
return;
|
|
280
281
|
}
|
|
281
282
|
await delay(100);
|
|
282
283
|
}
|
|
283
|
-
throw new Error(
|
|
284
|
+
throw new Error("Page did not reach ready state in time");
|
|
284
285
|
}
|
|
285
286
|
async function currentUrl(Runtime) {
|
|
286
287
|
const { result } = await Runtime.evaluate({
|
|
287
288
|
expression: 'typeof location === "object" && location.href ? location.href : null',
|
|
288
289
|
returnByValue: true,
|
|
289
290
|
});
|
|
290
|
-
return typeof result?.value ===
|
|
291
|
+
return typeof result?.value === "string" ? result.value : null;
|
|
291
292
|
}
|
|
292
293
|
function isAuthLoginUrl(url) {
|
|
293
294
|
try {
|
|
294
295
|
const parsed = new URL(url);
|
|
295
|
-
if (parsed.hostname.includes(
|
|
296
|
+
if (parsed.hostname.includes("auth.openai.com")) {
|
|
296
297
|
return true;
|
|
297
298
|
}
|
|
298
299
|
return /^\/log-?in/i.test(parsed.pathname);
|
|
@@ -325,8 +326,11 @@ async function waitForPrompt(Runtime, timeoutMs) {
|
|
|
325
326
|
return false;
|
|
326
327
|
}
|
|
327
328
|
async function isCloudflareInterstitial(Runtime) {
|
|
328
|
-
const { result: titleResult } = await Runtime.evaluate({
|
|
329
|
-
|
|
329
|
+
const { result: titleResult } = await Runtime.evaluate({
|
|
330
|
+
expression: "document.title",
|
|
331
|
+
returnByValue: true,
|
|
332
|
+
});
|
|
333
|
+
const title = typeof titleResult.value === "string" ? titleResult.value : "";
|
|
330
334
|
const challengeTitle = CLOUDFLARE_TITLE.toLowerCase();
|
|
331
335
|
if (title.toLowerCase().includes(challengeTitle)) {
|
|
332
336
|
return true;
|
|
@@ -337,11 +341,27 @@ async function isCloudflareInterstitial(Runtime) {
|
|
|
337
341
|
});
|
|
338
342
|
return Boolean(result.value);
|
|
339
343
|
}
|
|
344
|
+
async function isChatGptAccountSecurityBlock(Runtime) {
|
|
345
|
+
try {
|
|
346
|
+
const outcome = await Runtime.evaluate({
|
|
347
|
+
expression: `(() => {
|
|
348
|
+
const text = String(document.body?.innerText || '').toLowerCase().replace(/\\s+/g, ' ');
|
|
349
|
+
return text.includes('suspicious activity detected') &&
|
|
350
|
+
text.includes('secure your account') &&
|
|
351
|
+
text.includes('regain access');
|
|
352
|
+
})()`,
|
|
353
|
+
returnByValue: true,
|
|
354
|
+
});
|
|
355
|
+
return Boolean(outcome?.result?.value);
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
340
361
|
function buildLoginProbeExpression(timeoutMs) {
|
|
341
362
|
return `(async () => {
|
|
342
363
|
// Learned: /backend-api/me is the most reliable "am I logged in" signal.
|
|
343
364
|
// Some UIs render without a session; use DOM + network for a robust answer.
|
|
344
|
-
const timer = setTimeout(() => {}, ${timeoutMs});
|
|
345
365
|
const pageUrl = typeof location === 'object' && location?.href ? location.href : null;
|
|
346
366
|
const onAuthPage =
|
|
347
367
|
typeof location === 'object' &&
|
|
@@ -367,12 +387,26 @@ function buildLoginProbeExpression(timeoutMs) {
|
|
|
367
387
|
const textMatches = (text) => {
|
|
368
388
|
if (!text) return false;
|
|
369
389
|
const normalized = text.toLowerCase().trim();
|
|
370
|
-
return
|
|
371
|
-
|
|
390
|
+
return (
|
|
391
|
+
['log in', 'login', 'sign in', 'signin', 'continue with', 'sign up for free'].some(
|
|
392
|
+
(needle) => normalized.startsWith(needle),
|
|
393
|
+
) ||
|
|
394
|
+
normalized.includes('get responses tailored to you') ||
|
|
395
|
+
normalized.includes('log in to get answers')
|
|
372
396
|
);
|
|
373
397
|
};
|
|
374
398
|
for (const node of candidates) {
|
|
375
399
|
if (!(node instanceof HTMLElement)) continue;
|
|
400
|
+
const rect = node.getBoundingClientRect();
|
|
401
|
+
const style = window.getComputedStyle(node);
|
|
402
|
+
if (
|
|
403
|
+
rect.width <= 0 ||
|
|
404
|
+
rect.height <= 0 ||
|
|
405
|
+
style.display === 'none' ||
|
|
406
|
+
style.visibility === 'hidden'
|
|
407
|
+
) {
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
376
410
|
const label =
|
|
377
411
|
node.textContent?.trim() ||
|
|
378
412
|
node.getAttribute('aria-label') ||
|
|
@@ -385,33 +419,45 @@ function buildLoginProbeExpression(timeoutMs) {
|
|
|
385
419
|
return false;
|
|
386
420
|
};
|
|
387
421
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
422
|
+
const readBackendStatus = async () => {
|
|
423
|
+
try {
|
|
424
|
+
if (typeof fetch === 'function') {
|
|
425
|
+
const controller = new AbortController();
|
|
426
|
+
const timeout = setTimeout(() => controller.abort(), ${timeoutMs});
|
|
427
|
+
try {
|
|
428
|
+
// Credentials included so we see a 200 only when cookies are valid.
|
|
429
|
+
const response = await fetch('/backend-api/me', {
|
|
430
|
+
cache: 'no-store',
|
|
431
|
+
credentials: 'include',
|
|
432
|
+
signal: controller.signal,
|
|
433
|
+
});
|
|
434
|
+
return { status: response.status || 0, error: null };
|
|
435
|
+
} finally {
|
|
436
|
+
clearTimeout(timeout);
|
|
437
|
+
}
|
|
404
438
|
}
|
|
439
|
+
} catch (err) {
|
|
440
|
+
return { status: 0, error: err ? String(err) : 'unknown' };
|
|
441
|
+
}
|
|
442
|
+
return { status: 0, error: null };
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
let { status, error } = await readBackendStatus();
|
|
446
|
+
let domLoginCta = hasLoginCta();
|
|
447
|
+
const settleDeadline = Date.now() + Math.min(${timeoutMs}, 2500);
|
|
448
|
+
while (!domLoginCta && Date.now() < settleDeadline) {
|
|
449
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
450
|
+
domLoginCta = hasLoginCta();
|
|
451
|
+
if (status === 0 || status === 401 || status === 403) {
|
|
452
|
+
const next = await readBackendStatus();
|
|
453
|
+
status = next.status;
|
|
454
|
+
error = next.error;
|
|
405
455
|
}
|
|
406
|
-
} catch (err) {
|
|
407
|
-
error = err ? String(err) : 'unknown';
|
|
408
456
|
}
|
|
409
457
|
|
|
410
|
-
const domLoginCta = hasLoginCta();
|
|
411
458
|
const loginSignals = domLoginCta || onAuthPage;
|
|
412
|
-
clearTimeout(timer);
|
|
413
459
|
return {
|
|
414
|
-
ok: !loginSignals &&
|
|
460
|
+
ok: !loginSignals && status === 200,
|
|
415
461
|
status,
|
|
416
462
|
redirected: false,
|
|
417
463
|
url: pageUrl,
|
|
@@ -423,23 +469,23 @@ function buildLoginProbeExpression(timeoutMs) {
|
|
|
423
469
|
})()`;
|
|
424
470
|
}
|
|
425
471
|
function normalizeLoginProbe(raw) {
|
|
426
|
-
if (!raw || typeof raw !==
|
|
472
|
+
if (!raw || typeof raw !== "object") {
|
|
427
473
|
return { ok: false, status: 0 };
|
|
428
474
|
}
|
|
429
475
|
const value = raw;
|
|
430
476
|
const statusRaw = value.status;
|
|
431
|
-
const status = typeof statusRaw ===
|
|
477
|
+
const status = typeof statusRaw === "number"
|
|
432
478
|
? statusRaw
|
|
433
|
-
: typeof statusRaw ===
|
|
479
|
+
: typeof statusRaw === "string" && !Number.isNaN(Number(statusRaw))
|
|
434
480
|
? Number(statusRaw)
|
|
435
481
|
: 0;
|
|
436
482
|
return {
|
|
437
483
|
ok: Boolean(value.ok),
|
|
438
484
|
status: Number.isFinite(status) ? status : 0,
|
|
439
|
-
url: typeof value.url ===
|
|
485
|
+
url: typeof value.url === "string" ? value.url : null,
|
|
440
486
|
redirected: Boolean(value.redirected),
|
|
441
|
-
error: typeof value.error ===
|
|
442
|
-
pageUrl: typeof value.pageUrl ===
|
|
487
|
+
error: typeof value.error === "string" ? value.error : null,
|
|
488
|
+
pageUrl: typeof value.pageUrl === "string" ? value.pageUrl : null,
|
|
443
489
|
domLoginCta: Boolean(value.domLoginCta),
|
|
444
490
|
onAuthPage: Boolean(value.onAuthPage),
|
|
445
491
|
};
|