@steipete/oracle 0.8.6 → 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.
Files changed (181) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +130 -45
  3. package/dist/bin/oracle-cli.js +613 -379
  4. package/dist/bin/oracle-mcp.js +2 -2
  5. package/dist/bin/oracle.js +165 -279
  6. package/dist/scripts/agent-send.js +31 -31
  7. package/dist/scripts/check.js +6 -6
  8. package/dist/scripts/debug/extract-chatgpt-response.js +10 -10
  9. package/dist/scripts/docs-list.js +30 -30
  10. package/dist/scripts/git-policy.js +25 -23
  11. package/dist/scripts/run-cli.js +8 -8
  12. package/dist/scripts/runner.js +203 -195
  13. package/dist/scripts/test-browser.js +21 -18
  14. package/dist/scripts/test-remote-chrome.js +20 -20
  15. package/dist/src/bridge/connection.js +18 -18
  16. package/dist/src/bridge/userConfigFile.js +7 -7
  17. package/dist/src/browser/actions/assistantResponse.js +149 -101
  18. package/dist/src/browser/actions/attachmentDataTransfer.js +49 -47
  19. package/dist/src/browser/actions/attachments.js +246 -150
  20. package/dist/src/browser/actions/domEvents.js +2 -2
  21. package/dist/src/browser/actions/modelSelection.js +314 -104
  22. package/dist/src/browser/actions/navigation.js +161 -136
  23. package/dist/src/browser/actions/promptComposer.js +100 -64
  24. package/dist/src/browser/actions/remoteFileTransfer.js +10 -10
  25. package/dist/src/browser/actions/thinkingTime.js +207 -110
  26. package/dist/src/browser/chromeLifecycle.js +62 -60
  27. package/dist/src/browser/config.js +34 -15
  28. package/dist/src/browser/constants.js +17 -12
  29. package/dist/src/browser/cookies.js +19 -19
  30. package/dist/src/browser/detect.js +62 -62
  31. package/dist/src/browser/domDebug.js +1 -1
  32. package/dist/src/browser/index.js +452 -303
  33. package/dist/src/browser/modelStrategy.js +1 -1
  34. package/dist/src/browser/pageActions.js +5 -5
  35. package/dist/src/browser/policies.js +16 -13
  36. package/dist/src/browser/profileState.js +44 -39
  37. package/dist/src/browser/prompt.js +72 -42
  38. package/dist/src/browser/promptSummary.js +5 -5
  39. package/dist/src/browser/providerDomFlow.js +17 -0
  40. package/dist/src/browser/providers/chatgptDomProvider.js +49 -0
  41. package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +254 -0
  42. package/dist/src/browser/providers/index.js +2 -0
  43. package/dist/src/browser/reattach.js +67 -34
  44. package/dist/src/browser/reattachHelpers.js +31 -26
  45. package/dist/src/browser/sessionRunner.js +37 -25
  46. package/dist/src/browser/utils.js +9 -9
  47. package/dist/src/browserMode.js +1 -1
  48. package/dist/src/cli/bridge/claudeConfig.js +16 -16
  49. package/dist/src/cli/bridge/client.js +28 -20
  50. package/dist/src/cli/bridge/codexConfig.js +16 -16
  51. package/dist/src/cli/bridge/doctor.js +47 -39
  52. package/dist/src/cli/bridge/host.js +58 -56
  53. package/dist/src/cli/browserConfig.js +65 -45
  54. package/dist/src/cli/browserDefaults.js +27 -26
  55. package/dist/src/cli/bundleWarnings.js +1 -1
  56. package/dist/src/cli/clipboard.js +11 -2
  57. package/dist/src/cli/detach.js +7 -4
  58. package/dist/src/cli/dryRun.js +29 -25
  59. package/dist/src/cli/duplicatePromptGuard.js +3 -3
  60. package/dist/src/cli/engine.js +9 -9
  61. package/dist/src/cli/errorUtils.js +1 -1
  62. package/dist/src/cli/fileSize.js +11 -0
  63. package/dist/src/cli/format.js +2 -2
  64. package/dist/src/cli/help.js +28 -28
  65. package/dist/src/cli/hiddenAliases.js +3 -3
  66. package/dist/src/cli/markdownBundle.js +12 -8
  67. package/dist/src/cli/markdownRenderer.js +15 -15
  68. package/dist/src/cli/notifier.js +77 -67
  69. package/dist/src/cli/options.js +145 -87
  70. package/dist/src/cli/oscUtils.js +1 -1
  71. package/dist/src/cli/promptRequirement.js +2 -2
  72. package/dist/src/cli/renderOutput.js +1 -1
  73. package/dist/src/cli/rootAlias.js +1 -1
  74. package/dist/src/cli/runOptions.js +37 -25
  75. package/dist/src/cli/sessionCommand.js +31 -21
  76. package/dist/src/cli/sessionDisplay.js +182 -79
  77. package/dist/src/cli/sessionLineage.js +60 -0
  78. package/dist/src/cli/sessionRunner.js +118 -90
  79. package/dist/src/cli/sessionTable.js +28 -24
  80. package/dist/src/cli/stdin.js +22 -0
  81. package/dist/src/cli/tagline.js +121 -124
  82. package/dist/src/cli/tui/index.js +140 -127
  83. package/dist/src/cli/writeOutputPath.js +5 -5
  84. package/dist/src/config.js +7 -7
  85. package/dist/src/gemini-web/browserSessionManager.js +80 -0
  86. package/dist/src/gemini-web/client.js +81 -64
  87. package/dist/src/gemini-web/executionMode.js +16 -0
  88. package/dist/src/gemini-web/executor.js +327 -169
  89. package/dist/src/gemini-web/index.js +1 -1
  90. package/dist/src/mcp/server.js +16 -12
  91. package/dist/src/mcp/tools/consult.js +81 -64
  92. package/dist/src/mcp/tools/sessionResources.js +12 -12
  93. package/dist/src/mcp/tools/sessions.js +26 -17
  94. package/dist/src/mcp/types.js +5 -5
  95. package/dist/src/mcp/utils.js +15 -7
  96. package/dist/src/oracle/background.js +15 -15
  97. package/dist/src/oracle/claude.js +53 -25
  98. package/dist/src/oracle/client.js +84 -46
  99. package/dist/src/oracle/config.js +124 -58
  100. package/dist/src/oracle/errors.js +38 -38
  101. package/dist/src/oracle/files.js +69 -45
  102. package/dist/src/oracle/finishLine.js +10 -8
  103. package/dist/src/oracle/format.js +3 -3
  104. package/dist/src/oracle/gemini.js +37 -30
  105. package/dist/src/oracle/logging.js +7 -7
  106. package/dist/src/oracle/markdown.js +28 -28
  107. package/dist/src/oracle/modelResolver.js +16 -16
  108. package/dist/src/oracle/multiModelRunner.js +12 -12
  109. package/dist/src/oracle/oscProgress.js +8 -8
  110. package/dist/src/oracle/promptAssembly.js +6 -3
  111. package/dist/src/oracle/request.js +23 -15
  112. package/dist/src/oracle/run.js +172 -140
  113. package/dist/src/oracle/runUtils.js +8 -5
  114. package/dist/src/oracle/tokenEstimate.js +6 -6
  115. package/dist/src/oracle/tokenStats.js +5 -5
  116. package/dist/src/oracle/tokenStringifier.js +5 -5
  117. package/dist/src/oracle.js +12 -12
  118. package/dist/src/oracleHome.js +3 -3
  119. package/dist/src/remote/client.js +25 -25
  120. package/dist/src/remote/health.js +20 -20
  121. package/dist/src/remote/remoteServiceConfig.js +9 -9
  122. package/dist/src/remote/server.js +129 -118
  123. package/dist/src/sessionManager.js +81 -75
  124. package/dist/src/sessionStore.js +3 -3
  125. package/dist/src/version.js +10 -10
  126. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  127. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  128. package/dist/vendor/oracle-notifier/README.md +2 -0
  129. package/package.json +69 -65
  130. package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  131. package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  132. package/vendor/oracle-notifier/README.md +2 -0
  133. package/dist/markdansi/types/index.js +0 -4
  134. package/dist/oracle/bin/oracle-cli.js +0 -472
  135. package/dist/oracle/src/browser/actions/assistantResponse.js +0 -471
  136. package/dist/oracle/src/browser/actions/attachments.js +0 -82
  137. package/dist/oracle/src/browser/actions/modelSelection.js +0 -190
  138. package/dist/oracle/src/browser/actions/navigation.js +0 -75
  139. package/dist/oracle/src/browser/actions/promptComposer.js +0 -167
  140. package/dist/oracle/src/browser/chromeLifecycle.js +0 -104
  141. package/dist/oracle/src/browser/config.js +0 -33
  142. package/dist/oracle/src/browser/constants.js +0 -40
  143. package/dist/oracle/src/browser/cookies.js +0 -210
  144. package/dist/oracle/src/browser/domDebug.js +0 -36
  145. package/dist/oracle/src/browser/index.js +0 -331
  146. package/dist/oracle/src/browser/pageActions.js +0 -5
  147. package/dist/oracle/src/browser/prompt.js +0 -88
  148. package/dist/oracle/src/browser/promptSummary.js +0 -20
  149. package/dist/oracle/src/browser/sessionRunner.js +0 -80
  150. package/dist/oracle/src/browser/utils.js +0 -62
  151. package/dist/oracle/src/browserMode.js +0 -1
  152. package/dist/oracle/src/cli/browserConfig.js +0 -44
  153. package/dist/oracle/src/cli/dryRun.js +0 -59
  154. package/dist/oracle/src/cli/engine.js +0 -17
  155. package/dist/oracle/src/cli/errorUtils.js +0 -9
  156. package/dist/oracle/src/cli/help.js +0 -70
  157. package/dist/oracle/src/cli/markdownRenderer.js +0 -15
  158. package/dist/oracle/src/cli/options.js +0 -103
  159. package/dist/oracle/src/cli/promptRequirement.js +0 -14
  160. package/dist/oracle/src/cli/rootAlias.js +0 -30
  161. package/dist/oracle/src/cli/sessionCommand.js +0 -77
  162. package/dist/oracle/src/cli/sessionDisplay.js +0 -270
  163. package/dist/oracle/src/cli/sessionRunner.js +0 -94
  164. package/dist/oracle/src/heartbeat.js +0 -43
  165. package/dist/oracle/src/oracle/client.js +0 -48
  166. package/dist/oracle/src/oracle/config.js +0 -29
  167. package/dist/oracle/src/oracle/errors.js +0 -101
  168. package/dist/oracle/src/oracle/files.js +0 -220
  169. package/dist/oracle/src/oracle/format.js +0 -33
  170. package/dist/oracle/src/oracle/fsAdapter.js +0 -7
  171. package/dist/oracle/src/oracle/oscProgress.js +0 -60
  172. package/dist/oracle/src/oracle/request.js +0 -48
  173. package/dist/oracle/src/oracle/run.js +0 -444
  174. package/dist/oracle/src/oracle/tokenStats.js +0 -39
  175. package/dist/oracle/src/oracle/types.js +0 -1
  176. package/dist/oracle/src/oracle.js +0 -9
  177. package/dist/oracle/src/sessionManager.js +0 -205
  178. package/dist/oracle/src/version.js +0 -39
  179. package/dist/scripts/chrome/browser-tools.js +0 -295
  180. package/dist/src/browser/profileSync.js +0 -141
  181. /package/dist/{oracle/src/browser/types.js → src/gemini-web/executionClients.js} +0 -0
