@steipete/oracle 0.16.1 → 0.17.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/README.md +72 -337
- package/dist/bin/oracle-cli.js +184 -55
- package/dist/docs-site/.nojekyll +0 -0
- package/dist/docs-site/CNAME +1 -0
- package/dist/docs-site/RELEASING.html +410 -0
- package/dist/docs-site/agents.html +374 -0
- package/dist/docs-site/anthropic.html +368 -0
- package/dist/docs-site/bridge.html +416 -0
- package/dist/docs-site/browser-mode.html +594 -0
- package/dist/docs-site/chromium-forks.html +347 -0
- package/dist/docs-site/cli-reference.html +346 -0
- package/dist/docs-site/configuration.html +462 -0
- package/dist/docs-site/favicon.svg +14 -0
- package/dist/docs-site/followup.html +375 -0
- package/dist/docs-site/gemini.html +383 -0
- package/dist/docs-site/grok.html +325 -0
- package/dist/docs-site/index.html +360 -0
- package/dist/docs-site/install.html +335 -0
- package/dist/docs-site/linux.html +321 -0
- package/dist/docs-site/llms.txt +43 -0
- package/dist/docs-site/manual-tests.html +596 -0
- package/dist/docs-site/mcp.html +391 -0
- package/dist/docs-site/multimodel.html +364 -0
- package/dist/docs-site/mythical-pro-agents.html +360 -0
- package/dist/docs-site/notifier.html +338 -0
- package/dist/docs-site/openai-endpoints.html +410 -0
- package/dist/docs-site/openrouter.html +344 -0
- package/dist/docs-site/quickstart.html +369 -0
- package/dist/docs-site/refactor/ux.html +532 -0
- package/dist/docs-site/sessions.html +389 -0
- package/dist/docs-site/social-card.png +0 -0
- package/dist/docs-site/social-card.svg +79 -0
- package/dist/docs-site/spec.html +363 -0
- package/dist/docs-site/testing.html +320 -0
- package/dist/docs-site/tui-debug.html +326 -0
- package/dist/docs-site/windows-work.html +324 -0
- package/dist/docs-site/windows.html +320 -0
- package/dist/scripts/test-browser.js +1 -25
- package/dist/src/browser/actions/modelSelection.js +36 -34
- package/dist/src/browser/actions/navigation.js +8 -3
- package/dist/src/browser/actions/thinkingTime.js +39 -3
- package/dist/src/browser/chromeLifecycle.js +2 -37
- package/dist/src/browser/index.js +23 -8
- package/dist/src/browser/modelDisplay.js +67 -0
- package/dist/src/browser/reattach.js +14 -1
- package/dist/src/browser/recoverConversation.js +2 -1
- package/dist/src/browser/sessionRunner.js +9 -10
- package/dist/src/browser/wslHost.js +50 -0
- package/dist/src/cli/browserConfig.js +31 -11
- package/dist/src/cli/detach.js +21 -4
- package/dist/src/cli/dryRun.js +13 -2
- package/dist/src/cli/engine.js +2 -2
- package/dist/src/cli/options.js +4 -0
- package/dist/src/cli/sessionDisplay.js +60 -8
- package/dist/src/cli/sessionLifecycle.js +2 -1
- package/dist/src/cli/sessionRunner.js +110 -60
- package/dist/src/cli/sessionTable.js +5 -1
- package/dist/src/cli/tui/index.js +12 -4
- package/dist/src/duration.js +3 -0
- package/dist/src/gemini-web/client.js +19 -1
- package/dist/src/oracle/modelResolver.js +8 -1
- package/dist/src/oracle/request.js +9 -2
- package/dist/src/oracle/run.js +43 -3
- package/dist/src/sessionManager.js +41 -12
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/package.json +15 -15
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import { rm } from "node:fs/promises";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
4
2
|
import net from "node:net";
|
|
5
3
|
import CDP from "chrome-remote-interface";
|
|
6
4
|
import { launch, Launcher } from "chrome-launcher";
|
|
7
5
|
import { cleanupStaleProfileState } from "./profileState.js";
|
|
8
6
|
import { delay } from "./utils.js";
|
|
7
|
+
import { isWsl, resolveWslChromeLaunchRoute } from "./wslHost.js";
|
|
9
8
|
export async function launchChrome(config, userDataDir, logger) {
|
|
10
|
-
const connectHost =
|
|
11
|
-
const debugBindAddress = connectHost && connectHost !== "127.0.0.1" ? "0.0.0.0" : connectHost;
|
|
9
|
+
const { connectHost, debugBindAddress, usePatchedLauncher } = resolveWslChromeLaunchRoute();
|
|
12
10
|
const debugPort = config.debugPort ?? parseDebugPortEnv();
|
|
13
11
|
const chromeFlags = buildChromeFlags(config.headless ?? false, debugBindAddress, config.hideWindow ?? false);
|
|
14
|
-
const usePatchedLauncher = Boolean(connectHost && connectHost !== "127.0.0.1");
|
|
15
12
|
// copy-profile reuses a copied signed-in profile whose cookies are
|
|
16
13
|
// Keychain-encrypted, so it must launch with the real Keychain (not mocked):
|
|
17
14
|
// strip the keychain-mocking flags from both chrome-launcher's defaults and
|
|
@@ -595,38 +592,6 @@ function parseDebugPortEnv() {
|
|
|
595
592
|
}
|
|
596
593
|
return value;
|
|
597
594
|
}
|
|
598
|
-
function resolveRemoteDebugHost() {
|
|
599
|
-
const override = process.env.ORACLE_BROWSER_REMOTE_DEBUG_HOST?.trim() || process.env.WSL_HOST_IP?.trim();
|
|
600
|
-
if (override) {
|
|
601
|
-
return override;
|
|
602
|
-
}
|
|
603
|
-
if (!isWsl()) {
|
|
604
|
-
return null;
|
|
605
|
-
}
|
|
606
|
-
try {
|
|
607
|
-
const resolv = readFileSync("/etc/resolv.conf", "utf8");
|
|
608
|
-
for (const line of resolv.split("\n")) {
|
|
609
|
-
const match = line.match(/^nameserver\s+([0-9.]+)/);
|
|
610
|
-
if (match?.[1]) {
|
|
611
|
-
return match[1];
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
catch {
|
|
616
|
-
// ignore; fall back to localhost
|
|
617
|
-
}
|
|
618
|
-
return null;
|
|
619
|
-
}
|
|
620
|
-
function isWsl() {
|
|
621
|
-
if (process.platform !== "linux") {
|
|
622
|
-
return false;
|
|
623
|
-
}
|
|
624
|
-
if (process.env.WSL_DISTRO_NAME) {
|
|
625
|
-
return true;
|
|
626
|
-
}
|
|
627
|
-
const release = os.release();
|
|
628
|
-
return release.toLowerCase().includes("microsoft");
|
|
629
|
-
}
|
|
630
595
|
async function launchWithCustomHost({ chromeFlags, chromePath, userDataDir, host, requestedPort, ignoreDefaultFlags, }) {
|
|
631
596
|
const launcher = new Launcher({
|
|
632
597
|
chromePath: chromePath ?? undefined,
|
|
@@ -71,6 +71,9 @@ function classifyPreservedBrowserError(error, headless) {
|
|
|
71
71
|
function shouldPreserveBrowserOnError(error, headless) {
|
|
72
72
|
return classifyPreservedBrowserError(error, headless) !== null;
|
|
73
73
|
}
|
|
74
|
+
function normalizeAuthenticatedModelSelectionError(error) {
|
|
75
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
76
|
+
}
|
|
74
77
|
function shouldKeepLocalBrowserOpen(options) {
|
|
75
78
|
if (options.usingCopiedProfile)
|
|
76
79
|
return false;
|
|
@@ -1054,11 +1057,9 @@ export async function runBrowserMode(options) {
|
|
|
1054
1057
|
}
|
|
1055
1058
|
},
|
|
1056
1059
|
})).catch((error) => {
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
: "";
|
|
1061
|
-
throw new Error(`${base}${hint}`);
|
|
1060
|
+
// Login has already been verified above. Preserve the picker failure instead of
|
|
1061
|
+
// misdiagnosing an unavailable model as missing cookies.
|
|
1062
|
+
throw normalizeAuthenticatedModelSelectionError(error);
|
|
1062
1063
|
});
|
|
1063
1064
|
await raceWithDisconnect(ensurePromptReady(Runtime, config.inputTimeoutMs, logger));
|
|
1064
1065
|
logger(`Prompt textarea ready (after model switch, ${promptText.length.toLocaleString()} chars queued)`);
|
|
@@ -1342,7 +1343,11 @@ export async function runBrowserMode(options) {
|
|
|
1342
1343
|
if (conversationUrl && isConversationUrl(conversationUrl)) {
|
|
1343
1344
|
logger(`[browser] Rechecking assistant response at ${conversationUrl}`);
|
|
1344
1345
|
await raceWithDisconnect(Page.navigate({ url: conversationUrl }));
|
|
1345
|
-
await raceWithDisconnect(
|
|
1346
|
+
await raceWithDisconnect(waitForResumedConversationHydration(Runtime, recheckTimeoutMs || 30_000, logger, {
|
|
1347
|
+
requirePriorTurns: true,
|
|
1348
|
+
requirePromptReady: false,
|
|
1349
|
+
expectedConversationUrl: conversationUrl,
|
|
1350
|
+
}));
|
|
1346
1351
|
}
|
|
1347
1352
|
// Validate session before attempting recheck - sessions can expire during the delay
|
|
1348
1353
|
const sessionValid = await validateChatGPTSession(Runtime, logger);
|
|
@@ -2607,7 +2612,11 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2607
2612
|
lastUrl = conversationUrl;
|
|
2608
2613
|
logger(`[browser] Rechecking assistant response at ${conversationUrl}`);
|
|
2609
2614
|
await Page.navigate({ url: conversationUrl });
|
|
2610
|
-
await
|
|
2615
|
+
await waitForResumedConversationHydration(Runtime, recheckTimeoutMs || 30_000, logger, {
|
|
2616
|
+
requirePriorTurns: true,
|
|
2617
|
+
requirePromptReady: false,
|
|
2618
|
+
expectedConversationUrl: conversationUrl,
|
|
2619
|
+
});
|
|
2611
2620
|
}
|
|
2612
2621
|
// Validate session before attempting recheck - sessions can expire during the delay
|
|
2613
2622
|
const sessionValid = await validateChatGPTSession(Runtime, logger);
|
|
@@ -3051,10 +3060,12 @@ export const __test__ = {
|
|
|
3051
3060
|
isManualLoginProfileInitialized,
|
|
3052
3061
|
isImageOnlyUiChromeText,
|
|
3053
3062
|
listIgnoredRemoteChromeFlags,
|
|
3063
|
+
normalizeAuthenticatedModelSelectionError,
|
|
3054
3064
|
resolveManualLoginWaitMs,
|
|
3055
3065
|
shouldCleanupBlankTabsAfterLastLease,
|
|
3056
3066
|
shouldCloseOwnedRunTargetAfterRun,
|
|
3057
3067
|
shouldKeepLocalBrowserOpen,
|
|
3068
|
+
waitForAssistantResponseWithReload,
|
|
3058
3069
|
};
|
|
3059
3070
|
export { syncCookies } from "./cookies.js";
|
|
3060
3071
|
export { navigateToChatGPT, ensureNotBlocked, ensurePromptReady, ensureModelSelection, submitPrompt, waitForAssistantResponse, captureAssistantMarkdown, uploadAttachmentFile, waitForAttachmentCompletion, } from "./pageActions.js";
|
|
@@ -3086,7 +3097,11 @@ async function waitForAssistantResponseWithReload(Runtime, Page, timeoutMs, logg
|
|
|
3086
3097
|
}
|
|
3087
3098
|
logger("Assistant response stalled; reloading conversation and retrying once");
|
|
3088
3099
|
await Page.navigate({ url: conversationUrl });
|
|
3089
|
-
await
|
|
3100
|
+
await waitForResumedConversationHydration(Runtime, timeoutMs, logger, {
|
|
3101
|
+
requirePriorTurns: true,
|
|
3102
|
+
requirePromptReady: false,
|
|
3103
|
+
expectedConversationUrl: conversationUrl,
|
|
3104
|
+
});
|
|
3090
3105
|
return await waitForAssistantResponse(Runtime, timeoutMs, logger, minTurnIndex, expectedConversationId);
|
|
3091
3106
|
}
|
|
3092
3107
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
function cleanLabel(value) {
|
|
2
|
+
const label = value?.trim();
|
|
3
|
+
return label ? label : null;
|
|
4
|
+
}
|
|
5
|
+
function sameLabel(left, right) {
|
|
6
|
+
return left.localeCompare(right, undefined, { sensitivity: "accent" }) === 0;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Describe what a browser run will try to select without presenting the target as observed fact.
|
|
10
|
+
*/
|
|
11
|
+
export function formatBrowserModelTarget({ model, desiredModel, modelStrategy, }) {
|
|
12
|
+
const requested = cleanLabel(model) ?? "n/a";
|
|
13
|
+
if (modelStrategy === "current" || modelStrategy === "ignore") {
|
|
14
|
+
return `picker=${modelStrategy}; requested=${requested}`;
|
|
15
|
+
}
|
|
16
|
+
const target = cleanLabel(desiredModel);
|
|
17
|
+
if (!target) {
|
|
18
|
+
return requested;
|
|
19
|
+
}
|
|
20
|
+
return `target=${target}; requested=${requested}`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Prefer picker evidence only when Oracle verified it. Otherwise retain the requested CLI key.
|
|
24
|
+
* In particular, a bare `Pro` picker label must not be expanded to a server-side model version.
|
|
25
|
+
*/
|
|
26
|
+
export function resolveBrowserModelDisplayName({ model, evidence, }) {
|
|
27
|
+
const verifiedLabel = evidence?.verified ? cleanLabel(evidence.resolvedLabel) : null;
|
|
28
|
+
return verifiedLabel ?? cleanLabel(model) ?? "n/a";
|
|
29
|
+
}
|
|
30
|
+
export function formatBrowserModelWithRequestedKey(input) {
|
|
31
|
+
const displayName = resolveBrowserModelDisplayName(input);
|
|
32
|
+
const requested = cleanLabel(input.model);
|
|
33
|
+
if (!requested || sameLabel(displayName, requested)) {
|
|
34
|
+
return displayName;
|
|
35
|
+
}
|
|
36
|
+
return `${displayName} (requested ${requested})`;
|
|
37
|
+
}
|
|
38
|
+
export function resolveSessionBrowserModelDisplayName(metadata, model = metadata.model) {
|
|
39
|
+
const sessionModel = cleanLabel(metadata.model);
|
|
40
|
+
const requestedModel = cleanLabel(model);
|
|
41
|
+
const evidenceApplies = requestedModel === null
|
|
42
|
+
? sessionModel === null
|
|
43
|
+
: sessionModel !== null && sameLabel(requestedModel, sessionModel);
|
|
44
|
+
return resolveBrowserModelDisplayName({
|
|
45
|
+
model,
|
|
46
|
+
evidence: evidenceApplies ? metadata.browser?.modelSelection : undefined,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export function formatSessionBrowserModelWithRequestedKey(metadata, model = metadata.model) {
|
|
50
|
+
const sessionModel = cleanLabel(metadata.model);
|
|
51
|
+
const requestedModel = cleanLabel(model);
|
|
52
|
+
const evidenceApplies = requestedModel === null
|
|
53
|
+
? sessionModel === null
|
|
54
|
+
: sessionModel !== null && sameLabel(requestedModel, sessionModel);
|
|
55
|
+
return formatBrowserModelWithRequestedKey({
|
|
56
|
+
model,
|
|
57
|
+
evidence: evidenceApplies ? metadata.browser?.modelSelection : undefined,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
export function formatBrowserModelSelectionEvidence(evidence, model) {
|
|
61
|
+
const requestedKey = cleanLabel(model) ?? "(none)";
|
|
62
|
+
const target = cleanLabel(evidence.requestedModel) ?? "(none)";
|
|
63
|
+
const resolvedLabel = cleanLabel(evidence.resolvedLabel) ?? "(unavailable)";
|
|
64
|
+
const strategy = evidence.strategy ?? "(default)";
|
|
65
|
+
const verified = evidence.verified ? "yes" : "no";
|
|
66
|
+
return `requestedKey=${requestedKey}; target=${target}; resolvedLabel=${resolvedLabel}; status=${evidence.status}; strategy=${strategy}; verified=${verified}; source=${evidence.source}; capturedAt=${evidence.capturedAt}`;
|
|
67
|
+
}
|
|
@@ -2,7 +2,7 @@ import CDP from "chrome-remote-interface";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { mkdtemp, mkdir, rm } from "node:fs/promises";
|
|
5
|
-
import { waitForAssistantResponse, captureAssistantMarkdown, navigateToChatGPT, ensureNotBlocked, ensureLoggedIn, ensurePromptReady, } from "./pageActions.js";
|
|
5
|
+
import { waitForAssistantResponse, captureAssistantMarkdown, navigateToChatGPT, ensureNotBlocked, ensureLoggedIn, ensurePromptReady, waitForResumedConversationHydration, } from "./pageActions.js";
|
|
6
6
|
import { launchChrome, connectToChrome, positionChromeWindowOffscreen, connectToRemoteChromeTarget, listRemoteChromeTargets, } from "./chromeLifecycle.js";
|
|
7
7
|
import { resolveBrowserConfig } from "./config.js";
|
|
8
8
|
import { clearStaleChatGptConversationCookies, syncCookies } from "./cookies.js";
|
|
@@ -98,6 +98,13 @@ export async function resumeBrowserSession(runtime, config, logger, deps = {}) {
|
|
|
98
98
|
const pingTimeoutMs = Math.min(5_000, Math.max(1_500, Math.floor(timeoutMs * 0.05)));
|
|
99
99
|
await withTimeout(Runtime.evaluate({ expression: "1+1", returnByValue: true }), pingTimeoutMs, "Reattach target did not respond");
|
|
100
100
|
await ensureConversationOpen();
|
|
101
|
+
const waitForHydration = deps.waitForConversationHydration ?? waitForResumedConversationHydration;
|
|
102
|
+
const expectedConversationUrl = buildConversationUrl(runtime, resolveBrowserConfig(config ?? {}).url);
|
|
103
|
+
await waitForHydration(Runtime, timeoutMs, logger, {
|
|
104
|
+
requirePriorTurns: true,
|
|
105
|
+
requirePromptReady: false,
|
|
106
|
+
expectedConversationUrl: expectedConversationUrl ?? undefined,
|
|
107
|
+
});
|
|
101
108
|
const minTurnIndex = (await readPromptPreviewTurnIndex(Runtime, deps.promptPreview)) ??
|
|
102
109
|
(deps.promptPreview ? null : await readConversationTurnIndex(Runtime, logger));
|
|
103
110
|
if (config?.researchMode === "deep") {
|
|
@@ -226,6 +233,12 @@ async function resumeBrowserSessionViaNewChrome(runtime, config, logger, deps) {
|
|
|
226
233
|
}
|
|
227
234
|
await waitForLocationChange(Runtime, 15_000);
|
|
228
235
|
}
|
|
236
|
+
const waitForHydration = deps.waitForConversationHydration ?? waitForResumedConversationHydration;
|
|
237
|
+
await waitForHydration(Runtime, resolved.inputTimeoutMs, logger, {
|
|
238
|
+
requirePriorTurns: true,
|
|
239
|
+
requirePromptReady: false,
|
|
240
|
+
expectedConversationUrl: conversationUrl ?? undefined,
|
|
241
|
+
});
|
|
229
242
|
const waitForResponse = deps.waitForAssistantResponse ?? waitForAssistantResponse;
|
|
230
243
|
const captureMarkdown = deps.captureAssistantMarkdown ?? captureAssistantMarkdown;
|
|
231
244
|
const timeoutMs = resolved.timeoutMs ?? 120_000;
|
|
@@ -67,7 +67,8 @@ export function isRecoveredConversationHarvestReady(harvested) {
|
|
|
67
67
|
(typeof harvested.lastAssistantTurnIndex === "number" &&
|
|
68
68
|
typeof harvested.lastUserTurnIndex === "number" &&
|
|
69
69
|
harvested.lastAssistantTurnIndex > harvested.lastUserTurnIndex);
|
|
70
|
-
|
|
70
|
+
const hasHydratedUserTurn = typeof harvested.lastUserTurnIndex === "number" && harvested.lastUserTurnIndex >= 0;
|
|
71
|
+
return ((harvested.stopExists === true && hasHydratedUserTurn) ||
|
|
71
72
|
((harvested.assistantCount ?? 0) > 0 &&
|
|
72
73
|
assistantFollowsLatestUser &&
|
|
73
74
|
latestAssistant.trim().length > 0 &&
|
|
@@ -5,6 +5,7 @@ import { runBrowserMode } from "../browserMode.js";
|
|
|
5
5
|
import { assembleBrowserPrompt } from "./prompt.js";
|
|
6
6
|
import { BrowserAutomationError } from "../oracle/errors.js";
|
|
7
7
|
import { appendArtifacts, saveBrowserTranscriptArtifact, saveDeepResearchReportArtifact, } from "./artifacts.js";
|
|
8
|
+
import { formatBrowserModelSelectionEvidence, formatBrowserModelTarget, resolveBrowserModelDisplayName, } from "./modelDisplay.js";
|
|
8
9
|
const LARGE_PRO_FAST_INPUT_TOKEN_THRESHOLD = 25_000;
|
|
9
10
|
const LARGE_PRO_FAST_ELAPSED_MS_THRESHOLD = 120_000;
|
|
10
11
|
function buildUnavailableModelSelectionEvidence(browserConfig) {
|
|
@@ -21,13 +22,6 @@ function buildUnavailableModelSelectionEvidence(browserConfig) {
|
|
|
21
22
|
capturedAt: new Date().toISOString(),
|
|
22
23
|
};
|
|
23
24
|
}
|
|
24
|
-
function formatModelSelectionEvidence(evidence) {
|
|
25
|
-
const requested = evidence.requestedModel ?? "(none)";
|
|
26
|
-
const resolved = evidence.resolvedLabel ?? "(unavailable)";
|
|
27
|
-
const strategy = evidence.strategy ?? "(default)";
|
|
28
|
-
const verified = evidence.verified ? "yes" : "no";
|
|
29
|
-
return `[browser] Model selection evidence: requested=${requested}; resolved=${resolved}; status=${evidence.status}; strategy=${strategy}; verified=${verified}.`;
|
|
30
|
-
}
|
|
31
25
|
function isRequestedProBrowserRun(runOptions, browserConfig, evidence) {
|
|
32
26
|
const candidates = [
|
|
33
27
|
runOptions.model,
|
|
@@ -87,7 +81,12 @@ export async function runBrowserSessionExecution({ runOptions, browserConfig, cw
|
|
|
87
81
|
if (promptArtifacts.bundled) {
|
|
88
82
|
log(chalk.dim(`Packed ${promptArtifacts.bundled.originalCount} files into 1 bundle (contents counted in token estimate).`));
|
|
89
83
|
}
|
|
90
|
-
const
|
|
84
|
+
const launchModel = formatBrowserModelTarget({
|
|
85
|
+
model: runOptions.model,
|
|
86
|
+
desiredModel: browserConfig.desiredModel,
|
|
87
|
+
modelStrategy: browserConfig.modelStrategy,
|
|
88
|
+
});
|
|
89
|
+
const headerLine = `Launching browser mode (${launchModel}) with ~${promptArtifacts.estimatedInputTokens.toLocaleString()} tokens.`;
|
|
91
90
|
const automationLogger = ((message) => {
|
|
92
91
|
if (typeof message !== "string")
|
|
93
92
|
return;
|
|
@@ -150,7 +149,7 @@ export async function runBrowserSessionExecution({ runOptions, browserConfig, cw
|
|
|
150
149
|
}
|
|
151
150
|
const modelSelection = browserResult.modelSelection ?? buildUnavailableModelSelectionEvidence(browserConfig);
|
|
152
151
|
if (modelSelection) {
|
|
153
|
-
log(
|
|
152
|
+
log(`[browser] Model selection evidence: ${formatBrowserModelSelectionEvidence(modelSelection, runOptions.model)}`);
|
|
154
153
|
}
|
|
155
154
|
const warnings = buildBrowserRunWarnings({
|
|
156
155
|
runOptions,
|
|
@@ -199,7 +198,7 @@ export async function runBrowserSessionExecution({ runOptions, browserConfig, cw
|
|
|
199
198
|
})();
|
|
200
199
|
const { line1, line2 } = formatFinishLine({
|
|
201
200
|
elapsedMs: browserResult.tookMs,
|
|
202
|
-
model: `${runOptions.model}[browser]`,
|
|
201
|
+
model: `${resolveBrowserModelDisplayName({ model: runOptions.model, evidence: modelSelection })}[browser]`,
|
|
203
202
|
tokensPart,
|
|
204
203
|
detailParts: [
|
|
205
204
|
runOptions.file && runOptions.file.length > 0 ? `files=${runOptions.file.length}` : null,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
export function isWsl() {
|
|
4
|
+
if (process.platform !== "linux")
|
|
5
|
+
return false;
|
|
6
|
+
if (process.env.WSL_DISTRO_NAME)
|
|
7
|
+
return true;
|
|
8
|
+
return os.release().toLowerCase().includes("microsoft");
|
|
9
|
+
}
|
|
10
|
+
export function parseWslResolverHost(resolvConf) {
|
|
11
|
+
for (const line of resolvConf.split("\n")) {
|
|
12
|
+
const match = line.match(/^nameserver\s+([0-9.]+)/);
|
|
13
|
+
if (match?.[1]) {
|
|
14
|
+
return match[1].startsWith("127.") ? "127.0.0.1" : match[1];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
export function resolveWslHost() {
|
|
20
|
+
if (!isWsl())
|
|
21
|
+
return null;
|
|
22
|
+
try {
|
|
23
|
+
return parseWslResolverHost(readFileSync("/etc/resolv.conf", "utf8"));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function resolveWslChromeHost(options = {}) {
|
|
30
|
+
const remoteDebugHost = options.remoteDebugHost === undefined
|
|
31
|
+
? process.env.ORACLE_BROWSER_REMOTE_DEBUG_HOST
|
|
32
|
+
: options.remoteDebugHost;
|
|
33
|
+
const wslHostIp = options.wslHostIp === undefined ? process.env.WSL_HOST_IP : options.wslHostIp;
|
|
34
|
+
const override = remoteDebugHost?.trim() || wslHostIp?.trim();
|
|
35
|
+
if (override)
|
|
36
|
+
return override;
|
|
37
|
+
if (options.resolvConf !== undefined) {
|
|
38
|
+
return options.resolvConf === null ? null : parseWslResolverHost(options.resolvConf);
|
|
39
|
+
}
|
|
40
|
+
return resolveWslHost();
|
|
41
|
+
}
|
|
42
|
+
export function resolveWslChromeLaunchRoute(options = {}) {
|
|
43
|
+
const connectHost = resolveWslChromeHost(options);
|
|
44
|
+
const usePatchedLauncher = Boolean(connectHost && connectHost !== "127.0.0.1");
|
|
45
|
+
return {
|
|
46
|
+
connectHost,
|
|
47
|
+
debugBindAddress: usePatchedLauncher ? "0.0.0.0" : connectHost,
|
|
48
|
+
usePatchedLauncher,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import chalk from "chalk";
|
|
3
4
|
import { normalizeThinkingTimeLevel } from "../oracle/thinkingTime.js";
|
|
4
5
|
import { CHATGPT_URL, DEFAULT_MODEL_STRATEGY, DEFAULT_MODEL_TARGET } from "../browser/constants.js";
|
|
5
6
|
import { normalizeChatgptUrl } from "../browser/utils.js";
|
|
@@ -86,6 +87,7 @@ export async function buildBrowserConfig(options) {
|
|
|
86
87
|
const isChatGptModel = baseModel.startsWith("gpt-") && !baseModel.includes("codex");
|
|
87
88
|
const shouldUseOverride = !isChatGptModel && normalizedOverride.length > 0 && normalizedOverride !== baseModel;
|
|
88
89
|
const modelStrategy = normalizeBrowserModelStrategy(options.browserModelStrategy) ?? DEFAULT_MODEL_STRATEGY;
|
|
90
|
+
assertBrowserModelAvailable(options.model, modelStrategy);
|
|
89
91
|
const cookieNames = parseCookieNames(options.browserCookieNames ?? process.env.ORACLE_BROWSER_COOKIE_NAMES);
|
|
90
92
|
let inline = await resolveInlineCookies({
|
|
91
93
|
inlineArg: options.browserInlineCookies,
|
|
@@ -123,38 +125,38 @@ export async function buildBrowserConfig(options) {
|
|
|
123
125
|
url,
|
|
124
126
|
debugPort: selectBrowserPort(options),
|
|
125
127
|
timeoutMs: options.browserTimeout
|
|
126
|
-
?
|
|
128
|
+
? parseBrowserDuration(options.browserTimeout, "--browser-timeout", DEFAULT_BROWSER_TIMEOUT_MS)
|
|
127
129
|
: undefined,
|
|
128
130
|
inputTimeoutMs: options.browserInputTimeout
|
|
129
|
-
?
|
|
131
|
+
? parseBrowserDuration(options.browserInputTimeout, "--browser-input-timeout", DEFAULT_BROWSER_INPUT_TIMEOUT_MS)
|
|
130
132
|
: undefined,
|
|
131
133
|
attachmentTimeoutMs: options.browserAttachmentTimeout
|
|
132
|
-
?
|
|
134
|
+
? parseBrowserDuration(options.browserAttachmentTimeout, "--browser-attachment-timeout", DEFAULT_BROWSER_ATTACHMENT_TIMEOUT_MS)
|
|
133
135
|
: undefined,
|
|
134
136
|
assistantRecheckDelayMs: options.browserRecheckDelay
|
|
135
|
-
?
|
|
137
|
+
? parseBrowserDuration(options.browserRecheckDelay, "--browser-recheck-delay", 0)
|
|
136
138
|
: undefined,
|
|
137
139
|
assistantRecheckTimeoutMs: options.browserRecheckTimeout
|
|
138
|
-
?
|
|
140
|
+
? parseBrowserDuration(options.browserRecheckTimeout, "--browser-recheck-timeout", DEFAULT_BROWSER_RECHECK_TIMEOUT_MS)
|
|
139
141
|
: undefined,
|
|
140
142
|
reuseChromeWaitMs: options.browserReuseWait
|
|
141
|
-
?
|
|
143
|
+
? parseBrowserDuration(options.browserReuseWait, "--browser-reuse-wait", 0)
|
|
142
144
|
: undefined,
|
|
143
145
|
profileLockTimeoutMs: options.browserProfileLockTimeout
|
|
144
|
-
?
|
|
146
|
+
? parseBrowserDuration(options.browserProfileLockTimeout, "--browser-profile-lock-timeout", 0)
|
|
145
147
|
: undefined,
|
|
146
148
|
maxConcurrentTabs: parseMaxConcurrentTabs(options.browserMaxConcurrentTabs),
|
|
147
149
|
autoReattachDelayMs: options.browserAutoReattachDelay
|
|
148
|
-
?
|
|
150
|
+
? parseBrowserDuration(options.browserAutoReattachDelay, "--browser-auto-reattach-delay", 0)
|
|
149
151
|
: undefined,
|
|
150
152
|
autoReattachIntervalMs: options.browserAutoReattachInterval
|
|
151
|
-
?
|
|
153
|
+
? parseBrowserDuration(options.browserAutoReattachInterval, "--browser-auto-reattach-interval", 0)
|
|
152
154
|
: undefined,
|
|
153
155
|
autoReattachTimeoutMs: options.browserAutoReattachTimeout
|
|
154
|
-
?
|
|
156
|
+
? parseBrowserDuration(options.browserAutoReattachTimeout, "--browser-auto-reattach-timeout", DEFAULT_BROWSER_AUTO_REATTACH_TIMEOUT_MS)
|
|
155
157
|
: undefined,
|
|
156
158
|
cookieSyncWaitMs: options.browserCookieWait
|
|
157
|
-
?
|
|
159
|
+
? parseBrowserDuration(options.browserCookieWait, "--browser-cookie-wait", 0)
|
|
158
160
|
: undefined,
|
|
159
161
|
cookieSync: options.browserNoCookieSync ? false : undefined,
|
|
160
162
|
cookieNames,
|
|
@@ -178,6 +180,17 @@ export async function buildBrowserConfig(options) {
|
|
|
178
180
|
archiveConversations: options.browserArchive,
|
|
179
181
|
};
|
|
180
182
|
}
|
|
183
|
+
function assertBrowserModelAvailable(model, modelStrategy) {
|
|
184
|
+
if (modelStrategy !== "select")
|
|
185
|
+
return;
|
|
186
|
+
const normalized = normalizeChatGptModelForBrowser(model);
|
|
187
|
+
if (normalized !== "gpt-5.2" &&
|
|
188
|
+
normalized !== "gpt-5.2-instant" &&
|
|
189
|
+
normalized !== "gpt-5.2-thinking") {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
throw new Error(`Browser model "${model}" is retired because ChatGPT no longer offers GPT-5.2 base, Instant, or Thinking. Choose a current GPT-5.5/GPT-5.6 browser model, use --browser-model-strategy current to keep ChatGPT's active model, or use --engine api to retain the GPT-5.2 API alias.`);
|
|
193
|
+
}
|
|
181
194
|
function validateAttachRunningOptions(options, { attachRunning, hasInlineCookies, }) {
|
|
182
195
|
if (!attachRunning) {
|
|
183
196
|
return;
|
|
@@ -218,6 +231,13 @@ function parseMaxConcurrentTabs(raw) {
|
|
|
218
231
|
}
|
|
219
232
|
return Math.trunc(value);
|
|
220
233
|
}
|
|
234
|
+
function parseBrowserDuration(raw, optionName, fallbackMs) {
|
|
235
|
+
const parsed = parseDuration(raw, Number.NaN);
|
|
236
|
+
if (Number.isFinite(parsed))
|
|
237
|
+
return parsed;
|
|
238
|
+
console.log(chalk.yellow(`Warning: invalid ${optionName} duration "${raw}"; using fallback ${fallbackMs}ms.`));
|
|
239
|
+
return fallbackMs;
|
|
240
|
+
}
|
|
221
241
|
export function mapModelToBrowserLabel(model) {
|
|
222
242
|
const normalized = normalizeChatGptModelForBrowser(model);
|
|
223
243
|
// Iterate ordered array to find first match (most specific first)
|
package/dist/src/cli/detach.js
CHANGED
|
@@ -1,14 +1,31 @@
|
|
|
1
1
|
import { isProModel } from "../oracle/modelResolver.js";
|
|
2
2
|
export function shouldDetachSession({
|
|
3
3
|
// Params kept for policy tweaks.
|
|
4
|
-
engine, model, waitPreference, disableDetachEnv, }) {
|
|
4
|
+
engine, model, reasoningMode, waitPreference, disableDetachEnv, }) {
|
|
5
5
|
if (disableDetachEnv)
|
|
6
6
|
return false;
|
|
7
|
-
//
|
|
7
|
+
// Keep long local browser Pro work in a separate process even while the CLI
|
|
8
|
+
// stays attached to its session log. If the foreground stream is interrupted,
|
|
9
|
+
// the worker can still finish the browser run and persist the answer.
|
|
10
|
+
if (engine === "browser" && isProModel(model))
|
|
11
|
+
return true;
|
|
12
|
+
// For API runs, explicit --wait keeps execution in the foreground.
|
|
8
13
|
if (waitPreference)
|
|
9
14
|
return false;
|
|
10
|
-
//
|
|
11
|
-
if (isProModel(model) && engine === "api")
|
|
15
|
+
// Pro-tier API runs start detached by default.
|
|
16
|
+
if ((isProModel(model) || reasoningMode === "pro") && engine === "api")
|
|
12
17
|
return true;
|
|
13
18
|
return false;
|
|
14
19
|
}
|
|
20
|
+
export function stopDetachedWorker(workerPid, kill = process.kill) {
|
|
21
|
+
try {
|
|
22
|
+
kill(workerPid, "SIGTERM");
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ESRCH") {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
package/dist/src/cli/dryRun.js
CHANGED
|
@@ -5,6 +5,7 @@ import { assembleBrowserPrompt } from "../browser/prompt.js";
|
|
|
5
5
|
import { buildTokenEstimateSuffix, formatAttachmentLabel } from "../browser/promptSummary.js";
|
|
6
6
|
import { buildCookiePlan } from "../browser/policies.js";
|
|
7
7
|
import { describeBrowserControlPlan, formatBrowserControlPlan } from "../browser/controlPlan.js";
|
|
8
|
+
import { formatBrowserModelTarget } from "../browser/modelDisplay.js";
|
|
8
9
|
export async function runDryRunSummary({ engine, runOptions, cwd, version, log, browserConfig, }, deps = {}) {
|
|
9
10
|
if (engine === "browser") {
|
|
10
11
|
await runBrowserDryRun({ runOptions, cwd, version, log, browserConfig }, deps);
|
|
@@ -45,7 +46,12 @@ async function runBrowserDryRun({ runOptions, cwd, version, log, browserConfig,
|
|
|
45
46
|
const assemblePromptImpl = deps.assembleBrowserPromptImpl ?? assembleBrowserPrompt;
|
|
46
47
|
const artifacts = await assemblePromptImpl(runOptions, { cwd });
|
|
47
48
|
const suffix = buildTokenEstimateSuffix(artifacts);
|
|
48
|
-
const
|
|
49
|
+
const displayModel = formatBrowserModelTarget({
|
|
50
|
+
model: runOptions.model,
|
|
51
|
+
desiredModel: browserConfig?.desiredModel,
|
|
52
|
+
modelStrategy: browserConfig?.modelStrategy,
|
|
53
|
+
});
|
|
54
|
+
const headerLine = `[dry-run] Oracle (${version}) would launch browser mode (${displayModel}) with ~${artifacts.estimatedInputTokens.toLocaleString()} tokens${suffix}.`;
|
|
49
55
|
log(chalk.cyan(headerLine));
|
|
50
56
|
logBrowserControlPlan(browserConfig, log, "dry-run");
|
|
51
57
|
logBrowserFollowUpSummary(runOptions.browserFollowUps, log, "dry-run");
|
|
@@ -95,7 +101,12 @@ export async function runBrowserPreview({ runOptions, cwd, version, previewMode,
|
|
|
95
101
|
const assemblePromptImpl = deps.assembleBrowserPromptImpl ?? assembleBrowserPrompt;
|
|
96
102
|
const artifacts = await assemblePromptImpl(runOptions, { cwd });
|
|
97
103
|
const suffix = buildTokenEstimateSuffix(artifacts);
|
|
98
|
-
const
|
|
104
|
+
const displayModel = formatBrowserModelTarget({
|
|
105
|
+
model: runOptions.model,
|
|
106
|
+
desiredModel: browserConfig?.desiredModel,
|
|
107
|
+
modelStrategy: browserConfig?.modelStrategy,
|
|
108
|
+
});
|
|
109
|
+
const headerLine = `[preview] Oracle (${version}) browser mode (${displayModel}) with ~${artifacts.estimatedInputTokens.toLocaleString()} tokens${suffix}.`;
|
|
99
110
|
log(chalk.cyan(headerLine));
|
|
100
111
|
logBrowserControlPlan(browserConfig, log, "preview");
|
|
101
112
|
logBrowserFollowUpSummary(runOptions.browserFollowUps, log, "preview");
|
package/dist/src/cli/engine.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isProModel } from "../oracle/modelResolver.js";
|
|
2
|
-
export function defaultWaitPreference(model, engine) {
|
|
2
|
+
export function defaultWaitPreference(model, engine, reasoningMode) {
|
|
3
3
|
// Pro-class API runs can take a long time; prefer non-blocking unless explicitly overridden.
|
|
4
|
-
if (engine === "api" && isProModel(model)) {
|
|
4
|
+
if (engine === "api" && (isProModel(model) || reasoningMode === "pro")) {
|
|
5
5
|
return false;
|
|
6
6
|
}
|
|
7
7
|
return true; // browser or non-pro models are fast enough to block by default
|
package/dist/src/cli/options.js
CHANGED
|
@@ -187,6 +187,10 @@ export function resolveApiModel(modelValue) {
|
|
|
187
187
|
if (normalized.includes("/")) {
|
|
188
188
|
return normalized;
|
|
189
189
|
}
|
|
190
|
+
const gpt56Label = parseBrowserGpt56Label(normalized);
|
|
191
|
+
if (gpt56Label?.variant.split(" ").includes("pro")) {
|
|
192
|
+
throw new InvalidArgumentError("GPT-5.6 Pro is an API reasoning mode, not a model slug. Use --model gpt-5.6-sol --reasoning-mode pro.");
|
|
193
|
+
}
|
|
190
194
|
if (normalized.includes("grok")) {
|
|
191
195
|
return "grok-4.1";
|
|
192
196
|
}
|