@steipete/oracle 0.12.1 → 0.14.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/README.md +72 -61
- package/dist/bin/oracle-cli.js +77 -68
- package/dist/bin/oracle-mcp.js +0 -0
- package/dist/docs-site/.nojekyll +0 -0
- package/dist/docs-site/CNAME +1 -0
- package/dist/docs-site/RELEASING.html +410 -0
- package/dist/docs-site/agents.html +374 -0
- package/dist/docs-site/anthropic.html +368 -0
- package/dist/docs-site/bridge.html +400 -0
- package/dist/docs-site/browser-mode.html +593 -0
- package/dist/docs-site/chromium-forks.html +347 -0
- package/dist/docs-site/cli-reference.html +346 -0
- package/dist/docs-site/configuration.html +452 -0
- package/dist/docs-site/favicon.svg +14 -0
- package/dist/docs-site/followup.html +375 -0
- package/dist/docs-site/gemini.html +383 -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 +43 -0
- package/dist/docs-site/manual-tests.html +596 -0
- package/dist/docs-site/mcp.html +391 -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 +387 -0
- package/dist/docs-site/openrouter.html +344 -0
- package/dist/docs-site/quickstart.html +369 -0
- package/dist/docs-site/refactor/ux.html +532 -0
- package/dist/docs-site/sessions.html +388 -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 +320 -0
- package/dist/docs-site/tui-debug.html +326 -0
- package/dist/docs-site/windows-work.html +323 -0
- package/dist/docs-site/windows.html +320 -0
- package/dist/src/browser/actions/assistantResponse.js +91 -9
- package/dist/src/browser/actions/deepResearch.js +180 -52
- package/dist/src/browser/actions/modelSelection.js +111 -24
- package/dist/src/browser/actions/navigation.js +358 -67
- package/dist/src/browser/actions/promptComposer.js +241 -46
- package/dist/src/browser/actions/thinkingTime.js +308 -89
- package/dist/src/browser/artifacts.js +19 -0
- package/dist/src/browser/chatgptFiles.js +797 -0
- package/dist/src/browser/chatgptImages.js +130 -3
- package/dist/src/browser/chromeLifecycle.js +4 -0
- package/dist/src/browser/config.js +4 -0
- package/dist/src/browser/index.js +353 -41
- package/dist/src/browser/pageActions.js +1 -1
- package/dist/src/browser/policies.js +2 -6
- package/dist/src/browser/projectSourcesRunner.js +14 -2
- package/dist/src/browser/prompt.js +11 -15
- package/dist/src/browser/providers/chatgptDomProvider.js +1 -0
- 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/bridge/doctor.js +7 -2
- package/dist/src/cli/browserConfig.js +13 -2
- package/dist/src/cli/browserDefaults.js +3 -0
- package/dist/src/cli/browserTabs.js +129 -54
- package/dist/src/cli/engine.js +6 -2
- package/dist/src/cli/followup.js +72 -0
- package/dist/src/cli/help.js +1 -1
- package/dist/src/cli/options.js +20 -0
- package/dist/src/cli/reattachGuidance.js +8 -0
- package/dist/src/cli/runOptions.js +10 -31
- 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/config.js +164 -9
- 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 +127 -0
- package/dist/src/mcp/tools/consult.js +261 -167
- package/dist/src/mcp/types.js +2 -0
- 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/tokenStats.js +5 -1
- package/dist/src/oracle.js +1 -1
- package/dist/src/sessionManager.js +1 -0
- 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 +20 -29
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
|
@@ -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,188 @@ 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 createAssistantTimeoutError(params) {
|
|
244
|
+
const [uiWarning] = await collectChatGptUiWarnings(params.Runtime);
|
|
245
|
+
if (!uiWarning) {
|
|
246
|
+
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);
|
|
247
|
+
}
|
|
248
|
+
params.logger(`[browser] ChatGPT UI warning detected (${uiWarning.type}): ${uiWarning.message}`);
|
|
249
|
+
return new BrowserAutomationError(`ChatGPT displayed a ${formatChatGptUiWarningType(uiWarning.type)} warning while waiting for the assistant: ${uiWarning.message}`, {
|
|
250
|
+
stage: "assistant-timeout",
|
|
251
|
+
code: "chatgpt-ui-warning",
|
|
252
|
+
uiWarning,
|
|
253
|
+
runtime: params.runtime,
|
|
254
|
+
diagnostics: params.diagnostics,
|
|
255
|
+
}, params.cause);
|
|
256
|
+
}
|
|
80
257
|
function listIgnoredRemoteChromeFlags(config) {
|
|
81
258
|
return [
|
|
82
259
|
config.headless ? "--browser-headless" : null,
|
|
@@ -273,6 +450,19 @@ function resolveRemoteTabLeaseProfileDir(config) {
|
|
|
273
450
|
export function resolveRemoteTabLeaseProfileDirForTest(config) {
|
|
274
451
|
return resolveRemoteTabLeaseProfileDir(config);
|
|
275
452
|
}
|
|
453
|
+
function isLocalChromeHost(host) {
|
|
454
|
+
const normalized = host
|
|
455
|
+
.trim()
|
|
456
|
+
.toLowerCase()
|
|
457
|
+
.replace(/^\[|\]$/g, "");
|
|
458
|
+
if (normalized === "localhost" || normalized === "::1") {
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
return net.isIPv4(normalized) && normalized.startsWith("127.");
|
|
462
|
+
}
|
|
463
|
+
export function isLocalChromeHostForTest(host) {
|
|
464
|
+
return isLocalChromeHost(host);
|
|
465
|
+
}
|
|
276
466
|
async function closeRemoteConnectionAfterRun(options) {
|
|
277
467
|
if (options.connectionClosedUnexpectedly) {
|
|
278
468
|
return;
|
|
@@ -310,6 +500,7 @@ export async function runBrowserMode(options) {
|
|
|
310
500
|
const attachments = options.attachments ?? [];
|
|
311
501
|
const fallbackSubmission = options.fallbackSubmission;
|
|
312
502
|
let config = resolveBrowserConfig(options.config);
|
|
503
|
+
const isResumingConversation = Boolean(config.resumeConversationUrl);
|
|
313
504
|
const followUpPrompts = normalizeBrowserFollowUpPrompts(options.followUpPrompts);
|
|
314
505
|
if (config.researchMode === "deep" && followUpPrompts.length > 0) {
|
|
315
506
|
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.", {
|
|
@@ -596,9 +787,18 @@ export async function runBrowserMode(options) {
|
|
|
596
787
|
});
|
|
597
788
|
}
|
|
598
789
|
if (config.browserTabRef) {
|
|
790
|
+
if (isResumingConversation) {
|
|
791
|
+
await raceWithDisconnect(navigateToChatGPT(Page, Runtime, config.resumeConversationUrl, logger));
|
|
792
|
+
}
|
|
599
793
|
await raceWithDisconnect(ensureNotBlocked(Runtime, config.headless, logger));
|
|
600
794
|
await raceWithDisconnect(ensureLoggedIn(Runtime, logger));
|
|
601
795
|
await raceWithDisconnect(ensurePromptReady(Runtime, config.inputTimeoutMs, logger));
|
|
796
|
+
if (isResumingConversation) {
|
|
797
|
+
await raceWithDisconnect(waitForResumedConversationHydration(Runtime, config.inputTimeoutMs, logger, {
|
|
798
|
+
requirePriorTurns: true,
|
|
799
|
+
expectedConversationUrl: config.resumeConversationUrl,
|
|
800
|
+
}));
|
|
801
|
+
}
|
|
602
802
|
}
|
|
603
803
|
else {
|
|
604
804
|
const baseUrl = CHATGPT_URL;
|
|
@@ -616,7 +816,12 @@ export async function runBrowserMode(options) {
|
|
|
616
816
|
profileDir: userDataDir,
|
|
617
817
|
keepBrowser: effectiveKeepBrowser,
|
|
618
818
|
}));
|
|
619
|
-
if (
|
|
819
|
+
if (isResumingConversation) {
|
|
820
|
+
await raceWithDisconnect(navigateToChatGPT(Page, Runtime, config.resumeConversationUrl, logger));
|
|
821
|
+
await raceWithDisconnect(ensureNotBlocked(Runtime, config.headless, logger));
|
|
822
|
+
await raceWithDisconnect(ensurePromptReady(Runtime, config.inputTimeoutMs, logger));
|
|
823
|
+
}
|
|
824
|
+
else if (config.url !== baseUrl) {
|
|
620
825
|
await raceWithDisconnect(navigateToPromptReadyWithFallback(Page, Runtime, {
|
|
621
826
|
url: config.url,
|
|
622
827
|
fallbackUrl: baseUrl,
|
|
@@ -628,6 +833,17 @@ export async function runBrowserMode(options) {
|
|
|
628
833
|
else {
|
|
629
834
|
await raceWithDisconnect(ensurePromptReady(Runtime, config.inputTimeoutMs, logger));
|
|
630
835
|
}
|
|
836
|
+
if (isResumingConversation) {
|
|
837
|
+
// A resumed thread loads its prior history after navigation; ChatGPT can reset the
|
|
838
|
+
// composer mid-hydration and wipe a freshly-typed prompt. Wait for hydration to settle
|
|
839
|
+
// and re-confirm the composer before the prompt is typed/submitted below. Wrapped in
|
|
840
|
+
// raceWithDisconnect so a dropped client aborts immediately instead of polling to the
|
|
841
|
+
// hydration deadline. Shared with the remote path via the same helper.
|
|
842
|
+
await raceWithDisconnect(waitForResumedConversationHydration(Runtime, config.inputTimeoutMs, logger, {
|
|
843
|
+
requirePriorTurns: true,
|
|
844
|
+
expectedConversationUrl: config.resumeConversationUrl,
|
|
845
|
+
}));
|
|
846
|
+
}
|
|
631
847
|
}
|
|
632
848
|
logger(`Prompt textarea ready (initial focus, ${promptText.length.toLocaleString()} chars queued)`);
|
|
633
849
|
const captureRuntimeSnapshot = async () => {
|
|
@@ -707,7 +923,7 @@ export async function runBrowserMode(options) {
|
|
|
707
923
|
};
|
|
708
924
|
await captureRuntimeSnapshot();
|
|
709
925
|
const modelStrategy = config.modelStrategy ?? DEFAULT_MODEL_STRATEGY;
|
|
710
|
-
if (config.desiredModel && modelStrategy !== "ignore") {
|
|
926
|
+
if (config.desiredModel && modelStrategy !== "ignore" && !isResumingConversation) {
|
|
711
927
|
modelSelectionEvidence = await raceWithDisconnect(withRetries(() => ensureModelSelection(Runtime, config.desiredModel, logger, modelStrategy), {
|
|
712
928
|
retries: 2,
|
|
713
929
|
delayMs: 300,
|
|
@@ -726,9 +942,11 @@ export async function runBrowserMode(options) {
|
|
|
726
942
|
await raceWithDisconnect(ensurePromptReady(Runtime, config.inputTimeoutMs, logger));
|
|
727
943
|
logger(`Prompt textarea ready (after model switch, ${promptText.length.toLocaleString()} chars queued)`);
|
|
728
944
|
}
|
|
729
|
-
else if (modelStrategy === "ignore") {
|
|
945
|
+
else if (modelStrategy === "ignore" || isResumingConversation) {
|
|
730
946
|
modelSelectionEvidence = buildSkippedModelSelectionEvidence(config.desiredModel, modelStrategy);
|
|
731
|
-
logger(
|
|
947
|
+
logger(isResumingConversation
|
|
948
|
+
? "Model picker: skipped (resumed conversation)"
|
|
949
|
+
: "Model picker: skipped (strategy=ignore)");
|
|
732
950
|
}
|
|
733
951
|
const deepResearch = config.researchMode === "deep";
|
|
734
952
|
// Handle thinking time selection if specified. Deep Research owns its own effort flow.
|
|
@@ -779,6 +997,10 @@ export async function runBrowserMode(options) {
|
|
|
779
997
|
const baselineSnapshot = await readAssistantSnapshot(Runtime).catch(() => null);
|
|
780
998
|
const baselineAssistantText = typeof baselineSnapshot?.text === "string" ? baselineSnapshot.text.trim() : "";
|
|
781
999
|
const attachmentNames = submissionAttachments.map((a) => path.basename(a.path));
|
|
1000
|
+
const attachmentExpectations = submissionAttachments.map((a) => ({
|
|
1001
|
+
name: path.basename(a.path),
|
|
1002
|
+
generatedBundle: a.generatedBundle === true,
|
|
1003
|
+
}));
|
|
782
1004
|
let inputOnlyAttachments = false;
|
|
783
1005
|
await raceWithDisconnect(clearPromptComposer(Runtime, logger));
|
|
784
1006
|
await raceWithDisconnect(ensurePromptReady(Runtime, config.inputTimeoutMs, logger));
|
|
@@ -800,7 +1022,8 @@ export async function runBrowserMode(options) {
|
|
|
800
1022
|
const baseTimeout = config.inputTimeoutMs ?? 30_000;
|
|
801
1023
|
const perFileTimeout = 20_000;
|
|
802
1024
|
const waitBudget = Math.max(baseTimeout, 45_000) + (submissionAttachments.length - 1) * perFileTimeout;
|
|
803
|
-
|
|
1025
|
+
const attachmentWaitBudget = Math.max(config.attachmentTimeoutMs ?? 0, waitBudget);
|
|
1026
|
+
await waitForAttachmentCompletion(Runtime, attachmentWaitBudget, attachmentNames, logger);
|
|
804
1027
|
logger("All attachments uploaded");
|
|
805
1028
|
}
|
|
806
1029
|
let baselineTurns = await readConversationTurnCount(Runtime, logger);
|
|
@@ -811,10 +1034,14 @@ export async function runBrowserMode(options) {
|
|
|
811
1034
|
logger,
|
|
812
1035
|
timeoutMs: config.timeoutMs,
|
|
813
1036
|
inputTimeoutMs: config.inputTimeoutMs ?? undefined,
|
|
1037
|
+
attachmentTimeoutMs: config.attachmentTimeoutMs ?? undefined,
|
|
814
1038
|
baselineTurns: baselineTurns ?? undefined,
|
|
815
|
-
attachmentNames,
|
|
1039
|
+
attachmentNames: attachmentExpectations,
|
|
816
1040
|
onPromptSubmitted: markPromptSubmitted,
|
|
817
1041
|
};
|
|
1042
|
+
const deepResearchTargetKeys = deepResearch && client
|
|
1043
|
+
? await captureDeepResearchTargetKeys(client).catch(() => [])
|
|
1044
|
+
: undefined;
|
|
818
1045
|
await runProviderSubmissionFlow(chatgptDomProvider, {
|
|
819
1046
|
prompt,
|
|
820
1047
|
evaluate: async () => undefined,
|
|
@@ -847,7 +1074,7 @@ export async function runBrowserMode(options) {
|
|
|
847
1074
|
}
|
|
848
1075
|
// Reattach needs a /c/ URL; ChatGPT can update it late, so poll in the background.
|
|
849
1076
|
scheduleConversationHint("post-submit", config.timeoutMs ?? 120_000);
|
|
850
|
-
return { baselineTurns, baselineAssistantText };
|
|
1077
|
+
return { baselineTurns, baselineAssistantText, deepResearchTargetKeys };
|
|
851
1078
|
};
|
|
852
1079
|
const reloadPromptComposer = async () => {
|
|
853
1080
|
logger("[browser] Composer became unresponsive; reloading page and retrying once.");
|
|
@@ -856,6 +1083,7 @@ export async function runBrowserMode(options) {
|
|
|
856
1083
|
};
|
|
857
1084
|
let baselineTurns = null;
|
|
858
1085
|
let baselineAssistantText = null;
|
|
1086
|
+
let deepResearchTargetKeys = [];
|
|
859
1087
|
await acquireProfileLockIfNeeded();
|
|
860
1088
|
try {
|
|
861
1089
|
const submission = await runSubmissionWithRecovery({
|
|
@@ -872,6 +1100,7 @@ export async function runBrowserMode(options) {
|
|
|
872
1100
|
});
|
|
873
1101
|
baselineTurns = submission.baselineTurns;
|
|
874
1102
|
baselineAssistantText = submission.baselineAssistantText;
|
|
1103
|
+
deepResearchTargetKeys = submission.deepResearchTargetKeys ?? [];
|
|
875
1104
|
}
|
|
876
1105
|
finally {
|
|
877
1106
|
await releaseProfileLockIfHeld();
|
|
@@ -879,7 +1108,7 @@ export async function runBrowserMode(options) {
|
|
|
879
1108
|
const imageArtifactMinTurnIndex = baselineTurns;
|
|
880
1109
|
if (deepResearch) {
|
|
881
1110
|
await raceWithDisconnect(waitForResearchPlanAutoConfirm(Runtime, logger));
|
|
882
|
-
const researchResult = await raceWithDisconnect(waitForDeepResearchCompletion(Runtime, logger, config.timeoutMs, baselineTurns, Page, client));
|
|
1111
|
+
const researchResult = await raceWithDisconnect(waitForDeepResearchCompletion(Runtime, logger, config.timeoutMs, baselineTurns, Page, client, { ignoredTargetKeys: deepResearchTargetKeys }));
|
|
883
1112
|
await updateConversationHint("post-deep-research", 15_000).catch(() => false);
|
|
884
1113
|
runStatus = "complete";
|
|
885
1114
|
const durationMs = Date.now() - startedAt;
|
|
@@ -1068,7 +1297,13 @@ export async function runBrowserMode(options) {
|
|
|
1068
1297
|
promptSubmitted,
|
|
1069
1298
|
controllerPid: process.pid,
|
|
1070
1299
|
};
|
|
1071
|
-
throw
|
|
1300
|
+
throw await createAssistantTimeoutError({
|
|
1301
|
+
Runtime,
|
|
1302
|
+
logger,
|
|
1303
|
+
runtime,
|
|
1304
|
+
diagnostics,
|
|
1305
|
+
cause: error,
|
|
1306
|
+
});
|
|
1072
1307
|
}
|
|
1073
1308
|
}
|
|
1074
1309
|
else {
|
|
@@ -1257,6 +1492,9 @@ export async function runBrowserMode(options) {
|
|
|
1257
1492
|
throw new Error("Chrome disconnected before completion");
|
|
1258
1493
|
}
|
|
1259
1494
|
const imageArtifacts = await collectGeneratedImageArtifacts({
|
|
1495
|
+
Browser: client.Browser,
|
|
1496
|
+
Client: client,
|
|
1497
|
+
Page,
|
|
1260
1498
|
Runtime,
|
|
1261
1499
|
Network,
|
|
1262
1500
|
logger,
|
|
@@ -1271,16 +1509,28 @@ export async function runBrowserMode(options) {
|
|
|
1271
1509
|
if (imageArtifacts.markdownSuffix) {
|
|
1272
1510
|
answerMarkdown += imageArtifacts.markdownSuffix;
|
|
1273
1511
|
}
|
|
1512
|
+
const fileArtifacts = await collectChatGptFileArtifacts({
|
|
1513
|
+
Browser: client.Browser,
|
|
1514
|
+
Client: client,
|
|
1515
|
+
Page,
|
|
1516
|
+
Runtime,
|
|
1517
|
+
Network,
|
|
1518
|
+
answerText: [answerText, answerMarkdown, answerHtml].filter(Boolean).join("\n"),
|
|
1519
|
+
logger,
|
|
1520
|
+
minTurnIndex: imageArtifactMinTurnIndex,
|
|
1521
|
+
sessionId: options.sessionId,
|
|
1522
|
+
});
|
|
1274
1523
|
const savedImageArtifacts = appendArtifacts(undefined, imageArtifacts.savedImages);
|
|
1524
|
+
const savedBrowserArtifacts = appendArtifacts(savedImageArtifacts, fileArtifacts.savedFiles);
|
|
1275
1525
|
const transcriptArtifact = await saveOptionalArtifact(() => saveBrowserTranscriptArtifact({
|
|
1276
1526
|
sessionId: options.sessionId,
|
|
1277
1527
|
prompt: promptText,
|
|
1278
1528
|
answerMarkdown,
|
|
1279
1529
|
conversationUrl: lastUrl,
|
|
1280
|
-
artifacts:
|
|
1530
|
+
artifacts: savedBrowserArtifacts,
|
|
1281
1531
|
logger,
|
|
1282
1532
|
}), logger);
|
|
1283
|
-
const savedArtifacts = appendArtifacts(
|
|
1533
|
+
const savedArtifacts = appendArtifacts(savedBrowserArtifacts, [transcriptArtifact]);
|
|
1284
1534
|
const archive = await maybeArchiveCompletedConversation({
|
|
1285
1535
|
Runtime,
|
|
1286
1536
|
logger,
|
|
@@ -1288,7 +1538,8 @@ export async function runBrowserMode(options) {
|
|
|
1288
1538
|
conversationUrl: lastUrl,
|
|
1289
1539
|
followUpCount: followUpPrompts.length,
|
|
1290
1540
|
requiredArtifactsSaved: Boolean(transcriptArtifact) &&
|
|
1291
|
-
imageArtifacts.savedImages.length === imageArtifacts.imageCount
|
|
1541
|
+
imageArtifacts.savedImages.length === imageArtifacts.imageCount &&
|
|
1542
|
+
fileArtifacts.savedFiles.length === fileArtifacts.fileCount,
|
|
1292
1543
|
});
|
|
1293
1544
|
runStatus = "complete";
|
|
1294
1545
|
const durationMs = Date.now() - startedAt;
|
|
@@ -1301,6 +1552,8 @@ export async function runBrowserMode(options) {
|
|
|
1301
1552
|
artifacts: savedArtifacts,
|
|
1302
1553
|
generatedImages: imageArtifacts.generatedImages,
|
|
1303
1554
|
savedImages: imageArtifacts.savedImages,
|
|
1555
|
+
downloadableFiles: fileArtifacts.files,
|
|
1556
|
+
savedFiles: fileArtifacts.savedFiles,
|
|
1304
1557
|
archive,
|
|
1305
1558
|
modelSelection: modelSelectionEvidence,
|
|
1306
1559
|
tookMs: durationMs,
|
|
@@ -1674,13 +1927,23 @@ async function maybeReuseRunningChrome(userDataDir, logger, options = {}) {
|
|
|
1674
1927
|
let pid = await readChromePid(userDataDir);
|
|
1675
1928
|
if (!port) {
|
|
1676
1929
|
const discovered = await findRunningChromeDebugTargetForProfile(userDataDir);
|
|
1677
|
-
if (!discovered)
|
|
1930
|
+
if (!discovered) {
|
|
1931
|
+
if (pid) {
|
|
1932
|
+
logger(`No reachable Chrome DevTools target found for ${userDataDir}; clearing stale profile state before launching new Chrome.`);
|
|
1933
|
+
await cleanupStaleProfileState(userDataDir, logger, {
|
|
1934
|
+
lockRemovalMode: "if_oracle_pid_dead",
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1678
1937
|
return null;
|
|
1938
|
+
}
|
|
1679
1939
|
const discoveredProbe = await (options.probe ?? verifyDevToolsReachable)({
|
|
1680
1940
|
port: discovered.port,
|
|
1681
1941
|
});
|
|
1682
1942
|
if (!discoveredProbe.ok) {
|
|
1683
1943
|
logger(`Discovered Chrome for ${userDataDir} on port ${discovered.port} but it was unreachable (${discoveredProbe.error}); launching new Chrome.`);
|
|
1944
|
+
await cleanupStaleProfileState(userDataDir, logger, {
|
|
1945
|
+
lockRemovalMode: "if_oracle_pid_dead",
|
|
1946
|
+
});
|
|
1684
1947
|
return null;
|
|
1685
1948
|
}
|
|
1686
1949
|
await writeDevToolsActivePort(userDataDir, discovered.port);
|
|
@@ -1828,16 +2091,20 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1828
2091
|
removeDialogHandler = installJavaScriptDialogAutoDismissal(Page, logger);
|
|
1829
2092
|
// Skip cookie sync for remote Chrome - it already has cookies
|
|
1830
2093
|
logger("Skipping cookie sync for remote Chrome (using existing session)");
|
|
1831
|
-
if (
|
|
2094
|
+
if (config.resumeConversationUrl) {
|
|
2095
|
+
await navigateToChatGPT(Page, Runtime, config.resumeConversationUrl, logger);
|
|
2096
|
+
}
|
|
2097
|
+
else if (!attachedExistingTab) {
|
|
1832
2098
|
await navigateToChatGPT(Page, Runtime, config.url, logger);
|
|
1833
|
-
await ensureNotBlocked(Runtime, config.headless, logger);
|
|
1834
|
-
await ensureLoggedIn(Runtime, logger, { remoteSession: true });
|
|
1835
|
-
await ensurePromptReady(Runtime, config.inputTimeoutMs, logger);
|
|
1836
2099
|
}
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
2100
|
+
await ensureNotBlocked(Runtime, config.headless, logger);
|
|
2101
|
+
await ensureLoggedIn(Runtime, logger, { remoteSession: true });
|
|
2102
|
+
await ensurePromptReady(Runtime, config.inputTimeoutMs, logger);
|
|
2103
|
+
if (config.resumeConversationUrl) {
|
|
2104
|
+
await waitForResumedConversationHydration(Runtime, config.inputTimeoutMs, logger, {
|
|
2105
|
+
requirePriorTurns: true,
|
|
2106
|
+
expectedConversationUrl: config.resumeConversationUrl,
|
|
2107
|
+
});
|
|
1841
2108
|
}
|
|
1842
2109
|
logger(`Prompt textarea ready (initial focus, ${promptText.length.toLocaleString()} chars queued)`);
|
|
1843
2110
|
try {
|
|
@@ -1854,7 +2121,7 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1854
2121
|
// ignore
|
|
1855
2122
|
}
|
|
1856
2123
|
const modelStrategy = config.modelStrategy ?? DEFAULT_MODEL_STRATEGY;
|
|
1857
|
-
if (config.desiredModel && modelStrategy !== "ignore") {
|
|
2124
|
+
if (config.desiredModel && modelStrategy !== "ignore" && !config.resumeConversationUrl) {
|
|
1858
2125
|
modelSelectionEvidence = await withRetries(() => ensureModelSelection(Runtime, config.desiredModel, logger, modelStrategy), {
|
|
1859
2126
|
retries: 2,
|
|
1860
2127
|
delayMs: 300,
|
|
@@ -1867,9 +2134,11 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1867
2134
|
await ensurePromptReady(Runtime, config.inputTimeoutMs, logger);
|
|
1868
2135
|
logger(`Prompt textarea ready (after model switch, ${promptText.length.toLocaleString()} chars queued)`);
|
|
1869
2136
|
}
|
|
1870
|
-
else if (modelStrategy === "ignore") {
|
|
2137
|
+
else if (modelStrategy === "ignore" || config.resumeConversationUrl) {
|
|
1871
2138
|
modelSelectionEvidence = buildSkippedModelSelectionEvidence(config.desiredModel, modelStrategy);
|
|
1872
|
-
logger(
|
|
2139
|
+
logger(config.resumeConversationUrl
|
|
2140
|
+
? "Model picker: skipped (resumed conversation)"
|
|
2141
|
+
: "Model picker: skipped (strategy=ignore)");
|
|
1873
2142
|
}
|
|
1874
2143
|
const deepResearch = config.researchMode === "deep";
|
|
1875
2144
|
// Handle thinking time selection if specified. Deep Research owns its own effort flow.
|
|
@@ -1903,6 +2172,10 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1903
2172
|
const baselineSnapshot = await readAssistantSnapshot(Runtime).catch(() => null);
|
|
1904
2173
|
const baselineAssistantText = typeof baselineSnapshot?.text === "string" ? baselineSnapshot.text.trim() : "";
|
|
1905
2174
|
const attachmentNames = submissionAttachments.map((a) => path.basename(a.path));
|
|
2175
|
+
const attachmentExpectations = submissionAttachments.map((a) => ({
|
|
2176
|
+
name: path.basename(a.path),
|
|
2177
|
+
generatedBundle: a.generatedBundle === true,
|
|
2178
|
+
}));
|
|
1906
2179
|
await clearPromptComposer(Runtime, logger);
|
|
1907
2180
|
await ensurePromptReady(Runtime, config.inputTimeoutMs, logger);
|
|
1908
2181
|
if (submissionAttachments.length > 0) {
|
|
@@ -1920,7 +2193,8 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1920
2193
|
const baseTimeout = config.inputTimeoutMs ?? 30_000;
|
|
1921
2194
|
const perFileTimeout = 15_000;
|
|
1922
2195
|
const waitBudget = Math.max(baseTimeout, 30_000) + (submissionAttachments.length - 1) * perFileTimeout;
|
|
1923
|
-
|
|
2196
|
+
const attachmentWaitBudget = Math.max(config.attachmentTimeoutMs ?? 0, waitBudget);
|
|
2197
|
+
await waitForAttachmentCompletion(Runtime, attachmentWaitBudget, attachmentNames, logger);
|
|
1924
2198
|
logger("All attachments uploaded");
|
|
1925
2199
|
}
|
|
1926
2200
|
let baselineTurns = await readConversationTurnCount(Runtime, logger);
|
|
@@ -1930,10 +2204,14 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1930
2204
|
logger,
|
|
1931
2205
|
timeoutMs: config.timeoutMs,
|
|
1932
2206
|
inputTimeoutMs: config.inputTimeoutMs ?? undefined,
|
|
2207
|
+
attachmentTimeoutMs: config.attachmentTimeoutMs ?? undefined,
|
|
1933
2208
|
baselineTurns: baselineTurns ?? undefined,
|
|
1934
|
-
attachmentNames,
|
|
2209
|
+
attachmentNames: attachmentExpectations,
|
|
1935
2210
|
onPromptSubmitted: markPromptSubmitted,
|
|
1936
2211
|
};
|
|
2212
|
+
const deepResearchTargetKeys = deepResearch && client
|
|
2213
|
+
? await captureDeepResearchTargetKeys(client).catch(() => [])
|
|
2214
|
+
: undefined;
|
|
1937
2215
|
await runProviderSubmissionFlow(chatgptDomProvider, {
|
|
1938
2216
|
prompt,
|
|
1939
2217
|
evaluate: async () => undefined,
|
|
@@ -1946,7 +2224,7 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1946
2224
|
if (typeof providerBaselineTurns === "number" && Number.isFinite(providerBaselineTurns)) {
|
|
1947
2225
|
baselineTurns = providerBaselineTurns;
|
|
1948
2226
|
}
|
|
1949
|
-
return { baselineTurns, baselineAssistantText };
|
|
2227
|
+
return { baselineTurns, baselineAssistantText, deepResearchTargetKeys };
|
|
1950
2228
|
};
|
|
1951
2229
|
const reloadPromptComposer = async () => {
|
|
1952
2230
|
logger("[browser] Composer became unresponsive; reloading page and retrying once.");
|
|
@@ -1955,6 +2233,7 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1955
2233
|
};
|
|
1956
2234
|
let baselineTurns = null;
|
|
1957
2235
|
let baselineAssistantText = null;
|
|
2236
|
+
let deepResearchTargetKeys = [];
|
|
1958
2237
|
const submission = await runSubmissionWithRecovery({
|
|
1959
2238
|
prompt: promptText,
|
|
1960
2239
|
attachments,
|
|
@@ -1969,10 +2248,11 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1969
2248
|
});
|
|
1970
2249
|
baselineTurns = submission.baselineTurns;
|
|
1971
2250
|
baselineAssistantText = submission.baselineAssistantText;
|
|
2251
|
+
deepResearchTargetKeys = submission.deepResearchTargetKeys ?? [];
|
|
1972
2252
|
const imageArtifactMinTurnIndex = baselineTurns;
|
|
1973
2253
|
if (deepResearch) {
|
|
1974
2254
|
await waitForResearchPlanAutoConfirm(Runtime, logger);
|
|
1975
|
-
const researchResult = await waitForDeepResearchCompletion(Runtime, logger, config.timeoutMs, baselineTurns, Page, client);
|
|
2255
|
+
const researchResult = await waitForDeepResearchCompletion(Runtime, logger, config.timeoutMs, baselineTurns, Page, client, { ignoredTargetKeys: deepResearchTargetKeys });
|
|
1976
2256
|
await emitRuntimeHint();
|
|
1977
2257
|
const durationMs = Date.now() - startedAt;
|
|
1978
2258
|
const tokens = estimateTokenCount(researchResult.text);
|
|
@@ -2171,7 +2451,13 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2171
2451
|
promptSubmitted,
|
|
2172
2452
|
controllerPid: process.pid,
|
|
2173
2453
|
};
|
|
2174
|
-
throw
|
|
2454
|
+
throw await createAssistantTimeoutError({
|
|
2455
|
+
Runtime,
|
|
2456
|
+
logger,
|
|
2457
|
+
runtime,
|
|
2458
|
+
diagnostics,
|
|
2459
|
+
cause: error,
|
|
2460
|
+
});
|
|
2175
2461
|
}
|
|
2176
2462
|
}
|
|
2177
2463
|
else {
|
|
@@ -2316,7 +2602,11 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2316
2602
|
answerMarkdown = formatted.answerMarkdown;
|
|
2317
2603
|
answerHtml = "";
|
|
2318
2604
|
}
|
|
2605
|
+
const canSaveBrowserDownloadsLocally = isLocalChromeHost(host);
|
|
2319
2606
|
const imageArtifacts = await collectGeneratedImageArtifacts({
|
|
2607
|
+
Browser: canSaveBrowserDownloadsLocally ? client.Browser : undefined,
|
|
2608
|
+
Client: canSaveBrowserDownloadsLocally ? client : undefined,
|
|
2609
|
+
Page: canSaveBrowserDownloadsLocally ? Page : undefined,
|
|
2320
2610
|
Runtime,
|
|
2321
2611
|
Network,
|
|
2322
2612
|
logger,
|
|
@@ -2331,16 +2621,28 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2331
2621
|
if (imageArtifacts.markdownSuffix) {
|
|
2332
2622
|
answerMarkdown += imageArtifacts.markdownSuffix;
|
|
2333
2623
|
}
|
|
2624
|
+
const fileArtifacts = await collectChatGptFileArtifacts({
|
|
2625
|
+
Browser: client.Browser,
|
|
2626
|
+
Client: client,
|
|
2627
|
+
Page,
|
|
2628
|
+
Runtime,
|
|
2629
|
+
Network,
|
|
2630
|
+
answerText: [answerText, answerMarkdown, answerHtml].filter(Boolean).join("\n"),
|
|
2631
|
+
logger,
|
|
2632
|
+
minTurnIndex: imageArtifactMinTurnIndex,
|
|
2633
|
+
sessionId: options.sessionId,
|
|
2634
|
+
});
|
|
2334
2635
|
const savedImageArtifacts = appendArtifacts(undefined, imageArtifacts.savedImages);
|
|
2636
|
+
const savedBrowserArtifacts = appendArtifacts(savedImageArtifacts, fileArtifacts.savedFiles);
|
|
2335
2637
|
const transcriptArtifact = await saveOptionalArtifact(() => saveBrowserTranscriptArtifact({
|
|
2336
2638
|
sessionId: options.sessionId,
|
|
2337
2639
|
prompt: promptText,
|
|
2338
2640
|
answerMarkdown,
|
|
2339
2641
|
conversationUrl: lastUrl,
|
|
2340
|
-
artifacts:
|
|
2642
|
+
artifacts: savedBrowserArtifacts,
|
|
2341
2643
|
logger,
|
|
2342
2644
|
}), logger);
|
|
2343
|
-
const savedArtifacts = appendArtifacts(
|
|
2645
|
+
const savedArtifacts = appendArtifacts(savedBrowserArtifacts, [transcriptArtifact]);
|
|
2344
2646
|
const archive = await maybeArchiveCompletedConversation({
|
|
2345
2647
|
Runtime,
|
|
2346
2648
|
logger,
|
|
@@ -2348,7 +2650,8 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2348
2650
|
conversationUrl: lastUrl,
|
|
2349
2651
|
followUpCount: followUpPrompts.length,
|
|
2350
2652
|
requiredArtifactsSaved: Boolean(transcriptArtifact) &&
|
|
2351
|
-
imageArtifacts.savedImages.length === imageArtifacts.imageCount
|
|
2653
|
+
imageArtifacts.savedImages.length === imageArtifacts.imageCount &&
|
|
2654
|
+
fileArtifacts.savedFiles.length === fileArtifacts.fileCount,
|
|
2352
2655
|
});
|
|
2353
2656
|
const durationMs = Date.now() - startedAt;
|
|
2354
2657
|
const answerChars = answerText.length;
|
|
@@ -2373,6 +2676,10 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2373
2676
|
conversationId: lastUrl ? extractConversationIdFromUrl(lastUrl) : undefined,
|
|
2374
2677
|
promptSubmitted,
|
|
2375
2678
|
artifacts: savedArtifacts,
|
|
2679
|
+
generatedImages: imageArtifacts.generatedImages,
|
|
2680
|
+
savedImages: imageArtifacts.savedImages,
|
|
2681
|
+
downloadableFiles: fileArtifacts.files,
|
|
2682
|
+
savedFiles: fileArtifacts.savedFiles,
|
|
2376
2683
|
archive,
|
|
2377
2684
|
modelSelection: modelSelectionEvidence,
|
|
2378
2685
|
controllerPid: process.pid,
|
|
@@ -2439,8 +2746,12 @@ export { resolveBrowserConfig, DEFAULT_BROWSER_CONFIG } from "./config.js";
|
|
|
2439
2746
|
export const __test__ = {
|
|
2440
2747
|
assertManualLoginProfileReadyForRun,
|
|
2441
2748
|
closeRemoteConnectionAfterRun,
|
|
2749
|
+
classifyChatGptUiWarningText,
|
|
2750
|
+
collectChatGptUiWarnings,
|
|
2751
|
+
createAssistantTimeoutError,
|
|
2442
2752
|
detachKeptChromeProcess,
|
|
2443
2753
|
formatManualLoginSetupCommand,
|
|
2754
|
+
isAssistantResponseTimeoutError,
|
|
2444
2755
|
isManualLoginProfileInitialized,
|
|
2445
2756
|
isImageOnlyUiChromeText,
|
|
2446
2757
|
listIgnoredRemoteChromeFlags,
|
|
@@ -2496,7 +2807,8 @@ function isAssistantResponseTimeoutError(error) {
|
|
|
2496
2807
|
const message = error.message.toLowerCase();
|
|
2497
2808
|
if (!message)
|
|
2498
2809
|
return false;
|
|
2499
|
-
return (message
|
|
2810
|
+
return (message === "response timeout" ||
|
|
2811
|
+
message.includes("assistant-response") ||
|
|
2500
2812
|
message.includes("assistant response") ||
|
|
2501
2813
|
message.includes("watchdog") ||
|
|
2502
2814
|
message.includes("capture assistant response"));
|