@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,15 +1,15 @@
1
- import { INPUT_SELECTORS, PROMPT_PRIMARY_SELECTOR, PROMPT_FALLBACK_SELECTOR, SEND_BUTTON_SELECTORS, CONVERSATION_TURN_SELECTOR, STOP_BUTTON_SELECTOR, ASSISTANT_ROLE_SELECTOR, } from '../constants.js';
2
- import { delay } from '../utils.js';
3
- import { logDomFailure } from '../domDebug.js';
4
- import { buildClickDispatcher } from './domEvents.js';
5
- import { BrowserAutomationError } from '../../oracle/errors.js';
1
+ import { INPUT_SELECTORS, PROMPT_PRIMARY_SELECTOR, PROMPT_FALLBACK_SELECTOR, SEND_BUTTON_SELECTORS, CONVERSATION_TURN_SELECTOR, STOP_BUTTON_SELECTOR, ASSISTANT_ROLE_SELECTOR, } from "../constants.js";
2
+ import { delay } from "../utils.js";
3
+ import { logDomFailure } from "../domDebug.js";
4
+ import { buildClickDispatcher } from "./domEvents.js";
5
+ import { BrowserAutomationError } from "../../oracle/errors.js";
6
6
  const ENTER_KEY_EVENT = {
7
- key: 'Enter',
8
- code: 'Enter',
7
+ key: "Enter",
8
+ code: "Enter",
9
9
  windowsVirtualKeyCode: 13,
10
10
  nativeVirtualKeyCode: 13,
11
11
  };
12
- const ENTER_KEY_TEXT = '\r';
12
+ const ENTER_KEY_TEXT = "\r";
13
13
  export async function submitPrompt(deps, prompt, logger) {
14
14
  const { runtime, input } = deps;
15
15
  await waitForDomReady(runtime, logger, deps.inputTimeoutMs ?? undefined);
@@ -63,8 +63,8 @@ export async function submitPrompt(deps, prompt, logger) {
63
63
  awaitPromise: true,
64
64
  });
65
65
  if (!focusResult.result?.value?.focused) {
66
- await logDomFailure(runtime, logger, 'focus-textarea');
67
- throw new Error('Failed to focus prompt textarea');
66
+ await logDomFailure(runtime, logger, "focus-textarea");
67
+ throw new Error("Failed to focus prompt textarea");
68
68
  }
69
69
  await input.insertText({ text: prompt });
70
70
  // Some pages (notably ChatGPT when subscriptions/widgets load) need a brief settle
@@ -99,12 +99,12 @@ export async function submitPrompt(deps, prompt, logger) {
99
99
  })()`,
100
100
  returnByValue: true,
101
101
  });
102
- const editorTextRaw = verification.result?.value?.editorText ?? '';
103
- const fallbackValueRaw = verification.result?.value?.fallbackValue ?? '';
104
- const activeValueRaw = verification.result?.value?.activeValue ?? '';
105
- const editorTextTrimmed = editorTextRaw?.trim?.() ?? '';
106
- const fallbackValueTrimmed = fallbackValueRaw?.trim?.() ?? '';
107
- const activeValueTrimmed = activeValueRaw?.trim?.() ?? '';
102
+ const editorTextRaw = verification.result?.value?.editorText ?? "";
103
+ const fallbackValueRaw = verification.result?.value?.fallbackValue ?? "";
104
+ const activeValueRaw = verification.result?.value?.activeValue ?? "";
105
+ const editorTextTrimmed = editorTextRaw?.trim?.() ?? "";
106
+ const fallbackValueTrimmed = fallbackValueRaw?.trim?.() ?? "";
107
+ const activeValueTrimmed = activeValueRaw?.trim?.() ?? "";
108
108
  if (!editorTextTrimmed && !fallbackValueTrimmed && !activeValueTrimmed) {
109
109
  // Learned: occasionally Input.insertText doesn't land in the editor; force textContent/value + input events.
110
110
  await runtime.evaluate({
@@ -152,16 +152,16 @@ export async function submitPrompt(deps, prompt, logger) {
152
152
  })()`,