@@ -1,31 +1,32 @@
1
- import { CLOUDFLARE_SCRIPT_SELECTOR, CLOUDFLARE_TITLE, INPUT_SELECTORS, } from '../constants.js';
2
- import { delay } from '../utils.js';
3
- import { logDomFailure } from '../domDebug.js';
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";
4
5
  export function installJavaScriptDialogAutoDismissal(Page, logger) {
5
6
  const pageAny = Page;
6
- if (typeof pageAny.on !== 'function' || typeof pageAny.handleJavaScriptDialog !== 'function') {
7
+ if (typeof pageAny.on !== "function" || typeof pageAny.handleJavaScriptDialog !== "function") {
7
8
  return () => { };
8
9
  }
9
10
  const handler = async (params) => {
10
- const type = typeof params?.type === 'string' ? params.type : 'unknown';
11
- const message = typeof params?.message === 'string' ? params.message : '';
12
- 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)}` : ""}`);
13
14
  try {
14
- await pageAny.handleJavaScriptDialog?.({ accept: true, promptText: '' });
15
+ await pageAny.handleJavaScriptDialog?.({ accept: true, promptText: "" });
15
16
  }
16
17
  catch (error) {
17
18
  const msg = error instanceof Error ? error.message : String(error);
18
19
  logger(`[nav] failed to dismiss JS dialog: ${msg}`);
19
20
  }
