@andrewting19/oracle 0.9.8 → 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.
|
@@ -123,14 +123,14 @@ export async function connectToChrome(port, logger, host) {
|
|
|
123
123
|
logger("Connected to Chrome DevTools protocol");
|
|
124
124
|
return client;
|
|
125
125
|
}
|
|
126
|
-
export async function connectToRemoteChrome(host, port, logger, targetUrl) {
|
|
126
|
+
export async function connectToRemoteChrome(host, port, logger, targetUrl, rootSession) {
|
|
127
127
|
if (targetUrl) {
|
|
128
128
|
const targetConnection = await connectToNewTarget(host, port, targetUrl, logger, {
|
|
129
|
-
opened: () => `Opened dedicated remote Chrome
|
|
129
|
+
opened: (targetId) => `Opened dedicated remote Chrome ${rootSession ? "hidden " : ""}target ${targetId}`,
|
|
130
130
|
openFailed: (message) => `Failed to open dedicated remote Chrome tab (${message}); falling back to first target.`,
|
|
131
131
|
attachFailed: (targetId, message) => `Failed to attach to dedicated remote Chrome tab ${targetId} (${message}); falling back to first target.`,
|
|
132
132
|
closeFailed: (targetId, message) => `Failed to close unused remote Chrome tab ${targetId}: ${message}`,
|
|
133
|
-
});
|
|
133
|
+
}, rootSession);
|
|
134
134
|
if (targetConnection) {
|
|
135
135
|
return { client: targetConnection.client, targetId: targetConnection.targetId };
|
|
136
136
|
}
|
|
@@ -154,9 +154,35 @@ export async function closeRemoteChromeTarget(host, port, targetId, logger) {
|
|
|
154
154
|
logger(`Failed to close remote Chrome tab ${targetId}: ${message}`);
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
|
-
async function connectToNewTarget(host, port, url, logger, messages) {
|
|
157
|
+
async function connectToNewTarget(host, port, url, logger, messages, rootSession) {
|
|
158
158
|
try {
|
|
159
|
-
|
|
159
|
+
let target;
|
|
160
|
+
if (rootSession) {
|
|
161
|
+
// Hidden target: not in tab strip, no window, no focus steal.
|
|
162
|
+
// Lifetime tied to rootSession (kept alive by the serve).
|
|
163
|
+
const result = await rootSession.Target.createTarget({
|
|
164
|
+
url,
|
|
165
|
+
hidden: true,
|
|
166
|
+
background: true,
|
|
167
|
+
});
|
|
168
|
+
target = { id: result.targetId };
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
// No root session: try background + focus:false, fallback to CDP.New()
|
|
172
|
+
try {
|
|
173
|
+
const bgClient = await CDP({ host, port });
|
|
174
|
+
const result = await bgClient.Target.createTarget({
|
|
175
|
+
url,
|
|
176
|
+
background: true,
|
|
177
|
+
focus: false,
|
|
178
|
+
});
|
|
179
|
+
target = { id: result.targetId };
|
|
180
|
+
await bgClient.close();
|
|
181
|
+
}
|
|
182
|
+
catch (_bgErr) {
|
|
183
|
+
target = await CDP.New({ host, port, url });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
160
186
|
try {
|
|
161
187
|
const client = await CDP({ host, port, target: target.id });
|
|
162
188
|
if (messages.opened) {
|
|
@@ -1023,8 +1023,8 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1023
1023
|
if (!remoteChromeConfig) {
|
|
1024
1024
|
throw new Error("Remote Chrome configuration missing. Pass --remote-chrome <host:port> to use this mode.");
|
|
1025
1025
|
}
|
|
1026
|
-
const { host, port } = remoteChromeConfig;
|
|
1027
|
-
logger(`Connecting to remote Chrome at ${host}:${port}`);
|
|
1026
|
+
const { host, port, rootSession } = remoteChromeConfig;
|
|
1027
|
+
logger(`Connecting to remote Chrome at ${host}:${port}${rootSession ? " (hidden targets)" : ""}`);
|
|
1028
1028
|
let client = null;
|
|
1029
1029
|
let remoteTargetId = null;
|
|
1030
1030
|
let lastUrl;
|
|
@@ -1054,7 +1054,7 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1054
1054
|
let stopThinkingMonitor = null;
|
|
1055
1055
|
let removeDialogHandler = null;
|
|
1056
1056
|
try {
|
|
1057
|
-
const connection = await connectToRemoteChrome(host, port, logger, config.url);
|
|
1057
|
+
const connection = await connectToRemoteChrome(host, port, logger, config.url, rootSession);
|
|
1058
1058
|
client = connection.client;
|
|
1059
1059
|
remoteTargetId = connection.targetId ?? null;
|
|
1060
1060
|
await emitRuntimeHint();
|
|
@@ -1069,8 +1069,19 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1069
1069
|
}
|
|
1070
1070
|
await Promise.all(domainEnablers);
|
|
1071
1071
|
removeDialogHandler = installJavaScriptDialogAutoDismissal(Page, logger);
|
|
1072
|
-
//
|
|
1073
|
-
|
|
1072
|
+
// Sync cookies into this tab for remote Chrome (needed for headed + Cloudflare).
|
|
1073
|
+
try {
|
|
1074
|
+
const cookieCount = await syncCookies(Network, config.url, config.chromeProfile, logger, {
|
|
1075
|
+
allowErrors: true,
|
|
1076
|
+
filterNames: config.cookieNames ?? undefined,
|
|
1077
|
+
cookiePath: config.chromeCookiePath ?? undefined,
|
|
1078
|
+
waitMs: config.cookieSyncWaitMs ?? 0,
|
|
1079
|
+
});
|
|
1080
|
+
logger(`Remote tab: synced ${cookieCount} cookies`);
|
|
1081
|
+
}
|
|
1082
|
+
catch (_syncErr) {
|
|
1083
|
+
logger("Remote tab: cookie sync failed, continuing with existing session");
|
|
1084
|
+
}
|
|
1074
1085
|
await navigateToChatGPT(Page, Runtime, config.url, logger);
|
|
1075
1086
|
await ensureNotBlocked(Runtime, config.headless, logger);
|
|
1076
1087
|
await ensureLoggedIn(Runtime, logger, { remoteSession: true });
|
|
@@ -12,7 +12,6 @@ import { CHATGPT_URL } from "../browser/constants.js";
|
|
|
12
12
|
import { getCliVersion } from "../version.js";
|
|
13
13
|
import { cleanupStaleProfileState, readDevToolsPort, verifyDevToolsReachable, writeChromePid, writeDevToolsActivePort, } from "../browser/profileState.js";
|
|
14
14
|
import { normalizeChatgptUrl } from "../browser/utils.js";
|
|
15
|
-
import { connectToChrome } from "../browser/chromeLifecycle.js";
|
|
16
15
|
async function findAvailablePort() {
|
|
17
16
|
return await new Promise((resolve, reject) => {
|
|
18
17
|
const srv = net.createServer();
|
|
@@ -270,12 +269,15 @@ export async function serveRemote(options = {}) {
|
|
|
270
269
|
console.log(`Detected ${cookies.length} ChatGPT cookies on this host; runs will reuse this session.`);
|
|
271
270
|
}
|
|
272
271
|
// Launch a shared Chrome instance for concurrent tab-based runs.
|
|
272
|
+
// Each run gets a hidden CDP target (no tab strip entry, no window, no focus stealing).
|
|
273
273
|
let sharedChrome;
|
|
274
|
+
let sharedChromeProcess = null;
|
|
275
|
+
let sharedRootSession = null;
|
|
274
276
|
if (!preferManualLogin) {
|
|
275
277
|
try {
|
|
276
278
|
const userDataDir = await mkdtemp(path.join(os.tmpdir(), "oracle-serve-chrome-"));
|
|
277
|
-
// Launch headed Chrome with
|
|
278
|
-
//
|
|
279
|
+
// Launch headed Chrome with no initial window. Headed mode passes Cloudflare;
|
|
280
|
+
// hidden CDP targets prevent focus stealing on macOS.
|
|
279
281
|
const { launch: launchChromeRaw } = await import("chrome-launcher");
|
|
280
282
|
const chrome = await launchChromeRaw({
|
|
281
283
|
chromeFlags: [
|
|
@@ -296,13 +298,23 @@ export async function serveRemote(options = {}) {
|
|
|
296
298
|
userDataDir,
|
|
297
299
|
handleSIGINT: false,
|
|
298
300
|
});
|
|
299
|
-
|
|
300
|
-
console.log(`Shared Chrome launched (pid ${chrome.pid}, port ${chrome.port}, no-startup-window).
|
|
301
|
-
//
|
|
301
|
+
sharedChromeProcess = chrome;
|
|
302
|
+
console.log(`Shared Chrome launched (pid ${chrome.pid}, port ${chrome.port}, headed + no-startup-window). Runs will use hidden CDP targets.`);
|
|
303
|
+
// Create a persistent root CDP session. Hidden target lifetimes are tied to
|
|
304
|
+
// the session that created them, so this must stay alive for the serve lifetime.
|
|
305
|
+
const CDP = (await import("chrome-remote-interface")).default;
|
|
306
|
+
const rootSession = await CDP({ host: "127.0.0.1", port: chrome.port });
|
|
307
|
+
sharedRootSession = rootSession;
|
|
308
|
+
// Sync ChatGPT cookies into the shared Chrome via a hidden target.
|
|
302
309
|
try {
|
|
303
310
|
const { syncCookies } = await import("../browser/cookies.js");
|
|
304
|
-
const
|
|
305
|
-
|
|
311
|
+
const { targetId } = await rootSession.Target.createTarget({
|
|
312
|
+
url: "about:blank",
|
|
313
|
+
hidden: true,
|
|
314
|
+
background: true,
|
|
315
|
+
});
|
|
316
|
+
const syncClient = await CDP({ host: "127.0.0.1", port: chrome.port, target: targetId });
|
|
317
|
+
const { Network } = syncClient;
|
|
306
318
|
await Network.enable({});
|
|
307
319
|
const syncLogger = ((msg) => { if (msg)
|
|
308
320
|
console.log(`[shared-chrome] ${msg}`); });
|
|
@@ -311,12 +323,14 @@ export async function serveRemote(options = {}) {
|
|
|
311
323
|
allowErrors: true,
|
|
312
324
|
});
|
|
313
325
|
console.log(`Synced ${count} ChatGPT cookies into shared Chrome.`);
|
|
314
|
-
await
|
|
326
|
+
await syncClient.close();
|
|
327
|
+
await rootSession.Target.closeTarget({ targetId });
|
|
315
328
|
}
|
|
316
329
|
catch (cookieErr) {
|
|
317
330
|
const msg = cookieErr instanceof Error ? cookieErr.message : String(cookieErr);
|
|
318
331
|
console.log(`Warning: failed to sync cookies into shared Chrome (${msg}). Runs may need to authenticate.`);
|
|
319
332
|
}
|
|
333
|
+
sharedChrome = { host: "127.0.0.1", port: chrome.port, rootSession };
|
|
320
334
|
}
|
|
321
335
|
catch (error) {
|
|
322
336
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -332,10 +346,27 @@ export async function serveRemote(options = {}) {
|
|
|
332
346
|
await new Promise((resolve) => {
|
|
333
347
|
const shutdown = () => {
|
|
334
348
|
console.log("Shutting down remote service...");
|
|
335
|
-
server
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
.
|
|
349
|
+
// Close root CDP session and kill shared Chrome before stopping the HTTP server.
|
|
350
|
+
const cleanupPromises = [];
|
|
351
|
+
if (sharedRootSession) {
|
|
352
|
+
cleanupPromises.push(sharedRootSession.close().catch((err) => {
|
|
353
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
354
|
+
console.error(`Failed to close root CDP session: ${msg}`);
|
|
355
|
+
}));
|
|
356
|
+
}
|
|
357
|
+
if (sharedChromeProcess) {
|
|
358
|
+
cleanupPromises.push(Promise.resolve(sharedChromeProcess.kill()).catch((err) => {
|
|
359
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
360
|
+
console.error(`Failed to kill shared Chrome: ${msg}`);
|
|
361
|
+
}));
|
|
362
|
+
}
|
|
363
|
+
Promise.all(cleanupPromises)
|
|
364
|
+
.finally(() => {
|
|
365
|
+
server
|
|
366
|
+
.close()
|
|
367
|
+
.catch((error) => console.error("Failed to close remote server:", error))
|
|
368
|
+
.finally(() => resolve());
|
|
369
|
+
});
|
|
339
370
|
};
|
|
340
371
|
process.on("SIGINT", shutdown);
|
|
341
372
|
process.on("SIGTERM", shutdown);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andrewting19/oracle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "CLI wrapper around OpenAI Responses API with GPT-5.4 Pro, GPT-5.4, GPT-5.2, GPT-5.1, and GPT-5.1 Codex high reasoning modes.",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"homepage": "https://github.com/steipete/oracle#readme",
|