153
153
  returnByValue: true,
154
154
  });
155
- const observedEditor = postVerification.result?.value?.editorText ?? '';
156
- const observedFallback = postVerification.result?.value?.fallbackValue ?? '';
157
- const observedActive = postVerification.result?.value?.activeValue ?? '';
155
+ const observedEditor = postVerification.result?.value?.editorText ?? "";
156
+ const observedFallback = postVerification.result?.value?.fallbackValue ?? "";
157
+ const observedActive = postVerification.result?.value?.activeValue ?? "";
158
158
  const observedLength = Math.max(observedEditor.length, observedFallback.length, observedActive.length);
159
159
  if (promptLength >= 50_000 && observedLength > 0 && observedLength < promptLength - 2_000) {
160
160
  // Learned: very large prompts can truncate silently; fail fast so we can fall back to file uploads.
161
- await logDomFailure(runtime, logger, 'prompt-too-large');
162
- throw new BrowserAutomationError('Prompt appears truncated in the composer (likely too large).', {
163
- stage: 'submit-prompt',
164
- code: 'prompt-too-large',
161
+ await logDomFailure(runtime, logger, "prompt-too-large");
162
+ throw new BrowserAutomationError("Prompt appears truncated in the composer (likely too large).", {
163
+ stage: "submit-prompt",
164
+ code: "prompt-too-large",
165
165
  promptLength,
166
166
  observedLength,
167
167
  });
@@ -169,19 +169,19 @@ export async function submitPrompt(deps, prompt, logger) {
169
169
  const clicked = await attemptSendButton(runtime, logger, deps?.attachmentNames);
170
170
  if (!clicked) {
171
171
  await input.dispatchKeyEvent({
172
- type: 'keyDown',
172
+ type: "keyDown",
173
173
  ...ENTER_KEY_EVENT,
174
174
  text: ENTER_KEY_TEXT,
175
175
  unmodifiedText: ENTER_KEY_TEXT,
176
176
  });
177
177
  await input.dispatchKeyEvent({
178
- type: 'keyUp',
178
+ type: "keyUp",
179
179
  ...ENTER_KEY_EVENT,
180
180
  });
181
- logger('Submitted prompt via Enter key');
181
+ logger("Submitted prompt via Enter key");
182
182
  }
183
183
  else {
184
- logger('Clicked send button');
184
+ logger("Clicked send button");
185
185
  }
186
186
  const commitTimeoutMs = Math.max(60_000, deps.inputTimeoutMs ?? 0);
187
187
  // Learned: the send button can succeed but the turn doesn't appear immediately; verify commit via turns/stop button.
@@ -231,8 +231,8 @@ export async function clearPromptComposer(Runtime, logger) {
231
231
  returnByValue: true,
232
232
  });
233
233
  if (!result.result?.value?.cleared) {
234
- await logDomFailure(Runtime, logger, 'clear-composer');
235
- throw new Error('Failed to clear prompt composer');
234
+ await logDomFailure(Runtime, logger, "clear-composer");
235
+ throw new Error("Failed to clear prompt composer");
236
236
  }
237
237
  await delay(250);
238
238
  }
@@ -265,24 +265,42 @@ function buildAttachmentReadyExpression(attachmentNames) {
265
265
  document.querySelector('form') ||
266
266
  document.body ||
267
267
  document;
268
- const match = (node, name) => (node?.textContent || '').toLowerCase().includes(name);
268
+ const labelText = (node) =>
269
+ [
270
+ node?.textContent,
271
+ node?.getAttribute?.('aria-label'),
272
+ node?.getAttribute?.('title'),
273
+ node?.getAttribute?.('data-testid'),
274
+ ]
275
+ .filter(Boolean)
276
+ .join(' ')
277
+ .toLowerCase();
278
+ const match = (node, name) => labelText(node).includes(name);
269
279
 
270
280
  // Restrict to attachment affordances; never scan generic div/span nodes (prompt text can contain the file name).
271
281
  const attachmentSelectors = [
272
282
  '[data-testid*="chip"]',
273
283
  '[data-testid*="attachment"]',
274
284
  '[data-testid*="upload"]',
275
- '[aria-label="Remove file"]',
276
- 'button[aria-label="Remove file"]',
285
+ '[data-testid*="file"]',
286
+ '[aria-label*="Remove file"]',
287
+ 'button[aria-label*="Remove file"]',
288
+ '[aria-label*="remove file"]',
289
+ 'button[aria-label*="remove file"]',
277
290
  ];
291
+ const attachmentRoots = Array.from(new Set([composer, document])).filter(Boolean);
278
292
 
279
293
  const chipsReady = names.every((name) =>
280
- Array.from(composer.querySelectorAll(attachmentSelectors.join(','))).some((node) => match(node, name)),
294
+ attachmentRoots.some((root) =>
295
+ Array.from(root.querySelectorAll(attachmentSelectors.join(','))).some((node) => match(node, name)),
296
+ ),
281
297
  );
282
298
  const inputsReady = names.every((name) =>
283
- Array.from(composer.querySelectorAll('input[type="file"]')).some((el) =>
284
- Array.from((el instanceof HTMLInputElement ? el.files : []) || []).some((file) =>
285
- file?.name?.toLowerCase?.().includes(name),
299
+ attachmentRoots.some((root) =>
300
+ Array.from(root.querySelectorAll('input[type="file"]')).some((el) =>
301
+ Array.from((el instanceof HTMLInputElement ? el.files : []) || []).some((file) =>
302
+ file?.name?.toLowerCase?.().includes(name),
303
+ ),
286
304
  ),
287
305
  ),
288
306
  );
@@ -297,28 +315,36 @@ async function attemptSendButton(Runtime, _logger, attachmentNames) {
297
315
  const script = `(() => {
298
316
  ${buildClickDispatcher()}
299
317
  const selectors = ${JSON.stringify(SEND_BUTTON_SELECTORS)};
300
- let button = null;
318
+ const isVisible = (node) => {
319
+ if (!(node instanceof HTMLElement)) return false;
320
+ const rect = node.getBoundingClientRect();
321
+ if (rect.width <= 0 || rect.height <= 0) return false;
322
+ const style = window.getComputedStyle(node);
323
+ return style.display !== 'none' && style.visibility !== 'hidden';
324
+ };
325
+ const isEnabled = (node) => {
326
+ const ariaDisabled = node.getAttribute('aria-disabled');
327
+ const dataDisabled = node.getAttribute('data-disabled');
328
+ const style = window.getComputedStyle(node);
329
+ return !(
330
+ node.hasAttribute('disabled') ||
331
+ ariaDisabled === 'true' ||
332
+ dataDisabled === 'true' ||
333
+ style.pointerEvents === 'none' ||
334
+ style.display === 'none'
335
+ );
336
+ };
337
+ const candidates = [];
301
338
  for (const selector of selectors) {
302
- button = document.querySelector(selector);
303
- if (button) break;
339
+ candidates.push(...Array.from(document.querySelectorAll(selector)));
304
340
  }
341
+ const button = candidates.find((node) => isVisible(node) && isEnabled(node)) || null;
305
342
  if (!button) return 'missing';
306
- const ariaDisabled = button.getAttribute('aria-disabled');
307
- const dataDisabled = button.getAttribute('data-disabled');
308
- const style = window.getComputedStyle(button);
309
- const disabled =
310
- button.hasAttribute('disabled') ||
311
- ariaDisabled === 'true' ||
312
- dataDisabled === 'true' ||
313
- style.pointerEvents === 'none' ||
314
- style.display === 'none';
315
- // Learned: some send buttons render but are inert; only click when truly enabled.
316
- if (disabled) return 'disabled';
317
343
  // Use unified pointer/mouse sequence to satisfy React handlers.
318
344
  dispatchClickSequence(button);
319
345
  return 'clicked';
320
346
  })()`;
321
- const deadline = Date.now() + 8_000;
347
+ const deadline = Date.now() + 20_000;
322
348
  while (Date.now() < deadline) {
323
349
  const needAttachment = Array.isArray(attachmentNames) && attachmentNames.length > 0;
324
350
  if (needAttachment) {
@@ -332,14 +358,21 @@ async function attemptSendButton(Runtime, _logger, attachmentNames) {
332
358
  }
333
359
  }
334
360
  const { result } = await Runtime.evaluate({ expression: script, returnByValue: true });
335
- if (result.value === 'clicked') {
361
+ if (result.value === "clicked") {
336
362
  return true;
337
363
  }
338
- if (result.value === 'missing') {
364
+ if (result.value === "missing") {
339
365
  break;
340
366
  }
341
367
  await delay(100);
342
368
  }
369
+ if (Array.isArray(attachmentNames) && attachmentNames.length > 0) {
370
+ throw new BrowserAutomationError("Attachments never reached a clickable send button before timeout.", {
371
+ stage: "submit-prompt",
372
+ code: "attachment-send-not-ready",
373
+ attachmentNames,
374
+ });
375
+ }
343
376
  return false;
344
377
  }
345
378
  async function verifyPromptCommitted(Runtime, prompt, timeoutMs, logger, baselineTurns) {
@@ -351,7 +384,7 @@ async function verifyPromptCommitted(Runtime, prompt, timeoutMs, logger, baselin
351
384
  const stopSelectorLiteral = JSON.stringify(STOP_BUTTON_SELECTOR);
352
385
  const assistantSelectorLiteral = JSON.stringify(ASSISTANT_ROLE_SELECTOR);
353
386
  const turnSelectorLiteral = JSON.stringify(CONVERSATION_TURN_SELECTOR);
354
- let baseline = typeof baselineTurns === 'number' && Number.isFinite(baselineTurns) && baselineTurns >= 0
387
+ let baseline = typeof baselineTurns === "number" && Number.isFinite(baselineTurns) && baselineTurns >= 0
355
388
  ? Math.floor(baselineTurns)
356
389
  : null;
357
390
  if (baseline === null) {
@@ -360,7 +393,7 @@ async function verifyPromptCommitted(Runtime, prompt, timeoutMs, logger, baselin
360
393
  expression: `document.querySelectorAll(${turnSelectorLiteral}).length`,
361
394
  returnByValue: true,
362
395
  });
363
- const raw = typeof result?.value === 'number' ? result.value : Number(result?.value);
396
+ const raw = typeof result?.value === "number" ? result.value : Number(result?.value);
364
397
  if (Number.isFinite(raw)) {
365
398
  baseline = Math.max(0, Math.floor(raw));
366
399
  }
@@ -450,15 +483,15 @@ async function verifyPromptCommitted(Runtime, prompt, timeoutMs, logger, baselin
450
483
  const info = result.value;
451
484
  const turnsCount = result.value?.turnsCount;
452
485
  const matchesPrompt = Boolean(info?.lastMatched || info?.userMatched || info?.prefixMatched);
453
- const baselineUnknown = typeof info?.baseline === 'number' ? info.baseline < 0 : baselineLiteral < 0;
486
+ const baselineUnknown = typeof info?.baseline === "number" ? info.baseline < 0 : baselineLiteral < 0;
454
487
  if (matchesPrompt && (baselineUnknown || info?.hasNewTurn)) {
455
- return typeof turnsCount === 'number' && Number.isFinite(turnsCount) ? turnsCount : null;
488
+ return typeof turnsCount === "number" && Number.isFinite(turnsCount) ? turnsCount : null;
456
489
  }
457
490
  const fallbackCommit = info?.composerCleared &&
458
491
  Boolean(info?.hasNewTurn) &&
459
492
  ((info?.stopVisible ?? false) || info?.assistantVisible || info?.inConversation);
460
493
  if (fallbackCommit) {
461
- return typeof turnsCount === 'number' && Number.isFinite(turnsCount) ? turnsCount : null;
494
+ return typeof turnsCount === "number" && Number.isFinite(turnsCount) ? turnsCount : null;
462
495
  }
463
496
  await delay(100);
464
497
  }
@@ -466,20 +499,23 @@ async function verifyPromptCommitted(Runtime, prompt, timeoutMs, logger, baselin
466
499
  logger(`Prompt commit check failed; latest state: ${await Runtime.evaluate({
467
500
  expression: script,
468
501
  returnByValue: true,
469
- }).then((res) => JSON.stringify(res?.result?.value)).catch(() => 'unavailable')}`);
470
- await logDomFailure(Runtime, logger, 'prompt-commit');
502
+ })
503
+ .then((res) => JSON.stringify(res?.result?.value))
504
+ .catch(() => "unavailable")}`);
505
+ await logDomFailure(Runtime, logger, "prompt-commit");
471
506
  }
472
507
  if (prompt.trim().length >= 50_000) {
473
- throw new BrowserAutomationError('Prompt did not appear in conversation before timeout (likely too large).', {
474
- stage: 'submit-prompt',
475
- code: 'prompt-too-large',
508
+ throw new BrowserAutomationError("Prompt did not appear in conversation before timeout (likely too large).", {
509
+ stage: "submit-prompt",
510
+ code: "prompt-too-large",
476
511
  promptLength: prompt.trim().length,
477
512
  timeoutMs,
478
513
  });
479
514
  }
480
- throw new Error('Prompt did not appear in conversation before timeout (send may have failed)');
515
+ throw new Error("Prompt did not appear in conversation before timeout (send may have failed)");
481
516
  }
482
517
  // biome-ignore lint/style/useNamingConvention: test-only export used in vitest suite
483
518
  export const __test__ = {
519
+ attemptSendButton,
484
520
  verifyPromptCommitted,
485
521
  };
@@ -1,9 +1,9 @@
1
- import path from 'node:path';
2
- import { FILE_INPUT_SELECTORS } from '../constants.js';
3
- import { waitForAttachmentVisible } from './attachments.js';
4
- import { delay } from '../utils.js';
5
- import { logDomFailure } from '../domDebug.js';
6
- import { transferAttachmentViaDataTransfer } from './attachmentDataTransfer.js';
1
+ import path from "node:path";
2
+ import { FILE_INPUT_SELECTORS } from "../constants.js";
3
+ import { waitForAttachmentVisible } from "./attachments.js";
4
+ import { delay } from "../utils.js";
5
+ import { logDomFailure } from "../domDebug.js";
6
+ import { transferAttachmentViaDataTransfer } from "./attachmentDataTransfer.js";
7
7
  /**
8
8
  * Upload file to remote Chrome by transferring content via CDP
9
9
  * Used when browser is on a different machine than CLI
@@ -11,7 +11,7 @@ import { transferAttachmentViaDataTransfer } from './attachmentDataTransfer.js';
11
11
  export async function uploadAttachmentViaDataTransfer(deps, attachment, logger) {
12
12
  const { runtime, dom } = deps;
13
13
  if (!dom) {
14
- throw new Error('DOM domain unavailable while uploading attachments.');
14
+ throw new Error("DOM domain unavailable while uploading attachments.");
15
15
  }
16
16
  logger(`Transferring ${path.basename(attachment.path)} to remote browser...`);
17
17
  // Find file input element
@@ -25,13 +25,13 @@ export async function uploadAttachmentViaDataTransfer(deps, attachment, logger)
25
25
  }
26
26
  }
27
27
  if (!fileInputSelector) {
28
- await logDomFailure(runtime, logger, 'file-input');
29
- throw new Error('Unable to locate ChatGPT file attachment input.');
28
+ await logDomFailure(runtime, logger, "file-input");
29
+ throw new Error("Unable to locate ChatGPT file attachment input.");
30
30
  }
31
31
  const transferResult = await transferAttachmentViaDataTransfer(runtime, attachment, fileInputSelector);
32
32
  logger(`File transferred: ${transferResult.fileName} (${transferResult.size} bytes)`);
33
33
  // Give ChatGPT a moment to process the file
34
34
  await delay(500);
35
35
  await waitForAttachmentVisible(runtime, transferResult.fileName, 10_000, logger);
36
- logger('Attachment queued');
36
+ logger("Attachment queued");
37
37
  }