20
21
  };
21
- pageAny.on('javascriptDialogOpening', handler);
22
+ pageAny.on("javascriptDialogOpening", handler);
22
23
  return () => {
23
24
  try {
24
- pageAny.off?.('javascriptDialogOpening', handler);
25
+ pageAny.off?.("javascriptDialogOpening", handler);
25
26
  }
26
27
  catch {
27
28
  try {
28
- pageAny.removeListener?.('javascriptDialogOpening', handler);
29
+ pageAny.removeListener?.("javascriptDialogOpening", handler);
29
30
  }
30
31
  catch {
31
32
  // ignore
@@ -91,13 +92,13 @@ async function dismissBlockingUi(Runtime, logger) {
91
92
  }).catch(() => null);
92
93
  const value = outcome?.result?.value;
93
94
  if (value?.dismissed) {
94
- logger(`[nav] dismissed blocking UI (${value.action ?? 'unknown'})`);
95
+ logger(`[nav] dismissed blocking UI (${value.action ?? "unknown"})`);
95
96
  return true;
96
97
  }
97
98
  return false;
98
99
  }
99
100
  export async function navigateToPromptReadyWithFallback(Page, Runtime, options, deps = {}) {
100
- const { url, fallbackUrl, timeoutMs, fallbackTimeoutMs, headless, logger, } = options;
101
+ const { url, fallbackUrl, timeoutMs, fallbackTimeoutMs, headless, logger } = options;
101
102
  const navigate = deps.navigateToChatGPT ?? navigateToChatGPT;
102
103
  const ensureBlocked = deps.ensureNotBlocked ?? ensureNotBlocked;
103
104
  const ensureReady = deps.ensurePromptReady ?? ensurePromptReady;
@@ -114,6 +115,8 @@ export async function navigateToPromptReadyWithFallback(Page, Runtime, options,
114
115
  }
115
116
  const fallbackTimeout = fallbackTimeoutMs ?? Math.max(timeoutMs * 2, 120_000);
116
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);
117
120
  await navigate(Page, Runtime, fallbackUrl, logger);
118
121
  await ensureBlocked(Runtime, headless, logger);
119
122
  await dismissBlockingUi(Runtime, logger).catch(() => false);
@@ -124,10 +127,10 @@ export async function navigateToPromptReadyWithFallback(Page, Runtime, options,
124
127
  export async function ensureNotBlocked(Runtime, headless, logger) {
125
128
  if (await isCloudflareInterstitial(Runtime)) {
126
129
  const message = headless
127
- ? 'Cloudflare challenge detected in headless mode. Re-run with --headful so you can solve the challenge.'
128
- : 'Cloudflare challenge detected. Complete the “Just a moment…” check in the open browser, then rerun.';
129
- logger('Cloudflare anti-bot page detected');
130
- throw new Error(message);
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 });
131
134
  }
132
135
  }
