@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.
Files changed (71) hide show
  1. package/README.md +22 -11
  2. package/dist/bin/oracle-cli.js +71 -71
  3. package/dist/src/browser/actions/assistantResponse.js +93 -10
  4. package/dist/src/browser/actions/deepResearch.js +180 -52
  5. package/dist/src/browser/actions/modelSelection.js +357 -40
  6. package/dist/src/browser/actions/navigation.js +294 -65
  7. package/dist/src/browser/actions/promptComposer.js +50 -4
  8. package/dist/src/browser/actions/thinkingTime.js +517 -105
  9. package/dist/src/browser/artifacts.js +19 -0
  10. package/dist/src/browser/chatgptFiles.js +797 -0
  11. package/dist/src/browser/chatgptImages.js +256 -27
  12. package/dist/src/browser/chromeLifecycle.js +4 -0
  13. package/dist/src/browser/config.js +2 -0
  14. package/dist/src/browser/index.js +404 -41
  15. package/dist/src/browser/pageActions.js +1 -1
  16. package/dist/src/browser/policies.js +2 -6
  17. package/dist/src/browser/projectSourcesRunner.js +16 -3
  18. package/dist/src/browser/prompt.js +161 -36
  19. package/dist/src/browser/reattach.js +6 -2
  20. package/dist/src/browser/reattachability.js +22 -10
  21. package/dist/src/browser/recoverConversation.js +73 -0
  22. package/dist/src/browser/sessionRunner.js +4 -1
  23. package/dist/src/cli/browserConfig.js +6 -2
  24. package/dist/src/cli/browserDefaults.js +2 -1
  25. package/dist/src/cli/browserTabs.js +129 -54
  26. package/dist/src/cli/followup.js +72 -0
  27. package/dist/src/cli/help.js +1 -1
  28. package/dist/src/cli/options.js +24 -0
  29. package/dist/src/cli/reattachGuidance.js +8 -0
  30. package/dist/src/cli/runOptions.js +2 -12
  31. package/dist/src/cli/sessionCommand.js +4 -0
  32. package/dist/src/cli/sessionDisplay.js +17 -3
  33. package/dist/src/cli/sessionLineage.js +7 -4
  34. package/dist/src/cli/sessionRunner.js +16 -1
  35. package/dist/src/gemini-web/client.js +5 -10
  36. package/dist/src/gemini-web/executor.js +1 -24
  37. package/dist/src/gemini-web/models.js +83 -0
  38. package/dist/src/mcp/server.js +2 -0
  39. package/dist/src/mcp/tools/chatgptImage.js +132 -0
  40. package/dist/src/mcp/tools/consult.js +270 -175
  41. package/dist/src/mcp/types.js +13 -2
  42. package/dist/src/mcp/utils.js +87 -1
  43. package/dist/src/oracle/config.js +27 -1
  44. package/dist/src/oracle/files.js +4 -6
  45. package/dist/src/oracle/geminiModels.js +2 -0
  46. package/dist/src/oracle/markdown.js +36 -3
  47. package/dist/src/oracle/modelResolver.js +21 -13
  48. package/dist/src/oracle/promptAssembly.js +2 -4
  49. package/dist/src/oracle/request.js +3 -5
  50. package/dist/src/oracle/thinkingTime.js +40 -0
  51. package/dist/src/oracle/tokenStats.js +5 -1
  52. package/dist/src/oracle.js +1 -1
  53. package/dist/src/sessionManager.js +1 -0
  54. package/dist/vendor/oracle-notifier/build-notifier.sh +0 -0
  55. package/package.json +47 -58
  56. package/vendor/oracle-notifier/build-notifier.sh +0 -0
  57. package/dist/bin/oracle.js +0 -569
  58. package/dist/src/browser/chromeCookies.js +0 -312
  59. package/dist/src/browser/keytarShim.js +0 -56
  60. package/dist/src/browser/windowsCookies.js +0 -219
  61. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  62. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +0 -20
  63. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  64. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
  65. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +0 -128
  66. package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  67. package/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +0 -20
  68. package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  69. package/vendor/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
  70. package/vendor/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +0 -128
  71. 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
- if (!url.includes('/backend-api/estuary/content?id=file_')) return false;
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: typeof item?.url === "string" ? item.url : "",
107
- alt: typeof item?.alt === "string" ? item.alt : undefined,
108
- width: typeof item?.width === "number" ? item.width : undefined,
109
- height: typeof item?.height === "number" ? item.height : undefined,
110
- fileId: typeof item?.url === "string" ? extractFileId(item.url) : undefined,
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 || `generated-${Date.now().toString(36)}`;
173
- const stem = sanitizeGeneratedImageStem(stemSource) || `generated-${Date.now().toString(36)}`;
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
- return path.join(baseDir, `${stem}.png`);
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 response = await fetch(image.url, {
206
- headers: {
207
- cookie: cookieHeader,
208
- "user-agent": "Mozilla/5.0",
209
- },
210
- redirect: "follow",
211
- });
212
- if (!response.ok) {
213
- throw new Error(`download failed: ${response.status} ${response.statusText}`);
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: image.url,
227
- url: image.url,
228
- finalUrl: response.url,
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,