@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
|
@@ -1,12 +1,41 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
3
4
|
import { ASSISTANT_ROLE_SELECTOR, CONVERSATION_TURN_SELECTOR } from "./constants.js";
|
|
4
5
|
import { delay } from "./utils.js";
|
|
5
6
|
import { readAssistantSnapshot } from "./pageActions.js";
|
|
6
7
|
import { getOracleHomeDir } from "../oracleHome.js";
|
|
7
8
|
import { resolveSessionArtifactsDir } from "./artifacts.js";
|
|
9
|
+
import { saveAssistantDownloadButtonArtifacts } from "./chatgptFiles.js";
|
|
8
10
|
const GENERATED_IMAGE_WAIT_MIN_MS = 15_000;
|
|
9
11
|
const GENERATED_IMAGE_WAIT_MAX_MS = 15 * 60_000;
|
|
12
|
+
const CHATGPT_GENERATED_IMAGE_BASE_URL = "https://chatgpt.com/";
|
|
13
|
+
function isAllowedChatGptHost(hostname) {
|
|
14
|
+
const value = hostname.toLowerCase();
|
|
15
|
+
return value === "chatgpt.com" || value === "chat.openai.com";
|
|
16
|
+
}
|
|
17
|
+
function normalizeGeneratedImageUrl(value) {
|
|
18
|
+
const raw = String(value ?? "").trim();
|
|
19
|
+
if (!raw)
|
|
20
|
+
return undefined;
|
|
21
|
+
let url;
|
|
22
|
+
try {
|
|
23
|
+
url = new URL(raw, CHATGPT_GENERATED_IMAGE_BASE_URL);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
if (url.protocol !== "https:" || url.port || !isAllowedChatGptHost(url.hostname)) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
if (url.pathname !== "/backend-api/estuary/content") {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
if (!(url.searchParams.get("id") ?? "").startsWith("file_")) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
return url.href;
|
|
38
|
+
}
|
|
10
39
|
function extractFileId(url) {
|
|
11
40
|
try {
|
|
12
41
|
return new URL(url).searchParams.get("id") ?? undefined;
|
|
@@ -39,8 +68,12 @@ function buildAssistantImageExpression(minTurnIndex) {
|
|
|
39
68
|
const CONVERSATION_SELECTOR = ${conversationLiteral};
|
|
40
69
|
const ASSISTANT_SELECTOR = ${assistantLiteral};
|
|
41
70
|
const isGeneratedImage = (img) => {
|
|
42
|
-
const url = img?.src || '';
|
|
43
|
-
|
|
71
|
+
const url = new URL(img?.src || '', location.origin || 'https://chatgpt.com');
|
|
72
|
+
const host = url.hostname.toLowerCase();
|
|
73
|
+
if (url.protocol !== 'https:' || url.port) return false;
|
|
74
|
+
if (host !== 'chatgpt.com' && host !== 'chat.openai.com') return false;
|
|
75
|
+
if (url.pathname !== '/backend-api/estuary/content') return false;
|
|
76
|
+
if (!String(url.searchParams.get('id') || '').startsWith('file_')) return false;
|
|
44
77
|
const alt = String(img.alt || '').toLowerCase();
|
|
45
78
|
if (alt.includes('generated image')) return true;
|
|
46
79
|
let node = img;
|
|
@@ -102,13 +135,16 @@ export async function readAssistantGeneratedImages(Runtime, minTurnIndex) {
|
|
|
102
135
|
});
|
|
103
136
|
const raw = Array.isArray(result?.value) ? result.value : [];
|
|
104
137
|
const normalized = raw
|
|
105
|
-
.map((item) =>
|
|
106
|
-
url
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
138
|
+
.map((item) => {
|
|
139
|
+
const url = normalizeGeneratedImageUrl(typeof item?.url === "string" ? item.url : "");
|
|
140
|
+
return {
|
|
141
|
+
url: url ?? "",
|
|
142
|
+
alt: typeof item?.alt === "string" ? item.alt : undefined,
|
|
143
|
+
width: typeof item?.width === "number" ? item.width : undefined,
|
|
144
|
+
height: typeof item?.height === "number" ? item.height : undefined,
|
|
145
|
+
fileId: url ? extractFileId(url) : undefined,
|
|
146
|
+
};
|
|
147
|
+
})
|
|
112
148
|
.filter((item) => item.url.length > 0);
|
|
113
149
|
return dedupeImages(normalized);
|
|
114
150
|
}
|
|
@@ -150,6 +186,29 @@ function contentTypeToExtension(contentType) {
|
|
|
150
186
|
return "svg";
|
|
151
187
|
return "bin";
|
|
152
188
|
}
|
|
189
|
+
function detectImageFile(buffer) {
|
|
190
|
+
if (buffer.length >= 8 &&
|
|
191
|
+
buffer.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))) {
|
|
192
|
+
return { extension: "png", mimeType: "image/png" };
|
|
193
|
+
}
|
|
194
|
+
if (buffer.length >= 3 && buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) {
|
|
195
|
+
return { extension: "jpg", mimeType: "image/jpeg" };
|
|
196
|
+
}
|
|
197
|
+
if (buffer.length >= 12 &&
|
|
198
|
+
buffer.subarray(0, 4).toString("ascii") === "RIFF" &&
|
|
199
|
+
buffer.subarray(8, 12).toString("ascii") === "WEBP") {
|
|
200
|
+
return { extension: "webp", mimeType: "image/webp" };
|
|
201
|
+
}
|
|
202
|
+
const signature = buffer.subarray(0, 6).toString("ascii");
|
|
203
|
+
if (signature === "GIF87a" || signature === "GIF89a") {
|
|
204
|
+
return { extension: "gif", mimeType: "image/gif" };
|
|
205
|
+
}
|
|
206
|
+
const text = buffer.subarray(0, Math.min(buffer.length, 1024)).toString("utf8").trimStart();
|
|
207
|
+
if (/^(?:<\?xml[^>]*>\s*)?<svg[\s>]/i.test(text)) {
|
|
208
|
+
return { extension: "svg", mimeType: "image/svg+xml" };
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
153
212
|
function resolveSiblingImagePath(basePath, index, extension) {
|
|
154
213
|
const ext = path.extname(basePath);
|
|
155
214
|
const dir = path.dirname(basePath);
|
|
@@ -169,12 +228,13 @@ function sanitizeGeneratedImageStem(value) {
|
|
|
169
228
|
}
|
|
170
229
|
function resolveDefaultGeneratedImagePath(images, sessionId) {
|
|
171
230
|
const primary = images[0];
|
|
172
|
-
const stemSource = primary?.fileId || primary?.alt || primary?.url ||
|
|
173
|
-
const stem = sanitizeGeneratedImageStem(stemSource) ||
|
|
231
|
+
const stemSource = primary?.fileId || primary?.alt || primary?.url || "generated";
|
|
232
|
+
const stem = sanitizeGeneratedImageStem(stemSource) || "generated";
|
|
174
233
|
const baseDir = sessionId
|
|
175
234
|
? resolveSessionArtifactsDir(sessionId)
|
|
176
235
|
: path.join(getOracleHomeDir(), ".temp");
|
|
177
|
-
|
|
236
|
+
const uniqueSuffix = sessionId ? "" : `-${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
|
|
237
|
+
return path.join(baseDir, `${stem}${uniqueSuffix}.png`);
|
|
178
238
|
}
|
|
179
239
|
async function buildCookieHeader(Network) {
|
|
180
240
|
const response = await Network.getCookies({ urls: ["https://chatgpt.com/"] });
|
|
@@ -183,8 +243,51 @@ async function buildCookieHeader(Network) {
|
|
|
183
243
|
.map((cookie) => `${cookie.name}=${cookie.value}`)
|
|
184
244
|
.join("; ");
|
|
185
245
|
}
|
|
246
|
+
async function fetchGeneratedImageInBrowserContext(Runtime, url) {
|
|
247
|
+
const expression = `
|
|
248
|
+
(async () => {
|
|
249
|
+
const url = ${JSON.stringify(url)};
|
|
250
|
+
const response = await fetch(url, { credentials: 'include', redirect: 'follow' });
|
|
251
|
+
const contentType = response.headers.get('content-type') || '';
|
|
252
|
+
const buffer = await response.arrayBuffer();
|
|
253
|
+
const bytes = new Uint8Array(buffer);
|
|
254
|
+
let binary = '';
|
|
255
|
+
for (let index = 0; index < bytes.length; index += 0x8000) {
|
|
256
|
+
binary += String.fromCharCode(...bytes.slice(index, index + 0x8000));
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
ok: response.ok,
|
|
260
|
+
status: response.status,
|
|
261
|
+
statusText: response.statusText,
|
|
262
|
+
contentType,
|
|
263
|
+
finalUrl: response.url,
|
|
264
|
+
b64: btoa(binary),
|
|
265
|
+
};
|
|
266
|
+
})()
|
|
267
|
+
`;
|
|
268
|
+
const { result, exceptionDetails } = await Runtime.evaluate({
|
|
269
|
+
expression,
|
|
270
|
+
awaitPromise: true,
|
|
271
|
+
returnByValue: true,
|
|
272
|
+
timeout: 120_000,
|
|
273
|
+
});
|
|
274
|
+
if (exceptionDetails) {
|
|
275
|
+
throw new Error("browser-context fetch threw an exception");
|
|
276
|
+
}
|
|
277
|
+
const value = result?.value;
|
|
278
|
+
if (!value?.ok || typeof value.b64 !== "string") {
|
|
279
|
+
const status = typeof value?.status === "number" ? value.status : "unknown";
|
|
280
|
+
const statusText = typeof value?.statusText === "string" ? value.statusText : "";
|
|
281
|
+
throw new Error(`browser-context fetch failed: ${status} ${statusText}`.trim());
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
buffer: Buffer.from(value.b64, "base64"),
|
|
285
|
+
contentType: typeof value.contentType === "string" ? value.contentType : null,
|
|
286
|
+
finalUrl: typeof value.finalUrl === "string" && value.finalUrl ? value.finalUrl : url,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
186
289
|
export async function saveChatGptGeneratedImages(params) {
|
|
187
|
-
const { Network, images, outputPath, logger } = params;
|
|
290
|
+
const { Network, Runtime, images, outputPath, logger } = params;
|
|
188
291
|
if (!images.length)
|
|
189
292
|
return { saved: false, imageCount: 0, savedImages: [], errors: [] };
|
|
190
293
|
const cookieHeader = await buildCookieHeader(Network);
|
|
@@ -202,20 +305,41 @@ export async function saveChatGptGeneratedImages(params) {
|
|
|
202
305
|
for (let index = 0; index < images.length; index += 1) {
|
|
203
306
|
const image = images[index];
|
|
204
307
|
try {
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
308
|
+
const imageUrl = normalizeGeneratedImageUrl(image.url);
|
|
309
|
+
if (!imageUrl) {
|
|
310
|
+
throw new Error("rejected non-ChatGPT generated image URL");
|
|
311
|
+
}
|
|
312
|
+
let contentType = null;
|
|
313
|
+
let finalUrl = imageUrl;
|
|
314
|
+
let buffer;
|
|
315
|
+
try {
|
|
316
|
+
const response = await fetch(imageUrl, {
|
|
317
|
+
headers: {
|
|
318
|
+
cookie: cookieHeader,
|
|
319
|
+
"user-agent": "Mozilla/5.0",
|
|
320
|
+
},
|
|
321
|
+
redirect: "follow",
|
|
322
|
+
});
|
|
323
|
+
if (!response.ok) {
|
|
324
|
+
throw new Error(`download failed: ${response.status} ${response.statusText}`);
|
|
325
|
+
}
|
|
326
|
+
contentType = response.headers.get("content-type");
|
|
327
|
+
finalUrl = response.url;
|
|
328
|
+
buffer = Buffer.from(await response.arrayBuffer());
|
|
329
|
+
}
|
|
330
|
+
catch (downloadError) {
|
|
331
|
+
if (!Runtime) {
|
|
332
|
+
throw downloadError;
|
|
333
|
+
}
|
|
334
|
+
const message = downloadError instanceof Error ? downloadError.message : String(downloadError);
|
|
335
|
+
logger?.(`[browser] ChatGPT generated image download failed via Node fetch; retrying in browser context (${image.fileId ?? imageUrl}: ${message}).`);
|
|
336
|
+
const browserFetch = await fetchGeneratedImageInBrowserContext(Runtime, imageUrl);
|
|
337
|
+
contentType = browserFetch.contentType;
|
|
338
|
+
finalUrl = browserFetch.finalUrl;
|
|
339
|
+
buffer = browserFetch.buffer;
|
|
214
340
|
}
|
|
215
|
-
const contentType = response.headers.get("content-type");
|
|
216
341
|
const extension = contentTypeToExtension(contentType);
|
|
217
342
|
const targetPath = resolveSiblingImagePath(path.resolve(outputPath), index, extension);
|
|
218
|
-
const buffer = Buffer.from(await response.arrayBuffer());
|
|
219
343
|
await fs.writeFile(targetPath, buffer);
|
|
220
344
|
savedImages.push({
|
|
221
345
|
kind: "image",
|
|
@@ -223,9 +347,9 @@ export async function saveChatGptGeneratedImages(params) {
|
|
|
223
347
|
label: index === 0 ? "Generated image" : `Generated image ${index + 1}`,
|
|
224
348
|
mimeType: contentType ?? undefined,
|
|
225
349
|
sizeBytes: buffer.length,
|
|
226
|
-
sourceUrl:
|
|
227
|
-
url:
|
|
228
|
-
finalUrl
|
|
350
|
+
sourceUrl: imageUrl,
|
|
351
|
+
url: imageUrl,
|
|
352
|
+
finalUrl,
|
|
229
353
|
alt: image.alt,
|
|
230
354
|
width: image.width,
|
|
231
355
|
height: image.height,
|
|
@@ -245,14 +369,89 @@ export async function saveChatGptGeneratedImages(params) {
|
|
|
245
369
|
errors,
|
|
246
370
|
};
|
|
247
371
|
}
|
|
372
|
+
async function saveGeneratedImageButtonArtifacts(params) {
|
|
373
|
+
const buttonDownloads = await saveAssistantDownloadButtonArtifacts({
|
|
374
|
+
Browser: params.Browser,
|
|
375
|
+
Client: params.Client,
|
|
376
|
+
Page: params.Page,
|
|
377
|
+
Runtime: params.Runtime,
|
|
378
|
+
logger: params.logger,
|
|
379
|
+
files: [],
|
|
380
|
+
allowGenericDownloadLabels: true,
|
|
381
|
+
downloadPath: path.dirname(params.targetPath),
|
|
382
|
+
minTurnIndex: params.minTurnIndex,
|
|
383
|
+
});
|
|
384
|
+
const buttonImages = [];
|
|
385
|
+
for (const download of buttonDownloads) {
|
|
386
|
+
const contents = await fs.readFile(download.path);
|
|
387
|
+
const detected = detectImageFile(contents);
|
|
388
|
+
if (!detected) {
|
|
389
|
+
await fs.unlink(download.path).catch(() => undefined);
|
|
390
|
+
params.logger?.(`[browser] Ignored non-image assistant download: ${download.label}`);
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
const index = buttonImages.length;
|
|
394
|
+
const resolvedPath = resolveSiblingImagePath(params.targetPath, index, detected.extension);
|
|
395
|
+
if (path.resolve(download.path) !== resolvedPath) {
|
|
396
|
+
await fs.copyFile(download.path, resolvedPath);
|
|
397
|
+
await fs.unlink(download.path);
|
|
398
|
+
}
|
|
399
|
+
const stat = await fs.stat(resolvedPath);
|
|
400
|
+
buttonImages.push({
|
|
401
|
+
kind: "image",
|
|
402
|
+
path: resolvedPath,
|
|
403
|
+
label: index === 0 ? "Generated image" : `Generated image ${index + 1}`,
|
|
404
|
+
mimeType: detected.mimeType,
|
|
405
|
+
sizeBytes: stat.size,
|
|
406
|
+
sourceUrl: "browser-download",
|
|
407
|
+
url: "browser-download",
|
|
408
|
+
finalUrl: "browser-download",
|
|
409
|
+
alt: download.label,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
if (buttonImages.length > 0) {
|
|
413
|
+
params.logger?.(`[browser] Saved ${buttonImages.length} generated image download artifact(s).`);
|
|
414
|
+
}
|
|
415
|
+
return buttonImages;
|
|
416
|
+
}
|
|
417
|
+
function formatButtonImageArtifacts(buttonImages, answerText) {
|
|
418
|
+
const primaryPath = buttonImages[0]?.path ?? "";
|
|
419
|
+
return {
|
|
420
|
+
generatedImages: buttonImages.map((image) => ({
|
|
421
|
+
url: image.url,
|
|
422
|
+
alt: image.alt,
|
|
423
|
+
})),
|
|
424
|
+
savedImages: buttonImages,
|
|
425
|
+
imageCount: buttonImages.length,
|
|
426
|
+
markdownSuffix: buttonImages.length > 1
|
|
427
|
+
? `\n\n*Generated ${buttonImages.length} image(s). Saved ${buttonImages.length} file(s) starting at: ${primaryPath}*`
|
|
428
|
+
: `\n\n*Generated 1 image(s). Saved to: ${primaryPath}*`,
|
|
429
|
+
answerText,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
248
432
|
export async function collectGeneratedImageArtifacts(params) {
|
|
249
433
|
const explicitTargetPath = params.generateImagePath ?? params.outputPath;
|
|
250
434
|
let generatedImages = await readAssistantGeneratedImagesWithFallback(params.Runtime, params.minTurnIndex ?? undefined);
|
|
251
435
|
let latestAnswerText = params.answerText;
|
|
252
436
|
if (explicitTargetPath && generatedImages.length === 0) {
|
|
437
|
+
await params.checkBlockingUiWarning?.();
|
|
438
|
+
const targetPath = path.resolve(explicitTargetPath);
|
|
439
|
+
const buttonImages = await saveGeneratedImageButtonArtifacts({
|
|
440
|
+
Browser: params.Browser,
|
|
441
|
+
Client: params.Client,
|
|
442
|
+
Page: params.Page,
|
|
443
|
+
Runtime: params.Runtime,
|
|
444
|
+
logger: params.logger,
|
|
445
|
+
minTurnIndex: params.minTurnIndex,
|
|
446
|
+
targetPath,
|
|
447
|
+
});
|
|
448
|
+
if (buttonImages.length > 0) {
|
|
449
|
+
return formatButtonImageArtifacts(buttonImages, latestAnswerText);
|
|
450
|
+
}
|
|
253
451
|
const deadline = Date.now() + resolveGeneratedImageWaitTimeoutMs(params.waitTimeoutMs);
|
|
254
452
|
while (Date.now() < deadline) {
|
|
255
453
|
await delay(1500);
|
|
454
|
+
await params.checkBlockingUiWarning?.();
|
|
256
455
|
generatedImages = await readAssistantGeneratedImagesWithFallback(params.Runtime, params.minTurnIndex ?? undefined);
|
|
257
456
|
if (generatedImages.length > 0) {
|
|
258
457
|
break;
|
|
@@ -263,6 +462,21 @@ export async function collectGeneratedImageArtifacts(params) {
|
|
|
263
462
|
latestAnswerText = snapshotText;
|
|
264
463
|
}
|
|
265
464
|
}
|
|
465
|
+
if (generatedImages.length === 0) {
|
|
466
|
+
await params.checkBlockingUiWarning?.();
|
|
467
|
+
const delayedButtonImages = await saveGeneratedImageButtonArtifacts({
|
|
468
|
+
Browser: params.Browser,
|
|
469
|
+
Client: params.Client,
|
|
470
|
+
Page: params.Page,
|
|
471
|
+
Runtime: params.Runtime,
|
|
472
|
+
logger: params.logger,
|
|
473
|
+
minTurnIndex: params.minTurnIndex,
|
|
474
|
+
targetPath,
|
|
475
|
+
});
|
|
476
|
+
if (delayedButtonImages.length > 0) {
|
|
477
|
+
return formatButtonImageArtifacts(delayedButtonImages, latestAnswerText);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
266
480
|
}
|
|
267
481
|
const imageCount = generatedImages.length;
|
|
268
482
|
if (explicitTargetPath && imageCount === 0) {
|
|
@@ -283,11 +497,26 @@ export async function collectGeneratedImageArtifacts(params) {
|
|
|
283
497
|
}
|
|
284
498
|
const saved = await saveChatGptGeneratedImages({
|
|
285
499
|
Network: params.Network,
|
|
500
|
+
Runtime: params.Runtime,
|
|
286
501
|
images: generatedImages,
|
|
287
502
|
outputPath: targetPath,
|
|
288
503
|
logger: params.logger,
|
|
289
504
|
});
|
|
290
505
|
if (!saved.saved) {
|
|
506
|
+
if (explicitTargetPath) {
|
|
507
|
+
const buttonImages = await saveGeneratedImageButtonArtifacts({
|
|
508
|
+
Browser: params.Browser,
|
|
509
|
+
Client: params.Client,
|
|
510
|
+
Page: params.Page,
|
|
511
|
+
Runtime: params.Runtime,
|
|
512
|
+
logger: params.logger,
|
|
513
|
+
minTurnIndex: params.minTurnIndex,
|
|
514
|
+
targetPath: path.resolve(explicitTargetPath),
|
|
515
|
+
});
|
|
516
|
+
if (buttonImages.length > 0) {
|
|
517
|
+
return formatButtonImageArtifacts(buttonImages, latestAnswerText);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
291
520
|
const detail = saved.errors.length > 0 ? `\n${saved.errors.join("\n")}` : "";
|
|
292
521
|
if (explicitTargetPath) {
|
|
293
522
|
throw new Error(`No images generated. Response text:\n${latestAnswerText || "(empty response)"}${detail}`);
|
|
@@ -340,6 +340,10 @@ function createSessionBoundChromeClient(browser, sessionId) {
|
|
|
340
340
|
};
|
|
341
341
|
return {
|
|
342
342
|
...browser,
|
|
343
|
+
// Raw `send` here is the browser-level send (not session-bound), so callers
|
|
344
|
+
// that issue Target.* via `send` must pass this page session id explicitly to
|
|
345
|
+
// stay scoped to this tab (e.g. Deep Research OOPIF auto-attach).
|
|
346
|
+
oraclePageSessionId: sessionId,
|
|
343
347
|
Network: bindDomain("Network"),
|
|
344
348
|
Page: bindDomain("Page"),
|
|
345
349
|
Runtime: bindDomain("Runtime"),
|
|
@@ -55,6 +55,7 @@ export const DEFAULT_BROWSER_CONFIG = {
|
|
|
55
55
|
manualLoginCookieSync: false,
|
|
56
56
|
researchMode: "off",
|
|
57
57
|
archiveConversations: "auto",
|
|
58
|
+
resumeConversationUrl: null,
|
|
58
59
|
};
|
|
59
60
|
export function resolveBrowserConfig(config) {
|
|
60
61
|
const debugPortEnv = parseDebugPort(process.env.ORACLE_BROWSER_PORT ?? process.env.ORACLE_BROWSER_DEBUG_PORT);
|
|
@@ -112,6 +113,7 @@ export function resolveBrowserConfig(config) {
|
|
|
112
113
|
thinkingTime: config?.thinkingTime,
|
|
113
114
|
researchMode,
|
|
114
115
|
archiveConversations,
|
|
116
|
+
resumeConversationUrl: config?.resumeConversationUrl ?? DEFAULT_BROWSER_CONFIG.resumeConversationUrl,
|
|
115
117
|
manualLogin,
|
|
116
118
|
manualLoginProfileDir: manualLogin ? resolvedProfileDir : null,
|
|
117
119
|
manualLoginCookieSync: config?.manualLoginCookieSync ?? DEFAULT_BROWSER_CONFIG.manualLoginCookieSync,
|