@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.
Files changed (97) hide show
  1. package/README.md +72 -61
  2. package/dist/bin/oracle-cli.js +77 -68
  3. package/dist/bin/oracle-mcp.js +0 -0
  4. package/dist/docs-site/.nojekyll +0 -0
  5. package/dist/docs-site/CNAME +1 -0
  6. package/dist/docs-site/RELEASING.html +410 -0
  7. package/dist/docs-site/agents.html +374 -0
  8. package/dist/docs-site/anthropic.html +368 -0
  9. package/dist/docs-site/bridge.html +400 -0
  10. package/dist/docs-site/browser-mode.html +593 -0
  11. package/dist/docs-site/chromium-forks.html +347 -0
  12. package/dist/docs-site/cli-reference.html +346 -0
  13. package/dist/docs-site/configuration.html +452 -0
  14. package/dist/docs-site/favicon.svg +14 -0
  15. package/dist/docs-site/followup.html +375 -0
  16. package/dist/docs-site/gemini.html +383 -0
  17. package/dist/docs-site/grok.html +325 -0
  18. package/dist/docs-site/index.html +360 -0
  19. package/dist/docs-site/install.html +335 -0
  20. package/dist/docs-site/linux.html +321 -0
  21. package/dist/docs-site/llms.txt +43 -0
  22. package/dist/docs-site/manual-tests.html +596 -0
  23. package/dist/docs-site/mcp.html +391 -0
  24. package/dist/docs-site/multimodel.html +364 -0
  25. package/dist/docs-site/mythical-pro-agents.html +360 -0
  26. package/dist/docs-site/notifier.html +338 -0
  27. package/dist/docs-site/openai-endpoints.html +387 -0
  28. package/dist/docs-site/openrouter.html +344 -0
  29. package/dist/docs-site/quickstart.html +369 -0
  30. package/dist/docs-site/refactor/ux.html +532 -0
  31. package/dist/docs-site/sessions.html +388 -0
  32. package/dist/docs-site/social-card.png +0 -0
  33. package/dist/docs-site/social-card.svg +79 -0
  34. package/dist/docs-site/spec.html +363 -0
  35. package/dist/docs-site/testing.html +320 -0
  36. package/dist/docs-site/tui-debug.html +326 -0
  37. package/dist/docs-site/windows-work.html +323 -0
  38. package/dist/docs-site/windows.html +320 -0
  39. package/dist/src/browser/actions/assistantResponse.js +91 -9
  40. package/dist/src/browser/actions/deepResearch.js +180 -52
  41. package/dist/src/browser/actions/modelSelection.js +111 -24
  42. package/dist/src/browser/actions/navigation.js +358 -67
  43. package/dist/src/browser/actions/promptComposer.js +241 -46
  44. package/dist/src/browser/actions/thinkingTime.js +308 -89
  45. package/dist/src/browser/artifacts.js +19 -0
  46. package/dist/src/browser/chatgptFiles.js +797 -0
  47. package/dist/src/browser/chatgptImages.js +130 -3
  48. package/dist/src/browser/chromeLifecycle.js +4 -0
  49. package/dist/src/browser/config.js +4 -0
  50. package/dist/src/browser/index.js +353 -41
  51. package/dist/src/browser/pageActions.js +1 -1
  52. package/dist/src/browser/policies.js +2 -6
  53. package/dist/src/browser/projectSourcesRunner.js +14 -2
  54. package/dist/src/browser/prompt.js +11 -15
  55. package/dist/src/browser/providers/chatgptDomProvider.js +1 -0
  56. package/dist/src/browser/reattach.js +6 -2
  57. package/dist/src/browser/reattachability.js +22 -10
  58. package/dist/src/browser/recoverConversation.js +73 -0
  59. package/dist/src/browser/sessionRunner.js +4 -1
  60. package/dist/src/cli/bridge/doctor.js +7 -2
  61. package/dist/src/cli/browserConfig.js +13 -2
  62. package/dist/src/cli/browserDefaults.js +3 -0
  63. package/dist/src/cli/browserTabs.js +129 -54
  64. package/dist/src/cli/engine.js +6 -2
  65. package/dist/src/cli/followup.js +72 -0
  66. package/dist/src/cli/help.js +1 -1
  67. package/dist/src/cli/options.js +20 -0
  68. package/dist/src/cli/reattachGuidance.js +8 -0
  69. package/dist/src/cli/runOptions.js +10 -31
  70. package/dist/src/cli/sessionCommand.js +4 -0
  71. package/dist/src/cli/sessionDisplay.js +17 -3
  72. package/dist/src/cli/sessionLineage.js +7 -4
  73. package/dist/src/cli/sessionRunner.js +16 -1
  74. package/dist/src/config.js +164 -9
  75. package/dist/src/gemini-web/client.js +5 -10
  76. package/dist/src/gemini-web/executor.js +1 -24
  77. package/dist/src/gemini-web/models.js +83 -0
  78. package/dist/src/mcp/server.js +2 -0
  79. package/dist/src/mcp/tools/chatgptImage.js +127 -0
  80. package/dist/src/mcp/tools/consult.js +261 -167
  81. package/dist/src/mcp/types.js +2 -0
  82. package/dist/src/mcp/utils.js +87 -1
  83. package/dist/src/oracle/config.js +27 -1
  84. package/dist/src/oracle/files.js +4 -6
  85. package/dist/src/oracle/geminiModels.js +2 -0
  86. package/dist/src/oracle/markdown.js +36 -3
  87. package/dist/src/oracle/modelResolver.js +21 -13
  88. package/dist/src/oracle/promptAssembly.js +2 -4
  89. package/dist/src/oracle/request.js +3 -5
  90. package/dist/src/oracle/tokenStats.js +5 -1
  91. package/dist/src/oracle.js +1 -1
  92. package/dist/src/sessionManager.js +1 -0
  93. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  94. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  95. package/package.json +20 -29
  96. package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  97. package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
