@steipete/oracle 0.13.0 → 0.14.1
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/README.md +22 -11
- package/dist/bin/oracle-cli.js +71 -71
- package/dist/src/browser/actions/assistantResponse.js +93 -10
- package/dist/src/browser/actions/deepResearch.js +180 -52
- package/dist/src/browser/actions/modelSelection.js +357 -40
- package/dist/src/browser/actions/navigation.js +294 -65
- package/dist/src/browser/actions/promptComposer.js +50 -4
- package/dist/src/browser/actions/thinkingTime.js +517 -105
- package/dist/src/browser/artifacts.js +19 -0
- package/dist/src/browser/chatgptFiles.js +797 -0
- package/dist/src/browser/chatgptImages.js +256 -27
- package/dist/src/browser/chromeLifecycle.js +4 -0
- package/dist/src/browser/config.js +2 -0
- package/dist/src/browser/index.js +404 -41
- package/dist/src/browser/pageActions.js +1 -1
- package/dist/src/browser/policies.js +2 -6
- package/dist/src/browser/projectSourcesRunner.js +16 -3
- package/dist/src/browser/prompt.js +161 -36
- package/dist/src/browser/reattach.js +6 -2
- package/dist/src/browser/reattachability.js +22 -10
- package/dist/src/browser/recoverConversation.js +73 -0
- package/dist/src/browser/sessionRunner.js +4 -1
- package/dist/src/cli/browserConfig.js +6 -2
- package/dist/src/cli/browserDefaults.js +2 -1
- package/dist/src/cli/browserTabs.js +129 -54
- package/dist/src/cli/followup.js +72 -0
- package/dist/src/cli/help.js +1 -1
- package/dist/src/cli/options.js +24 -0
- package/dist/src/cli/reattachGuidance.js +8 -0
- package/dist/src/cli/runOptions.js +2 -12
- package/dist/src/cli/sessionCommand.js +4 -0
- package/dist/src/cli/sessionDisplay.js +17 -3
- package/dist/src/cli/sessionLineage.js +7 -4
- package/dist/src/cli/sessionRunner.js +16 -1
- package/dist/src/gemini-web/client.js +5 -10
- package/dist/src/gemini-web/executor.js +1 -24
- package/dist/src/gemini-web/models.js +83 -0
- package/dist/src/mcp/server.js +2 -0
- package/dist/src/mcp/tools/chatgptImage.js +132 -0
- package/dist/src/mcp/tools/consult.js +270 -175
- package/dist/src/mcp/types.js +13 -2
- package/dist/src/mcp/utils.js +87 -1
- package/dist/src/oracle/config.js +27 -1
- package/dist/src/oracle/files.js +4 -6
- package/dist/src/oracle/geminiModels.js +2 -0
- package/dist/src/oracle/markdown.js +36 -3
- package/dist/src/oracle/modelResolver.js +21 -13
- package/dist/src/oracle/promptAssembly.js +2 -4
- package/dist/src/oracle/request.js +3 -5
- package/dist/src/oracle/thinkingTime.js +40 -0
- package/dist/src/oracle/tokenStats.js +5 -1
- package/dist/src/oracle.js +1 -1
- package/dist/src/sessionManager.js +1 -0
- package/dist/vendor/oracle-notifier/build-notifier.sh +0 -0
- package/package.json +47 -58
- package/vendor/oracle-notifier/build-notifier.sh +0 -0
- package/dist/bin/oracle.js +0 -569
- package/dist/src/browser/chromeCookies.js +0 -312
- package/dist/src/browser/keytarShim.js +0 -56
- package/dist/src/browser/windowsCookies.js +0 -219
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +0 -20
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +0 -128
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +0 -20
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +0 -128
- package/vendor/oracle-notifier/README.md +0 -26
|
@@ -5,12 +5,12 @@ import net from "node:net";
|
|
|
5
5
|
import { resolveBrowserConfig } from "./config.js";
|
|
6
6
|
import { launchChrome, registerTerminationHooks, hideChromeWindow, connectToRemoteChrome, connectWithNewTab, closeTab, closeRemoteChromeTarget, closeBlankChromeTabs, } from "./chromeLifecycle.js";
|
|
7
7
|
import { syncCookies } from "./cookies.js";
|
|
8
|
-
import { navigateToChatGPT, navigateToPromptReadyWithFallback, ensureNotBlocked, ensureLoggedIn, ensurePromptReady, installJavaScriptDialogAutoDismissal, ensureModelSelection, clearPromptComposer, waitForAssistantResponse, captureAssistantMarkdown, clearComposerAttachments, uploadAttachmentFile, waitForAttachmentCompletion, waitForUserTurnAttachments, readAssistantSnapshot, } from "./pageActions.js";
|
|
8
|
+
import { navigateToChatGPT, navigateToPromptReadyWithFallback, ensureNotBlocked, ensureLoggedIn, ensurePromptReady, waitForResumedConversationHydration, installJavaScriptDialogAutoDismissal, ensureModelSelection, clearPromptComposer, waitForAssistantResponse, captureAssistantMarkdown, clearComposerAttachments, uploadAttachmentFile, waitForAttachmentCompletion, waitForUserTurnAttachments, readAssistantSnapshot, } from "./pageActions.js";
|
|
9
9
|
import { INPUT_SELECTORS } from "./constants.js";
|
|
10
10
|
import { uploadAttachmentViaDataTransfer } from "./actions/remoteFileTransfer.js";
|
|
11
11
|
import { ensureThinkingTime } from "./actions/thinkingTime.js";
|
|
12
12
|
import { startThinkingStatusMonitor } from "./actions/thinkingStatus.js";
|
|
13
|
-
import { activateDeepResearch, waitForDeepResearchCompletion, waitForResearchPlanAutoConfirm, } from "./actions/deepResearch.js";
|
|
13
|
+
import { activateDeepResearch, captureDeepResearchTargetKeys, waitForDeepResearchCompletion, waitForResearchPlanAutoConfirm, } from "./actions/deepResearch.js";
|
|
14
14
|
import { estimateTokenCount, withRetries, delay } from "./utils.js";
|
|
15
15
|
import { formatElapsed } from "../oracle/format.js";
|
|
16
16
|
import { CHATGPT_URL, CONVERSATION_TURN_SELECTOR, DEFAULT_MODEL_STRATEGY } from "./constants.js";
|
|
@@ -20,6 +20,7 @@ import { cleanupStaleProfileState, acquireProfileRunLock, findRunningChromeDebug
|
|
|
20
20
|
import { acquireBrowserTabLease, hasOtherActiveBrowserTabLeases, } from "./tabLeaseRegistry.js";
|
|
21
21
|
import { appendArtifacts, saveBrowserTranscriptArtifact, saveDeepResearchReportArtifact, } from "./artifacts.js";
|
|
22
22
|
import { collectGeneratedImageArtifacts } from "./chatgptImages.js";
|
|
23
|
+
import { collectChatGptFileArtifacts } from "./chatgptFiles.js";
|
|
23
24
|
import { runProviderSubmissionFlow } from "./providerDomFlow.js";
|
|
24
25
|
import { chatgptDomProvider } from "./providers/index.js";
|
|
25
26
|
import { resolveAttachRunningConnection } from "./attachRunning.js";
|
|
@@ -71,12 +72,207 @@ export function shouldPreserveBrowserOnErrorForTest(error, headless) {
|
|
|
71
72
|
export function classifyPreservedBrowserErrorForTest(error, headless) {
|
|
72
73
|
return classifyPreservedBrowserError(error, headless);
|
|
73
74
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
const MAX_CHATGPT_UI_WARNING_CHARS = 300;
|
|
76
|
+
const MAX_CHATGPT_UI_WARNINGS = 3;
|
|
77
|
+
function classifyChatGptUiWarningText(text) {
|
|
78
|
+
const normalized = text.toLowerCase();
|
|
79
|
+
if (/\btoo many requests\b/.test(normalized) ||
|
|
80
|
+
/\bsending too many requests\b/.test(normalized) ||
|
|
81
|
+
/\btoo quickly\b/.test(normalized) ||
|
|
82
|
+
/\btemporarily limited access\b/.test(normalized) ||
|
|
83
|
+
/\bplease wait a few minutes\b/.test(normalized) ||
|
|
84
|
+
/\brate limit(?:ed)?\b/.test(normalized) ||
|
|
85
|
+
/\bslow down\b/.test(normalized)) {
|
|
86
|
+
return "rate_limit";
|
|
87
|
+
}
|
|
88
|
+
if (/\btemporarily unavailable\b/.test(normalized) ||
|
|
89
|
+
/\bsomething went wrong\b/.test(normalized) ||
|
|
90
|
+
/\bfailed to generate\b/.test(normalized) ||
|
|
91
|
+
/\btry again later\b/.test(normalized)) {
|
|
92
|
+
return "temporary_unavailable";
|
|
93
|
+
}
|
|
94
|
+
if (/\bverify you are human\b/.test(normalized) ||
|
|
95
|
+
/\bunusual activity\b/.test(normalized) ||
|
|
96
|
+
/\bcloudflare\b/.test(normalized) ||
|
|
97
|
+
/\bchallenge\b/.test(normalized) ||
|
|
98
|
+
/\blogin required\b/.test(normalized) ||
|
|
99
|
+
/\bsign in\b/.test(normalized)) {
|
|
100
|
+
return "auth_or_challenge";
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
function sanitizeChatGptUiWarningText(text) {
|
|
105
|
+
return text
|
|
106
|
+
.replace(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, "[redacted-email]")
|
|
107
|
+
.replace(/\b((?:access|auth|session)[-_ ]?token|token)\s*[:=]\s*["']?[^\s"',;]+/gi, "$1=[redacted]")
|
|
108
|
+
.replace(/\b(?:sk-(?:ant-|or-)?|xai-)[A-Za-z0-9_-]{8,}\b/g, "[redacted-token]");
|
|
109
|
+
}
|
|
110
|
+
function normalizeUiWarningCandidate(value) {
|
|
111
|
+
if (!value || typeof value !== "object")
|
|
112
|
+
return null;
|
|
113
|
+
const candidate = value;
|
|
114
|
+
const text = typeof candidate.text === "string"
|
|
115
|
+
? sanitizeChatGptUiWarningText(candidate.text.replace(/\s+/g, " ").trim())
|
|
116
|
+
: "";
|
|
117
|
+
if (!text)
|
|
118
|
+
return null;
|
|
119
|
+
return {
|
|
120
|
+
text: text.slice(0, MAX_CHATGPT_UI_WARNING_CHARS),
|
|
121
|
+
source: typeof candidate.source === "string" ? candidate.source : null,
|
|
122
|
+
role: typeof candidate.role === "string" ? candidate.role : null,
|
|
123
|
+
ariaLive: typeof candidate.ariaLive === "string" ? candidate.ariaLive : null,
|
|
124
|
+
selector: typeof candidate.selector === "string" ? candidate.selector : null,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
async function collectChatGptUiWarnings(Runtime) {
|
|
128
|
+
try {
|
|
129
|
+
const { result } = await Runtime.evaluate({
|
|
130
|
+
awaitPromise: true,
|
|
131
|
+
returnByValue: true,
|
|
132
|
+
expression: `(() => {
|
|
133
|
+
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;
|
|
134
|
+
const selectors = [
|
|
135
|
+
'[role="alert"]',
|
|
136
|
+
'[role="status"]',
|
|
137
|
+
'[role="dialog"]',
|
|
138
|
+
'[aria-live]',
|
|
139
|
+
'[data-testid*="toast" i]',
|
|
140
|
+
'[data-testid*="banner" i]',
|
|
141
|
+
'[data-testid*="error" i]',
|
|
142
|
+
'[class*="toast" i]',
|
|
143
|
+
'[class*="banner" i]'
|
|
144
|
+
];
|
|
145
|
+
const isVisible = (element) => {
|
|
146
|
+
if (!(element instanceof HTMLElement)) return false;
|
|
147
|
+
let current = element;
|
|
148
|
+
while (current) {
|
|
149
|
+
const currentStyle = window.getComputedStyle(current);
|
|
150
|
+
if (
|
|
151
|
+
!currentStyle ||
|
|
152
|
+
currentStyle.display === 'none' ||
|
|
153
|
+
currentStyle.visibility === 'hidden' ||
|
|
154
|
+
currentStyle.visibility === 'collapse' ||
|
|
155
|
+
Number.parseFloat(currentStyle.opacity || '1') === 0
|
|
156
|
+
) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
current = current.parentElement;
|
|
160
|
+
}
|
|
161
|
+
const rect = element.getBoundingClientRect();
|
|
162
|
+
return rect.width > 0 && rect.height > 0;
|
|
163
|
+
};
|
|
164
|
+
const describe = (element, source, selector = null) => ({
|
|
165
|
+
text: (element.innerText || element.textContent || '').replace(/\\s+/g, ' ').trim().slice(0, 1000),
|
|
166
|
+
source,
|
|
167
|
+
selector,
|
|
168
|
+
role: element.getAttribute('role'),
|
|
169
|
+
ariaLive: element.getAttribute('aria-live')
|
|
170
|
+
});
|
|
171
|
+
const out = [];
|
|
172
|
+
const seen = new Set();
|
|
173
|
+
const warningContainers = [];
|
|
174
|
+
const overlapsWarningContainer = (element) => warningContainers.some((container) => (
|
|
175
|
+
container !== element && (container.contains(element) || element.contains(container))
|
|
176
|
+
));
|
|
177
|
+
const add = (element, entry) => {
|
|
178
|
+
if (!entry.text || !warningPattern.test(entry.text)) return;
|
|
179
|
+
const key = entry.text + '|' + (entry.role || '') + '|' + (entry.ariaLive || '');
|
|
180
|
+
if (seen.has(key)) return;
|
|
181
|
+
seen.add(key);
|
|
182
|
+
warningContainers.push(element);
|
|
183
|
+
out.push(entry);
|
|
184
|
+
};
|
|
185
|
+
for (const selector of selectors) {
|
|
186
|
+
if (out.length >= 5) break;
|
|
187
|
+
let elements = [];
|
|
188
|
+
try {
|
|
189
|
+
elements = Array.from(document.querySelectorAll(selector));
|
|
190
|
+
} catch {
|
|
191
|
+
elements = [];
|
|
192
|
+
}
|
|
193
|
+
for (const element of elements) {
|
|
194
|
+
if (out.length >= 5) break;
|
|
195
|
+
if (overlapsWarningContainer(element)) continue;
|
|
196
|
+
if (isVisible(element)) add(element, describe(element, 'selector', selector));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return out.slice(0, 5);
|
|
200
|
+
})()`,
|
|
201
|
+
});
|
|
202
|
+
const rawWarnings = Array.isArray(result?.value) ? result.value : [];
|
|
203
|
+
const warnings = [];
|
|
204
|
+
const seen = new Set();
|
|
205
|
+
for (const raw of rawWarnings) {
|
|
206
|
+
const candidate = normalizeUiWarningCandidate(raw);
|
|
207
|
+
if (!candidate)
|
|
208
|
+
continue;
|
|
209
|
+
const type = classifyChatGptUiWarningText(candidate.text);
|
|
210
|
+
if (!type)
|
|
211
|
+
continue;
|
|
212
|
+
const key = `${type}:${candidate.text}`;
|
|
213
|
+
if (seen.has(key))
|
|
214
|
+
continue;
|
|
215
|
+
seen.add(key);
|
|
216
|
+
warnings.push({
|
|
217
|
+
type,
|
|
218
|
+
message: candidate.text,
|
|
219
|
+
source: candidate.source,
|
|
220
|
+
role: candidate.role,
|
|
221
|
+
ariaLive: candidate.ariaLive,
|
|
222
|
+
selector: candidate.selector,
|
|
223
|
+
});
|
|
224
|
+
if (warnings.length >= MAX_CHATGPT_UI_WARNINGS)
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
return warnings;
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function formatChatGptUiWarningType(type) {
|
|
234
|
+
switch (type) {
|
|
235
|
+
case "rate_limit":
|
|
236
|
+
return "rate-limit";
|
|
237
|
+
case "temporary_unavailable":
|
|
238
|
+
return "temporary-unavailable";
|
|
239
|
+
case "auth_or_challenge":
|
|
240
|
+
return "authentication/challenge";
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async function createChatGptUiWarningError(params) {
|
|
244
|
+
const [uiWarning] = await collectChatGptUiWarnings(params.Runtime);
|
|
245
|
+
if (!uiWarning)
|
|
246
|
+
return null;
|
|
247
|
+
params.logger(`[browser] ChatGPT UI warning detected (${uiWarning.type}): ${uiWarning.message}`);
|
|
248
|
+
return new BrowserAutomationError(`ChatGPT displayed a ${formatChatGptUiWarningType(uiWarning.type)} warning while waiting for ${params.waitTarget}: ${uiWarning.message}`, {
|
|
249
|
+
stage: params.stage,
|
|
250
|
+
code: "chatgpt-ui-warning",
|
|
251
|
+
uiWarning,
|
|
252
|
+
runtime: params.runtime,
|
|
253
|
+
diagnostics: params.diagnostics,
|
|
254
|
+
}, params.cause);
|
|
255
|
+
}
|
|
256
|
+
async function throwChatGptUiWarningIfPresent(params) {
|
|
257
|
+
const error = await createChatGptUiWarningError(params);
|
|
258
|
+
if (error)
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
async function createAssistantTimeoutError(params) {
|
|
262
|
+
const warningError = await createChatGptUiWarningError({
|
|
263
|
+
Runtime: params.Runtime,
|
|
264
|
+
logger: params.logger,
|
|
265
|
+
runtime: params.runtime,
|
|
266
|
+
stage: "assistant-timeout",
|
|
267
|
+
waitTarget: "the assistant",
|
|
268
|
+
diagnostics: params.diagnostics,
|
|
269
|
+
cause: params.cause,
|
|
270
|
+
});
|
|
271
|
+
if (!warningError) {
|
|
272
|
+
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);
|
|
273
|
+
}
|
|
274
|
+
return warningError;
|
|
275
|
+
}
|
|
80
276
|
function listIgnoredRemoteChromeFlags(config) {
|
|
81
277
|
return [
|
|
82
278
|
config.headless ? "--browser-headless" : null,
|
|
@@ -157,7 +353,8 @@ function isImageOnlyUiChromeText(text) {
|
|
|
157
353
|
return (normalized.length === 0 ||
|
|
158
354
|
normalized === "edit" ||
|
|
159
355
|
normalized === "stopped thinking" ||
|
|
160
|
-
normalized === "stopped thinking edit"
|
|
356
|
+
normalized === "stopped thinking edit" ||
|
|
357
|
+
/^thought for \d+(?:\.\d+)?\s*(?:s|sec|secs|second|seconds|m|min|mins|minute|minutes|h|hr|hrs|hour|hours)\s+edit$/.test(normalized));
|
|
161
358
|
}
|
|
162
359
|
function normalizeBrowserFollowUpPrompts(values) {
|
|
163
360
|
return (values ?? []).map((entry) => entry.trim()).filter(Boolean);
|
|
@@ -273,6 +470,19 @@ function resolveRemoteTabLeaseProfileDir(config) {
|
|
|
273
470
|
export function resolveRemoteTabLeaseProfileDirForTest(config) {
|
|
274
471
|
return resolveRemoteTabLeaseProfileDir(config);
|
|
275
472
|
}
|
|
473
|
+
function isLocalChromeHost(host) {
|
|
474
|
+
const normalized = host
|
|
475
|
+
.trim()
|
|
476
|
+
.toLowerCase()
|
|
477
|
+
.replace(/^\[|\]$/g, "");
|
|
478
|
+
if (normalized === "localhost" || normalized === "::1") {
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
481
|
+
return net.isIPv4(normalized) && normalized.startsWith("127.");
|
|
482
|
+
}
|
|
483
|
+
export function isLocalChromeHostForTest(host) {
|
|
484
|
+
return isLocalChromeHost(host);
|
|
485
|
+
}
|
|
276
486
|
async function closeRemoteConnectionAfterRun(options) {
|
|
277
487
|
if (options.connectionClosedUnexpectedly) {
|
|
278
488
|
return;
|
|
@@ -310,6 +520,7 @@ export async function runBrowserMode(options) {
|
|
|
310
520
|
const attachments = options.attachments ?? [];
|
|
311
521
|
const fallbackSubmission = options.fallbackSubmission;
|
|
312
522
|
let config = resolveBrowserConfig(options.config);
|
|
523
|
+
const isResumingConversation = Boolean(config.resumeConversationUrl);
|
|
313
524
|
const followUpPrompts = normalizeBrowserFollowUpPrompts(options.followUpPrompts);
|
|
314
525
|
if (config.researchMode === "deep" && followUpPrompts.length > 0) {
|
|
315
526
|
throw new BrowserAutomationError("Browser follow-ups are not supported with Deep Research mode. Put the full research plan into the initial prompt or run a normal browser consult for multi-turn review.", {
|
|
@@ -499,9 +710,10 @@ export async function runBrowserMode(options) {
|
|
|
499
710
|
}
|
|
500
711
|
else {
|
|
501
712
|
const strictTabIsolation = Boolean(manualLogin && reusedChrome);
|
|
713
|
+
const devtoolsRetries = manualLogin ? 6 : 0;
|
|
502
714
|
const connection = await connectWithNewTab(chrome.port, logger, config.url, chromeHost, {
|
|
503
715
|
fallbackToDefault: !strictTabIsolation,
|
|
504
|
-
retries:
|
|
716
|
+
retries: devtoolsRetries,
|
|
505
717
|
retryDelayMs: 500,
|
|
506
718
|
});
|
|
507
719
|
client = connection.client;
|
|
@@ -596,9 +808,18 @@ export async function runBrowserMode(options) {
|
|
|
596
808
|
});
|
|
597
809
|
}
|
|
598
810
|
if (config.browserTabRef) {
|
|
811
|
+
if (isResumingConversation) {
|
|
812
|
+
await raceWithDisconnect(navigateToChatGPT(Page, Runtime, config.resumeConversationUrl, logger));
|
|
813
|
+
}
|
|
599
814
|
await raceWithDisconnect(ensureNotBlocked(Runtime, config.headless, logger));
|
|
600
815
|
await raceWithDisconnect(ensureLoggedIn(Runtime, logger));
|
|
601
816
|
await raceWithDisconnect(ensurePromptReady(Runtime, config.inputTimeoutMs, logger));
|
|
817
|
+
if (isResumingConversation) {
|
|
818
|
+
await raceWithDisconnect(waitForResumedConversationHydration(Runtime, config.inputTimeoutMs, logger, {
|
|
819
|
+
requirePriorTurns: true,
|
|
820
|
+
expectedConversationUrl: config.resumeConversationUrl,
|
|
821
|
+
}));
|
|
822
|
+
}
|
|
602
823
|
}
|
|
603
824
|
else {
|
|
604
825
|
const baseUrl = CHATGPT_URL;
|
|
@@ -616,7 +837,12 @@ export async function runBrowserMode(options) {
|
|
|
616
837
|
profileDir: userDataDir,
|
|
617
838
|
keepBrowser: effectiveKeepBrowser,
|
|
618
839
|
}));
|
|
619
|
-
if (
|
|
840
|
+
if (isResumingConversation) {
|
|
841
|
+
await raceWithDisconnect(navigateToChatGPT(Page, Runtime, config.resumeConversationUrl, logger));
|
|
842
|
+
await raceWithDisconnect(ensureNotBlocked(Runtime, config.headless, logger));
|
|
843
|
+
await raceWithDisconnect(ensurePromptReady(Runtime, config.inputTimeoutMs, logger));
|
|
844
|
+
}
|
|
845
|
+
else if (config.url !== baseUrl) {
|
|
620
846
|
await raceWithDisconnect(navigateToPromptReadyWithFallback(Page, Runtime, {
|
|
621
847
|
url: config.url,
|
|
622
848
|
fallbackUrl: baseUrl,
|
|
@@ -628,6 +854,17 @@ export async function runBrowserMode(options) {
|
|
|
628
854
|
else {
|
|
629
855
|
await raceWithDisconnect(ensurePromptReady(Runtime, config.inputTimeoutMs, logger));
|
|
630
856
|
}
|
|
857
|
+
if (isResumingConversation) {
|
|
858
|
+
// A resumed thread loads its prior history after navigation; ChatGPT can reset the
|
|
859
|
+
// composer mid-hydration and wipe a freshly-typed prompt. Wait for hydration to settle
|
|
860
|
+
// and re-confirm the composer before the prompt is typed/submitted below. Wrapped in
|
|
861
|
+
// raceWithDisconnect so a dropped client aborts immediately instead of polling to the
|
|
862
|
+
// hydration deadline. Shared with the remote path via the same helper.
|
|
863
|
+
await raceWithDisconnect(waitForResumedConversationHydration(Runtime, config.inputTimeoutMs, logger, {
|
|
864
|
+
requirePriorTurns: true,
|
|
865
|
+
expectedConversationUrl: config.resumeConversationUrl,
|
|
866
|
+
}));
|
|
867
|
+
}
|
|
631
868
|
}
|
|
632
869
|
logger(`Prompt textarea ready (initial focus, ${promptText.length.toLocaleString()} chars queued)`);
|
|
633
870
|
const captureRuntimeSnapshot = async () => {
|
|
@@ -707,7 +944,7 @@ export async function runBrowserMode(options) {
|
|
|
707
944
|
};
|
|
708
945
|
await captureRuntimeSnapshot();
|
|
709
946
|
const modelStrategy = config.modelStrategy ?? DEFAULT_MODEL_STRATEGY;
|
|
710
|
-
if (config.desiredModel && modelStrategy !== "ignore") {
|
|
947
|
+
if (config.desiredModel && modelStrategy !== "ignore" && !isResumingConversation) {
|
|
711
948
|
modelSelectionEvidence = await raceWithDisconnect(withRetries(() => ensureModelSelection(Runtime, config.desiredModel, logger, modelStrategy), {
|
|
712
949
|
retries: 2,
|
|
713
950
|
delayMs: 300,
|
|
@@ -726,9 +963,11 @@ export async function runBrowserMode(options) {
|
|
|
726
963
|
await raceWithDisconnect(ensurePromptReady(Runtime, config.inputTimeoutMs, logger));
|
|
727
964
|
logger(`Prompt textarea ready (after model switch, ${promptText.length.toLocaleString()} chars queued)`);
|
|
728
965
|
}
|
|
729
|
-
else if (modelStrategy === "ignore") {
|
|
966
|
+
else if (modelStrategy === "ignore" || isResumingConversation) {
|
|
730
967
|
modelSelectionEvidence = buildSkippedModelSelectionEvidence(config.desiredModel, modelStrategy);
|
|
731
|
-
logger(
|
|
968
|
+
logger(isResumingConversation
|
|
969
|
+
? "Model picker: skipped (resumed conversation)"
|
|
970
|
+
: "Model picker: skipped (strategy=ignore)");
|
|
732
971
|
}
|
|
733
972
|
const deepResearch = config.researchMode === "deep";
|
|
734
973
|
// Handle thinking time selection if specified. Deep Research owns its own effort flow.
|
|
@@ -779,6 +1018,10 @@ export async function runBrowserMode(options) {
|
|
|
779
1018
|
const baselineSnapshot = await readAssistantSnapshot(Runtime).catch(() => null);
|
|
780
1019
|
const baselineAssistantText = typeof baselineSnapshot?.text === "string" ? baselineSnapshot.text.trim() : "";
|
|
781
1020
|
const attachmentNames = submissionAttachments.map((a) => path.basename(a.path));
|
|
1021
|
+
const attachmentExpectations = submissionAttachments.map((a) => ({
|
|
1022
|
+
name: path.basename(a.path),
|
|
1023
|
+
generatedBundle: a.generatedBundle === true,
|
|
1024
|
+
}));
|
|
782
1025
|
let inputOnlyAttachments = false;
|
|
783
1026
|
await raceWithDisconnect(clearPromptComposer(Runtime, logger));
|
|
784
1027
|
await raceWithDisconnect(ensurePromptReady(Runtime, config.inputTimeoutMs, logger));
|
|
@@ -814,9 +1057,12 @@ export async function runBrowserMode(options) {
|
|
|
814
1057
|
inputTimeoutMs: config.inputTimeoutMs ?? undefined,
|
|
815
1058
|
attachmentTimeoutMs: config.attachmentTimeoutMs ?? undefined,
|
|
816
1059
|
baselineTurns: baselineTurns ?? undefined,
|
|
817
|
-
attachmentNames,
|
|
1060
|
+
attachmentNames: attachmentExpectations,
|
|
818
1061
|
onPromptSubmitted: markPromptSubmitted,
|
|
819
1062
|
};
|
|
1063
|
+
const deepResearchTargetKeys = deepResearch && client
|
|
1064
|
+
? await captureDeepResearchTargetKeys(client).catch(() => [])
|
|
1065
|
+
: undefined;
|
|
820
1066
|
await runProviderSubmissionFlow(chatgptDomProvider, {
|
|
821
1067
|
prompt,
|
|
822
1068
|
evaluate: async () => undefined,
|
|
@@ -849,7 +1095,7 @@ export async function runBrowserMode(options) {
|
|
|
849
1095
|
}
|
|
850
1096
|
// Reattach needs a /c/ URL; ChatGPT can update it late, so poll in the background.
|
|
851
1097
|
scheduleConversationHint("post-submit", config.timeoutMs ?? 120_000);
|
|
852
|
-
return { baselineTurns, baselineAssistantText };
|
|
1098
|
+
return { baselineTurns, baselineAssistantText, deepResearchTargetKeys };
|
|
853
1099
|
};
|
|
854
1100
|
const reloadPromptComposer = async () => {
|
|
855
1101
|
logger("[browser] Composer became unresponsive; reloading page and retrying once.");
|
|
@@ -858,6 +1104,7 @@ export async function runBrowserMode(options) {
|
|
|
858
1104
|
};
|
|
859
1105
|
let baselineTurns = null;
|
|
860
1106
|
let baselineAssistantText = null;
|
|
1107
|
+
let deepResearchTargetKeys = [];
|
|
861
1108
|
await acquireProfileLockIfNeeded();
|
|
862
1109
|
try {
|
|
863
1110
|
const submission = await runSubmissionWithRecovery({
|
|
@@ -874,6 +1121,7 @@ export async function runBrowserMode(options) {
|
|
|
874
1121
|
});
|
|
875
1122
|
baselineTurns = submission.baselineTurns;
|
|
876
1123
|
baselineAssistantText = submission.baselineAssistantText;
|
|
1124
|
+
deepResearchTargetKeys = submission.deepResearchTargetKeys ?? [];
|
|
877
1125
|
}
|
|
878
1126
|
finally {
|
|
879
1127
|
await releaseProfileLockIfHeld();
|
|
@@ -881,7 +1129,7 @@ export async function runBrowserMode(options) {
|
|
|
881
1129
|
const imageArtifactMinTurnIndex = baselineTurns;
|
|
882
1130
|
if (deepResearch) {
|
|
883
1131
|
await raceWithDisconnect(waitForResearchPlanAutoConfirm(Runtime, logger));
|
|
884
|
-
const researchResult = await raceWithDisconnect(waitForDeepResearchCompletion(Runtime, logger, config.timeoutMs, baselineTurns, Page, client));
|
|
1132
|
+
const researchResult = await raceWithDisconnect(waitForDeepResearchCompletion(Runtime, logger, config.timeoutMs, baselineTurns, Page, client, { ignoredTargetKeys: deepResearchTargetKeys }));
|
|
885
1133
|
await updateConversationHint("post-deep-research", 15_000).catch(() => false);
|
|
886
1134
|
runStatus = "complete";
|
|
887
1135
|
const durationMs = Date.now() - startedAt;
|
|
@@ -1070,7 +1318,13 @@ export async function runBrowserMode(options) {
|
|
|
1070
1318
|
promptSubmitted,
|
|
1071
1319
|
controllerPid: process.pid,
|
|
1072
1320
|
};
|
|
1073
|
-
throw
|
|
1321
|
+
throw await createAssistantTimeoutError({
|
|
1322
|
+
Runtime,
|
|
1323
|
+
logger,
|
|
1324
|
+
runtime,
|
|
1325
|
+
diagnostics,
|
|
1326
|
+
cause: error,
|
|
1327
|
+
});
|
|
1074
1328
|
}
|
|
1075
1329
|
}
|
|
1076
1330
|
else {
|
|
@@ -1259,6 +1513,9 @@ export async function runBrowserMode(options) {
|
|
|
1259
1513
|
throw new Error("Chrome disconnected before completion");
|
|
1260
1514
|
}
|
|
1261
1515
|
const imageArtifacts = await collectGeneratedImageArtifacts({
|
|
1516
|
+
Browser: client.Browser,
|
|
1517
|
+
Client: client,
|
|
1518
|
+
Page,
|
|
1262
1519
|
Runtime,
|
|
1263
1520
|
Network,
|
|
1264
1521
|
logger,
|
|
@@ -1268,21 +1525,50 @@ export async function runBrowserMode(options) {
|
|
|
1268
1525
|
outputPath: options.outputPath,
|
|
1269
1526
|
answerText,
|
|
1270
1527
|
waitTimeoutMs: options.config?.timeoutMs,
|
|
1528
|
+
checkBlockingUiWarning: () => throwChatGptUiWarningIfPresent({
|
|
1529
|
+
Runtime,
|
|
1530
|
+
logger,
|
|
1531
|
+
stage: "image-artifact-wait",
|
|
1532
|
+
waitTarget: "generated image artifacts",
|
|
1533
|
+
runtime: {
|
|
1534
|
+
chromePid: chrome.pid,
|
|
1535
|
+
chromePort: chrome.port,
|
|
1536
|
+
chromeHost,
|
|
1537
|
+
userDataDir,
|
|
1538
|
+
chromeTargetId: lastTargetId,
|
|
1539
|
+
tabUrl: lastUrl,
|
|
1540
|
+
conversationId: lastUrl ? extractConversationIdFromUrl(lastUrl) : undefined,
|
|
1541
|
+
promptSubmitted,
|
|
1542
|
+
controllerPid: process.pid,
|
|
1543
|
+
},
|
|
1544
|
+
}),
|
|
1271
1545
|
});
|
|
1272
1546
|
answerText = imageArtifacts.answerText || answerText;
|
|
1273
1547
|
if (imageArtifacts.markdownSuffix) {
|
|
1274
1548
|
answerMarkdown += imageArtifacts.markdownSuffix;
|
|
1275
1549
|
}
|
|
1550
|
+
const fileArtifacts = await collectChatGptFileArtifacts({
|
|
1551
|
+
Browser: client.Browser,
|
|
1552
|
+
Client: client,
|
|
1553
|
+
Page,
|
|
1554
|
+
Runtime,
|
|
1555
|
+
Network,
|
|
1556
|
+
answerText: [answerText, answerMarkdown, answerHtml].filter(Boolean).join("\n"),
|
|
1557
|
+
logger,
|
|
1558
|
+
minTurnIndex: imageArtifactMinTurnIndex,
|
|
1559
|
+
sessionId: options.sessionId,
|
|
1560
|
+
});
|
|
1276
1561
|
const savedImageArtifacts = appendArtifacts(undefined, imageArtifacts.savedImages);
|
|
1562
|
+
const savedBrowserArtifacts = appendArtifacts(savedImageArtifacts, fileArtifacts.savedFiles);
|
|
1277
1563
|
const transcriptArtifact = await saveOptionalArtifact(() => saveBrowserTranscriptArtifact({
|
|
1278
1564
|
sessionId: options.sessionId,
|
|
1279
1565
|
prompt: promptText,
|
|
1280
1566
|
answerMarkdown,
|
|
1281
1567
|
conversationUrl: lastUrl,
|
|
1282
|
-
artifacts:
|
|
1568
|
+
artifacts: savedBrowserArtifacts,
|
|
1283
1569
|
logger,
|
|
1284
1570
|
}), logger);
|
|
1285
|
-
const savedArtifacts = appendArtifacts(
|
|
1571
|
+
const savedArtifacts = appendArtifacts(savedBrowserArtifacts, [transcriptArtifact]);
|
|
1286
1572
|
const archive = await maybeArchiveCompletedConversation({
|
|
1287
1573
|
Runtime,
|
|
1288
1574
|
logger,
|
|
@@ -1290,7 +1576,8 @@ export async function runBrowserMode(options) {
|
|
|
1290
1576
|
conversationUrl: lastUrl,
|
|
1291
1577
|
followUpCount: followUpPrompts.length,
|
|
1292
1578
|
requiredArtifactsSaved: Boolean(transcriptArtifact) &&
|
|
1293
|
-
imageArtifacts.savedImages.length === imageArtifacts.imageCount
|
|
1579
|
+
imageArtifacts.savedImages.length === imageArtifacts.imageCount &&
|
|
1580
|
+
fileArtifacts.savedFiles.length === fileArtifacts.fileCount,
|
|
1294
1581
|
});
|
|
1295
1582
|
runStatus = "complete";
|
|
1296
1583
|
const durationMs = Date.now() - startedAt;
|
|
@@ -1303,6 +1590,8 @@ export async function runBrowserMode(options) {
|
|
|
1303
1590
|
artifacts: savedArtifacts,
|
|
1304
1591
|
generatedImages: imageArtifacts.generatedImages,
|
|
1305
1592
|
savedImages: imageArtifacts.savedImages,
|
|
1593
|
+
downloadableFiles: fileArtifacts.files,
|
|
1594
|
+
savedFiles: fileArtifacts.savedFiles,
|
|
1306
1595
|
archive,
|
|
1307
1596
|
modelSelection: modelSelectionEvidence,
|
|
1308
1597
|
tookMs: durationMs,
|
|
@@ -1676,13 +1965,23 @@ async function maybeReuseRunningChrome(userDataDir, logger, options = {}) {
|
|
|
1676
1965
|
let pid = await readChromePid(userDataDir);
|
|
1677
1966
|
if (!port) {
|
|
1678
1967
|
const discovered = await findRunningChromeDebugTargetForProfile(userDataDir);
|
|
1679
|
-
if (!discovered)
|
|
1968
|
+
if (!discovered) {
|
|
1969
|
+
if (pid) {
|
|
1970
|
+
logger(`No reachable Chrome DevTools target found for ${userDataDir}; clearing stale profile state before launching new Chrome.`);
|
|
1971
|
+
await cleanupStaleProfileState(userDataDir, logger, {
|
|
1972
|
+
lockRemovalMode: "if_oracle_pid_dead",
|
|
1973
|
+
});
|
|
1974
|
+
}
|
|
1680
1975
|
return null;
|
|
1976
|
+
}
|
|
1681
1977
|
const discoveredProbe = await (options.probe ?? verifyDevToolsReachable)({
|
|
1682
1978
|
port: discovered.port,
|
|
1683
1979
|
});
|
|
1684
1980
|
if (!discoveredProbe.ok) {
|
|
1685
1981
|
logger(`Discovered Chrome for ${userDataDir} on port ${discovered.port} but it was unreachable (${discoveredProbe.error}); launching new Chrome.`);
|
|
1982
|
+
await cleanupStaleProfileState(userDataDir, logger, {
|
|
1983
|
+
lockRemovalMode: "if_oracle_pid_dead",
|
|
1984
|
+
});
|
|
1686
1985
|
return null;
|
|
1687
1986
|
}
|
|
1688
1987
|
await writeDevToolsActivePort(userDataDir, discovered.port);
|
|
@@ -1830,16 +2129,20 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1830
2129
|
removeDialogHandler = installJavaScriptDialogAutoDismissal(Page, logger);
|
|
1831
2130
|
// Skip cookie sync for remote Chrome - it already has cookies
|
|
1832
2131
|
logger("Skipping cookie sync for remote Chrome (using existing session)");
|
|
1833
|
-
if (
|
|
2132
|
+
if (config.resumeConversationUrl) {
|
|
2133
|
+
await navigateToChatGPT(Page, Runtime, config.resumeConversationUrl, logger);
|
|
2134
|
+
}
|
|
2135
|
+
else if (!attachedExistingTab) {
|
|
1834
2136
|
await navigateToChatGPT(Page, Runtime, config.url, logger);
|
|
1835
|
-
await ensureNotBlocked(Runtime, config.headless, logger);
|
|
1836
|
-
await ensureLoggedIn(Runtime, logger, { remoteSession: true });
|
|
1837
|
-
await ensurePromptReady(Runtime, config.inputTimeoutMs, logger);
|
|
1838
2137
|
}
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
2138
|
+
await ensureNotBlocked(Runtime, config.headless, logger);
|
|
2139
|
+
await ensureLoggedIn(Runtime, logger, { remoteSession: true });
|
|
2140
|
+
await ensurePromptReady(Runtime, config.inputTimeoutMs, logger);
|
|
2141
|
+
if (config.resumeConversationUrl) {
|
|
2142
|
+
await waitForResumedConversationHydration(Runtime, config.inputTimeoutMs, logger, {
|
|
2143
|
+
requirePriorTurns: true,
|
|
2144
|
+
expectedConversationUrl: config.resumeConversationUrl,
|
|
2145
|
+
});
|
|
1843
2146
|
}
|
|
1844
2147
|
logger(`Prompt textarea ready (initial focus, ${promptText.length.toLocaleString()} chars queued)`);
|
|
1845
2148
|
try {
|
|
@@ -1856,7 +2159,7 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1856
2159
|
// ignore
|
|
1857
2160
|
}
|
|
1858
2161
|
const modelStrategy = config.modelStrategy ?? DEFAULT_MODEL_STRATEGY;
|
|
1859
|
-
if (config.desiredModel && modelStrategy !== "ignore") {
|
|
2162
|
+
if (config.desiredModel && modelStrategy !== "ignore" && !config.resumeConversationUrl) {
|
|
1860
2163
|
modelSelectionEvidence = await withRetries(() => ensureModelSelection(Runtime, config.desiredModel, logger, modelStrategy), {
|
|
1861
2164
|
retries: 2,
|
|
1862
2165
|
delayMs: 300,
|
|
@@ -1869,9 +2172,11 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1869
2172
|
await ensurePromptReady(Runtime, config.inputTimeoutMs, logger);
|
|
1870
2173
|
logger(`Prompt textarea ready (after model switch, ${promptText.length.toLocaleString()} chars queued)`);
|
|
1871
2174
|
}
|
|
1872
|
-
else if (modelStrategy === "ignore") {
|
|
2175
|
+
else if (modelStrategy === "ignore" || config.resumeConversationUrl) {
|
|
1873
2176
|
modelSelectionEvidence = buildSkippedModelSelectionEvidence(config.desiredModel, modelStrategy);
|
|
1874
|
-
logger(
|
|
2177
|
+
logger(config.resumeConversationUrl
|
|
2178
|
+
? "Model picker: skipped (resumed conversation)"
|
|
2179
|
+
: "Model picker: skipped (strategy=ignore)");
|
|
1875
2180
|
}
|
|
1876
2181
|
const deepResearch = config.researchMode === "deep";
|
|
1877
2182
|
// Handle thinking time selection if specified. Deep Research owns its own effort flow.
|
|
@@ -1905,6 +2210,10 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1905
2210
|
const baselineSnapshot = await readAssistantSnapshot(Runtime).catch(() => null);
|
|
1906
2211
|
const baselineAssistantText = typeof baselineSnapshot?.text === "string" ? baselineSnapshot.text.trim() : "";
|
|
1907
2212
|
const attachmentNames = submissionAttachments.map((a) => path.basename(a.path));
|
|
2213
|
+
const attachmentExpectations = submissionAttachments.map((a) => ({
|
|
2214
|
+
name: path.basename(a.path),
|
|
2215
|
+
generatedBundle: a.generatedBundle === true,
|
|
2216
|
+
}));
|
|
1908
2217
|
await clearPromptComposer(Runtime, logger);
|
|
1909
2218
|
await ensurePromptReady(Runtime, config.inputTimeoutMs, logger);
|
|
1910
2219
|
if (submissionAttachments.length > 0) {
|
|
@@ -1935,9 +2244,12 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1935
2244
|
inputTimeoutMs: config.inputTimeoutMs ?? undefined,
|
|
1936
2245
|
attachmentTimeoutMs: config.attachmentTimeoutMs ?? undefined,
|
|
1937
2246
|
baselineTurns: baselineTurns ?? undefined,
|
|
1938
|
-
attachmentNames,
|
|
2247
|
+
attachmentNames: attachmentExpectations,
|
|
1939
2248
|
onPromptSubmitted: markPromptSubmitted,
|
|
1940
2249
|
};
|
|
2250
|
+
const deepResearchTargetKeys = deepResearch && client
|
|
2251
|
+
? await captureDeepResearchTargetKeys(client).catch(() => [])
|
|
2252
|
+
: undefined;
|
|
1941
2253
|
await runProviderSubmissionFlow(chatgptDomProvider, {
|
|
1942
2254
|
prompt,
|
|
1943
2255
|
evaluate: async () => undefined,
|
|
@@ -1950,7 +2262,7 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1950
2262
|
if (typeof providerBaselineTurns === "number" && Number.isFinite(providerBaselineTurns)) {
|
|
1951
2263
|
baselineTurns = providerBaselineTurns;
|
|
1952
2264
|
}
|
|
1953
|
-
return { baselineTurns, baselineAssistantText };
|
|
2265
|
+
return { baselineTurns, baselineAssistantText, deepResearchTargetKeys };
|
|
1954
2266
|
};
|
|
1955
2267
|
const reloadPromptComposer = async () => {
|
|
1956
2268
|
logger("[browser] Composer became unresponsive; reloading page and retrying once.");
|
|
@@ -1959,6 +2271,7 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1959
2271
|
};
|
|
1960
2272
|
let baselineTurns = null;
|
|
1961
2273
|
let baselineAssistantText = null;
|
|
2274
|
+
let deepResearchTargetKeys = [];
|
|
1962
2275
|
const submission = await runSubmissionWithRecovery({
|
|
1963
2276
|
prompt: promptText,
|
|
1964
2277
|
attachments,
|
|
@@ -1973,10 +2286,11 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1973
2286
|
});
|
|
1974
2287
|
baselineTurns = submission.baselineTurns;
|
|
1975
2288
|
baselineAssistantText = submission.baselineAssistantText;
|
|
2289
|
+
deepResearchTargetKeys = submission.deepResearchTargetKeys ?? [];
|
|
1976
2290
|
const imageArtifactMinTurnIndex = baselineTurns;
|
|
1977
2291
|
if (deepResearch) {
|
|
1978
2292
|
await waitForResearchPlanAutoConfirm(Runtime, logger);
|
|
1979
|
-
const researchResult = await waitForDeepResearchCompletion(Runtime, logger, config.timeoutMs, baselineTurns, Page, client);
|
|
2293
|
+
const researchResult = await waitForDeepResearchCompletion(Runtime, logger, config.timeoutMs, baselineTurns, Page, client, { ignoredTargetKeys: deepResearchTargetKeys });
|
|
1980
2294
|
await emitRuntimeHint();
|
|
1981
2295
|
const durationMs = Date.now() - startedAt;
|
|
1982
2296
|
const tokens = estimateTokenCount(researchResult.text);
|
|
@@ -2175,7 +2489,13 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2175
2489
|
promptSubmitted,
|
|
2176
2490
|
controllerPid: process.pid,
|
|
2177
2491
|
};
|
|
2178
|
-
throw
|
|
2492
|
+
throw await createAssistantTimeoutError({
|
|
2493
|
+
Runtime,
|
|
2494
|
+
logger,
|
|
2495
|
+
runtime,
|
|
2496
|
+
diagnostics,
|
|
2497
|
+
cause: error,
|
|
2498
|
+
});
|
|
2179
2499
|
}
|
|
2180
2500
|
}
|
|
2181
2501
|
else {
|
|
@@ -2320,7 +2640,11 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2320
2640
|
answerMarkdown = formatted.answerMarkdown;
|
|
2321
2641
|
answerHtml = "";
|
|
2322
2642
|
}
|
|
2643
|
+
const canSaveBrowserDownloadsLocally = isLocalChromeHost(host);
|
|
2323
2644
|
const imageArtifacts = await collectGeneratedImageArtifacts({
|
|
2645
|
+
Browser: canSaveBrowserDownloadsLocally ? client.Browser : undefined,
|
|
2646
|
+
Client: canSaveBrowserDownloadsLocally ? client : undefined,
|
|
2647
|
+
Page: canSaveBrowserDownloadsLocally ? Page : undefined,
|
|
2324
2648
|
Runtime,
|
|
2325
2649
|
Network,
|
|
2326
2650
|
logger,
|
|
@@ -2330,21 +2654,50 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2330
2654
|
outputPath: options.outputPath,
|
|
2331
2655
|
answerText,
|
|
2332
2656
|
waitTimeoutMs: options.config?.timeoutMs,
|
|
2657
|
+
checkBlockingUiWarning: () => throwChatGptUiWarningIfPresent({
|
|
2658
|
+
Runtime,
|
|
2659
|
+
logger,
|
|
2660
|
+
stage: "image-artifact-wait",
|
|
2661
|
+
waitTarget: "generated image artifacts",
|
|
2662
|
+
runtime: {
|
|
2663
|
+
chromePort: port,
|
|
2664
|
+
chromeHost: host,
|
|
2665
|
+
chromeBrowserWSEndpoint: browserWSEndpoint,
|
|
2666
|
+
chromeProfileRoot,
|
|
2667
|
+
chromeTargetId: remoteTargetId ?? undefined,
|
|
2668
|
+
tabUrl: lastUrl,
|
|
2669
|
+
conversationId: lastUrl ? extractConversationIdFromUrl(lastUrl) : undefined,
|
|
2670
|
+
promptSubmitted,
|
|
2671
|
+
controllerPid: process.pid,
|
|
2672
|
+
},
|
|
2673
|
+
}),
|
|
2333
2674
|
});
|
|
2334
2675
|
answerText = imageArtifacts.answerText || answerText;
|
|
2335
2676
|
if (imageArtifacts.markdownSuffix) {
|
|
2336
2677
|
answerMarkdown += imageArtifacts.markdownSuffix;
|
|
2337
2678
|
}
|
|
2679
|
+
const fileArtifacts = await collectChatGptFileArtifacts({
|
|
2680
|
+
Browser: client.Browser,
|
|
2681
|
+
Client: client,
|
|
2682
|
+
Page,
|
|
2683
|
+
Runtime,
|
|
2684
|
+
Network,
|
|
2685
|
+
answerText: [answerText, answerMarkdown, answerHtml].filter(Boolean).join("\n"),
|
|
2686
|
+
logger,
|
|
2687
|
+
minTurnIndex: imageArtifactMinTurnIndex,
|
|
2688
|
+
sessionId: options.sessionId,
|
|
2689
|
+
});
|
|
2338
2690
|
const savedImageArtifacts = appendArtifacts(undefined, imageArtifacts.savedImages);
|
|
2691
|
+
const savedBrowserArtifacts = appendArtifacts(savedImageArtifacts, fileArtifacts.savedFiles);
|
|
2339
2692
|
const transcriptArtifact = await saveOptionalArtifact(() => saveBrowserTranscriptArtifact({
|
|
2340
2693
|
sessionId: options.sessionId,
|
|
2341
2694
|
prompt: promptText,
|
|
2342
2695
|
answerMarkdown,
|
|
2343
2696
|
conversationUrl: lastUrl,
|
|
2344
|
-
artifacts:
|
|
2697
|
+
artifacts: savedBrowserArtifacts,
|
|
2345
2698
|
logger,
|
|
2346
2699
|
}), logger);
|
|
2347
|
-
const savedArtifacts = appendArtifacts(
|
|
2700
|
+
const savedArtifacts = appendArtifacts(savedBrowserArtifacts, [transcriptArtifact]);
|
|
2348
2701
|
const archive = await maybeArchiveCompletedConversation({
|
|
2349
2702
|
Runtime,
|
|
2350
2703
|
logger,
|
|
@@ -2352,7 +2705,8 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2352
2705
|
conversationUrl: lastUrl,
|
|
2353
2706
|
followUpCount: followUpPrompts.length,
|
|
2354
2707
|
requiredArtifactsSaved: Boolean(transcriptArtifact) &&
|
|
2355
|
-
imageArtifacts.savedImages.length === imageArtifacts.imageCount
|
|
2708
|
+
imageArtifacts.savedImages.length === imageArtifacts.imageCount &&
|
|
2709
|
+
fileArtifacts.savedFiles.length === fileArtifacts.fileCount,
|
|
2356
2710
|
});
|
|
2357
2711
|
const durationMs = Date.now() - startedAt;
|
|
2358
2712
|
const answerChars = answerText.length;
|
|
@@ -2377,6 +2731,10 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2377
2731
|
conversationId: lastUrl ? extractConversationIdFromUrl(lastUrl) : undefined,
|
|
2378
2732
|
promptSubmitted,
|
|
2379
2733
|
artifacts: savedArtifacts,
|
|
2734
|
+
generatedImages: imageArtifacts.generatedImages,
|
|
2735
|
+
savedImages: imageArtifacts.savedImages,
|
|
2736
|
+
downloadableFiles: fileArtifacts.files,
|
|
2737
|
+
savedFiles: fileArtifacts.savedFiles,
|
|
2380
2738
|
archive,
|
|
2381
2739
|
modelSelection: modelSelectionEvidence,
|
|
2382
2740
|
controllerPid: process.pid,
|
|
@@ -2443,8 +2801,12 @@ export { resolveBrowserConfig, DEFAULT_BROWSER_CONFIG } from "./config.js";
|
|
|
2443
2801
|
export const __test__ = {
|
|
2444
2802
|
assertManualLoginProfileReadyForRun,
|
|
2445
2803
|
closeRemoteConnectionAfterRun,
|
|
2804
|
+
classifyChatGptUiWarningText,
|
|
2805
|
+
collectChatGptUiWarnings,
|
|
2806
|
+
createAssistantTimeoutError,
|
|
2446
2807
|
detachKeptChromeProcess,
|
|
2447
2808
|
formatManualLoginSetupCommand,
|
|
2809
|
+
isAssistantResponseTimeoutError,
|
|
2448
2810
|
isManualLoginProfileInitialized,
|
|
2449
2811
|
isImageOnlyUiChromeText,
|
|
2450
2812
|
listIgnoredRemoteChromeFlags,
|
|
@@ -2500,7 +2862,8 @@ function isAssistantResponseTimeoutError(error) {
|
|
|
2500
2862
|
const message = error.message.toLowerCase();
|
|
2501
2863
|
if (!message)
|
|
2502
2864
|
return false;
|
|
2503
|
-
return (message
|
|
2865
|
+
return (message === "response timeout" ||
|
|
2866
|
+
message.includes("assistant-response") ||
|
|
2504
2867
|
message.includes("assistant response") ||
|
|
2505
2868
|
message.includes("watchdog") ||
|
|
2506
2869
|
message.includes("capture assistant response"));
|