@steipete/oracle 0.9.0 → 0.10.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/LICENSE +1 -1
- package/README.md +61 -48
- package/dist/bin/oracle-cli.js +455 -402
- package/dist/bin/oracle-mcp.js +2 -2
- package/dist/bin/oracle.js +165 -279
- package/dist/scripts/agent-send.js +31 -31
- package/dist/scripts/check.js +6 -6
- package/dist/scripts/debug/extract-chatgpt-response.js +10 -10
- package/dist/scripts/docs-list.js +30 -30
- package/dist/scripts/git-policy.js +25 -23
- package/dist/scripts/run-cli.js +8 -8
- package/dist/scripts/runner.js +203 -195
- package/dist/scripts/test-browser.js +21 -18
- package/dist/scripts/test-remote-chrome.js +20 -20
- package/dist/src/bridge/connection.js +18 -18
- package/dist/src/bridge/userConfigFile.js +7 -7
- package/dist/src/browser/actions/assistantResponse.js +149 -101
- package/dist/src/browser/actions/attachmentDataTransfer.js +49 -47
- package/dist/src/browser/actions/attachments.js +246 -150
- package/dist/src/browser/actions/domEvents.js +2 -2
- package/dist/src/browser/actions/modelSelection.js +275 -117
- package/dist/src/browser/actions/navigation.js +161 -137
- package/dist/src/browser/actions/promptComposer.js +100 -64
- package/dist/src/browser/actions/remoteFileTransfer.js +10 -10
- package/dist/src/browser/actions/thinkingTime.js +207 -110
- package/dist/src/browser/chromeLifecycle.js +62 -60
- package/dist/src/browser/config.js +34 -15
- package/dist/src/browser/constants.js +17 -12
- package/dist/src/browser/cookies.js +19 -19
- package/dist/src/browser/detect.js +62 -62
- package/dist/src/browser/domDebug.js +1 -1
- package/dist/src/browser/index.js +390 -295
- package/dist/src/browser/modelStrategy.js +1 -1
- package/dist/src/browser/pageActions.js +5 -5
- package/dist/src/browser/policies.js +16 -13
- package/dist/src/browser/profileState.js +44 -39
- package/dist/src/browser/prompt.js +72 -42
- package/dist/src/browser/promptSummary.js +5 -5
- package/dist/src/browser/providerDomFlow.js +1 -1
- package/dist/src/browser/providers/chatgptDomProvider.js +9 -9
- package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +51 -42
- package/dist/src/browser/providers/index.js +2 -2
- package/dist/src/browser/reattach.js +67 -34
- package/dist/src/browser/reattachHelpers.js +31 -26
- package/dist/src/browser/sessionRunner.js +37 -25
- package/dist/src/browser/utils.js +9 -9
- package/dist/src/browserMode.js +1 -1
- package/dist/src/cli/bridge/claudeConfig.js +16 -16
- package/dist/src/cli/bridge/client.js +28 -20
- package/dist/src/cli/bridge/codexConfig.js +16 -16
- package/dist/src/cli/bridge/doctor.js +47 -39
- package/dist/src/cli/bridge/host.js +58 -56
- package/dist/src/cli/browserConfig.js +62 -48
- package/dist/src/cli/browserDefaults.js +27 -26
- package/dist/src/cli/bundleWarnings.js +1 -1
- package/dist/src/cli/clipboard.js +11 -2
- package/dist/src/cli/detach.js +2 -2
- package/dist/src/cli/dryRun.js +29 -25
- package/dist/src/cli/duplicatePromptGuard.js +3 -3
- package/dist/src/cli/engine.js +9 -9
- package/dist/src/cli/errorUtils.js +1 -1
- package/dist/src/cli/fileSize.js +3 -3
- package/dist/src/cli/format.js +2 -2
- package/dist/src/cli/help.js +28 -28
- package/dist/src/cli/hiddenAliases.js +3 -3
- package/dist/src/cli/markdownBundle.js +7 -7
- package/dist/src/cli/markdownRenderer.js +15 -15
- package/dist/src/cli/notifier.js +77 -67
- package/dist/src/cli/options.js +127 -106
- package/dist/src/cli/oscUtils.js +1 -1
- package/dist/src/cli/promptRequirement.js +2 -2
- package/dist/src/cli/renderOutput.js +1 -1
- package/dist/src/cli/rootAlias.js +1 -1
- package/dist/src/cli/runOptions.js +32 -28
- package/dist/src/cli/sessionCommand.js +31 -21
- package/dist/src/cli/sessionDisplay.js +95 -81
- package/dist/src/cli/sessionLineage.js +6 -2
- package/dist/src/cli/sessionRunner.js +103 -93
- package/dist/src/cli/sessionTable.js +26 -23
- package/dist/src/cli/stdin.js +22 -0
- package/dist/src/cli/tagline.js +121 -124
- package/dist/src/cli/tui/index.js +139 -128
- package/dist/src/cli/writeOutputPath.js +5 -5
- package/dist/src/config.js +7 -7
- package/dist/src/gemini-web/browserSessionManager.js +19 -15
- package/dist/src/gemini-web/client.js +76 -70
- package/dist/src/gemini-web/executionMode.js +6 -8
- package/dist/src/gemini-web/executor.js +98 -93
- package/dist/src/gemini-web/index.js +1 -1
- package/dist/src/mcp/server.js +16 -12
- package/dist/src/mcp/tools/consult.js +51 -47
- package/dist/src/mcp/tools/sessionResources.js +12 -12
- package/dist/src/mcp/tools/sessions.js +26 -17
- package/dist/src/mcp/types.js +5 -5
- package/dist/src/mcp/utils.js +15 -7
- package/dist/src/oracle/background.js +15 -15
- package/dist/src/oracle/claude.js +53 -25
- package/dist/src/oracle/client.js +50 -41
- package/dist/src/oracle/config.js +96 -66
- package/dist/src/oracle/errors.js +38 -38
- package/dist/src/oracle/files.js +55 -46
- package/dist/src/oracle/finishLine.js +10 -8
- package/dist/src/oracle/format.js +3 -3
- package/dist/src/oracle/gemini.js +37 -33
- package/dist/src/oracle/logging.js +7 -7
- package/dist/src/oracle/markdown.js +28 -28
- package/dist/src/oracle/modelResolver.js +16 -16
- package/dist/src/oracle/multiModelRunner.js +12 -12
- package/dist/src/oracle/oscProgress.js +8 -8
- package/dist/src/oracle/promptAssembly.js +6 -3
- package/dist/src/oracle/request.js +16 -13
- package/dist/src/oracle/run.js +156 -134
- package/dist/src/oracle/runUtils.js +8 -5
- package/dist/src/oracle/tokenEstimate.js +6 -6
- package/dist/src/oracle/tokenStats.js +5 -5
- package/dist/src/oracle/tokenStringifier.js +5 -5
- package/dist/src/oracle.js +12 -12
- package/dist/src/oracleHome.js +3 -3
- package/dist/src/remote/client.js +25 -25
- package/dist/src/remote/health.js +20 -20
- package/dist/src/remote/remoteServiceConfig.js +9 -9
- package/dist/src/remote/server.js +129 -118
- package/dist/src/sessionManager.js +77 -75
- package/dist/src/sessionStore.js +3 -3
- package/dist/src/version.js +10 -10
- package/dist/vendor/oracle-notifier/README.md +2 -0
- package/package.json +66 -62
- package/vendor/oracle-notifier/README.md +2 -0
- package/dist/markdansi/types/index.js +0 -4
- package/dist/oracle/bin/oracle-cli.js +0 -472
- package/dist/oracle/src/browser/actions/assistantResponse.js +0 -471
- package/dist/oracle/src/browser/actions/attachments.js +0 -82
- package/dist/oracle/src/browser/actions/modelSelection.js +0 -190
- package/dist/oracle/src/browser/actions/navigation.js +0 -75
- package/dist/oracle/src/browser/actions/promptComposer.js +0 -167
- package/dist/oracle/src/browser/chromeLifecycle.js +0 -104
- package/dist/oracle/src/browser/config.js +0 -33
- package/dist/oracle/src/browser/constants.js +0 -40
- package/dist/oracle/src/browser/cookies.js +0 -210
- package/dist/oracle/src/browser/domDebug.js +0 -36
- package/dist/oracle/src/browser/index.js +0 -331
- package/dist/oracle/src/browser/pageActions.js +0 -5
- package/dist/oracle/src/browser/prompt.js +0 -88
- package/dist/oracle/src/browser/promptSummary.js +0 -20
- package/dist/oracle/src/browser/sessionRunner.js +0 -80
- package/dist/oracle/src/browser/types.js +0 -1
- package/dist/oracle/src/browser/utils.js +0 -62
- package/dist/oracle/src/browserMode.js +0 -1
- package/dist/oracle/src/cli/browserConfig.js +0 -44
- package/dist/oracle/src/cli/dryRun.js +0 -59
- package/dist/oracle/src/cli/engine.js +0 -17
- package/dist/oracle/src/cli/errorUtils.js +0 -9
- package/dist/oracle/src/cli/help.js +0 -70
- package/dist/oracle/src/cli/markdownRenderer.js +0 -15
- package/dist/oracle/src/cli/options.js +0 -103
- package/dist/oracle/src/cli/promptRequirement.js +0 -14
- package/dist/oracle/src/cli/rootAlias.js +0 -30
- package/dist/oracle/src/cli/sessionCommand.js +0 -77
- package/dist/oracle/src/cli/sessionDisplay.js +0 -270
- package/dist/oracle/src/cli/sessionRunner.js +0 -94
- package/dist/oracle/src/heartbeat.js +0 -43
- package/dist/oracle/src/oracle/client.js +0 -48
- package/dist/oracle/src/oracle/config.js +0 -29
- package/dist/oracle/src/oracle/errors.js +0 -101
- package/dist/oracle/src/oracle/files.js +0 -220
- package/dist/oracle/src/oracle/format.js +0 -33
- package/dist/oracle/src/oracle/fsAdapter.js +0 -7
- package/dist/oracle/src/oracle/oscProgress.js +0 -60
- package/dist/oracle/src/oracle/request.js +0 -48
- package/dist/oracle/src/oracle/run.js +0 -444
- package/dist/oracle/src/oracle/tokenStats.js +0 -39
- package/dist/oracle/src/oracle/types.js +0 -1
- package/dist/oracle/src/oracle.js +0 -9
- package/dist/oracle/src/sessionManager.js +0 -205
- package/dist/oracle/src/version.js +0 -39
- package/dist/scripts/chrome/browser-tools.js +0 -295
- package/dist/src/browser/profileSync.js +0 -141
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import { rm } from
|
|
2
|
-
import { readFileSync } from
|
|
3
|
-
import os from
|
|
4
|
-
import net from
|
|
5
|
-
import { execFile } from
|
|
6
|
-
import { promisify } from
|
|
7
|
-
import CDP from
|
|
8
|
-
import { launch, Launcher } from
|
|
9
|
-
import { cleanupStaleProfileState } from
|
|
10
|
-
import { delay } from
|
|
1
|
+
import { rm } from "node:fs/promises";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import net from "node:net";
|
|
5
|
+
import { execFile } from "node:child_process";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
import CDP from "chrome-remote-interface";
|
|
8
|
+
import { launch, Launcher } from "chrome-launcher";
|
|
9
|
+
import { cleanupStaleProfileState } from "./profileState.js";
|
|
10
|
+
import { delay } from "./utils.js";
|
|
11
11
|
const execFileAsync = promisify(execFile);
|
|
12
12
|
export async function launchChrome(config, userDataDir, logger) {
|
|
13
13
|
const connectHost = resolveRemoteDebugHost();
|
|
14
|
-
const debugBindAddress = connectHost && connectHost !==
|
|
14
|
+
const debugBindAddress = connectHost && connectHost !== "127.0.0.1" ? "0.0.0.0" : connectHost;
|
|
15
15
|
const debugPort = config.debugPort ?? parseDebugPortEnv();
|
|
16
16
|
const chromeFlags = buildChromeFlags(config.headless ?? false, debugBindAddress);
|
|
17
|
-
const usePatchedLauncher = Boolean(connectHost && connectHost !==
|
|
17
|
+
const usePatchedLauncher = Boolean(connectHost && connectHost !== "127.0.0.1");
|
|
18
18
|
const launcher = usePatchedLauncher
|
|
19
19
|
? await launchWithCustomHost({
|
|
20
20
|
chromeFlags,
|
|
21
21
|
chromePath: config.chromePath ?? undefined,
|
|
22
22
|
userDataDir,
|
|
23
|
-
host: connectHost ??
|
|
23
|
+
host: connectHost ?? "127.0.0.1",
|
|
24
24
|
requestedPort: debugPort ?? undefined,
|
|
25
25
|
})
|
|
26
26
|
: await launch({
|
|
@@ -30,13 +30,13 @@ export async function launchChrome(config, userDataDir, logger) {
|
|
|
30
30
|
handleSIGINT: false,
|
|
31
31
|
port: debugPort ?? undefined,
|
|
32
32
|
});
|
|
33
|
-
const pidLabel = typeof launcher.pid ===
|
|
34
|
-
const hostLabel = connectHost ? ` on ${connectHost}` :
|
|
33
|
+
const pidLabel = typeof launcher.pid === "number" ? ` (pid ${launcher.pid})` : "";
|
|
34
|
+
const hostLabel = connectHost ? ` on ${connectHost}` : "";
|
|
35
35
|
logger(`Launched Chrome${pidLabel} on port ${launcher.port}${hostLabel}`);
|
|
36
|
-
return Object.assign(launcher, { host: connectHost ??
|
|
36
|
+
return Object.assign(launcher, { host: connectHost ?? "127.0.0.1" });
|
|
37
37
|
}
|
|
38
38
|
export function registerTerminationHooks(chrome, userDataDir, keepBrowser, logger, opts) {
|
|
39
|
-
const signals = [
|
|
39
|
+
const signals = ["SIGINT", "SIGTERM", "SIGQUIT"];
|
|
40
40
|
let handling;
|
|
41
41
|
const handleSignal = (signal) => {
|
|
42
42
|
if (handling) {
|
|
@@ -46,7 +46,7 @@ export function registerTerminationHooks(chrome, userDataDir, keepBrowser, logge
|
|
|
46
46
|
const inFlight = opts?.isInFlight?.() ?? false;
|
|
47
47
|
const leaveRunning = keepBrowser || inFlight;
|
|
48
48
|
if (leaveRunning) {
|
|
49
|
-
logger(`Received ${signal}; leaving Chrome running${inFlight ?
|
|
49
|
+
logger(`Received ${signal}; leaving Chrome running${inFlight ? " (assistant response pending)" : ""}`);
|
|
50
50
|
}
|
|
51
51
|
else {
|
|
52
52
|
logger(`Received ${signal}; terminating Chrome process`);
|
|
@@ -69,18 +69,18 @@ export function registerTerminationHooks(chrome, userDataDir, keepBrowser, logge
|
|
|
69
69
|
if (opts?.preserveUserDataDir) {
|
|
70
70
|
// Preserve the profile directory (manual login), but clear reattach hints so we don't
|
|
71
71
|
// try to reuse a dead DevTools port on the next run.
|
|
72
|
-
await cleanupStaleProfileState(userDataDir, logger, { lockRemovalMode:
|
|
72
|
+
await cleanupStaleProfileState(userDataDir, logger, { lockRemovalMode: "never" }).catch(() => undefined);
|
|
73
73
|
}
|
|
74
74
|
else {
|
|
75
75
|
await rm(userDataDir, { recursive: true, force: true }).catch(() => undefined);
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
})().finally(() => {
|
|
79
|
-
const exitCode = signal ===
|
|
79
|
+
const exitCode = signal === "SIGINT" ? 130 : 1;
|
|
80
80
|
// Vitest treats any `process.exit()` call as an unhandled failure, even if mocked.
|
|
81
81
|
// Keep production behavior (hard-exit on signals) while letting tests observe state changes.
|
|
82
82
|
process.exitCode = exitCode;
|
|
83
|
-
const isTestRun = process.env.VITEST ===
|
|
83
|
+
const isTestRun = process.env.VITEST === "1" || process.env.NODE_ENV === "test";
|
|
84
84
|
if (!isTestRun) {
|
|
85
85
|
process.exit(exitCode);
|
|
86
86
|
}
|
|
@@ -96,12 +96,12 @@ export function registerTerminationHooks(chrome, userDataDir, keepBrowser, logge
|
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
98
|
export async function hideChromeWindow(chrome, logger) {
|
|
99
|
-
if (process.platform !==
|
|
100
|
-
logger(
|
|
99
|
+
if (process.platform !== "darwin") {
|
|
100
|
+
logger("Window hiding is only supported on macOS");
|
|
101
101
|
return;
|
|
102
102
|
}
|
|
103
103
|
if (!chrome.pid) {
|
|
104
|
-
logger(
|
|
104
|
+
logger("Unable to hide window: missing Chrome PID");
|
|
105
105
|
return;
|
|
106
106
|
}
|
|
107
107
|
const script = `tell application "System Events"
|
|
@@ -110,8 +110,8 @@ export async function hideChromeWindow(chrome, logger) {
|
|
|
110
110
|
end try
|
|
111
111
|
end tell`;
|
|
112
112
|
try {
|
|
113
|
-
await execFileAsync(
|
|
114
|
-
logger(
|
|
113
|
+
await execFileAsync("osascript", ["-e", script]);
|
|
114
|
+
logger("Chrome window hidden (Cmd-H)");
|
|
115
115
|
}
|
|
116
116
|
catch (error) {
|
|
117
117
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -120,7 +120,7 @@ export async function hideChromeWindow(chrome, logger) {
|
|
|
120
120
|
}
|
|
121
121
|
export async function connectToChrome(port, logger, host) {
|
|
122
122
|
const client = await CDP({ port, host });
|
|
123
|
-
logger(
|
|
123
|
+
logger("Connected to Chrome DevTools protocol");
|
|
124
124
|
return client;
|
|
125
125
|
}
|
|
126
126
|
export async function connectToRemoteChrome(host, port, logger, targetUrl) {
|
|
@@ -183,12 +183,14 @@ async function connectToNewTarget(host, port, url, logger, messages) {
|
|
|
183
183
|
return null;
|
|
184
184
|
}
|
|
185
185
|
export async function connectWithNewTab(port, logger, initialUrl, host, options) {
|
|
186
|
-
const effectiveHost = host ??
|
|
187
|
-
const url = initialUrl ??
|
|
186
|
+
const effectiveHost = host ?? "127.0.0.1";
|
|
187
|
+
const url = initialUrl ?? "about:blank";
|
|
188
188
|
const fallbackToDefault = options?.fallbackToDefault ?? true;
|
|
189
189
|
const retries = Math.max(0, options?.retries ?? 0);
|
|
190
190
|
const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 250);
|
|
191
|
-
const fallbackLabel = fallbackToDefault
|
|
191
|
+
const fallbackLabel = fallbackToDefault
|
|
192
|
+
? "falling back to default target."
|
|
193
|
+
: "strict mode: not falling back.";
|
|
192
194
|
let attempt = 0;
|
|
193
195
|
while (attempt <= retries) {
|
|
194
196
|
const targetConnection = await connectToNewTarget(effectiveHost, port, url, logger, {
|
|
@@ -207,13 +209,13 @@ export async function connectWithNewTab(port, logger, initialUrl, host, options)
|
|
|
207
209
|
await delay(retryDelayMs * attempt);
|
|
208
210
|
}
|
|
209
211
|
if (!fallbackToDefault) {
|
|
210
|
-
throw new Error(
|
|
212
|
+
throw new Error("Failed to open isolated browser tab; refusing to attach to default target.");
|
|
211
213
|
}
|
|
212
214
|
const client = await connectToChrome(port, logger, effectiveHost);
|
|
213
215
|
return { client };
|
|
214
216
|
}
|
|
215
217
|
export async function closeTab(port, targetId, logger, host) {
|
|
216
|
-
const effectiveHost = host ??
|
|
218
|
+
const effectiveHost = host ?? "127.0.0.1";
|
|
217
219
|
try {
|
|
218
220
|
await CDP.Close({ host: effectiveHost, port, id: targetId });
|
|
219
221
|
logger(`Closed isolated browser tab (target=${targetId})`);
|
|
@@ -225,33 +227,33 @@ export async function closeTab(port, targetId, logger, host) {
|
|
|
225
227
|
}
|
|
226
228
|
function buildChromeFlags(headless, debugBindAddress) {
|
|
227
229
|
const flags = [
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
230
|
+
"--disable-background-networking",
|
|
231
|
+
"--disable-background-timer-throttling",
|
|
232
|
+
"--disable-breakpad",
|
|
233
|
+
"--disable-client-side-phishing-detection",
|
|
234
|
+
"--disable-default-apps",
|
|
235
|
+
"--disable-hang-monitor",
|
|
236
|
+
"--disable-popup-blocking",
|
|
237
|
+
"--disable-prompt-on-repost",
|
|
238
|
+
"--disable-sync",
|
|
239
|
+
"--disable-translate",
|
|
240
|
+
"--metrics-recording-only",
|
|
241
|
+
"--no-first-run",
|
|
242
|
+
"--safebrowsing-disable-auto-update",
|
|
243
|
+
"--disable-features=TranslateUI,AutomationControlled",
|
|
244
|
+
"--mute-audio",
|
|
245
|
+
"--window-size=1280,720",
|
|
246
|
+
"--lang=en-US",
|
|
247
|
+
"--accept-lang=en-US,en",
|
|
246
248
|
];
|
|
247
|
-
if (process.platform !==
|
|
248
|
-
flags.push(
|
|
249
|
+
if (process.platform !== "win32" && !isWsl()) {
|
|
250
|
+
flags.push("--password-store=basic", "--use-mock-keychain");
|
|
249
251
|
}
|
|
250
252
|
if (debugBindAddress) {
|
|
251
253
|
flags.push(`--remote-debugging-address=${debugBindAddress}`);
|
|
252
254
|
}
|
|
253
255
|
if (headless) {
|
|
254
|
-
flags.push(
|
|
256
|
+
flags.push("--headless=new");
|
|
255
257
|
}
|
|
256
258
|
return flags;
|
|
257
259
|
}
|
|
@@ -274,8 +276,8 @@ function resolveRemoteDebugHost() {
|
|
|
274
276
|
return null;
|
|
275
277
|
}
|
|
276
278
|
try {
|
|
277
|
-
const resolv = readFileSync(
|
|
278
|
-
for (const line of resolv.split(
|
|
279
|
+
const resolv = readFileSync("/etc/resolv.conf", "utf8");
|
|
280
|
+
for (const line of resolv.split("\n")) {
|
|
279
281
|
const match = line.match(/^nameserver\s+([0-9.]+)/);
|
|
280
282
|
if (match?.[1]) {
|
|
281
283
|
return match[1];
|
|
@@ -288,14 +290,14 @@ function resolveRemoteDebugHost() {
|
|
|
288
290
|
return null;
|
|
289
291
|
}
|
|
290
292
|
function isWsl() {
|
|
291
|
-
if (process.platform !==
|
|
293
|
+
if (process.platform !== "linux") {
|
|
292
294
|
return false;
|
|
293
295
|
}
|
|
294
296
|
if (process.env.WSL_DISTRO_NAME) {
|
|
295
297
|
return true;
|
|
296
298
|
}
|
|
297
299
|
const release = os.release();
|
|
298
|
-
return release.toLowerCase().includes(
|
|
300
|
+
return release.toLowerCase().includes("microsoft");
|
|
299
301
|
}
|
|
300
302
|
async function launchWithCustomHost({ chromeFlags, chromePath, userDataDir, host, requestedPort, }) {
|
|
301
303
|
const launcher = new Launcher({
|
|
@@ -310,7 +312,7 @@ async function launchWithCustomHost({ chromeFlags, chromePath, userDataDir, host
|
|
|
310
312
|
patched.isDebuggerReady = function patchedIsDebuggerReady() {
|
|
311
313
|
const debugPort = this.port ?? 0;
|
|
312
314
|
if (!debugPort) {
|
|
313
|
-
return Promise.reject(new Error(
|
|
315
|
+
return Promise.reject(new Error("Missing Chrome debug port"));
|
|
314
316
|
}
|
|
315
317
|
return new Promise((resolve, reject) => {
|
|
316
318
|
const client = net.createConnection({ port: debugPort, host });
|
|
@@ -320,11 +322,11 @@ async function launchWithCustomHost({ chromeFlags, chromePath, userDataDir, host
|
|
|
320
322
|
client.destroy();
|
|
321
323
|
client.unref();
|
|
322
324
|
};
|
|
323
|
-
client.once(
|
|
325
|
+
client.once("error", (err) => {
|
|
324
326
|
cleanup();
|
|
325
327
|
reject(err);
|
|
326
328
|
});
|
|
327
|
-
client.once(
|
|
329
|
+
client.once("connect", () => {
|
|
328
330
|
cleanup();
|
|
329
331
|
resolve();
|
|
330
332
|
});
|
|
@@ -1,8 +1,19 @@
|
|
|
1
|
-
import { CHATGPT_URL, DEFAULT_MODEL_STRATEGY, DEFAULT_MODEL_TARGET } from
|
|
2
|
-
import { normalizeBrowserModelStrategy } from
|
|
3
|
-
import { isTemporaryChatUrl, normalizeChatgptUrl } from
|
|
4
|
-
import os from
|
|
5
|
-
import path from
|
|
1
|
+
import { CHATGPT_URL, DEFAULT_MODEL_STRATEGY, DEFAULT_MODEL_TARGET } from "./constants.js";
|
|
2
|
+
import { normalizeBrowserModelStrategy } from "./modelStrategy.js";
|
|
3
|
+
import { isTemporaryChatUrl, normalizeChatgptUrl } from "./utils.js";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
export const DEFAULT_CHATGPT_COOKIE_NAMES = [
|
|
7
|
+
"__Secure-next-auth.session-token",
|
|
8
|
+
"__Secure-next-auth.session-token.0",
|
|
9
|
+
"__Secure-next-auth.session-token.1",
|
|
10
|
+
"_account",
|
|
11
|
+
"cf_clearance",
|
|
12
|
+
"__cf_bm",
|
|
13
|
+
"_cfuvid",
|
|
14
|
+
"CF_Authorization",
|
|
15
|
+
"__cflb",
|
|
16
|
+
];
|
|
6
17
|
export const DEFAULT_BROWSER_CONFIG = {
|
|
7
18
|
chromeProfile: null,
|
|
8
19
|
chromePath: null,
|
|
@@ -20,7 +31,7 @@ export const DEFAULT_BROWSER_CONFIG = {
|
|
|
20
31
|
autoReattachIntervalMs: 0,
|
|
21
32
|
autoReattachTimeoutMs: 120_000,
|
|
22
33
|
cookieSync: true,
|
|
23
|
-
cookieNames:
|
|
34
|
+
cookieNames: DEFAULT_CHATGPT_COOKIE_NAMES,
|
|
24
35
|
cookieSyncWaitMs: 0,
|
|
25
36
|
inlineCookies: null,
|
|
26
37
|
inlineCookiesSource: null,
|
|
@@ -38,27 +49,27 @@ export const DEFAULT_BROWSER_CONFIG = {
|
|
|
38
49
|
};
|
|
39
50
|
export function resolveBrowserConfig(config) {
|
|
40
51
|
const debugPortEnv = parseDebugPort(process.env.ORACLE_BROWSER_PORT ?? process.env.ORACLE_BROWSER_DEBUG_PORT);
|
|
41
|
-
const envAllowCookieErrors = (process.env.ORACLE_BROWSER_ALLOW_COOKIE_ERRORS ??
|
|
42
|
-
(process.env.ORACLE_BROWSER_ALLOW_COOKIE_ERRORS ??
|
|
52
|
+
const envAllowCookieErrors = (process.env.ORACLE_BROWSER_ALLOW_COOKIE_ERRORS ?? "").trim().toLowerCase() === "true" ||
|
|
53
|
+
(process.env.ORACLE_BROWSER_ALLOW_COOKIE_ERRORS ?? "").trim() === "1";
|
|
43
54
|
const rawUrl = config?.chatgptUrl ?? config?.url ?? DEFAULT_BROWSER_CONFIG.url;
|
|
44
55
|
const normalizedUrl = normalizeChatgptUrl(rawUrl ?? DEFAULT_BROWSER_CONFIG.url, DEFAULT_BROWSER_CONFIG.url);
|
|
45
56
|
const desiredModel = config?.desiredModel ?? DEFAULT_BROWSER_CONFIG.desiredModel ?? DEFAULT_MODEL_TARGET;
|
|
46
57
|
const modelStrategy = normalizeBrowserModelStrategy(config?.modelStrategy) ??
|
|
47
58
|
DEFAULT_BROWSER_CONFIG.modelStrategy ??
|
|
48
59
|
DEFAULT_MODEL_STRATEGY;
|
|
49
|
-
if (modelStrategy ===
|
|
50
|
-
|
|
60
|
+
if (modelStrategy === "select" &&
|
|
61
|
+
isTemporaryChatUrl(normalizedUrl) &&
|
|
62
|
+
/\bpro\b/i.test(desiredModel)) {
|
|
63
|
+
throw new Error("Temporary Chat mode does not expose Pro models in the ChatGPT model picker. " +
|
|
51
64
|
'Remove "temporary-chat=true" from your browser URL, or use a non-Pro model label (e.g. "GPT-5.2").');
|
|
52
65
|
}
|
|
53
|
-
const isWindows = process.platform ===
|
|
66
|
+
const isWindows = process.platform === "win32";
|
|
54
67
|
const manualLogin = config?.manualLogin ?? (isWindows ? true : DEFAULT_BROWSER_CONFIG.manualLogin);
|
|
55
68
|
const cookieSyncDefault = isWindows ? false : DEFAULT_BROWSER_CONFIG.cookieSync;
|
|
56
|
-
const resolvedProfileDir = config?.manualLoginProfileDir
|
|
57
|
-
process.env.ORACLE_BROWSER_PROFILE_DIR ??
|
|
58
|
-
path.join(os.homedir(), '.oracle', 'browser-profile');
|
|
69
|
+
const resolvedProfileDir = resolveManualLoginProfileDir(config?.manualLoginProfileDir, process.env.ORACLE_BROWSER_PROFILE_DIR);
|
|
59
70
|
return {
|
|
60
71
|
...DEFAULT_BROWSER_CONFIG,
|
|
61
|
-
...
|
|
72
|
+
...config,
|
|
62
73
|
url: normalizedUrl,
|
|
63
74
|
chatgptUrl: normalizedUrl,
|
|
64
75
|
timeoutMs: config?.timeoutMs ?? DEFAULT_BROWSER_CONFIG.timeoutMs,
|
|
@@ -101,3 +112,11 @@ function parseDebugPort(raw) {
|
|
|
101
112
|
}
|
|
102
113
|
return value;
|
|
103
114
|
}
|
|
115
|
+
function resolveManualLoginProfileDir(...candidates) {
|
|
116
|
+
for (const candidate of candidates) {
|
|
117
|
+
const profileDir = candidate?.trim();
|
|
118
|
+
if (profileDir)
|
|
119
|
+
return profileDir;
|
|
120
|
+
}
|
|
121
|
+
return path.join(os.homedir(), ".oracle", "browser-profile");
|
|
122
|
+
}
|
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
export const CHATGPT_URL =
|
|
2
|
-
export const DEFAULT_MODEL_TARGET =
|
|
3
|
-
export const DEFAULT_MODEL_STRATEGY =
|
|
4
|
-
export const COOKIE_URLS = [
|
|
1
|
+
export const CHATGPT_URL = "https://chatgpt.com/";
|
|
2
|
+
export const DEFAULT_MODEL_TARGET = "GPT-5.5 Pro";
|
|
3
|
+
export const DEFAULT_MODEL_STRATEGY = "select";
|
|
4
|
+
export const COOKIE_URLS = [
|
|
5
|
+
"https://chatgpt.com",
|
|
6
|
+
"https://chat.openai.com",
|
|
7
|
+
"https://atlas.openai.com",
|
|
8
|
+
];
|
|
5
9
|
export const INPUT_SELECTORS = [
|
|
6
10
|
'textarea[data-id="prompt-textarea"]',
|
|
7
11
|
'textarea[placeholder*="Send a message"]',
|
|
8
12
|
'textarea[aria-label="Message ChatGPT"]',
|
|
9
|
-
|
|
13
|
+
"textarea:not([disabled])",
|
|
10
14
|
'textarea[name="prompt-textarea"]',
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
"#prompt-textarea",
|
|
16
|
+
".ProseMirror",
|
|
13
17
|
'[contenteditable="true"][data-virtualkeyboard="true"]',
|
|
14
18
|
];
|
|
15
19
|
export const ANSWER_SELECTORS = [
|
|
@@ -24,12 +28,12 @@ export const ANSWER_SELECTORS = [
|
|
|
24
28
|
'[data-turn="assistant"]',
|
|
25
29
|
];
|
|
26
30
|
export const CONVERSATION_TURN_SELECTOR = 'article[data-testid^="conversation-turn"], div[data-testid^="conversation-turn"], section[data-testid^="conversation-turn"], ' +
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
"article[data-message-author-role], div[data-message-author-role], section[data-message-author-role], " +
|
|
32
|
+
"article[data-turn], div[data-turn], section[data-turn]";
|
|
29
33
|
export const ASSISTANT_ROLE_SELECTOR = '[data-message-author-role="assistant"], [data-turn="assistant"]';
|
|
30
34
|
export const CLOUDFLARE_SCRIPT_SELECTOR = 'script[src*="/challenge-platform/"]';
|
|
31
|
-
export const CLOUDFLARE_TITLE =
|
|
32
|
-
export const PROMPT_PRIMARY_SELECTOR =
|
|
35
|
+
export const CLOUDFLARE_TITLE = "just a moment";
|
|
36
|
+
export const PROMPT_PRIMARY_SELECTOR = "#prompt-textarea";
|
|
33
37
|
export const PROMPT_FALLBACK_SELECTOR = 'textarea[name="prompt-textarea"]';
|
|
34
38
|
export const FILE_INPUT_SELECTORS = [
|
|
35
39
|
'form input[type="file"]:not([accept])',
|
|
@@ -65,7 +69,8 @@ export const SEND_BUTTON_SELECTORS = [
|
|
|
65
69
|
'button[aria-label*="Send"]',
|
|
66
70
|
];
|
|
67
71
|
export const SEND_BUTTON_SELECTOR = SEND_BUTTON_SELECTORS[0];
|
|
68
|
-
export const MODEL_BUTTON_SELECTOR = '[data-testid="model-switcher-dropdown-button"]';
|
|
72
|
+
export const MODEL_BUTTON_SELECTOR = '[data-testid="model-switcher-dropdown-button"], button.__composer-pill[aria-haspopup="menu"]';
|
|
73
|
+
export const COMPOSER_MODEL_SIGNAL_SELECTOR = '[data-testid="composer-footer-actions"]';
|
|
69
74
|
export const COPY_BUTTON_SELECTOR = 'button[data-testid="copy-turn-action-button"]';
|
|
70
75
|
// Action buttons that only appear once a turn has finished rendering.
|
|
71
76
|
export const FINISHED_ACTIONS_SELECTOR = 'button[data-testid="copy-turn-action-button"], button[data-testid="good-response-turn-action-button"], button[data-testid="bad-response-turn-action-button"], button[aria-label="Share"]';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { COOKIE_URLS } from
|
|
2
|
-
import { delay } from
|
|
3
|
-
import { getCookies } from
|
|
1
|
+
import { COOKIE_URLS } from "./constants.js";
|
|
2
|
+
import { delay } from "./utils.js";
|
|
3
|
+
import { getCookies } from "@steipete/sweet-cookie";
|
|
4
4
|
export class ChromeCookieSyncError extends Error {
|
|
5
5
|
}
|
|
6
6
|
export async function syncCookies(Network, url, profile, logger, options = {}) {
|
|
@@ -55,7 +55,7 @@ async function readChromeCookiesWithWait(url, profile, filterNames, cookiePath,
|
|
|
55
55
|
return cookies;
|
|
56
56
|
}
|
|
57
57
|
const waitLabel = waitMs >= 1000 ? `${Math.round(waitMs / 1000)}s` : `${waitMs}ms`;
|
|
58
|
-
const message = firstError instanceof Error ? firstError.message : String(firstError ??
|
|
58
|
+
const message = firstError instanceof Error ? firstError.message : String(firstError ?? "");
|
|
59
59
|
if (firstError) {
|
|
60
60
|
logger(`[cookies] Cookie read failed (${message}); waiting ${waitLabel} then retrying once.`);
|
|
61
61
|
}
|
|
@@ -68,27 +68,27 @@ async function readChromeCookiesWithWait(url, profile, filterNames, cookiePath,
|
|
|
68
68
|
async function readChromeCookies(url, profile, filterNames, cookiePath) {
|
|
69
69
|
const origins = Array.from(new Set([stripQuery(url), ...COOKIE_URLS]));
|
|
70
70
|
const chromeProfile = cookiePath ?? profile ?? undefined;
|
|
71
|
-
const timeoutMs = readDuration(
|
|
71
|
+
const timeoutMs = readDuration("ORACLE_COOKIE_LOAD_TIMEOUT_MS", 5_000);
|
|
72
72
|
// Learned: read from multiple origins to capture auth cookies that land on chat.openai.com + atlas.
|
|
73
73
|
const { cookies, warnings } = await getCookies({
|
|
74
74
|
url,
|
|
75
75
|
origins,
|
|
76
76
|
names: filterNames?.length ? filterNames : undefined,
|
|
77
|
-
browsers: [
|
|
78
|
-
mode:
|
|
77
|
+
browsers: ["chrome"],
|
|
78
|
+
mode: "merge",
|
|
79
79
|
chromeProfile,
|
|
80
80
|
timeoutMs,
|
|
81
81
|
});
|
|
82
|
-
if (process.env.ORACLE_DEBUG_COOKIES ===
|
|
82
|
+
if (process.env.ORACLE_DEBUG_COOKIES === "1" && warnings.length) {
|
|
83
83
|
// eslint-disable-next-line no-console
|
|
84
|
-
console.log(`[cookies] sweet-cookie warnings:\n- ${warnings.join(
|
|
84
|
+
console.log(`[cookies] sweet-cookie warnings:\n- ${warnings.join("\n- ")}`);
|
|
85
85
|
}
|
|
86
86
|
const merged = new Map();
|
|
87
87
|
for (const cookie of cookies) {
|
|
88
88
|
const normalized = toCdpCookie(cookie);
|
|
89
89
|
if (!normalized)
|
|
90
90
|
continue;
|
|
91
|
-
const key = `${normalized.domain ??
|
|
91
|
+
const key = `${normalized.domain ?? ""}:${normalized.name}`;
|
|
92
92
|
if (!merged.has(key))
|
|
93
93
|
merged.set(key, normalized);
|
|
94
94
|
}
|
|
@@ -102,10 +102,10 @@ function normalizeInlineCookies(rawCookies, fallbackHost) {
|
|
|
102
102
|
// Learned: inline cookies may omit url/domain; default to current host with a safe path.
|
|
103
103
|
const normalized = {
|
|
104
104
|
name: cookie.name,
|
|
105
|
-
value: cookie.value ??
|
|
105
|
+
value: cookie.value ?? "",
|
|
106
106
|
url: cookie.url,
|
|
107
107
|
domain: cookie.domain ?? fallbackHost,
|
|
108
|
-
path: cookie.path ??
|
|
108
|
+
path: cookie.path ?? "/",
|
|
109
109
|
expires: normalizeExpiration(cookie.expires),
|
|
110
110
|
secure: cookie.secure ?? true,
|
|
111
111
|
httpOnly: cookie.httpOnly ?? false,
|
|
@@ -125,13 +125,13 @@ function toCdpCookie(cookie) {
|
|
|
125
125
|
name: cookie.name,
|
|
126
126
|
value: cookie.value,
|
|
127
127
|
domain: cookie.domain,
|
|
128
|
-
path: cookie.path ??
|
|
128
|
+
path: cookie.path ?? "/",
|
|
129
129
|
secure: cookie.secure ?? true,
|
|
130
130
|
httpOnly: cookie.httpOnly ?? false,
|
|
131
131
|
};
|
|
132
|
-
if (typeof cookie.expires ===
|
|
132
|
+
if (typeof cookie.expires === "number")
|
|
133
133
|
out.expires = cookie.expires;
|
|
134
|
-
if (cookie.sameSite ===
|
|
134
|
+
if (cookie.sameSite === "Lax" || cookie.sameSite === "Strict" || cookie.sameSite === "None") {
|
|
135
135
|
out.sameSite = cookie.sameSite;
|
|
136
136
|
}
|
|
137
137
|
return out;
|
|
@@ -139,10 +139,10 @@ function toCdpCookie(cookie) {
|
|
|
139
139
|
function attachUrl(cookie, fallbackUrl) {
|
|
140
140
|
const cookieWithUrl = { ...cookie };
|
|
141
141
|
if (!cookieWithUrl.url) {
|
|
142
|
-
if (!cookieWithUrl.domain || cookieWithUrl.domain ===
|
|
142
|
+
if (!cookieWithUrl.domain || cookieWithUrl.domain === "localhost") {
|
|
143
143
|
cookieWithUrl.url = fallbackUrl;
|
|
144
144
|
}
|
|
145
|
-
else if (!cookieWithUrl.domain.startsWith(
|
|
145
|
+
else if (!cookieWithUrl.domain.startsWith(".")) {
|
|
146
146
|
cookieWithUrl.url = `https://${cookieWithUrl.domain}`;
|
|
147
147
|
}
|
|
148
148
|
}
|
|
@@ -156,8 +156,8 @@ function attachUrl(cookie, fallbackUrl) {
|
|
|
156
156
|
function stripQuery(url) {
|
|
157
157
|
try {
|
|
158
158
|
const parsed = new URL(url);
|
|
159
|
-
parsed.hash =
|
|
160
|
-
parsed.search =
|
|
159
|
+
parsed.hash = "";
|
|
160
|
+
parsed.search = "";
|
|
161
161
|
return parsed.toString();
|
|
162
162
|
}
|
|
163
163
|
catch {
|