@@ -1,10 +1,12 @@
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;
10
12
  function extractFileId(url) {
@@ -150,6 +152,29 @@ function contentTypeToExtension(contentType) {
150
152
  return "svg";
151
153
  return "bin";
152
154
  }
155
+ function detectImageFile(buffer) {
156
+ if (buffer.length >= 8 &&
157
+ buffer.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))) {
158
+ return { extension: "png", mimeType: "image/png" };
159
+ }
160
+ if (buffer.length >= 3 && buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) {
161
+ return { extension: "jpg", mimeType: "image/jpeg" };
162
+ }
163
+ if (buffer.length >= 12 &&
164
+ buffer.subarray(0, 4).toString("ascii") === "RIFF" &&
165
+ buffer.subarray(8, 12).toString("ascii") === "WEBP") {
166
+ return { extension: "webp", mimeType: "image/webp" };
167
+ }
168
+ const signature = buffer.subarray(0, 6).toString("ascii");
169
+ if (signature === "GIF87a" || signature === "GIF89a") {
170
+ return { extension: "gif", mimeType: "image/gif" };
171
+ }
172
+ const text = buffer.subarray(0, Math.min(buffer.length, 1024)).toString("utf8").trimStart();
173
+ if (/^(?:<\?xml[^>]*>\s*)?<svg[\s>]/i.test(text)) {
174
+ return { extension: "svg", mimeType: "image/svg+xml" };
175
+ }
176
+ return null;
177
+ }
153
178
  function resolveSiblingImagePath(basePath, index, extension) {
154
179
  const ext = path.extname(basePath);
155
180
  const dir = path.dirname(basePath);
@@ -169,12 +194,13 @@ function sanitizeGeneratedImageStem(value) {
169
194
  }
170
195
  function resolveDefaultGeneratedImagePath(images, sessionId) {
171
196
  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)}`;
197
+ const stemSource = primary?.fileId || primary?.alt || primary?.url || "generated";
198
+ const stem = sanitizeGeneratedImageStem(stemSource) || "generated";
174
199
  const baseDir = sessionId
175
200
  ? resolveSessionArtifactsDir(sessionId)
176
201
  : path.join(getOracleHomeDir(), ".temp");
177
- return path.join(baseDir, `${stem}.png`);
202
+ const uniqueSuffix = sessionId ? "" : `-${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
203
+ return path.join(baseDir, `${stem}${uniqueSuffix}.png`);
178
204
  }
179
205
  async function buildCookieHeader(Network) {
180
206
  const response = await Network.getCookies({ urls: ["https://chatgpt.com/"] });
@@ -245,11 +271,84 @@ export async function saveChatGptGeneratedImages(params) {
245
271
  errors,
246
272
  };
247
273
  }
