@steipete/oracle 0.20.0 → 0.20.2

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 (32) hide show
  1. package/dist/bin/oracle-cli.js +66 -25
  2. package/dist/src/browser/actions/modelSelection.js +36 -4
  3. package/dist/src/browser/actions/thinkingTime.js +11 -5
  4. package/dist/src/browser/browserConnection.js +59 -0
  5. package/dist/src/browser/chatgptImages.js +1 -1
  6. package/dist/src/browser/chromeLifecycle.js +105 -36
  7. package/dist/src/browser/index.js +41 -8
  8. package/dist/src/browser/liveTabs.js +94 -29
  9. package/dist/src/browser/profileState.js +6 -1
  10. package/dist/src/browser/promptFingerprint.js +54 -0
  11. package/dist/src/browser/reattach.js +157 -108
  12. package/dist/src/browser/recoveryTarget.js +31 -6
  13. package/dist/src/browser/sessionRunner.js +30 -7
  14. package/dist/src/browser/targetClaim.js +2 -2
  15. package/dist/src/cli/browserDefaults.js +3 -0
  16. package/dist/src/cli/browserTabs.js +105 -20
  17. package/dist/src/cli/detach.js +10 -1
  18. package/dist/src/cli/detachedSession.js +36 -0
  19. package/dist/src/cli/errorUtils.js +9 -0
  20. package/dist/src/cli/sessionDisplay.js +11 -3
  21. package/dist/src/cli/sessionRunner.js +150 -37
  22. package/dist/src/mcp/tools/consult.js +1 -7
  23. package/dist/src/remote/client.js +71 -14
  24. package/dist/src/remote/health.js +1 -0
  25. package/dist/src/remote/server.js +52 -10
  26. package/dist/src/remote/types.js +13 -0
  27. package/dist/src/sessionManager.js +8 -1
  28. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  29. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  30. package/package.json +9 -9
  31. package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  32. package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
@@ -14,20 +14,21 @@ import { resolveBrowserConfig } from "../browser/config.js";
14
14
  import { RunSlots } from "./runSlots.js";
15
15
  export { RunSlots } from "./runSlots.js";
16
16
  import { loadUserConfig } from "../config.js";
17
- import { MAX_REMOTE_ARTIFACT_BYTES } from "./types.js";
17
+ import { MAX_REMOTE_ARTIFACT_BYTES, pickRemoteImageMetadata } from "./types.js";
18
18
  import { getCookies } from "@steipete/sweet-cookie";
19
19
  import { CHATGPT_URL } from "../browser/constants.js";
20
20
  import { getCliVersion } from "../version.js";
21
21
  import { getOracleHomeDir } from "../oracleHome.js";
22
22
  import { cleanupStaleProfileState, readDevToolsPort, verifyDevToolsReachable, writeChromePid, writeDevToolsActivePort, } from "../browser/profileState.js";
23
23
  import { normalizeChatgptUrl } from "../browser/utils.js";
24
- import { computeFileSha256, sanitizeArtifactFilename, sanitizeArtifactMimeType, validateArtifactFile, } from "../browser/artifacts.js";
24
+ import { computeFileSha256, resolveSessionArtifactsDir, sanitizeArtifactFilename, sanitizeArtifactMimeType, validateArtifactFile, } from "../browser/artifacts.js";
25
25
  const ARTIFACT_PROTOCOL_VERSION = 1;
26
26
  const REMOTE_ARTIFACT_TTL_MS = 30 * 60 * 1000;
27
27
  const ARTIFACT_CAPABILITIES = {
28
28
  runCancellation: true,
29
29
  deferredFallbackBundling: true,
30
30
  artifactTransfer: true,
31
+ generatedImages: true,
31
32
  artifactProtocolVersion: ARTIFACT_PROTOCOL_VERSION,
32
33
  maxArtifactBytes: MAX_REMOTE_ARTIFACT_BYTES,
33
34
  };
@@ -334,6 +335,9 @@ export async function createRemoteServer(options = {}, deps = {}) {
334
335
  ? payload.options.sessionId.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 32)
335
336
  : "remote";
336
337
  payload.options.sessionId = `${clientSession || "remote"}-${runId}`;
338
+ const hostImageOutputPath = payload.options.imageOutputRequested === true
339
+ ? path.join(resolveSessionArtifactsDir(payload.options.sessionId), "generated.png")
340
+ : undefined;
337
341
  if (browserTabCap !== undefined)
338
342
  payload.browserConfig.maxConcurrentTabs = browserTabCap;
339
343
  signal?.throwIfAborted();
