@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,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
|
|
2
|
-
import { delay } from
|
|
3
|
-
import { logDomFailure } from
|
|
4
|
-
import { buildClickDispatcher } from
|
|
5
|
-
import { BrowserAutomationError } from
|
|
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:
|
|
8
|
-
code:
|
|
7
|
+
key: "Enter",
|
|
8
|
+
code: "Enter",
|
|
9
9
|
windowsVirtualKeyCode: 13,
|
|
10
10
|
nativeVirtualKeyCode: 13,
|
|
11
11
|
};
|
|
12
|
-
const ENTER_KEY_TEXT =
|
|
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,
|
|
67
|
-
throw new Error(
|
|
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,
|
|
162
|
-
throw new BrowserAutomationError(
|
|
163
|
-
stage:
|
|
164
|
-
code:
|
|
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:
|
|
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:
|
|
178
|
+
type: "keyUp",
|
|
179
179
|
...ENTER_KEY_EVENT,
|
|
180
180
|
});
|
|
181
|
-
logger(
|
|
181
|
+
logger("Submitted prompt via Enter key");
|
|
182
182
|
}
|
|
183
183
|
else {
|
|
184
|
-
logger(
|
|
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,
|
|
235
|
-
throw new Error(
|
|
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
|
|
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
|
-
'[
|
|
276
|
-
'
|
|
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
|
-
|
|
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
|
-
|
|
284
|
-
Array.from((
|
|
285
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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() +
|
|
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 ===
|
|
361
|
+
if (result.value === "clicked") {
|
|
336
362
|
return true;
|
|
337
363
|
}
|
|
338
|
-
if (result.value ===
|
|
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 ===
|
|
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 ===
|
|
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 ===
|
|
486
|
+
const baselineUnknown = typeof info?.baseline === "number" ? info.baseline < 0 : baselineLiteral < 0;
|
|
454
487
|
if (matchesPrompt && (baselineUnknown || info?.hasNewTurn)) {
|
|
455
|
-
return typeof turnsCount ===
|
|
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 ===
|
|
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
|
-
})
|
|
470
|
-
|
|
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(
|
|
474
|
-
stage:
|
|
475
|
-
code:
|
|
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(
|
|
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
|
|
2
|
-
import { FILE_INPUT_SELECTORS } from
|
|
3
|
-
import { waitForAttachmentVisible } from
|
|
4
|
-
import { delay } from
|
|
5
|
-
import { logDomFailure } from
|
|
6
|
-
import { transferAttachmentViaDataTransfer } from
|
|
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(
|
|
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,
|
|
29
|
-
throw new Error(
|
|
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(
|
|
36
|
+
logger("Attachment queued");
|
|
37
37
|
}
|