@steipete/oracle 0.20.2 → 0.21.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/dist/bin/oracle-cli.js +14 -42
- package/dist/docs-site/.nojekyll +0 -0
- package/dist/docs-site/CNAME +1 -0
- package/dist/docs-site/RELEASING.html +403 -0
- package/dist/docs-site/advisor.html +317 -0
- package/dist/docs-site/agents.html +375 -0
- package/dist/docs-site/anthropic.html +368 -0
- package/dist/docs-site/bridge.html +421 -0
- package/dist/docs-site/browser-mode.html +636 -0
- package/dist/docs-site/chromium-forks.html +355 -0
- package/dist/docs-site/cli-reference.html +347 -0
- package/dist/docs-site/configuration.html +464 -0
- package/dist/docs-site/favicon.svg +14 -0
- package/dist/docs-site/followup.html +375 -0
- package/dist/docs-site/gemini.html +387 -0
- package/dist/docs-site/grok.html +325 -0
- package/dist/docs-site/index.html +360 -0
- package/dist/docs-site/install.html +335 -0
- package/dist/docs-site/linux.html +321 -0
- package/dist/docs-site/llms.txt +44 -0
- package/dist/docs-site/manual-tests.html +621 -0
- package/dist/docs-site/mcp.html +412 -0
- package/dist/docs-site/multimodel.html +364 -0
- package/dist/docs-site/mythical-pro-agents.html +360 -0
- package/dist/docs-site/notifier.html +338 -0
- package/dist/docs-site/openai-endpoints.html +417 -0
- package/dist/docs-site/openrouter.html +344 -0
- package/dist/docs-site/quickstart.html +369 -0
- package/dist/docs-site/refactor/ux.html +321 -0
- package/dist/docs-site/sessions.html +397 -0
- package/dist/docs-site/social-card.png +0 -0
- package/dist/docs-site/social-card.svg +79 -0
- package/dist/docs-site/spec.html +363 -0
- package/dist/docs-site/testing.html +323 -0
- package/dist/docs-site/tui-debug.html +326 -0
- package/dist/docs-site/windows-work.html +348 -0
- package/dist/docs-site/windows.html +320 -0
- package/dist/src/browser/actions/promptComposer.js +0 -1
- package/dist/src/browser/chatgptConversation.js +309 -0
- package/dist/src/browser/config.js +2 -0
- package/dist/src/browser/configLogging.js +8 -0
- package/dist/src/browser/executor.js +19 -0
- package/dist/src/browser/index.js +80 -241
- package/dist/src/browser/provider.js +11 -0
- package/dist/src/browser/reattach.js +0 -1
- package/dist/src/browser/reattachHelpers.js +14 -15
- package/dist/src/browser/sessionRunner.js +7 -4
- package/dist/src/browser/uiWarnings.js +202 -0
- package/dist/src/cli/bridge/claudeConfig.js +0 -5
- package/dist/src/cli/browserConfig.js +1 -0
- package/dist/src/cli/browserDefaults.js +4 -0
- package/dist/src/cli/runOptions.js +2 -2
- package/dist/src/cli/sessionDisplay.js +0 -1
- package/dist/src/cli/sessionRunner.js +1 -0
- package/dist/src/config.js +1 -0
- package/dist/src/gemini-web/executor.js +11 -7
- package/dist/src/mcp/tools/consult.js +1 -1
- package/dist/src/oracle/background.js +0 -2
- package/dist/src/remote/client.js +8 -0
- package/dist/src/remote/server.js +13 -0
- package/dist/src/version.js +0 -1
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/package.json +7 -10
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/dist/scripts/check.js +0 -21
|
@@ -58,6 +58,7 @@ export const DEFAULT_BROWSER_CONFIG = {
|
|
|
58
58
|
researchMode: "off",
|
|
59
59
|
archiveConversations: "auto",
|
|
60
60
|
resumeConversationUrl: null,
|
|
61
|
+
captureProviderNative: false,
|
|
61
62
|
};
|
|
62
63
|
export function resolveBrowserConfig(config) {
|
|
63
64
|
const debugPortEnv = parseDebugPort(process.env.ORACLE_BROWSER_PORT ?? process.env.ORACLE_BROWSER_DEBUG_PORT);
|
|
@@ -117,6 +118,7 @@ export function resolveBrowserConfig(config) {
|
|
|
117
118
|
researchMode,
|
|
118
119
|
archiveConversations,
|
|
119
120
|
resumeConversationUrl: config?.resumeConversationUrl ?? DEFAULT_BROWSER_CONFIG.resumeConversationUrl,
|
|
121
|
+
captureProviderNative: config?.captureProviderNative ?? DEFAULT_BROWSER_CONFIG.captureProviderNative,
|
|
120
122
|
manualLogin,
|
|
121
123
|
manualLoginProfileDir: manualLogin ? resolvedProfileDir : null,
|
|
122
124
|
manualLoginCookieSync: config?.manualLoginCookieSync ?? DEFAULT_BROWSER_CONFIG.manualLoginCookieSync,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function redactBrowserConfigForDebugLog(config) {
|
|
2
|
+
const redacted = { ...config };
|
|
3
|
+
if (Array.isArray(config.inlineCookies)) {
|
|
4
|
+
redacted.inlineCookies = `[redacted:${config.inlineCookies.length} cookies]`;
|
|
5
|
+
redacted.inlineCookieCount = config.inlineCookies.length;
|
|
6
|
+
}
|
|
7
|
+
return redacted;
|
|
8
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { resolveBrowserProvider } from "./provider.js";
|
|
2
|
+
export async function resolveLocalBrowserExecutor(options) {
|
|
3
|
+
const provider = resolveBrowserProvider(options.model);
|
|
4
|
+
if (provider === "gemini") {
|
|
5
|
+
const { createGeminiWebExecutor } = await import("../gemini-web/index.js");
|
|
6
|
+
return createGeminiWebExecutor({
|
|
7
|
+
youtube: options.youtube,
|
|
8
|
+
generateImage: options.generateImage,
|
|
9
|
+
editImage: options.editImage,
|
|
10
|
+
outputPath: options.outputPath,
|
|
11
|
+
aspectRatio: options.aspectRatio,
|
|
12
|
+
showThoughts: options.geminiShowThoughts,
|
|
13
|
+
allowModelFallback: options.geminiAllowModelFallback,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
if (provider === "chatgpt")
|
|
17
|
+
return (await import("../browserMode.js")).runBrowserMode;
|
|
18
|
+
throw new Error(`Unsupported browser model: ${options.model}. Use a GPT or Gemini model.`);
|
|
19
|
+
}
|
|
@@ -6,6 +6,7 @@ import net from "node:net";
|
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
import { claimBrowserTarget } from "./targetClaim.js";
|
|
8
8
|
import { resolveBrowserConfig } from "./config.js";
|
|
9
|
+
import { redactBrowserConfigForDebugLog } from "./configLogging.js";
|
|
9
10
|
import { copyChromeProfile } from "./profileCopy.js";
|
|
10
11
|
import { BrowserCancellation, withoutBrowserCancellation } from "./cancellation.js";
|
|
11
12
|
import { launchChrome, registerTerminationHooks, positionChromeWindowOffscreen, positionChromeWindowOnscreen, connectToRemoteChrome, connectWithNewTab, closeTab, createChromePageTarget, ensureChromePageTargetAfterClose, closeBlankChromeTabs, } from "./chromeLifecycle.js";
|
|
@@ -15,7 +16,9 @@ import { INPUT_SELECTORS } from "./constants.js";
|
|
|
15
16
|
import { uploadAttachmentViaDataTransfer } from "./actions/remoteFileTransfer.js";
|
|
16
17
|
import { ensureThinkingTime } from "./actions/thinkingTime.js";
|
|
17
18
|
import { throwIfAssistantUiError } from "./actions/assistantResponse.js";
|
|
19
|
+
import { finalizeProviderNativeCapture, } from "./chatgptConversation.js";
|
|
18
20
|
import { startThinkingStatusMonitor } from "./actions/thinkingStatus.js";
|
|
21
|
+
import { classifyChatGptUiWarningText, collectChatGptUiWarnings, createAssistantTimeoutError, throwChatGptUiWarningIfPresent, } from "./uiWarnings.js";
|
|
19
22
|
import { activateDeepResearch, captureDeepResearchTargetKeys, waitForDeepResearchCompletion, waitForResearchPlanAutoConfirm, } from "./actions/deepResearch.js";
|
|
20
23
|
import { estimateTokenCount, withRetries, delay } from "./utils.js";
|
|
21
24
|
import { formatElapsed } from "../oracle/format.js";
|
|
@@ -44,17 +47,6 @@ import { extractStableConversationIdFromUrl as extractConversationIdFromUrl, isS
|
|
|
44
47
|
export { CHATGPT_URL, DEFAULT_MODEL_STRATEGY, DEFAULT_MODEL_TARGET } from "./constants.js";
|
|
45
48
|
export { parseDuration, delay, normalizeChatgptUrl, isTemporaryChatUrl } from "./utils.js";
|
|
46
49
|
export { formatThinkingLog, formatThinkingWaitingLog, buildThinkingStatusExpressionForTest, readThinkingStatusForTest, sanitizeThinkingText, startThinkingStatusMonitorForTest, } from "./actions/thinkingStatus.js";
|
|
47
|
-
function redactBrowserConfigForDebugLog(config) {
|
|
48
|
-
const redacted = { ...config };
|
|
49
|
-
if (Array.isArray(config.inlineCookies)) {
|
|
50
|
-
redacted.inlineCookies = `[redacted:${config.inlineCookies.length} cookies]`;
|
|
51
|
-
redacted.inlineCookieCount = config.inlineCookies.length;
|
|
52
|
-
}
|
|
53
|
-
return redacted;
|
|
54
|
-
}
|
|
55
|
-
export function redactBrowserConfigForDebugLogForTest(config) {
|
|
56
|
-
return redactBrowserConfigForDebugLog(config);
|
|
57
|
-
}
|
|
58
50
|
function isCloudflareChallengeError(error) {
|
|
59
51
|
if (!(error instanceof BrowserAutomationError))
|
|
60
52
|
return false;
|
|
@@ -97,207 +89,6 @@ function shouldApplyThinkingTimeSelection(config) {
|
|
|
97
89
|
// suppress an explicitly configured thinking-time selection.
|
|
98
90
|
return config.thinkingTime !== undefined;
|
|
99
91
|
}
|
|
100
|
-
const MAX_CHATGPT_UI_WARNING_CHARS = 300;
|
|
101
|
-
const MAX_CHATGPT_UI_WARNINGS = 3;
|
|
102
|
-
function classifyChatGptUiWarningText(text) {
|
|
103
|
-
const normalized = text.toLowerCase();
|
|
104
|
-
if (/\btoo many requests\b/.test(normalized) ||
|
|
105
|
-
/\bsending too many requests\b/.test(normalized) ||
|
|
106
|
-
/\btoo quickly\b/.test(normalized) ||
|
|
107
|
-
/\btemporarily limited access\b/.test(normalized) ||
|
|
108
|
-
/\bplease wait a few minutes\b/.test(normalized) ||
|
|
109
|
-
/\brate limit(?:ed)?\b/.test(normalized) ||
|
|
110
|
-
/\bslow down\b/.test(normalized)) {
|
|
111
|
-
return "rate_limit";
|
|
112
|
-
}
|
|
113
|
-
if (/\btemporarily unavailable\b/.test(normalized) ||
|
|
114
|
-
/\bsomething went wrong\b/.test(normalized) ||
|
|
115
|
-
/\bfailed to generate\b/.test(normalized) ||
|
|
116
|
-
/\btry again later\b/.test(normalized)) {
|
|
117
|
-
return "temporary_unavailable";
|
|
118
|
-
}
|
|
119
|
-
if (/\bverify you are human\b/.test(normalized) ||
|
|
120
|
-
/\bunusual activity\b/.test(normalized) ||
|
|
121
|
-
/\bcloudflare\b/.test(normalized) ||
|
|
122
|
-
/\bchallenge\b/.test(normalized) ||
|
|
123
|
-
/\blogin required\b/.test(normalized) ||
|
|
124
|
-
/\bsign in\b/.test(normalized)) {
|
|
125
|
-
return "auth_or_challenge";
|
|
126
|
-
}
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
function sanitizeChatGptUiWarningText(text) {
|
|
130
|
-
return text
|
|
131
|
-
.replace(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, "[redacted-email]")
|
|
132
|
-
.replace(/\b((?:access|auth|session)[-_ ]?token|token)\s*[:=]\s*["']?[^\s"',;]+/gi, "$1=[redacted]")
|
|
133
|
-
.replace(/\b(?:sk-(?:ant-|or-)?|xai-)[A-Za-z0-9_-]{8,}\b/g, "[redacted-token]");
|
|
134
|
-
}
|
|
135
|
-
function normalizeUiWarningCandidate(value) {
|
|
136
|
-
if (!value || typeof value !== "object")
|
|
137
|
-
return null;
|
|
138
|
-
const candidate = value;
|
|
139
|
-
const text = typeof candidate.text === "string"
|
|
140
|
-
? sanitizeChatGptUiWarningText(candidate.text.replace(/\s+/g, " ").trim())
|
|
141
|
-
: "";
|
|
142
|
-
if (!text)
|
|
143
|
-
return null;
|
|
144
|
-
return {
|
|
145
|
-
text: text.slice(0, MAX_CHATGPT_UI_WARNING_CHARS),
|
|
146
|
-
source: typeof candidate.source === "string" ? candidate.source : null,
|
|
147
|
-
role: typeof candidate.role === "string" ? candidate.role : null,
|
|
148
|
-
ariaLive: typeof candidate.ariaLive === "string" ? candidate.ariaLive : null,
|
|
149
|
-
selector: typeof candidate.selector === "string" ? candidate.selector : null,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
async function collectChatGptUiWarnings(Runtime) {
|
|
153
|
-
try {
|
|
154
|
-
const { result } = await Runtime.evaluate({
|
|
155
|
-
awaitPromise: true,
|
|
156
|
-
returnByValue: true,
|
|
157
|
-
expression: `(() => {
|
|
158
|
-
const warningPattern = /too many requests|sending too many requests|too quickly|temporarily limited access|please wait a few minutes|rate limit|rate limited|slow down|try again later|temporarily unavailable|something went wrong|failed to generate|verify you are human|unusual activity|cloudflare|challenge|login required|sign in/i;
|
|
159
|
-
const selectors = [
|
|
160
|
-
'[role="alert"]',
|
|
161
|
-
'[role="status"]',
|
|
162
|
-
'[role="dialog"]',
|
|
163
|
-
'[aria-live]',
|
|
164
|
-
'[data-testid*="toast" i]',
|
|
165
|
-
'[data-testid*="banner" i]',
|
|
166
|
-
'[data-testid*="error" i]',
|
|
167
|
-
'[class*="toast" i]',
|
|
168
|
-
'[class*="banner" i]'
|
|
169
|
-
];
|
|
170
|
-
const isVisible = (element) => {
|
|
171
|
-
if (!(element instanceof HTMLElement)) return false;
|
|
172
|
-
let current = element;
|
|
173
|
-
while (current) {
|
|
174
|
-
const currentStyle = window.getComputedStyle(current);
|
|
175
|
-
if (
|
|
176
|
-
!currentStyle ||
|
|
177
|
-
currentStyle.display === 'none' ||
|
|
178
|
-
currentStyle.visibility === 'hidden' ||
|
|
179
|
-
currentStyle.visibility === 'collapse' ||
|
|
180
|
-
Number.parseFloat(currentStyle.opacity || '1') === 0
|
|
181
|
-
) {
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
current = current.parentElement;
|
|
185
|
-
}
|
|
186
|
-
const rect = element.getBoundingClientRect();
|
|
187
|
-
return rect.width > 0 && rect.height > 0;
|
|
188
|
-
};
|
|
189
|
-
const describe = (element, source, selector = null) => ({
|
|
190
|
-
text: (element.innerText || element.textContent || '').replace(/\\s+/g, ' ').trim().slice(0, 1000),
|
|
191
|
-
source,
|
|
192
|
-
selector,
|
|
193
|
-
role: element.getAttribute('role'),
|
|
194
|
-
ariaLive: element.getAttribute('aria-live')
|
|
195
|
-
});
|
|
196
|
-
const out = [];
|
|
197
|
-
const seen = new Set();
|
|
198
|
-
const warningContainers = [];
|
|
199
|
-
const overlapsWarningContainer = (element) => warningContainers.some((container) => (
|
|
200
|
-
container !== element && (container.contains(element) || element.contains(container))
|
|
201
|
-
));
|
|
202
|
-
const add = (element, entry) => {
|
|
203
|
-
if (!entry.text || !warningPattern.test(entry.text)) return;
|
|
204
|
-
const key = entry.text + '|' + (entry.role || '') + '|' + (entry.ariaLive || '');
|
|
205
|
-
if (seen.has(key)) return;
|
|
206
|
-
seen.add(key);
|
|
207
|
-
warningContainers.push(element);
|
|
208
|
-
out.push(entry);
|
|
209
|
-
};
|
|
210
|
-
for (const selector of selectors) {
|
|
211
|
-
if (out.length >= 5) break;
|
|
212
|
-
let elements = [];
|
|
213
|
-
try {
|
|
214
|
-
elements = Array.from(document.querySelectorAll(selector));
|
|
215
|
-
} catch {
|
|
216
|
-
elements = [];
|
|
217
|
-
}
|
|
218
|
-
for (const element of elements) {
|
|
219
|
-
if (out.length >= 5) break;
|
|
220
|
-
if (overlapsWarningContainer(element)) continue;
|
|
221
|
-
if (isVisible(element)) add(element, describe(element, 'selector', selector));
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
return out.slice(0, 5);
|
|
225
|
-
})()`,
|
|
226
|
-
});
|
|
227
|
-
const rawWarnings = Array.isArray(result?.value) ? result.value : [];
|
|
228
|
-
const warnings = [];
|
|
229
|
-
const seen = new Set();
|
|
230
|
-
for (const raw of rawWarnings) {
|
|
231
|
-
const candidate = normalizeUiWarningCandidate(raw);
|
|
232
|
-
if (!candidate)
|
|
233
|
-
continue;
|
|
234
|
-
const type = classifyChatGptUiWarningText(candidate.text);
|
|
235
|
-
if (!type)
|
|
236
|
-
continue;
|
|
237
|
-
const key = `${type}:${candidate.text}`;
|
|
238
|
-
if (seen.has(key))
|
|
239
|
-
continue;
|
|
240
|
-
seen.add(key);
|
|
241
|
-
warnings.push({
|
|
242
|
-
type,
|
|
243
|
-
message: candidate.text,
|
|
244
|
-
source: candidate.source,
|
|
245
|
-
role: candidate.role,
|
|
246
|
-
ariaLive: candidate.ariaLive,
|
|
247
|
-
selector: candidate.selector,
|
|
248
|
-
});
|
|
249
|
-
if (warnings.length >= MAX_CHATGPT_UI_WARNINGS)
|
|
250
|
-
break;
|
|
251
|
-
}
|
|
252
|
-
return warnings;
|
|
253
|
-
}
|
|
254
|
-
catch {
|
|
255
|
-
return [];
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
function formatChatGptUiWarningType(type) {
|
|
259
|
-
switch (type) {
|
|
260
|
-
case "rate_limit":
|
|
261
|
-
return "rate-limit";
|
|
262
|
-
case "temporary_unavailable":
|
|
263
|
-
return "temporary-unavailable";
|
|
264
|
-
case "auth_or_challenge":
|
|
265
|
-
return "authentication/challenge";
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
async function createChatGptUiWarningError(params) {
|
|
269
|
-
const [uiWarning] = await collectChatGptUiWarnings(params.Runtime);
|
|
270
|
-
if (!uiWarning)
|
|
271
|
-
return null;
|
|
272
|
-
params.logger(`[browser] ChatGPT UI warning detected (${uiWarning.type}): ${uiWarning.message}`);
|
|
273
|
-
return new BrowserAutomationError(`ChatGPT displayed a ${formatChatGptUiWarningType(uiWarning.type)} warning while waiting for ${params.waitTarget}: ${uiWarning.message}`, {
|
|
274
|
-
stage: params.stage,
|
|
275
|
-
code: "chatgpt-ui-warning",
|
|
276
|
-
uiWarning,
|
|
277
|
-
runtime: params.runtime,
|
|
278
|
-
diagnostics: params.diagnostics,
|
|
279
|
-
}, params.cause);
|
|
280
|
-
}
|
|
281
|
-
async function throwChatGptUiWarningIfPresent(params) {
|
|
282
|
-
const error = await createChatGptUiWarningError(params);
|
|
283
|
-
if (error)
|
|
284
|
-
throw error;
|
|
285
|
-
}
|
|
286
|
-
async function createAssistantTimeoutError(params) {
|
|
287
|
-
const warningError = await createChatGptUiWarningError({
|
|
288
|
-
Runtime: params.Runtime,
|
|
289
|
-
logger: params.logger,
|
|
290
|
-
runtime: params.runtime,
|
|
291
|
-
stage: "assistant-timeout",
|
|
292
|
-
waitTarget: "the assistant",
|
|
293
|
-
diagnostics: params.diagnostics,
|
|
294
|
-
cause: params.cause,
|
|
295
|
-
});
|
|
296
|
-
if (!warningError) {
|
|
297
|
-
return new BrowserAutomationError("Assistant response timed out before completion; reattach later to capture the answer.", { stage: "assistant-timeout", runtime: params.runtime, diagnostics: params.diagnostics }, params.cause);
|
|
298
|
-
}
|
|
299
|
-
return warningError;
|
|
300
|
-
}
|
|
301
92
|
/**
|
|
302
93
|
* Make the page behave like a focused foreground tab.
|
|
303
94
|
*
|
|
@@ -653,6 +444,31 @@ function formatBrowserLeaseDiagnostics(options) {
|
|
|
653
444
|
`launch=${options.launchDisposition ?? "unknown"}`,
|
|
654
445
|
].join("; ");
|
|
655
446
|
}
|
|
447
|
+
/**
|
|
448
|
+
* Provider-native capture, gated on explicit opt-in.
|
|
449
|
+
*
|
|
450
|
+
* Off by default because it costs two extra authenticated requests per run and
|
|
451
|
+
* only matters when a caller intends to treat the transcript as evidence rather
|
|
452
|
+
* than as an answer. When it is on and it fails, the run is unaffected: the
|
|
453
|
+
* summary records why, and nothing throws.
|
|
454
|
+
*/
|
|
455
|
+
async function runProviderNativeCapture(params) {
|
|
456
|
+
if (!params.config.captureProviderNative) {
|
|
457
|
+
return { artifacts: [] };
|
|
458
|
+
}
|
|
459
|
+
const conversationId = params.conversationUrl
|
|
460
|
+
? extractConversationIdFromUrl(params.conversationUrl)
|
|
461
|
+
: undefined;
|
|
462
|
+
return finalizeProviderNativeCapture({
|
|
463
|
+
Runtime: params.Runtime,
|
|
464
|
+
conversationId,
|
|
465
|
+
conversationUrl: params.conversationUrl,
|
|
466
|
+
sessionId: params.sessionId,
|
|
467
|
+
answerMarkdown: params.answerMarkdown,
|
|
468
|
+
answerMessageId: params.answerMessageId,
|
|
469
|
+
logger: params.logger,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
656
472
|
function buildSkippedModelSelectionEvidence(desiredModel, strategy) {
|
|
657
473
|
return {
|
|
658
474
|
requestedModel: desiredModel ?? null,
|
|
@@ -892,6 +708,7 @@ async function runBrowserModeInternal(options, cancellation) {
|
|
|
892
708
|
const startedAt = Date.now();
|
|
893
709
|
let answerText = "";
|
|
894
710
|
let answerMarkdown = "";
|
|
711
|
+
let answerMessageId;
|
|
895
712
|
let answerHtml = "";
|
|
896
713
|
let runStatus = "attempted";
|
|
897
714
|
let connectionClosedUnexpectedly = false;
|
|
@@ -1437,15 +1254,23 @@ async function runBrowserModeInternal(options, cancellation) {
|
|
|
1437
1254
|
conversationUrl: lastUrl,
|
|
1438
1255
|
logger,
|
|
1439
1256
|
}), logger);
|
|
1257
|
+
const providerCapture = await runProviderNativeCapture({
|
|
1258
|
+
Runtime,
|
|
1259
|
+
config,
|
|
1260
|
+
conversationUrl: lastUrl,
|
|
1261
|
+
sessionId: options.sessionId,
|
|
1262
|
+
answerMarkdown: researchResult.text,
|
|
1263
|
+
logger,
|
|
1264
|
+
});
|
|
1440
1265
|
const transcriptArtifact = await saveOptionalArtifact(() => saveBrowserTranscriptArtifact({
|
|
1441
1266
|
sessionId: options.sessionId,
|
|
1442
1267
|
prompt: promptText,
|
|
1443
1268
|
answerMarkdown: researchResult.text,
|
|
1444
1269
|
conversationUrl: lastUrl,
|
|
1445
|
-
artifacts: appendArtifacts(undefined, [reportArtifact]),
|
|
1270
|
+
artifacts: appendArtifacts(appendArtifacts(undefined, [reportArtifact]), providerCapture.artifacts),
|
|
1446
1271
|
logger,
|
|
1447
1272
|
}), logger);
|
|
1448
|
-
const savedArtifacts = appendArtifacts(undefined, [reportArtifact, transcriptArtifact]);
|
|
1273
|
+
const savedArtifacts = appendArtifacts(appendArtifacts(undefined, [reportArtifact, transcriptArtifact]), providerCapture.artifacts);
|
|
1449
1274
|
const archive = await maybeArchiveCompletedConversation({
|
|
1450
1275
|
Runtime,
|
|
1451
1276
|
logger,
|
|
@@ -1459,6 +1284,7 @@ async function runBrowserModeInternal(options, cancellation) {
|
|
|
1459
1284
|
answerMarkdown: researchResult.text,
|
|
1460
1285
|
answerHtml: researchResult.html,
|
|
1461
1286
|
artifacts: savedArtifacts,
|
|
1287
|
+
providerNativeCapture: providerCapture.summary,
|
|
1462
1288
|
archive,
|
|
1463
1289
|
modelSelection: modelSelectionEvidence,
|
|
1464
1290
|
thinkingSelection: thinkingSelectionEvidence,
|
|
@@ -1771,6 +1597,7 @@ async function runBrowserModeInternal(options, cancellation) {
|
|
|
1771
1597
|
turnAnswerMarkdown = bestText;
|
|
1772
1598
|
}
|
|
1773
1599
|
}
|
|
1600
|
+
answerMessageId = turnAnswer.meta.messageId ?? undefined;
|
|
1774
1601
|
return {
|
|
1775
1602
|
label,
|
|
1776
1603
|
answerText: turnAnswerText,
|
|
@@ -1874,15 +1701,25 @@ async function runBrowserModeInternal(options, cancellation) {
|
|
|
1874
1701
|
});
|
|
1875
1702
|
const savedImageArtifacts = appendArtifacts(undefined, imageArtifacts.savedImages);
|
|
1876
1703
|
const savedBrowserArtifacts = appendArtifacts(savedImageArtifacts, fileArtifacts.savedFiles);
|
|
1704
|
+
const providerCapture = await runProviderNativeCapture({
|
|
1705
|
+
Runtime,
|
|
1706
|
+
config,
|
|
1707
|
+
conversationUrl: lastUrl,
|
|
1708
|
+
sessionId: options.sessionId,
|
|
1709
|
+
answerMarkdown,
|
|
1710
|
+
answerMessageId,
|
|
1711
|
+
logger,
|
|
1712
|
+
});
|
|
1713
|
+
const browserArtifactsWithCapture = appendArtifacts(savedBrowserArtifacts, providerCapture.artifacts);
|
|
1877
1714
|
const transcriptArtifact = await saveOptionalArtifact(() => saveBrowserTranscriptArtifact({
|
|
1878
1715
|
sessionId: options.sessionId,
|
|
1879
1716
|
prompt: promptText,
|
|
1880
1717
|
answerMarkdown,
|
|
1881
1718
|
conversationUrl: lastUrl,
|
|
1882
|
-
artifacts:
|
|
1719
|
+
artifacts: browserArtifactsWithCapture,
|
|
1883
1720
|
logger,
|
|
1884
1721
|
}), logger);
|
|
1885
|
-
const savedArtifacts = appendArtifacts(
|
|
1722
|
+
const savedArtifacts = appendArtifacts(browserArtifactsWithCapture, [transcriptArtifact]);
|
|
1886
1723
|
const archive = await maybeArchiveCompletedConversation({
|
|
1887
1724
|
Runtime,
|
|
1888
1725
|
logger,
|
|
@@ -1902,6 +1739,7 @@ async function runBrowserModeInternal(options, cancellation) {
|
|
|
1902
1739
|
answerMarkdown,
|
|
1903
1740
|
answerHtml: answerHtml.length > 0 ? answerHtml : undefined,
|
|
1904
1741
|
artifacts: savedArtifacts,
|
|
1742
|
+
providerNativeCapture: providerCapture.summary,
|
|
1905
1743
|
generatedImages: imageArtifacts.generatedImages,
|
|
1906
1744
|
savedImages: imageArtifacts.savedImages,
|
|
1907
1745
|
downloadableFiles: fileArtifacts.files,
|
|
@@ -2277,26 +2115,6 @@ async function maybeRecoverLongAssistantResponse({ runtime, baselineTurns, answe
|
|
|
2277
2115
|
}
|
|
2278
2116
|
return { answerText, answerMarkdown };
|
|
2279
2117
|
}
|
|
2280
|
-
async function _assertNavigatedToHttp(runtime, _logger, timeoutMs = 10_000) {
|
|
2281
|
-
const deadline = Date.now() + timeoutMs;
|
|
2282
|
-
let lastUrl = "";
|
|
2283
|
-
while (Date.now() < deadline) {
|
|
2284
|
-
const { result } = await runtime.evaluate({
|
|
2285
|
-
expression: 'typeof location === "object" && location.href ? location.href : ""',
|
|
2286
|
-
returnByValue: true,
|
|
2287
|
-
});
|
|
2288
|
-
const url = typeof result?.value === "string" ? result.value : "";
|
|
2289
|
-
lastUrl = url;
|
|
2290
|
-
if (/^https?:\/\//i.test(url)) {
|
|
2291
|
-
return url;
|
|
2292
|
-
}
|
|
2293
|
-
await delay(250);
|
|
2294
|
-
}
|
|
2295
|
-
throw new BrowserAutomationError("ChatGPT session not detected; page never left new tab.", {
|
|
2296
|
-
stage: "execute-browser",
|
|
2297
|
-
details: { url: lastUrl || "(empty)" },
|
|
2298
|
-
});
|
|
2299
|
-
}
|
|
2300
2118
|
function detachKeptChromeProcess(chrome) {
|
|
2301
2119
|
try {
|
|
2302
2120
|
chrome.process?.unref();
|
|
@@ -2463,6 +2281,7 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2463
2281
|
const startedAt = Date.now();
|
|
2464
2282
|
let answerText = "";
|
|
2465
2283
|
let answerMarkdown = "";
|
|
2284
|
+
let answerMessageId;
|
|
2466
2285
|
let answerHtml = "";
|
|
2467
2286
|
let connectionClosedUnexpectedly = false;
|
|
2468
2287
|
let runStatus = "attempted";
|
|
@@ -2782,15 +2601,23 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2782
2601
|
conversationUrl: lastUrl,
|
|
2783
2602
|
logger,
|
|
2784
2603
|
}), logger);
|
|
2604
|
+
const providerCapture = await runProviderNativeCapture({
|
|
2605
|
+
Runtime,
|
|
2606
|
+
config,
|
|
2607
|
+
conversationUrl: lastUrl,
|
|
2608
|
+
sessionId: options.sessionId,
|
|
2609
|
+
answerMarkdown: researchResult.text,
|
|
2610
|
+
logger,
|
|
2611
|
+
});
|
|
2785
2612
|
const transcriptArtifact = await saveOptionalArtifact(() => saveBrowserTranscriptArtifact({
|
|
2786
2613
|
sessionId: options.sessionId,
|
|
2787
2614
|
prompt: promptText,
|
|
2788
2615
|
answerMarkdown: researchResult.text,
|
|
2789
2616
|
conversationUrl: lastUrl,
|
|
2790
|
-
artifacts: appendArtifacts(undefined, [reportArtifact]),
|
|
2617
|
+
artifacts: appendArtifacts(appendArtifacts(undefined, [reportArtifact]), providerCapture.artifacts),
|
|
2791
2618
|
logger,
|
|
2792
2619
|
}), logger);
|
|
2793
|
-
const savedArtifacts = appendArtifacts(undefined, [reportArtifact, transcriptArtifact]);
|
|
2620
|
+
const savedArtifacts = appendArtifacts(appendArtifacts(undefined, [reportArtifact, transcriptArtifact]), providerCapture.artifacts);
|
|
2794
2621
|
const archive = await maybeArchiveCompletedConversation({
|
|
2795
2622
|
Runtime,
|
|
2796
2623
|
logger,
|
|
@@ -2805,6 +2632,7 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2805
2632
|
answerMarkdown: researchResult.text,
|
|
2806
2633
|
answerHtml: researchResult.html,
|
|
2807
2634
|
artifacts: savedArtifacts,
|
|
2635
|
+
providerNativeCapture: providerCapture.summary,
|
|
2808
2636
|
archive,
|
|
2809
2637
|
modelSelection: modelSelectionEvidence,
|
|
2810
2638
|
thinkingSelection: thinkingSelectionEvidence,
|
|
@@ -3082,6 +2910,7 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
3082
2910
|
turnAnswerMarkdown = bestText;
|
|
3083
2911
|
}
|
|
3084
2912
|
}
|
|
2913
|
+
answerMessageId = turnAnswer.meta.messageId ?? undefined;
|
|
3085
2914
|
return {
|
|
3086
2915
|
label,
|
|
3087
2916
|
answerText: turnAnswerText,
|
|
@@ -3177,15 +3006,25 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
3177
3006
|
});
|
|
3178
3007
|
const savedImageArtifacts = appendArtifacts(undefined, imageArtifacts.savedImages);
|
|
3179
3008
|
const savedBrowserArtifacts = appendArtifacts(savedImageArtifacts, fileArtifacts.savedFiles);
|
|
3009
|
+
const providerCapture = await runProviderNativeCapture({
|
|
3010
|
+
Runtime,
|
|
3011
|
+
config,
|
|
3012
|
+
conversationUrl: lastUrl,
|
|
3013
|
+
sessionId: options.sessionId,
|
|
3014
|
+
answerMarkdown,
|
|
3015
|
+
answerMessageId,
|
|
3016
|
+
logger,
|
|
3017
|
+
});
|
|
3018
|
+
const browserArtifactsWithCapture = appendArtifacts(savedBrowserArtifacts, providerCapture.artifacts);
|
|
3180
3019
|
const transcriptArtifact = await saveOptionalArtifact(() => saveBrowserTranscriptArtifact({
|
|
3181
3020
|
sessionId: options.sessionId,
|
|
3182
3021
|
prompt: promptText,
|
|
3183
3022
|
answerMarkdown,
|
|
3184
3023
|
conversationUrl: lastUrl,
|
|
3185
|
-
artifacts:
|
|
3024
|
+
artifacts: browserArtifactsWithCapture,
|
|
3186
3025
|
logger,
|
|
3187
3026
|
}), logger);
|
|
3188
|
-
const savedArtifacts = appendArtifacts(
|
|
3027
|
+
const savedArtifacts = appendArtifacts(browserArtifactsWithCapture, [transcriptArtifact]);
|
|
3189
3028
|
const archive = await maybeArchiveCompletedConversation({
|
|
3190
3029
|
Runtime,
|
|
3191
3030
|
logger,
|
|
@@ -3221,6 +3060,7 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
3221
3060
|
submittedPromptHash,
|
|
3222
3061
|
ownedRecoveryTarget,
|
|
3223
3062
|
artifacts: savedArtifacts,
|
|
3063
|
+
providerNativeCapture: providerCapture.summary,
|
|
3224
3064
|
generatedImages: imageArtifacts.generatedImages,
|
|
3225
3065
|
savedImages: imageArtifacts.savedImages,
|
|
3226
3066
|
downloadableFiles: fileArtifacts.files,
|
|
@@ -3333,7 +3173,6 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
3333
3173
|
}
|
|
3334
3174
|
export { estimateTokenCount } from "./utils.js";
|
|
3335
3175
|
export { resolveBrowserConfig, DEFAULT_BROWSER_CONFIG } from "./config.js";
|
|
3336
|
-
// biome-ignore lint/style/useNamingConvention: test-only export used in vitest suite
|
|
3337
3176
|
export const __test__ = {
|
|
3338
3177
|
assertManualLoginProfileReadyForRun,
|
|
3339
3178
|
closeRemoteConnectionAfterRun,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function resolveBrowserProvider(model) {
|
|
2
|
+
if (typeof model !== "string")
|
|
3
|
+
return undefined;
|
|
4
|
+
const normalized = model.trim().toLowerCase();
|
|
5
|
+
if (normalized.startsWith("gemini"))
|
|
6
|
+
return "gemini";
|
|
7
|
+
if (normalized.startsWith("gpt-"))
|
|
8
|
+
return "chatgpt";
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
export const REMOTE_GEMINI_UNSUPPORTED_MESSAGE = "Gemini browser runs are supported locally; remote browser services support ChatGPT only.";
|
|
@@ -405,7 +405,6 @@ async function readPromptPreviewTurnIndex(Runtime, promptPreview) {
|
|
|
405
405
|
});
|
|
406
406
|
return typeof result?.value === "number" ? result.value : null;
|
|
407
407
|
}
|
|
408
|
-
// biome-ignore lint/style/useNamingConvention: test-only export used in vitest suite
|
|
409
408
|
export const __test__ = {
|
|
410
409
|
pickTarget,
|
|
411
410
|
extractConversationIdFromUrl,
|
|
@@ -69,11 +69,9 @@ export async function openConversationFromSidebar(Runtime, options, attempt = 0)
|
|
|
69
69
|
expression: `(() => {
|
|
70
70
|
const conversationId = ${JSON.stringify(options.conversationId ?? null)};
|
|
71
71
|
const preferProjects = ${JSON.stringify(Boolean(options.preferProjects))};
|
|
72
|
-
const promptPreview = ${JSON.stringify(options.promptPreview ?? null)};
|
|
73
72
|
const attemptIndex = ${Math.max(0, attempt)};
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
const promptNeedles = Array.from(new Set([promptNeedleFull, promptNeedleShort].filter(Boolean)));
|
|
73
|
+
const promptNeedles = ${JSON.stringify(buildPromptPreviewNeedles(options.promptPreview, 100))};
|
|
74
|
+
const normalizeText = ${normalizeForComparison.toString()};
|
|
77
75
|
const nav = document.querySelector('nav') || document.querySelector('aside') || document.body;
|
|
78
76
|
if (preferProjects) {
|
|
79
77
|
const projectLink = Array.from(nav.querySelectorAll('a,button'))
|
|
@@ -135,7 +133,7 @@ export async function openConversationFromSidebar(Runtime, options, attempt = 0)
|
|
|
135
133
|
target = pick(mainCandidates.filter(byId)) || pick(navCandidates.filter(byId));
|
|
136
134
|
}
|
|
137
135
|
if (!target && promptNeedles.length > 0) {
|
|
138
|
-
const byPrompt = (item) => promptNeedles.some((needle) => item.text && item.text
|
|
136
|
+
const byPrompt = (item) => promptNeedles.some((needle) => item.text && normalizeText(item.text).includes(needle));
|
|
139
137
|
const sortBySpecificity = (items) =>
|
|
140
138
|
items
|
|
141
139
|
.filter(byPrompt)
|
|
@@ -200,14 +198,13 @@ export async function openConversationFromSidebarWithRetry(Runtime, options, tim
|
|
|
200
198
|
return false;
|
|
201
199
|
}
|
|
202
200
|
export async function waitForPromptPreview(Runtime, promptPreview, timeoutMs) {
|
|
203
|
-
const
|
|
204
|
-
const needleShort = needleFull.replace(/\\s*\\d{4,}\\s*$/, "").trim();
|
|
205
|
-
const needles = Array.from(new Set([needleFull, needleShort].filter(Boolean)));
|
|
201
|
+
const needles = buildPromptPreviewNeedles(promptPreview, 120);
|
|
206
202
|
if (needles.length === 0)
|
|
207
203
|
return false;
|
|
208
204
|
const selectorLiteral = JSON.stringify(CONVERSATION_TURN_SELECTOR);
|
|
209
205
|
const expression = `(() => {
|
|
210
206
|
const needles = ${JSON.stringify(needles)};
|
|
207
|
+
const normalizeText = ${normalizeForComparison.toString()};
|
|
211
208
|
const root =
|
|
212
209
|
document.querySelector('section[data-testid="screen-threadFlyOut"]') ||
|
|
213
210
|
document.querySelector('[data-testid="chat-thread"]') ||
|
|
@@ -215,20 +212,17 @@ export async function waitForPromptPreview(Runtime, promptPreview, timeoutMs) {
|
|
|
215
212
|
document.querySelector('[role="main"]');
|
|
216
213
|
if (!root) return false;
|
|
217
214
|
const userTurns = Array.from(root.querySelectorAll('[data-message-author-role="user"], [data-turn="user"]'));
|
|
218
|
-
const collectText = (nodes) =>
|
|
215
|
+
const collectText = (nodes) => normalizeText(
|
|
219
216
|
nodes
|
|
220
217
|
.map((node) => (node.innerText || node.textContent || ''))
|
|
221
|
-
.join(' ')
|
|
222
|
-
.toLowerCase();
|
|
218
|
+
.join(' '));
|
|
223
219
|
let text = collectText(userTurns);
|
|
224
|
-
let hasTurns = userTurns.length > 0;
|
|
225
220
|
if (!text) {
|
|
226
221
|
const turns = Array.from(root.querySelectorAll(${selectorLiteral}));
|
|
227
|
-
hasTurns = hasTurns || turns.length > 0;
|
|
228
222
|
text = collectText(turns);
|
|
229
223
|
}
|
|
230
224
|
if (!text) {
|
|
231
|
-
text = (root.innerText || root.textContent || '')
|
|
225
|
+
text = normalizeText(root.innerText || root.textContent || '');
|
|
232
226
|
}
|
|
233
227
|
return needles.some((needle) => text.includes(needle));
|
|
234
228
|
})()`;
|
|
@@ -282,9 +276,14 @@ export async function readConversationTurnIndex(Runtime, logger) {
|
|
|
282
276
|
function normalizeForComparison(text) {
|
|
283
277
|
return String(text || "")
|
|
284
278
|
.toLowerCase()
|
|
285
|
-
.replace(
|
|
279
|
+
.replace(/\s+/g, " ")
|
|
286
280
|
.trim();
|
|
287
281
|
}
|
|
282
|
+
function buildPromptPreviewNeedles(promptPreview, limit) {
|
|
283
|
+
const full = normalizeForComparison(promptPreview ?? "").slice(0, limit);
|
|
284
|
+
const withoutCounter = full.replace(/\s*\d{4,}\s*$/, "").trim();
|
|
285
|
+
return [...new Set([full, withoutCounter].filter(Boolean))];
|
|
286
|
+
}
|
|
288
287
|
export function buildPromptEchoMatcher(promptPreview) {
|
|
289
288
|
const normalizedPrompt = normalizeForComparison(promptPreview ?? "");
|
|
290
289
|
if (!normalizedPrompt) {
|