@@ -362,6 +366,7 @@ export async function createRemoteServer(options = {}, deps = {}) {
362
366
  heartbeatIntervalMs: payload.options.heartbeatIntervalMs,
363
367
  verbose: payload.options.verbose,
364
368
  sessionId: payload.options.sessionId,
369
+ generateImagePath: hostImageOutputPath,
365
370
  followUpPrompts: payload.options.followUpPrompts,
366
371
  });
367
372
  signal?.throwIfAborted();
@@ -615,13 +620,14 @@ function remoteArtifactKey(runId, artifactId) {
615
620
  async function registerRemoteArtifacts(params) {
616
621
  pruneExpiredArtifacts(params.artifactRegistry);
617
622
  const seen = new Set();
618
- const fileArtifacts = [
623
+ const transferableArtifacts = [
619
624
  ...(params.result.savedFiles ?? []),
620
- ...(params.result.artifacts ?? []).filter((artifact) => artifact.kind === "file"),
625
+ ...(params.result.savedImages ?? []),
626
+ ...(params.result.artifacts ?? []).filter((artifact) => artifact.kind === "file" || artifact.kind === "image"),
621
627
  ];
622
628
  const descriptors = [];
623
629
  const warnings = [];
624
- for (const artifact of fileArtifacts) {
630
+ for (const artifact of transferableArtifacts) {
625
631
  if (!artifact?.path || seen.has(artifact.path)) {
626
632
  continue;
627
633
  }
@@ -630,7 +636,9 @@ async function registerRemoteArtifacts(params) {
630
636
  const filename = sanitizeArtifactFilename(path.basename(artifact.path), "artifact.bin");
631
637
  params.logger(`[serve] Skipping remote artifact descriptor: ${error instanceof Error ? error.message : String(error)}`);
632
638
  warnings.push({
633
- code: "remote-artifact-registration-failed",
639
+ code: artifact.kind === "image"
640
+ ? "remote-image-registration-failed"
641
+ : "remote-artifact-registration-failed",
634
642
  severity: "warning",
635
643
  message: `Oracle captured the browser text response, but the bridge host could not prepare ${filename} for transfer. ` +
636
644
  "Open the ChatGPT browser on the bridge host, download the ZIP/file shown in the current response, and copy it to a cloud-readable path.",
@@ -675,7 +683,12 @@ async function buildRemoteArtifactRegistration(runId, artifact) {
675
683
  descriptor: {
676
684
  artifactId: randomUUID(),
677
685
  runId,
678
- kind: "file",
686
+ kind: artifact.kind === "image" ? "image" : "file",
687
+ ...(artifact.kind === "image"
688
+ ? {
689
+ image: pickRemoteImageMetadata(artifact),
690
+ }
691
+ : {}),
679
692
  filename,
680
693
  mimeType,
681
694
  byteSize: fileStat.size,
@@ -740,6 +753,7 @@ const CLIENT_BROWSER_CONFIG_FIELDS = [
740
753
  "chatgptUrl",
741
754
  "url",
742
755
  "desiredModel",
756
+ "modelIsImplicitDefault",
743
757
  "modelStrategy",
744
758
  "thinkingTime",
745
759
  "researchMode",
@@ -805,10 +819,37 @@ async function stageRemoteAttachments(payload, directory, defaultPrefix) {
805
819
  }
806
820
  // Return conversation identity and observed selection; keep process/profile details on the host.
807
821
  function sanitizeResult(result, warnings = []) {
822
+ const hostArtifactPaths = [
823
+ ...(result.savedFiles ?? []),
824
+ ...(result.savedImages ?? []),
825
+ ...(result.artifacts ?? []),
826
+ ]
827
+ .map((artifact) => artifact.path)
828
+ .filter((artifactPath) => Boolean(artifactPath));
829
+ const savedImagePaths = [
830
+ ...(result.savedImages ?? []),
831
+ ...(result.artifacts ?? []).filter((artifact) => artifact.kind === "image"),
832
+ ].map((artifact) => artifact.path);
833
+ const imageCount = new Set(savedImagePaths).size;
834
+ const sanitizeAnswer = (value) => {
835
+ let sanitized = value;
836
+ // Local save notices describe the host filesystem, not the client's transferred files.
837
+ for (const imagePath of savedImagePaths) {
838
+ sanitized = sanitized
839
+ ?.split(` Saved to: ${imagePath}`)
840
+ .join("")
841
+ .split(` Saved ${imageCount} file(s) starting at: ${imagePath}`)
842
+ .join("");
843
+ }
844
+ for (const artifactPath of hostArtifactPaths) {
845
+ sanitized = sanitized?.split(artifactPath).join(path.basename(artifactPath));
846
+ }
847
+ return sanitized;
848
+ };
808
849
  return {
809
- answerText: result.answerText,
810
- answerMarkdown: result.answerMarkdown,
811
- answerHtml: result.answerHtml,
850
+ answerText: sanitizeAnswer(result.answerText) ?? "",
851
+ answerMarkdown: sanitizeAnswer(result.answerMarkdown) ?? "",
852
+ answerHtml: sanitizeAnswer(result.answerHtml),
812
853
  tookMs: result.tookMs,
813
854
  answerTokens: result.answerTokens,
814
855
  answerChars: result.answerChars,
@@ -819,6 +860,7 @@ function sanitizeResult(result, warnings = []) {
819
860
  tabUrl: result.tabUrl,
820
861
  conversationId: result.conversationId,
821
862
  promptSubmitted: result.promptSubmitted,
863
+ submittedPromptHash: result.submittedPromptHash,
822
864
  warnings: warnings.length > 0 ? warnings : undefined,
823
865
  chromePid: undefined,
824
866
  chromePort: undefined,
@@ -1 +1,14 @@
1
1
  export const MAX_REMOTE_ARTIFACT_BYTES = 512 * 1024 * 1024;
2
+ export function pickRemoteImageMetadata(image = {}) {
3
+ return {
4
+ ...(Number.isSafeInteger(image.width) && Number(image.width) > 0
5
+ ? { width: Number(image.width) }
6
+ : {}),
7
+ ...(Number.isSafeInteger(image.height) && Number(image.height) > 0
8
+ ? { height: Number(image.height) }
9
+ : {}),
10
+ ...(typeof image.fileId === "string" && /^file[-_][a-z0-9_-]{1,200}$/i.test(image.fileId)
11
+ ? { fileId: image.fileId }
12
+ : {}),
13
+ };
14
+ }
@@ -304,7 +304,14 @@ export async function initializeSession(options, cwd, notifications, baseSlugOve
304
304
  })),
305
305
  cwd,
306
306
  mode,
307
- browser: browserConfig ? { config: browserConfig } : undefined,
307
+ browser: mode === "browser"
308
+ ? {
309
+ ...(browserConfig ? { config: browserConfig } : {}),
310
+ runtime: { submittedPromptHash: null },
311
+ }
312
+ : browserConfig
313
+ ? { config: browserConfig }
314
+ : undefined,
308
315
  notifications,
309
316
  options: {
310
317
  prompt: options.prompt,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steipete/oracle",
3
- "version": "0.20.0",
3
+ "version": "0.20.2",
4
4
  "description": "CLI wrapper around OpenAI Responses API with GPT-5.6 Sol, GPT-5.6, GPT-5.5 Pro, GPT-5.5, GPT-5.4, GPT-5.2, GPT-5.1, and GPT-5.1 Codex high reasoning modes.",
5
5
  "keywords": [],
6
6
  "homepage": "https://askoracle.sh",
@@ -70,17 +70,17 @@
70
70
  "dotenv": "^17.4.2",
71
71
  "fast-glob": "^3.3.3",
72
72
  "gpt-tokenizer": "^4.0.0",
73
- "inquirer": "14.2.1",
73
+ "inquirer": "14.2.2",
74
74
  "json5": "^2.2.3",
75
75
  "kleur": "^4.1.5",
76
76
  "markdansi": "0.3.3",
77
- "openai": "^7.10.0",
77
+ "openai": "^7.13.0",
78
78
  "osc-progress": "^0.3.3",
79
79
  "qs": "^6.16.0",
80
80
  "shiki": "^4.4.3",
81
81
  "toasted-notifier": "^10.1.0",
82
- "tokentally": "^0.1.5",
83
- "zod": "^4.5.4"
82
+ "tokentally": "^0.1.6",
83
+ "zod": "^4.6.1"
84
84
  },
85
85
  "devDependencies": {
86
86
  "@anthropic-ai/tokenizer": "^0.0.4",
@@ -88,13 +88,13 @@
88
88
  "@modelcontextprotocol/sdk": "^1.30.0",
89
89
  "@types/chrome-remote-interface": "^0.34.0",
90
90
  "@types/inquirer": "^9.0.10",
91
- "@types/node": "^26.4.1",
91
+ "@types/node": "^26.5.1",
92
92
  "@vitest/coverage-v8": "5.0.0",
93
- "devtools-protocol": "0.0.1692173",
93
+ "devtools-protocol": "0.0.1694333",
94
94
  "es-toolkit": "^1.52.0",
95
95
  "esbuild": "^0.28.2",
96
- "oxfmt": "0.66.0",
97
- "oxlint": "^1.81.0",
96
+ "oxfmt": "0.67.0",
97
+ "oxlint": "^1.82.0",
98
98
  "puppeteer-core": "^25.10.0",
99
99
  "tsx": "^4.23.13",
100
100
  "typescript": "^7.0.2",