133
136
  const LOGIN_CHECK_TIMEOUT_MS = 5_000;
@@ -156,98 +159,92 @@ export async function ensureLoggedIn(Runtime, logger, options = {}) {
156
159
  });
157
160
  const retryProbe = normalizeLoginProbe(retryOutcome.result?.value);
158
161
  if (retryProbe.ok) {
159
- logger('Login restored via Welcome back account picker');
162
+ logger("Login restored via Welcome back account picker");
160
163
  return;
161
164
  }
162
165
  logger(`Login retry after Welcome back failed (status=${retryProbe.status}, domLoginCta=${Boolean(retryProbe.domLoginCta)})`);
163
166
  }
164
- logger(`Login probe failed (status=${probe.status}, domLoginCta=${Boolean(probe.domLoginCta)}, onAuthPage=${Boolean(probe.onAuthPage)}, url=${probe.pageUrl ?? 'n/a'}, error=${probe.error ?? 'none'})`);
165
- const domLabel = probe.domLoginCta ? ' Login button detected on page.' : '';
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." : "";
166
169
  const cookieHint = options.remoteSession
167
- ? 'The remote Chrome session is not signed into ChatGPT. Sign in there, then rerun.'
170
+ ? "The remote Chrome session is not signed into ChatGPT. Sign in there, then rerun."
168
171
  : (options.appliedCookies ?? 0) === 0
