@andrewting19/oracle 0.10.2 → 0.11.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/dist/bin/oracle-cli.js
CHANGED
|
@@ -470,8 +470,11 @@ function resolveHeartbeatIntervalMs(seconds) {
|
|
|
470
470
|
return Math.round(seconds * 1000);
|
|
471
471
|
}
|
|
472
472
|
function assertFollowupSupported({ engine, model, baseUrl, azureEndpoint, }) {
|
|
473
|
+
// Browser engine: follow-up navigates to an existing ChatGPT conversation URL.
|
|
474
|
+
if (engine === "browser")
|
|
475
|
+
return;
|
|
473
476
|
if (engine !== "api") {
|
|
474
|
-
throw new Error("--followup requires --engine api.");
|
|
477
|
+
throw new Error("--followup requires --engine api or --engine browser.");
|
|
475
478
|
}
|
|
476
479
|
if (model.startsWith("gemini") || model.startsWith("claude")) {
|
|
477
480
|
throw new Error(`--followup is only supported for OpenAI Responses API runs. Model ${model} uses a provider client without previous_response_id support.`);
|
|
@@ -538,7 +541,7 @@ async function suggestFollowupSessionIds(input, limit = 3) {
|
|
|
538
541
|
.slice(0, limit);
|
|
539
542
|
return ranked.map((entry) => entry.id);
|
|
540
543
|
}
|
|
541
|
-
async function resolveFollowupReference(value, followupModel) {
|
|
544
|
+
async function resolveFollowupReference(value, followupModel, engine) {
|
|
542
545
|
const trimmed = value.trim();
|
|
543
546
|
if (trimmed.length === 0) {
|
|
544
547
|
throw new Error("--followup requires a session id or response id.");
|
|
@@ -555,6 +558,14 @@ async function resolveFollowupReference(value, followupModel) {
|
|
|
555
558
|
: "";
|
|
556
559
|
throw new Error(`No session found with ID ${trimmed}.${suggestionText} Run "oracle status --hours 72 --limit 20" to list recent sessions.`);
|
|
557
560
|
}
|
|
561
|
+
// Browser follow-up: extract conversation URL from the parent session.
|
|
562
|
+
if (engine === "browser") {
|
|
563
|
+
const conversationUrl = meta.browser?.runtime?.tabUrl;
|
|
564
|
+
if (!conversationUrl || !conversationUrl.includes("/c/")) {
|
|
565
|
+
throw new Error(`Session "${trimmed}" has no ChatGPT conversation URL stored. Cannot follow up in browser mode.`);
|
|
566
|
+
}
|
|
567
|
+
return { sessionId: meta.id, conversationUrl };
|
|
568
|
+
}
|
|
558
569
|
const fromMetadata = extractResponseIdFromSession(meta, followupModel);
|
|
559
570
|
if (fromMetadata) {
|
|
560
571
|
return { responseId: fromMetadata, sessionId: meta.id };
|
|
@@ -917,7 +928,7 @@ async function runRootCommand(options) {
|
|
|
917
928
|
if (normalizedMultiModels.length > 0) {
|
|
918
929
|
throw new Error("--followup cannot be combined with --models.");
|
|
919
930
|
}
|
|
920
|
-
const followup = await resolveFollowupReference(options.followup, options.followupModel);
|
|
931
|
+
const followup = await resolveFollowupReference(options.followup, options.followupModel, engine);
|
|
921
932
|
resolvedOptions.previousResponseId = followup.responseId;
|
|
922
933
|
resolvedOptions.followupSessionId = followup.sessionId;
|
|
923
934
|
resolvedOptions.followupModel = options.followupModel;
|
|
@@ -964,6 +975,7 @@ async function runRootCommand(options) {
|
|
|
964
975
|
options.prompt = `${options.prompt.trim()}\n${userConfig.promptSuffix}`;
|
|
965
976
|
}
|
|
966
977
|
resolvedOptions.prompt = options.prompt;
|
|
978
|
+
let browserFollowupConversationUrl;
|
|
967
979
|
if (options.followup) {
|
|
968
980
|
assertFollowupSupported({
|
|
969
981
|
engine,
|
|
@@ -974,10 +986,11 @@ async function runRootCommand(options) {
|
|
|
974
986
|
if (normalizedMultiModels.length > 0) {
|
|
975
987
|
throw new Error("--followup cannot be combined with --models.");
|
|
976
988
|
}
|
|
977
|
-
const followup = await resolveFollowupReference(options.followup, options.followupModel);
|
|
989
|
+
const followup = await resolveFollowupReference(options.followup, options.followupModel, engine);
|
|
978
990
|
resolvedOptions.previousResponseId = followup.responseId;
|
|
979
991
|
resolvedOptions.followupSessionId = followup.sessionId;
|
|
980
992
|
resolvedOptions.followupModel = options.followupModel;
|
|
993
|
+
browserFollowupConversationUrl = followup.conversationUrl;
|
|
981
994
|
}
|
|
982
995
|
const duplicateBlocked = await shouldBlockDuplicatePrompt({
|
|
983
996
|
prompt: resolvedOptions.prompt,
|
|
@@ -1018,6 +1031,13 @@ async function runRootCommand(options) {
|
|
|
1018
1031
|
browserModelLabel: browserModelLabelOverride,
|
|
1019
1032
|
})
|
|
1020
1033
|
: undefined;
|
|
1034
|
+
// For browser follow-ups, navigate to the existing conversation and skip model selection.
|
|
1035
|
+
if (browserConfig && browserFollowupConversationUrl) {
|
|
1036
|
+
browserConfig.url = browserFollowupConversationUrl;
|
|
1037
|
+
browserConfig.chatgptUrl = browserFollowupConversationUrl;
|
|
1038
|
+
browserConfig.modelStrategy = "ignore";
|
|
1039
|
+
console.log(chalk.dim(`Following up on conversation: ${browserFollowupConversationUrl}`));
|
|
1040
|
+
}
|
|
1021
1041
|
let browserDeps;
|
|
1022
1042
|
if (browserConfig && remoteHost) {
|
|
1023
1043
|
browserDeps = {
|
|
@@ -158,14 +158,24 @@ async function connectToNewTarget(host, port, url, logger, messages, rootSession
|
|
|
158
158
|
try {
|
|
159
159
|
let target;
|
|
160
160
|
if (rootSession) {
|
|
161
|
-
//
|
|
161
|
+
// Try hidden target (no tab strip, no window), fall back to background+focus:false.
|
|
162
162
|
// Lifetime tied to rootSession (kept alive by the serve).
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
163
|
+
try {
|
|
164
|
+
const result = await rootSession.Target.createTarget({
|
|
165
|
+
url,
|
|
166
|
+
hidden: true,
|
|
167
|
+
background: true,
|
|
168
|
+
});
|
|
169
|
+
target = { id: result.targetId };
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
const result = await rootSession.Target.createTarget({
|
|
173
|
+
url,
|
|
174
|
+
background: true,
|
|
175
|
+
focus: false,
|
|
176
|
+
});
|
|
177
|
+
target = { id: result.targetId };
|
|
178
|
+
}
|
|
169
179
|
}
|
|
170
180
|
else {
|
|
171
181
|
// No root session: try background + focus:false, fallback to CDP.New()
|
|
@@ -124,6 +124,8 @@ export async function runBrowserSessionExecution({ runOptions, browserConfig, cw
|
|
|
124
124
|
chromePort: browserResult.chromePort,
|
|
125
125
|
chromeHost: browserResult.chromeHost,
|
|
126
126
|
userDataDir: browserResult.userDataDir,
|
|
127
|
+
chromeTargetId: browserResult.chromeTargetId,
|
|
128
|
+
tabUrl: browserResult.tabUrl,
|
|
127
129
|
controllerPid: browserResult.controllerPid ?? process.pid,
|
|
128
130
|
},
|
|
129
131
|
answerText,
|
|
@@ -12,6 +12,7 @@ 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 { hideChromeWindow } from "../browser/chromeLifecycle.js";
|
|
15
16
|
async function findAvailablePort() {
|
|
16
17
|
return await new Promise((resolve, reject) => {
|
|
17
18
|
const srv = net.createServer();
|
|
@@ -290,6 +291,7 @@ export async function serveRemote(options = {}) {
|
|
|
290
291
|
"--disable-hang-monitor",
|
|
291
292
|
"--disable-popup-blocking",
|
|
292
293
|
"--disable-features=TranslateUI,AutomationControlled",
|
|
294
|
+
"--remote-allow-origins=*",
|
|
293
295
|
"--mute-audio",
|
|
294
296
|
"--window-size=1280,720",
|
|
295
297
|
"--password-store=basic",
|
|
@@ -313,15 +315,30 @@ export async function serveRemote(options = {}) {
|
|
|
313
315
|
console.log(`Root CDP session connecting to ${browserWsUrl}`);
|
|
314
316
|
const rootSession = await CDP({ target: browserWsUrl });
|
|
315
317
|
sharedRootSession = rootSession;
|
|
316
|
-
// Sync ChatGPT cookies into the shared Chrome via a
|
|
318
|
+
// Sync ChatGPT cookies into the shared Chrome via a background target.
|
|
319
|
+
// Try hidden first (no tab strip / no window), fall back to background+focus:false.
|
|
317
320
|
try {
|
|
318
321
|
const { syncCookies } = await import("../browser/cookies.js");
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
322
|
+
let syncTargetId;
|
|
323
|
+
try {
|
|
324
|
+
const result = await rootSession.Target.createTarget({
|
|
325
|
+
url: "about:blank",
|
|
326
|
+
hidden: true,
|
|
327
|
+
background: true,
|
|
328
|
+
});
|
|
329
|
+
syncTargetId = result.targetId;
|
|
330
|
+
console.log("Cookie sync: created hidden target.");
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
const result = await rootSession.Target.createTarget({
|
|
334
|
+
url: "about:blank",
|
|
335
|
+
background: true,
|
|
336
|
+
focus: false,
|
|
337
|
+
});
|
|
338
|
+
syncTargetId = result.targetId;
|
|
339
|
+
console.log("Cookie sync: hidden targets unavailable, using background+focus:false.");
|
|
340
|
+
}
|
|
341
|
+
const syncClient = await CDP({ host: "127.0.0.1", port: chrome.port, target: syncTargetId });
|
|
325
342
|
const { Network } = syncClient;
|
|
326
343
|
await Network.enable({});
|
|
327
344
|
const syncLogger = ((msg) => { if (msg)
|
|
@@ -332,12 +349,20 @@ export async function serveRemote(options = {}) {
|
|
|
332
349
|
});
|
|
333
350
|
console.log(`Synced ${count} ChatGPT cookies into shared Chrome.`);
|
|
334
351
|
await syncClient.close();
|
|
335
|
-
await rootSession.Target.closeTarget({ targetId });
|
|
352
|
+
await rootSession.Target.closeTarget({ targetId: syncTargetId });
|
|
336
353
|
}
|
|
337
354
|
catch (cookieErr) {
|
|
338
355
|
const msg = cookieErr instanceof Error ? cookieErr.message : String(cookieErr);
|
|
339
356
|
console.log(`Warning: failed to sync cookies into shared Chrome (${msg}). Runs may need to authenticate.`);
|
|
340
357
|
}
|
|
358
|
+
// Hide the serve Chrome from the macOS Dock (set visible = false).
|
|
359
|
+
// This is safe because we interact via CDP, not GUI.
|
|
360
|
+
try {
|
|
361
|
+
await hideChromeWindow(chrome, console.log);
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
// Non-fatal: hideChromeWindow logs its own errors.
|
|
365
|
+
}
|
|
341
366
|
sharedChrome = { host: "127.0.0.1", port: chrome.port, rootSession };
|
|
342
367
|
}
|
|
343
368
|
catch (error) {
|
|
@@ -398,6 +423,7 @@ function sanitizeResult(result) {
|
|
|
398
423
|
tookMs: result.tookMs,
|
|
399
424
|
answerTokens: result.answerTokens,
|
|
400
425
|
answerChars: result.answerChars,
|
|
426
|
+
tabUrl: result.tabUrl,
|
|
401
427
|
chromePid: undefined,
|
|
402
428
|
chromePort: undefined,
|
|
403
429
|
userDataDir: undefined,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andrewting19/oracle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.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",
|