274
+ async function saveGeneratedImageButtonArtifacts(params) {
275
+ const buttonDownloads = await saveAssistantDownloadButtonArtifacts({
276
+ Browser: params.Browser,
277
+ Client: params.Client,
278
+ Page: params.Page,
279
+ Runtime: params.Runtime,
280
+ logger: params.logger,
281
+ files: [],
282
+ allowGenericDownloadLabels: true,
283
+ downloadPath: path.dirname(params.targetPath),
284
+ minTurnIndex: params.minTurnIndex,
285
+ });
286
+ const buttonImages = [];
287
+ for (const download of buttonDownloads) {
288
+ const contents = await fs.readFile(download.path);
289
+ const detected = detectImageFile(contents);
290
+ if (!detected) {
291
+ await fs.unlink(download.path).catch(() => undefined);
292
+ params.logger?.(`[browser] Ignored non-image assistant download: ${download.label}`);
293
+ continue;
294
+ }
295
+ const index = buttonImages.length;
296
+ const resolvedPath = resolveSiblingImagePath(params.targetPath, index, detected.extension);
297
+ if (path.resolve(download.path) !== resolvedPath) {
298
+ await fs.copyFile(download.path, resolvedPath);
299
+ await fs.unlink(download.path);
300
+ }
301
+ const stat = await fs.stat(resolvedPath);
302
+ buttonImages.push({
303
+ kind: "image",
304
+ path: resolvedPath,
305
+ label: index === 0 ? "Generated image" : `Generated image ${index + 1}`,
306
+ mimeType: detected.mimeType,
307
+ sizeBytes: stat.size,
308
+ sourceUrl: "browser-download",
309
+ url: "browser-download",
310
+ finalUrl: "browser-download",
311
+ alt: download.label,
312
+ });
313
+ }
314
+ if (buttonImages.length > 0) {
315
+ params.logger?.(`[browser] Saved ${buttonImages.length} generated image download artifact(s).`);
316
+ }
317
+ return buttonImages;
318
+ }
319
+ function formatButtonImageArtifacts(buttonImages, answerText) {
320
+ const primaryPath = buttonImages[0]?.path ?? "";
321
+ return {
322
+ generatedImages: buttonImages.map((image) => ({
323
+ url: image.url,
324
+ alt: image.alt,
325
+ })),
326
+ savedImages: buttonImages,
327
+ imageCount: buttonImages.length,
328
+ markdownSuffix: buttonImages.length > 1
329
+ ? `\n\n*Generated ${buttonImages.length} image(s). Saved ${buttonImages.length} file(s) starting at: ${primaryPath}*`
330
+ : `\n\n*Generated 1 image(s). Saved to: ${primaryPath}*`,
331
+ answerText,
332
+ };
333
+ }
248
334
  export async function collectGeneratedImageArtifacts(params) {
249
335
  const explicitTargetPath = params.generateImagePath ?? params.outputPath;
250
336
  let generatedImages = await readAssistantGeneratedImagesWithFallback(params.Runtime, params.minTurnIndex ?? undefined);
251
337
  let latestAnswerText = params.answerText;
252
338
  if (explicitTargetPath && generatedImages.length === 0) {
339
+ const targetPath = path.resolve(explicitTargetPath);
340
+ const buttonImages = await saveGeneratedImageButtonArtifacts({
341
+ Browser: params.Browser,
342
+ Client: params.Client,
343
+ Page: params.Page,
344
+ Runtime: params.Runtime,
345
+ logger: params.logger,
346
+ minTurnIndex: params.minTurnIndex,
347
+ targetPath,
348
+ });
349
+ if (buttonImages.length > 0) {
350
+ return formatButtonImageArtifacts(buttonImages, latestAnswerText);
351
+ }
253
352
  const deadline = Date.now() + resolveGeneratedImageWaitTimeoutMs(params.waitTimeoutMs);
254
353
  while (Date.now() < deadline) {
255
354
  await delay(1500);
@@ -263,6 +362,20 @@ export async function collectGeneratedImageArtifacts(params) {
263
362
  latestAnswerText = snapshotText;
264
363
  }
265
364
  }
365
+ if (generatedImages.length === 0) {
366
+ const delayedButtonImages = await saveGeneratedImageButtonArtifacts({
367
+ Browser: params.Browser,
368
+ Client: params.Client,
369
+ Page: params.Page,
370
+ Runtime: params.Runtime,
371
+ logger: params.logger,
372
+ minTurnIndex: params.minTurnIndex,
373
+ targetPath,
374
+ });
375
+ if (delayedButtonImages.length > 0) {
376
+ return formatButtonImageArtifacts(delayedButtonImages, latestAnswerText);
377
+ }
378
+ }
266
379
  }
267
380
  const imageCount = generatedImages.length;
268
381
  if (explicitTargetPath && imageCount === 0) {
@@ -288,6 +401,20 @@ export async function collectGeneratedImageArtifacts(params) {
288
401
  logger: params.logger,
289
402
  });
290
403
  if (!saved.saved) {
404
+ if (explicitTargetPath) {
405
+ const buttonImages = await saveGeneratedImageButtonArtifacts({
406
+ Browser: params.Browser,
407
+ Client: params.Client,
408
+ Page: params.Page,
409
+ Runtime: params.Runtime,
410
+ logger: params.logger,
411
+ minTurnIndex: params.minTurnIndex,
412
+ targetPath: path.resolve(explicitTargetPath),
413
+ });
414
+ if (buttonImages.length > 0) {
415
+ return formatButtonImageArtifacts(buttonImages, latestAnswerText);
416
+ }
417
+ }
291
418
  const detail = saved.errors.length > 0 ? `\n${saved.errors.join("\n")}` : "";
292
419
  if (explicitTargetPath) {
293
420
  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"),
@@ -26,6 +26,7 @@ export const DEFAULT_BROWSER_CONFIG = {
26
26
  timeoutMs: 1_200_000,
27
27
  debugPort: null,
28
28
  inputTimeoutMs: 60_000,
29
+ attachmentTimeoutMs: 45_000,
29
30
  assistantRecheckDelayMs: 0,
30
31
  assistantRecheckTimeoutMs: 120_000,
31
32
  reuseChromeWaitMs: 10_000,
@@ -54,6 +55,7 @@ export const DEFAULT_BROWSER_CONFIG = {
54
55
  manualLoginCookieSync: false,
55
56
  researchMode: "off",
56
57
  archiveConversations: "auto",
58
+ resumeConversationUrl: null,
57
59
  };
58
60
  export function resolveBrowserConfig(config) {
59
61
  const debugPortEnv = parseDebugPort(process.env.ORACLE_BROWSER_PORT ?? process.env.ORACLE_BROWSER_DEBUG_PORT);
@@ -80,6 +82,7 @@ export function resolveBrowserConfig(config) {
80
82
  timeoutMs: config?.timeoutMs ?? defaultTimeoutMs,
81
83
  debugPort: config?.debugPort ?? debugPortEnv ?? DEFAULT_BROWSER_CONFIG.debugPort,
82
84
  inputTimeoutMs: config?.inputTimeoutMs ?? DEFAULT_BROWSER_CONFIG.inputTimeoutMs,
85
+ attachmentTimeoutMs: config?.attachmentTimeoutMs ?? DEFAULT_BROWSER_CONFIG.attachmentTimeoutMs,
83
86
  assistantRecheckDelayMs: config?.assistantRecheckDelayMs ?? DEFAULT_BROWSER_CONFIG.assistantRecheckDelayMs,
84
87
  assistantRecheckTimeoutMs: config?.assistantRecheckTimeoutMs ?? DEFAULT_BROWSER_CONFIG.assistantRecheckTimeoutMs,
85
88
  reuseChromeWaitMs: config?.reuseChromeWaitMs ?? DEFAULT_BROWSER_CONFIG.reuseChromeWaitMs,
@@ -110,6 +113,7 @@ export function resolveBrowserConfig(config) {
110
113
  thinkingTime: config?.thinkingTime,
111
114
  researchMode,
112
115
  archiveConversations,
116
+ resumeConversationUrl: config?.resumeConversationUrl ?? DEFAULT_BROWSER_CONFIG.resumeConversationUrl,
113
117
  manualLogin,
114
118
  manualLoginProfileDir: manualLogin ? resolvedProfileDir : null,
115
119
  manualLoginCookieSync: config?.manualLoginCookieSync ?? DEFAULT_BROWSER_CONFIG.manualLoginCookieSync,