169
- ? 'No ChatGPT cookies were applied; sign in to chatgpt.com in Chrome or pass inline cookies (--browser-inline-cookies[(-file)] / ORACLE_BROWSER_COOKIES_JSON).'
170
- : '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
+ ? "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).";
171
174
  throw new Error(`ChatGPT session not detected.${domLabel} ${cookieHint}`);
172
175
  }
173
176
  async function attemptWelcomeBackLogin(Runtime, logger) {
174
- const outcome = await Runtime.evaluate({
175
- expression: `(() => {
176
- // Learned: "Welcome back" shows as a modal with account chips; click the email chip.
177
- const TIMEOUT_MS = 30000;
178
- const getLabel = (node) =>
179
- (node?.textContent || node?.getAttribute?.('aria-label') || '').trim();
180
- const isAccount = (label) =>
181
- Boolean(label) &&
182
- label.includes('@') &&
183
- !/log in|sign up|create account|another account/i.test(label);
184
- const findAccount = () => {
185
- const candidates = Array.from(document.querySelectorAll('[role="button"],button,a'));
186
- return candidates.find((node) => isAccount(getLabel(node))) || null;
187
- };
188
- const clickAccount = () => {
189
- const account = findAccount();
190
- if (!account) return null;
177
+ const deadline = Date.now() + 30_000;
178
+ while (Date.now() < deadline) {
179
+ let outcome;
191
180
  try {
192
- (account).click();
193
- } catch (_error) {
194
- return { clicked: false, reason: 'click-failed' };
195
- }
196
- return { clicked: true, label: getLabel(account) };
197
- };
198
- const immediate = clickAccount();
199
- if (immediate) {
200
- return immediate;
201
- }
202
- const root = document.documentElement || document.body;
203
- if (!root) {
204
- return { clicked: false, reason: 'no-root' };
205
- }
206
- return new Promise((resolve) => {
207
- const timer = setTimeout(() => {
208
- observer.disconnect();
209
- resolve({ clicked: false, reason: 'timeout' });
210
- }, TIMEOUT_MS);
211
- const observer = new MutationObserver(() => {
212
- const result = clickAccount();
213
- if (result) {
214
- clearTimeout(timer);
215
- observer.disconnect();
216
- 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' };
217
194
  }
218
- });
219
- observer.observe(root, {
220
- subtree: true,
221
- childList: true,
222
- characterData: true,
223
- });
224
- });
225
- })()`,
226
- awaitPromise: true,
227
- returnByValue: true,
228
- });
229
- if (outcome.exceptionDetails) {
230
- const details = outcome.exceptionDetails;
231
- const description = (details.exception && typeof details.exception.description === 'string' && details.exception.description) ||
232
- details.text ||
233
- 'unknown error';
234
- logger(`Welcome back auto-select probe failed: ${description}`);
235
- }
236
- const result = outcome.result?.value;
237
- if (!result) {
238
- logger('Welcome back auto-select probe returned no result.');
239
- return false;
240
- }
241
- if (result?.clicked) {
242
- logger(`Welcome back modal detected; selected account ${result.label ?? '(unknown)'}`);
243
- return true;
244
- }
245
- if (result?.reason && result.reason !== 'timeout') {
246
- logger(`Welcome back modal present but auto-select failed (${result.reason}).`);
247
- }
248
- if (result?.reason === 'timeout') {
249
- logger('Welcome back modal not detected after login probe failure.');
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);
250
246
  }
