@steipete/oracle 0.9.0 → 0.10.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 +61 -48
- package/dist/bin/oracle-cli.js +455 -402
- 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/assistantResponse.js +149 -101
- package/dist/src/browser/actions/attachmentDataTransfer.js +49 -47
- package/dist/src/browser/actions/attachments.js +246 -150
- package/dist/src/browser/actions/domEvents.js +2 -2
- package/dist/src/browser/actions/modelSelection.js +275 -117
- package/dist/src/browser/actions/navigation.js +161 -137
- package/dist/src/browser/actions/promptComposer.js +100 -64
- package/dist/src/browser/actions/remoteFileTransfer.js +10 -10
- package/dist/src/browser/actions/thinkingTime.js +207 -110
- package/dist/src/browser/chromeLifecycle.js +62 -60
- package/dist/src/browser/config.js +34 -15
- package/dist/src/browser/constants.js +17 -12
- package/dist/src/browser/cookies.js +19 -19
- package/dist/src/browser/detect.js +62 -62
- package/dist/src/browser/domDebug.js +1 -1
- package/dist/src/browser/index.js +390 -295
- 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 +44 -39
- 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 +67 -34
- package/dist/src/browser/reattachHelpers.js +31 -26
- package/dist/src/browser/sessionRunner.js +37 -25
- package/dist/src/browser/utils.js +9 -9
- package/dist/src/browserMode.js +1 -1
- package/dist/src/cli/bridge/claudeConfig.js +16 -16
- 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 +62 -48
- package/dist/src/cli/browserDefaults.js +27 -26
- 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 +29 -25
- package/dist/src/cli/duplicatePromptGuard.js +3 -3
- 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 +127 -106
- package/dist/src/cli/oscUtils.js +1 -1
- 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 +31 -21
- package/dist/src/cli/sessionDisplay.js +95 -81
- package/dist/src/cli/sessionLineage.js +6 -2
- package/dist/src/cli/sessionRunner.js +103 -93
- 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/server.js +16 -12
- package/dist/src/mcp/tools/consult.js +51 -47
- package/dist/src/mcp/tools/sessionResources.js +12 -12
- package/dist/src/mcp/tools/sessions.js +26 -17
- package/dist/src/mcp/types.js +5 -5
- package/dist/src/mcp/utils.js +15 -7
- 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 +156 -134
- 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/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 +77 -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 +66 -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/types.js +0 -1
- 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
|
@@ -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,10 @@ 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 });
|
|
132
134
|
}
|
|
133
135
|
}
|
|
134
136
|
const LOGIN_CHECK_TIMEOUT_MS = 5_000;
|
|
@@ -157,98 +159,92 @@ export async function ensureLoggedIn(Runtime, logger, options = {}) {
|
|
|
157
159
|
});
|
|
158
160
|
const retryProbe = normalizeLoginProbe(retryOutcome.result?.value);
|
|
159
161
|
if (retryProbe.ok) {
|
|
160
|
-
logger(
|
|
162
|
+
logger("Login restored via Welcome back account picker");
|
|
161
163
|
return;
|
|
162
164
|
}
|
|
163
165
|
logger(`Login retry after Welcome back failed (status=${retryProbe.status}, domLoginCta=${Boolean(retryProbe.domLoginCta)})`);
|
|
164
166
|
}
|
|
165
|
-
logger(`Login probe failed (status=${probe.status}, domLoginCta=${Boolean(probe.domLoginCta)}, onAuthPage=${Boolean(probe.onAuthPage)}, url=${probe.pageUrl ??
|
|
166
|
-
const domLabel = probe.domLoginCta ?
|
|
167
|
+
logger(`Login probe failed (status=${probe.status}, domLoginCta=${Boolean(probe.domLoginCta)}, onAuthPage=${Boolean(probe.onAuthPage)}, url=${probe.pageUrl ?? "n/a"}, error=${probe.error ?? "none"})`);
|
|
168
|
+
const domLabel = probe.domLoginCta ? " Login button detected on page." : "";
|
|
167
169
|
const cookieHint = options.remoteSession
|
|
168
|
-
?
|
|
170
|
+
? "The remote Chrome session is not signed into ChatGPT. Sign in there, then rerun."
|
|
169
171
|
: (options.appliedCookies ?? 0) === 0
|
|
170
|
-
?
|
|
171
|
-
:
|
|
172
|
+
? "No ChatGPT cookies were applied; sign in to chatgpt.com in Chrome or pass inline cookies (--browser-inline-cookies[(-file)] / ORACLE_BROWSER_COOKIES_JSON)."
|
|
173
|
+
: "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
174
|
throw new Error(`ChatGPT session not detected.${domLabel} ${cookieHint}`);
|
|
173
175
|
}
|
|
174
176
|
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;
|
|
177
|
+
const deadline = Date.now() + 30_000;
|
|
178
|
+
while (Date.now() < deadline) {
|
|
179
|
+
let outcome;
|
|
192
180
|
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);
|
|
181
|
+
outcome = await Runtime.evaluate({
|
|
182
|
+
expression: `(() => {
|
|
183
|
+
// Learned: "Welcome back" shows as a modal with account chips; click the email chip.
|
|
184
|
+
const getLabel = (node) =>
|
|
185
|
+
(node?.textContent || node?.getAttribute?.('aria-label') || '').trim();
|
|
186
|
+
const isAccount = (label) =>
|
|
187
|
+
Boolean(label) &&
|
|
188
|
+
label.includes('@') &&
|
|
189
|
+
!/log in|sign up|create account|another account/i.test(label);
|
|
190
|
+
const candidates = Array.from(document.querySelectorAll('[role="button"],button,a'));
|
|
191
|
+
const account = candidates.find((node) => isAccount(getLabel(node))) || null;
|
|
192
|
+
if (!account) {
|
|
193
|
+
return { clicked: false, reason: 'not-found' };
|
|
218
194
|
}
|
|
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
|
-
|
|
195
|
+
const label = getLabel(account);
|
|
196
|
+
setTimeout(() => {
|
|
197
|
+
try {
|
|
198
|
+
account.click();
|
|
199
|
+
} catch {
|
|
200
|
+
// ignore; caller will re-probe login state
|
|
201
|
+
}
|
|
202
|
+
}, 0);
|
|
203
|
+
return { clicked: true, label };
|
|
204
|
+
})()`,
|
|
205
|
+
awaitPromise: false,
|
|
206
|
+
returnByValue: true,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
211
|
+
if (/navigated or closed|context was destroyed|target closed/i.test(message)) {
|
|
212
|
+
logger("Welcome back account click triggered navigation.");
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
logger(`Welcome back auto-select probe failed: ${message}`);
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
if (outcome.exceptionDetails) {
|
|
219
|
+
const details = outcome.exceptionDetails;
|
|
220
|
+
const description = (details.exception &&
|
|
221
|
+
typeof details.exception.description === "string" &&
|
|
222
|
+
details.exception.description) ||
|
|
223
|
+
details.text ||
|
|
224
|
+
"unknown error";
|
|
225
|
+
logger(`Welcome back auto-select probe failed: ${description}`);
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
const result = outcome.result?.value;
|
|
229
|
+
if (!result) {
|
|
230
|
+
logger("Welcome back auto-select probe returned no result.");
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
if (!("clicked" in result) && !("reason" in result)) {
|
|
234
|
+
logger("Welcome back auto-select probe returned an unexpected result.");
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
if (result.clicked) {
|
|
238
|
+
logger(`Welcome back modal detected; selected account ${result.label ?? "(unknown)"}`);
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
if (result.reason && result.reason !== "not-found") {
|
|
242
|
+
logger(`Welcome back modal present but auto-select failed (${result.reason}).`);
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
await delay(500);
|
|
251
246
|
}
|
|
247
|
+
logger("Welcome back modal not detected after login probe failure.");
|
|
252
248
|
return false;
|
|
253
249
|
}
|
|
254
250
|
export async function ensurePromptReady(Runtime, timeoutMs, logger) {
|
|
@@ -257,15 +253,15 @@ export async function ensurePromptReady(Runtime, timeoutMs, logger) {
|
|
|
257
253
|
const authUrl = await currentUrl(Runtime);
|
|
258
254
|
if (authUrl && isAuthLoginUrl(authUrl)) {
|
|
259
255
|
// Learned: auth.openai.com/login can appear after cookies are copied; allow manual login window.
|
|
260
|
-
logger(
|
|
256
|
+
logger("Auth login page detected; waiting for manual login to complete...");
|
|
261
257
|
const extended = Math.min(Math.max(timeoutMs, 60_000), 20 * 60_000);
|
|
262
258
|
const loggedIn = await waitForPrompt(Runtime, extended);
|
|
263
259
|
if (loggedIn) {
|
|
264
260
|
return;
|
|
265
261
|
}
|
|
266
262
|
}
|
|
267
|
-
await logDomFailure(Runtime, logger,
|
|
268
|
-
throw new Error(
|
|
263
|
+
await logDomFailure(Runtime, logger, "prompt-textarea");
|
|
264
|
+
throw new Error("Prompt textarea did not appear before timeout");
|
|
269
265
|
}
|
|
270
266
|
}
|
|
271
267
|
async function waitForDocumentReady(Runtime, timeoutMs) {
|
|
@@ -275,24 +271,24 @@ async function waitForDocumentReady(Runtime, timeoutMs) {
|
|
|
275
271
|
expression: `document.readyState`,
|
|
276
272
|
returnByValue: true,
|
|
277
273
|
});
|
|
278
|
-
if (result?.value ===
|
|
274
|
+
if (result?.value === "complete" || result?.value === "interactive") {
|
|
279
275
|
return;
|
|
280
276
|
}
|
|
281
277
|
await delay(100);
|
|
282
278
|
}
|
|
283
|
-
throw new Error(
|
|
279
|
+
throw new Error("Page did not reach ready state in time");
|
|
284
280
|
}
|
|
285
281
|
async function currentUrl(Runtime) {
|
|
286
282
|
const { result } = await Runtime.evaluate({
|
|
287
283
|
expression: 'typeof location === "object" && location.href ? location.href : null',
|
|
288
284
|
returnByValue: true,
|
|
289
285
|
});
|
|
290
|
-
return typeof result?.value ===
|
|
286
|
+
return typeof result?.value === "string" ? result.value : null;
|
|
291
287
|
}
|
|
292
288
|
function isAuthLoginUrl(url) {
|
|
293
289
|
try {
|
|
294
290
|
const parsed = new URL(url);
|
|
295
|
-
if (parsed.hostname.includes(
|
|
291
|
+
if (parsed.hostname.includes("auth.openai.com")) {
|
|
296
292
|
return true;
|
|
297
293
|
}
|
|
298
294
|
return /^\/log-?in/i.test(parsed.pathname);
|
|
@@ -325,8 +321,11 @@ async function waitForPrompt(Runtime, timeoutMs) {
|
|
|
325
321
|
return false;
|
|
326
322
|
}
|
|
327
323
|
async function isCloudflareInterstitial(Runtime) {
|
|
328
|
-
const { result: titleResult } = await Runtime.evaluate({
|
|
329
|
-
|
|
324
|
+
const { result: titleResult } = await Runtime.evaluate({
|
|
325
|
+
expression: "document.title",
|
|
326
|
+
returnByValue: true,
|
|
327
|
+
});
|
|
328
|
+
const title = typeof titleResult.value === "string" ? titleResult.value : "";
|
|
330
329
|
const challengeTitle = CLOUDFLARE_TITLE.toLowerCase();
|
|
331
330
|
if (title.toLowerCase().includes(challengeTitle)) {
|
|
332
331
|
return true;
|
|
@@ -341,7 +340,6 @@ function buildLoginProbeExpression(timeoutMs) {
|
|
|
341
340
|
return `(async () => {
|
|
342
341
|
// Learned: /backend-api/me is the most reliable "am I logged in" signal.
|
|
343
342
|
// Some UIs render without a session; use DOM + network for a robust answer.
|
|
344
|
-
const timer = setTimeout(() => {}, ${timeoutMs});
|
|
345
343
|
const pageUrl = typeof location === 'object' && location?.href ? location.href : null;
|
|
346
344
|
const onAuthPage =
|
|
347
345
|
typeof location === 'object' &&
|
|
@@ -367,12 +365,26 @@ function buildLoginProbeExpression(timeoutMs) {
|
|
|
367
365
|
const textMatches = (text) => {
|
|
368
366
|
if (!text) return false;
|
|
369
367
|
const normalized = text.toLowerCase().trim();
|
|
370
|
-
return
|
|
371
|
-
|
|
368
|
+
return (
|
|
369
|
+
['log in', 'login', 'sign in', 'signin', 'continue with', 'sign up for free'].some(
|
|
370
|
+
(needle) => normalized.startsWith(needle),
|
|
371
|
+
) ||
|
|
372
|
+
normalized.includes('get responses tailored to you') ||
|
|
373
|
+
normalized.includes('log in to get answers')
|
|
372
374
|
);
|
|
373
375
|
};
|
|
374
376
|
for (const node of candidates) {
|
|
375
377
|
if (!(node instanceof HTMLElement)) continue;
|
|
378
|
+
const rect = node.getBoundingClientRect();
|
|
379
|
+
const style = window.getComputedStyle(node);
|
|
380
|
+
if (
|
|
381
|
+
rect.width <= 0 ||
|
|
382
|
+
rect.height <= 0 ||
|
|
383
|
+
style.display === 'none' ||
|
|
384
|
+
style.visibility === 'hidden'
|
|
385
|
+
) {
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
376
388
|
const label =
|
|
377
389
|
node.textContent?.trim() ||
|
|
378
390
|
node.getAttribute('aria-label') ||
|
|
@@ -385,33 +397,45 @@ function buildLoginProbeExpression(timeoutMs) {
|
|
|
385
397
|
return false;
|
|
386
398
|
};
|
|
387
399
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
400
|
+
const readBackendStatus = async () => {
|
|
401
|
+
try {
|
|
402
|
+
if (typeof fetch === 'function') {
|
|
403
|
+
const controller = new AbortController();
|
|
404
|
+
const timeout = setTimeout(() => controller.abort(), ${timeoutMs});
|
|
405
|
+
try {
|
|
406
|
+
// Credentials included so we see a 200 only when cookies are valid.
|
|
407
|
+
const response = await fetch('/backend-api/me', {
|
|
408
|
+
cache: 'no-store',
|
|
409
|
+
credentials: 'include',
|
|
410
|
+
signal: controller.signal,
|
|
411
|
+
});
|
|
412
|
+
return { status: response.status || 0, error: null };
|
|
413
|
+
} finally {
|
|
414
|
+
clearTimeout(timeout);
|
|
415
|
+
}
|
|
404
416
|
}
|
|
417
|
+
} catch (err) {
|
|
418
|
+
return { status: 0, error: err ? String(err) : 'unknown' };
|
|
419
|
+
}
|
|
420
|
+
return { status: 0, error: null };
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
let { status, error } = await readBackendStatus();
|
|
424
|
+
let domLoginCta = hasLoginCta();
|
|
425
|
+
const settleDeadline = Date.now() + Math.min(${timeoutMs}, 2500);
|
|
426
|
+
while (!domLoginCta && Date.now() < settleDeadline) {
|
|
427
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
428
|
+
domLoginCta = hasLoginCta();
|
|
429
|
+
if (status === 0 || status === 401 || status === 403) {
|
|
430
|
+
const next = await readBackendStatus();
|
|
431
|
+
status = next.status;
|
|
432
|
+
error = next.error;
|
|
405
433
|
}
|
|
406
|
-
} catch (err) {
|
|
407
|
-
error = err ? String(err) : 'unknown';
|
|
408
434
|
}
|
|
409
435
|
|
|
410
|
-
const domLoginCta = hasLoginCta();
|
|
411
436
|
const loginSignals = domLoginCta || onAuthPage;
|
|
412
|
-
clearTimeout(timer);
|
|
413
437
|
return {
|
|
414
|
-
ok: !loginSignals &&
|
|
438
|
+
ok: !loginSignals && status === 200,
|
|
415
439
|
status,
|
|
416
440
|
redirected: false,
|
|
417
441
|
url: pageUrl,
|
|
@@ -423,23 +447,23 @@ function buildLoginProbeExpression(timeoutMs) {
|
|
|
423
447
|
})()`;
|
|
424
448
|
}
|
|
425
449
|
function normalizeLoginProbe(raw) {
|
|
426
|
-
if (!raw || typeof raw !==
|
|
450
|
+
if (!raw || typeof raw !== "object") {
|
|
427
451
|
return { ok: false, status: 0 };
|
|
428
452
|
}
|
|
429
453
|
const value = raw;
|
|
430
454
|
const statusRaw = value.status;
|
|
431
|
-
const status = typeof statusRaw ===
|
|
455
|
+
const status = typeof statusRaw === "number"
|
|
432
456
|
? statusRaw
|
|
433
|
-
: typeof statusRaw ===
|
|
457
|
+
: typeof statusRaw === "string" && !Number.isNaN(Number(statusRaw))
|
|
434
458
|
? Number(statusRaw)
|
|
435
459
|
: 0;
|
|
436
460
|
return {
|
|
437
461
|
ok: Boolean(value.ok),
|
|
438
462
|
status: Number.isFinite(status) ? status : 0,
|
|
439
|
-
url: typeof value.url ===
|
|
463
|
+
url: typeof value.url === "string" ? value.url : null,
|
|
440
464
|
redirected: Boolean(value.redirected),
|
|
441
|
-
error: typeof value.error ===
|
|
442
|
-
pageUrl: typeof value.pageUrl ===
|
|
465
|
+
error: typeof value.error === "string" ? value.error : null,
|
|
466
|
+
pageUrl: typeof value.pageUrl === "string" ? value.pageUrl : null,
|
|
443
467
|
domLoginCta: Boolean(value.domLoginCta),
|
|
444
468
|
onAuthPage: Boolean(value.onAuthPage),
|
|
445
469
|
};
|