247
+ logger("Welcome back modal not detected after login probe failure.");
251
248
  return false;
252
249
  }
253
250
  export async function ensurePromptReady(Runtime, timeoutMs, logger) {
@@ -256,15 +253,15 @@ export async function ensurePromptReady(Runtime, timeoutMs, logger) {
256
253
  const authUrl = await currentUrl(Runtime);
257
254
  if (authUrl && isAuthLoginUrl(authUrl)) {
258
255
  // Learned: auth.openai.com/login can appear after cookies are copied; allow manual login window.
259
- logger('Auth login page detected; waiting for manual login to complete...');
256
+ logger("Auth login page detected; waiting for manual login to complete...");
260
257
  const extended = Math.min(Math.max(timeoutMs, 60_000), 20 * 60_000);
261
258
  const loggedIn = await waitForPrompt(Runtime, extended);
262
259
  if (loggedIn) {
263
260
  return;
264
261
  }
265
262
  }
266
- await logDomFailure(Runtime, logger, 'prompt-textarea');
267
- throw new Error('Prompt textarea did not appear before timeout');
263
+ await logDomFailure(Runtime, logger, "prompt-textarea");
264
+ throw new Error("Prompt textarea did not appear before timeout");
268
265
  }
269
266
  }
270
267
  async function waitForDocumentReady(Runtime, timeoutMs) {
@@ -274,24 +271,24 @@ async function waitForDocumentReady(Runtime, timeoutMs) {
274
271
  expression: `document.readyState`,
275
272
  returnByValue: true,
276
273
  });
277
- if (result?.value === 'complete' || result?.value === 'interactive') {
274
+ if (result?.value === "complete" || result?.value === "interactive") {
278
275
  return;
279
276
  }
280
277
  await delay(100);
281
278
  }
282
- throw new Error('Page did not reach ready state in time');
279
+ throw new Error("Page did not reach ready state in time");
283
280
  }
284
281
  async function currentUrl(Runtime) {
285
282
  const { result } = await Runtime.evaluate({
286
283
  expression: 'typeof location === "object" && location.href ? location.href : null',
287
284
  returnByValue: true,
288
285
  });
289
- return typeof result?.value === 'string' ? result.value : null;
286
+ return typeof result?.value === "string" ? result.value : null;
290
287
  }
291
288
  function isAuthLoginUrl(url) {
292
289
  try {
293
290
  const parsed = new URL(url);
294
- if (parsed.hostname.includes('auth.openai.com')) {
291
+ if (parsed.hostname.includes("auth.openai.com")) {
295
292
  return true;
296
293
  }
297
294
  return /^\/log-?in/i.test(parsed.pathname);
@@ -324,8 +321,11 @@ async function waitForPrompt(Runtime, timeoutMs) {
324
321
  return false;
325
322
  }
326
323
  async function isCloudflareInterstitial(Runtime) {
327
- const { result: titleResult } = await Runtime.evaluate({ expression: 'document.title', returnByValue: true });
328
- const title = typeof titleResult.value === 'string' ? titleResult.value : '';
324
+ const { result: titleResult } = await Runtime.evaluate({
325
+ expression: "document.title",
326
+ returnByValue: true,
327
+ });
328
+ const title = typeof titleResult.value === "string" ? titleResult.value : "";
329
329
  const challengeTitle = CLOUDFLARE_TITLE.toLowerCase();
330
330
  if (title.toLowerCase().includes(challengeTitle)) {
331
331
  return true;
@@ -340,7 +340,6 @@ function buildLoginProbeExpression(timeoutMs) {
340
340
  return `(async () => {
341
341
  // Learned: /backend-api/me is the most reliable "am I logged in" signal.
342
342
  // Some UIs render without a session; use DOM + network for a robust answer.
343
- const timer = setTimeout(() => {}, ${timeoutMs});
344
343
  const pageUrl = typeof location === 'object' && location?.href ? location.href : null;
345
344
  const onAuthPage =
346
345
  typeof location === 'object' &&
@@ -366,12 +365,26 @@ function buildLoginProbeExpression(timeoutMs) {
366
365
  const textMatches = (text) => {
367
366
  if (!text) return false;
368
367
  const normalized = text.toLowerCase().trim();
369
- return ['log in', 'login', 'sign in', 'signin', 'continue with'].some((needle) =>
370
- normalized.startsWith(needle),
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')
371
374
  );
372
375
  };
373
376
  for (const node of candidates) {
374
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
+ }
375
388
  const label =
376
389
  node.textContent?.trim() ||
377
390
  node.getAttribute('aria-label') ||
@@ -384,33 +397,45 @@ function buildLoginProbeExpression(timeoutMs) {
384
397
  return false;
385
398
  };
386
399
 
387
- let status = 0;
388
- let error = null;
389
- try {
390
- if (typeof fetch === 'function') {
391
- const controller = new AbortController();
392
- const timeout = setTimeout(() => controller.abort(), ${timeoutMs});
393
- try {
394
- // Credentials included so we see a 200 only when cookies are valid.
395
- const response = await fetch('/backend-api/me', {
396
- cache: 'no-store',
397
- credentials: 'include',
398
- signal: controller.signal,
399
- });
400
- status = response.status || 0;
401
- } finally {
402
- clearTimeout(timeout);
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
+ }
403
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;
404
433
  }
405
- } catch (err) {
406
- error = err ? String(err) : 'unknown';
407
434
  }
408
435
 
409
- const domLoginCta = hasLoginCta();
410
436
  const loginSignals = domLoginCta || onAuthPage;
411
- clearTimeout(timer);
412
437
  return {
413
- ok: !loginSignals && (status === 0 || status === 200),
438
+ ok: !loginSignals && status === 200,
414
439
  status,
415
440
  redirected: false,
416
441
  url: pageUrl,
@@ -422,23 +447,23 @@ function buildLoginProbeExpression(timeoutMs) {
422
447
  })()`;
423
448
  }
424
449
  function normalizeLoginProbe(raw) {
425
- if (!raw || typeof raw !== 'object') {
450
+ if (!raw || typeof raw !== "object") {
426
451
  return { ok: false, status: 0 };
427
452
  }
428
453
  const value = raw;
429
454
  const statusRaw = value.status;
430
- const status = typeof statusRaw === 'number'
455
+ const status = typeof statusRaw === "number"
431
456
  ? statusRaw
432
- : typeof statusRaw === 'string' && !Number.isNaN(Number(statusRaw))
457
+ : typeof statusRaw === "string" && !Number.isNaN(Number(statusRaw))
433
458
  ? Number(statusRaw)
434
459
  : 0;
435
460
  return {
436
461
  ok: Boolean(value.ok),
437
462
  status: Number.isFinite(status) ? status : 0,
438
- url: typeof value.url === 'string' ? value.url : null,
463
+ url: typeof value.url === "string" ? value.url : null,
439
464
  redirected: Boolean(value.redirected),
440
- error: typeof value.error === 'string' ? value.error : null,
441
- pageUrl: typeof value.pageUrl === 'string' ? value.pageUrl : null,
465
+ error: typeof value.error === "string" ? value.error : null,
466
+ pageUrl: typeof value.pageUrl === "string" ? value.pageUrl : null,
442
467
  domLoginCta: Boolean(value.domLoginCta),
443
468
  onAuthPage: Boolean(value.onAuthPage),
444
469
  };