@steipete/oracle 0.18.0 → 0.19.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 +1 -1
- package/dist/bin/oracle-cli.js +65 -63
- package/dist/bin/oracle-mcp.js +0 -0
- package/dist/src/browser/actions/archiveConversation.js +31 -7
- package/dist/src/browser/actions/assistantResponse.js +43 -1
- package/dist/src/browser/actions/attachmentContext.js +239 -0
- package/dist/src/browser/actions/attachmentDataTransfer.js +19 -1
- package/dist/src/browser/actions/attachmentEvidence.js +78 -0
- package/dist/src/browser/actions/attachmentProgress.js +41 -0
- package/dist/src/browser/actions/attachmentPrompt.js +130 -0
- package/dist/src/browser/actions/attachments.js +306 -355
- package/dist/src/browser/actions/deepResearch.js +2 -1
- package/dist/src/browser/actions/modelSelection.js +13 -3
- package/dist/src/browser/actions/navigation.js +1 -1
- package/dist/src/browser/actions/promptComposer.js +361 -186
- package/dist/src/browser/actions/remoteFileTransfer.js +4 -2
- package/dist/src/browser/actions/thinkingStatus.js +2 -5
- package/dist/src/browser/actions/thinkingTime.js +119 -16
- package/dist/src/browser/attachRunning.js +46 -2
- package/dist/src/browser/attachmentValidation.js +53 -0
- package/dist/src/browser/cancellation.js +105 -0
- package/dist/src/browser/chatgptImages.js +2 -0
- package/dist/src/browser/chatgptThrottle.js +104 -0
- package/dist/src/browser/chromeLifecycle.js +267 -43
- package/dist/src/browser/config.js +13 -0
- package/dist/src/browser/detect.js +1 -1
- package/dist/src/browser/index.js +444 -261
- package/dist/src/browser/modelDisplay.js +9 -0
- package/dist/src/browser/pageActions.js +2 -2
- package/dist/src/browser/profileState.js +114 -9
- package/dist/src/browser/projectSourcesRunner.js +83 -15
- package/dist/src/browser/prompt.js +272 -113
- package/dist/src/browser/providers/chatgptDomProvider.js +2 -0
- package/dist/src/browser/reattach.js +10 -3
- package/dist/src/browser/sessionRunner.js +71 -11
- package/dist/src/browser/tabLeaseRegistry.js +414 -38
- package/dist/src/browser/utils.js +20 -2
- package/dist/src/cli/browserConfig.js +3 -1
- package/dist/src/cli/browserDefaults.js +10 -0
- package/dist/src/cli/browserTabs.js +12 -29
- package/dist/src/cli/detachedSession.js +69 -0
- package/dist/src/cli/dryRun.js +58 -48
- package/dist/src/cli/harvestIntegrity.js +140 -0
- package/dist/src/cli/serveBrowserConfig.js +24 -0
- package/dist/src/cli/sessionDisplay.js +9 -2
- package/dist/src/cli/sessionRunner.js +91 -7
- package/dist/src/config.js +1 -0
- package/dist/src/gemini-web/client.js +67 -21
- package/dist/src/gemini-web/executor.js +4 -0
- package/dist/src/gemini-web/http.js +15 -0
- package/dist/src/gemini-web/models.js +15 -11
- package/dist/src/mcp/server.js +11 -14
- package/dist/src/mcp/tools/chatgptImage.js +4 -4
- package/dist/src/mcp/tools/consult.js +84 -22
- package/dist/src/mcp/tools/projectSources.js +4 -6
- package/dist/src/mcp/tools/sessionResources.js +1 -1
- package/dist/src/mcp/tools/sessions.js +2 -2
- package/dist/src/mcp/tools/wait.js +182 -0
- package/dist/src/mcp/types.js +7 -0
- package/dist/src/oracle/errors.js +11 -0
- package/dist/src/oracle/providerRoutePlan.js +3 -2
- package/dist/src/remote/client.js +70 -6
- package/dist/src/remote/health.js +6 -2
- package/dist/src/remote/runSlots.js +83 -0
- package/dist/src/remote/server.js +345 -71
- package/dist/src/sessionManager.js +36 -1
- 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 +25 -23
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/dist/bin/oracle.js +0 -683
- package/dist/docs-site/.nojekyll +0 -0
- package/dist/docs-site/CNAME +0 -1
- package/dist/docs-site/RELEASING.html +0 -410
- package/dist/docs-site/agents.html +0 -374
- package/dist/docs-site/anthropic.html +0 -368
- package/dist/docs-site/bridge.html +0 -400
- package/dist/docs-site/browser-mode.html +0 -588
- package/dist/docs-site/chromium-forks.html +0 -347
- package/dist/docs-site/cli-reference.html +0 -346
- package/dist/docs-site/configuration.html +0 -436
- package/dist/docs-site/favicon.svg +0 -14
- package/dist/docs-site/followup.html +0 -368
- package/dist/docs-site/gemini.html +0 -379
- package/dist/docs-site/grok.html +0 -325
- package/dist/docs-site/index.html +0 -360
- package/dist/docs-site/install.html +0 -335
- package/dist/docs-site/linux.html +0 -321
- package/dist/docs-site/llms.txt +0 -43
- package/dist/docs-site/manual-tests.html +0 -595
- package/dist/docs-site/mcp.html +0 -368
- package/dist/docs-site/multimodel.html +0 -364
- package/dist/docs-site/mythical-pro-agents.html +0 -360
- package/dist/docs-site/notifier.html +0 -338
- package/dist/docs-site/openai-endpoints.html +0 -387
- package/dist/docs-site/openrouter.html +0 -344
- package/dist/docs-site/quickstart.html +0 -369
- package/dist/docs-site/refactor/ux.html +0 -532
- package/dist/docs-site/sessions.html +0 -388
- package/dist/docs-site/social-card.png +0 -0
- package/dist/docs-site/social-card.svg +0 -79
- package/dist/docs-site/spec.html +0 -363
- package/dist/docs-site/testing.html +0 -320
- package/dist/docs-site/tui-debug.html +0 -326
- package/dist/docs-site/windows-work.html +0 -323
- package/dist/docs-site/windows.html +0 -320
- 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/chromeCookies.js +0 -312
- package/dist/src/browser/keytarShim.js +0 -56
- package/dist/src/browser/profileSync.js +0 -141
- package/dist/src/browser/windowsCookies.js +0 -219
|
@@ -1,19 +1,29 @@
|
|
|
1
|
-
import { rm } from "node:fs/promises";
|
|
1
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import * as childProcess from "node:child_process";
|
|
2
3
|
import net from "node:net";
|
|
4
|
+
import path from "node:path";
|
|
3
5
|
import CDP from "chrome-remote-interface";
|
|
4
|
-
import { launch, Launcher } from "chrome-launcher";
|
|
6
|
+
import { launch, Launcher, } from "chrome-launcher";
|
|
5
7
|
import { cleanupStaleProfileState } from "./profileState.js";
|
|
6
8
|
import { delay } from "./utils.js";
|
|
7
9
|
import { isWsl, resolveWslChromeLaunchRoute } from "./wslHost.js";
|
|
8
10
|
export async function launchChrome(config, userDataDir, logger) {
|
|
9
11
|
const { connectHost, debugBindAddress, usePatchedLauncher } = resolveWslChromeLaunchRoute();
|
|
10
12
|
const debugPort = config.debugPort ?? parseDebugPortEnv();
|
|
13
|
+
const usingCopiedProfile = Boolean(config.copyProfileSource);
|
|
14
|
+
const detachSharedChrome = shouldDetachSharedChrome(config);
|
|
15
|
+
const launchedProfileDirectory = usingCopiedProfile && config.chromeProfile ? config.chromeProfile : "Default";
|
|
16
|
+
await prepareChromeWindowStateForHiddenLaunch({
|
|
17
|
+
config,
|
|
18
|
+
userDataDir,
|
|
19
|
+
profileDirectory: launchedProfileDirectory,
|
|
20
|
+
logger,
|
|
21
|
+
});
|
|
11
22
|
const chromeFlags = buildChromeFlags(config.headless ?? false, debugBindAddress, config.hideWindow ?? false);
|
|
12
23
|
// copy-profile reuses a copied signed-in profile whose cookies are
|
|
13
24
|
// Keychain-encrypted, so it must launch with the real Keychain (not mocked):
|
|
14
25
|
// strip the keychain-mocking flags from both chrome-launcher's defaults and
|
|
15
26
|
// Oracle's set, and ignore the defaults so they aren't re-added.
|
|
16
|
-
const usingCopiedProfile = Boolean(config.copyProfileSource);
|
|
17
27
|
if (usingCopiedProfile && config.chromeProfile) {
|
|
18
28
|
chromeFlags.push(`--profile-directory=${config.chromeProfile}`);
|
|
19
29
|
}
|
|
@@ -26,38 +36,231 @@ export async function launchChrome(config, userDataDir, logger) {
|
|
|
26
36
|
host: connectHost ?? "127.0.0.1",
|
|
27
37
|
requestedPort: debugPort ?? undefined,
|
|
28
38
|
ignoreDefaultFlags: launchOptions.ignoreDefaultFlags,
|
|
39
|
+
detachSharedChrome,
|
|
29
40
|
})
|
|
30
|
-
: await
|
|
41
|
+
: await launchWithStableProcessLifecycle({
|
|
31
42
|
chromePath: config.chromePath ?? undefined,
|
|
32
43
|
chromeFlags: launchOptions.chromeFlags,
|
|
33
44
|
userDataDir,
|
|
34
45
|
handleSIGINT: false,
|
|
35
46
|
port: debugPort ?? undefined,
|
|
36
47
|
ignoreDefaultFlags: launchOptions.ignoreDefaultFlags,
|
|
37
|
-
});
|
|
48
|
+
}, detachSharedChrome);
|
|
38
49
|
const pidLabel = typeof launcher.pid === "number" ? ` (pid ${launcher.pid})` : "";
|
|
39
50
|
const hostLabel = connectHost ? ` on ${connectHost}` : "";
|
|
40
51
|
logger(`Launched Chrome${pidLabel} on port ${launcher.port}${hostLabel}`);
|
|
52
|
+
if (detachSharedChrome) {
|
|
53
|
+
logger("[browser] Browser control: Windows Chrome lifecycle detached=true; windowsHide=true.");
|
|
54
|
+
}
|
|
41
55
|
return Object.assign(launcher, { host: connectHost ?? "127.0.0.1" });
|
|
42
56
|
}
|
|
43
|
-
|
|
57
|
+
function shouldDetachSharedChrome(config, platform = process.platform) {
|
|
58
|
+
return platform === "win32" && config.manualLogin === true && !config.copyProfileSource;
|
|
59
|
+
}
|
|
60
|
+
export const shouldDetachSharedChromeForTest = shouldDetachSharedChrome;
|
|
61
|
+
const spawnDetachedChromeOnWindows = ((command, args, options) => {
|
|
62
|
+
const child = childProcess.spawn(command, args, resolveChromeChildSpawnOptions(options, "win32"));
|
|
63
|
+
child.unref();
|
|
64
|
+
return child;
|
|
65
|
+
});
|
|
66
|
+
function resolveChromeChildSpawnOptions(options, platform = process.platform) {
|
|
67
|
+
return platform === "win32"
|
|
68
|
+
? {
|
|
69
|
+
...options,
|
|
70
|
+
detached: true,
|
|
71
|
+
windowsHide: true,
|
|
72
|
+
}
|
|
73
|
+
: options;
|
|
74
|
+
}
|
|
75
|
+
export function resolveChromeChildSpawnOptionsForTest(options, platform) {
|
|
76
|
+
return resolveChromeChildSpawnOptions(options, platform);
|
|
77
|
+
}
|
|
78
|
+
function chromeLauncherModuleOverrides(detachSharedChrome, platform = process.platform) {
|
|
79
|
+
return detachSharedChrome && platform === "win32"
|
|
80
|
+
? { spawn: spawnDetachedChromeOnWindows }
|
|
81
|
+
: undefined;
|
|
82
|
+
}
|
|
83
|
+
async function launchWithStableProcessLifecycle(options, detachSharedChrome) {
|
|
84
|
+
if (!detachSharedChrome) {
|
|
85
|
+
return launch(options);
|
|
86
|
+
}
|
|
87
|
+
const launcher = new Launcher(options, chromeLauncherModuleOverrides(detachSharedChrome));
|
|
88
|
+
await launcher.launch();
|
|
89
|
+
return launchedChromeFromLauncher(launcher);
|
|
90
|
+
}
|
|
91
|
+
function launchedChromeFromLauncher(launcher) {
|
|
92
|
+
return {
|
|
93
|
+
pid: launcher.pid ?? 0,
|
|
94
|
+
port: launcher.port ?? 0,
|
|
95
|
+
process: launcher.chromeProcess,
|
|
96
|
+
kill: () => launcher.kill(),
|
|
97
|
+
remoteDebuggingPipes: launcher.remoteDebuggingPipes,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export async function positionChromeWindowOffscreen(client, userDataDir, logger) {
|
|
44
101
|
if (process.platform !== "darwin") {
|
|
45
102
|
logger("Window hiding is only supported on macOS");
|
|
46
103
|
return;
|
|
47
104
|
}
|
|
105
|
+
let savedState = false;
|
|
48
106
|
try {
|
|
49
107
|
const { windowId } = await client.Browser.getWindowForTarget();
|
|
108
|
+
if (!(await readSavedChromeWindowState(userDataDir))) {
|
|
109
|
+
const { bounds } = await client.Browser.getWindowBounds({ windowId });
|
|
110
|
+
await writeSavedChromeWindowState(userDataDir, bounds);
|
|
111
|
+
savedState = true;
|
|
112
|
+
}
|
|
50
113
|
await client.Browser.setWindowBounds({
|
|
51
114
|
windowId,
|
|
52
115
|
bounds: { left: -32_000, top: -32_000, windowState: "normal" },
|
|
53
116
|
});
|
|
54
|
-
logger("Chrome window positioned off-screen");
|
|
55
117
|
}
|
|
56
118
|
catch (error) {
|
|
119
|
+
if (savedState) {
|
|
120
|
+
await rm(chromeWindowStatePath(userDataDir), { force: true }).catch(() => undefined);
|
|
121
|
+
}
|
|
57
122
|
const message = error instanceof Error ? error.message : String(error);
|
|
58
123
|
logger(`Failed to position Chrome window off-screen: ${message}`);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
logger("Chrome window positioned off-screen");
|
|
127
|
+
}
|
|
128
|
+
export async function positionChromeWindowOnscreen(client, userDataDir, logger) {
|
|
129
|
+
if (process.platform !== "darwin") {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
const savedState = await readSavedChromeWindowState(userDataDir);
|
|
134
|
+
if (!savedState) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const { windowId } = await client.Browser.getWindowForTarget();
|
|
138
|
+
await client.Browser.setWindowBounds({
|
|
139
|
+
windowId,
|
|
140
|
+
bounds: restoreWindowBounds(savedState.bounds),
|
|
141
|
+
});
|
|
142
|
+
await rm(chromeWindowStatePath(userDataDir), { force: true });
|
|
143
|
+
logger("Chrome window restored to its pre-hide bounds");
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
147
|
+
logger(`Failed to position Chrome window on-screen: ${message}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const CHROME_WINDOW_STATE_FILENAME = "oracle-window-state.json";
|
|
151
|
+
const DEFAULT_VISIBLE_WINDOW_BOUNDS = {
|
|
152
|
+
left: 80,
|
|
153
|
+
top: 80,
|
|
154
|
+
width: 1280,
|
|
155
|
+
height: 720,
|
|
156
|
+
windowState: "normal",
|
|
157
|
+
};
|
|
158
|
+
function chromeWindowStatePath(userDataDir) {
|
|
159
|
+
return path.join(userDataDir, CHROME_WINDOW_STATE_FILENAME);
|
|
160
|
+
}
|
|
161
|
+
async function readSavedChromeWindowState(userDataDir) {
|
|
162
|
+
try {
|
|
163
|
+
const parsed = JSON.parse(await readFile(chromeWindowStatePath(userDataDir), "utf8"));
|
|
164
|
+
const bounds = parseChromeWindowBounds(parsed.bounds);
|
|
165
|
+
if (parsed.version !== 1 || !bounds) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
return { version: 1, bounds };
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return null;
|
|
59
172
|
}
|
|
60
173
|
}
|
|
174
|
+
async function writeSavedChromeWindowState(userDataDir, bounds) {
|
|
175
|
+
await mkdir(userDataDir, { recursive: true });
|
|
176
|
+
await writeFile(chromeWindowStatePath(userDataDir), `${JSON.stringify({ version: 1, bounds })}\n`, "utf8");
|
|
177
|
+
}
|
|
178
|
+
async function prepareChromeWindowStateForHiddenLaunch({ config, userDataDir, profileDirectory, logger, }) {
|
|
179
|
+
if (process.platform !== "darwin" ||
|
|
180
|
+
config.headless ||
|
|
181
|
+
!config.hideWindow ||
|
|
182
|
+
(await readSavedChromeWindowState(userDataDir))) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const bounds = (await readPersistedChromeWindowBounds(userDataDir, profileDirectory)) ??
|
|
186
|
+
DEFAULT_VISIBLE_WINDOW_BOUNDS;
|
|
187
|
+
await writeSavedChromeWindowState(userDataDir, bounds);
|
|
188
|
+
logger("Recorded Chrome window placement before hidden launch");
|
|
189
|
+
}
|
|
190
|
+
async function readPersistedChromeWindowBounds(userDataDir, profileDirectory) {
|
|
191
|
+
const root = path.resolve(userDataDir);
|
|
192
|
+
const profile = path.resolve(root, profileDirectory);
|
|
193
|
+
if (path.dirname(profile) !== root) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
const preferences = JSON.parse(await readFile(path.join(profile, "Preferences"), "utf8"));
|
|
198
|
+
return persistedPlacementToBounds(preferences.browser?.window_placement);
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function persistedPlacementToBounds(placement) {
|
|
205
|
+
if (!placement) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
if (placement.maximized === true) {
|
|
209
|
+
return { windowState: "maximized" };
|
|
210
|
+
}
|
|
211
|
+
const left = finiteNumber(placement.left);
|
|
212
|
+
const top = finiteNumber(placement.top);
|
|
213
|
+
const right = finiteNumber(placement.right);
|
|
214
|
+
const bottom = finiteNumber(placement.bottom);
|
|
215
|
+
if (left === null || top === null || right === null || bottom === null) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
const width = right - left;
|
|
219
|
+
const height = bottom - top;
|
|
220
|
+
if (width <= 0 || height <= 0) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
return { left, top, width, height, windowState: "normal" };
|
|
224
|
+
}
|
|
225
|
+
function parseChromeWindowBounds(value) {
|
|
226
|
+
if (!value || typeof value !== "object") {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
const bounds = value;
|
|
230
|
+
const windowState = bounds.windowState ?? "normal";
|
|
231
|
+
if (windowState !== "normal") {
|
|
232
|
+
return ["minimized", "maximized", "fullscreen"].includes(windowState) ? { windowState } : null;
|
|
233
|
+
}
|
|
234
|
+
const left = finiteNumber(bounds.left);
|
|
235
|
+
const top = finiteNumber(bounds.top);
|
|
236
|
+
const width = finiteNumber(bounds.width);
|
|
237
|
+
const height = finiteNumber(bounds.height);
|
|
238
|
+
if (left === null ||
|
|
239
|
+
top === null ||
|
|
240
|
+
width === null ||
|
|
241
|
+
height === null ||
|
|
242
|
+
width <= 0 ||
|
|
243
|
+
height <= 0) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
return { left, top, width, height, windowState: "normal" };
|
|
247
|
+
}
|
|
248
|
+
function finiteNumber(value) {
|
|
249
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
250
|
+
}
|
|
251
|
+
function restoreWindowBounds(bounds) {
|
|
252
|
+
const windowState = bounds.windowState ?? "normal";
|
|
253
|
+
if (windowState !== "normal") {
|
|
254
|
+
return { windowState };
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
left: bounds.left ?? 80,
|
|
258
|
+
top: bounds.top ?? 80,
|
|
259
|
+
width: bounds.width,
|
|
260
|
+
height: bounds.height,
|
|
261
|
+
windowState: "normal",
|
|
262
|
+
};
|
|
263
|
+
}
|
|
61
264
|
export function registerTerminationHooks(chrome, userDataDir, keepBrowser, logger, opts) {
|
|
62
265
|
const signals = ["SIGINT", "SIGTERM", "SIGQUIT"];
|
|
63
266
|
let handling;
|
|
@@ -68,7 +271,8 @@ export function registerTerminationHooks(chrome, userDataDir, keepBrowser, logge
|
|
|
68
271
|
handling = true;
|
|
69
272
|
const inFlight = opts?.isInFlight?.() ?? false;
|
|
70
273
|
const forceCleanup = opts?.forceProfileCleanup ?? false;
|
|
71
|
-
const
|
|
274
|
+
const preserveSharedChrome = opts?.preserveSharedChromeOnSignal ?? false;
|
|
275
|
+
const leaveRunning = (keepBrowser || inFlight || preserveSharedChrome) && !forceCleanup;
|
|
72
276
|
if (leaveRunning) {
|
|
73
277
|
logger(`Received ${signal}; leaving Chrome running${inFlight ? " (assistant response pending)" : ""}`);
|
|
74
278
|
}
|
|
@@ -136,11 +340,12 @@ export async function connectToRemoteChrome(host, port, logger, targetUrl, brows
|
|
|
136
340
|
approvalWaitMs: options?.approvalWaitMs,
|
|
137
341
|
});
|
|
138
342
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
343
|
+
const newTargetUrl = targetUrl || (options?.fallbackToDefault === false ? "about:blank" : undefined);
|
|
344
|
+
if (newTargetUrl) {
|
|
345
|
+
const targetConnection = await connectToNewTarget(host, port, newTargetUrl, logger, {
|
|
346
|
+
opened: () => `Opened dedicated remote Chrome tab targeting ${newTargetUrl}`,
|
|
347
|
+
openFailed: (message) => `Failed to open dedicated remote Chrome tab (${message}); ${options?.fallbackToDefault === false ? "refusing to reuse an unrelated tab" : "falling back to first target"}.`,
|
|
348
|
+
attachFailed: (targetId, message) => `Failed to attach to dedicated remote Chrome tab ${targetId} (${message}); ${options?.fallbackToDefault === false ? "refusing to reuse an unrelated tab" : "falling back to first target"}.`,
|
|
144
349
|
closeFailed: (targetId, message) => `Failed to close unused remote Chrome tab ${targetId}: ${message}`,
|
|
145
350
|
});
|
|
146
351
|
if (targetConnection) {
|
|
@@ -153,6 +358,9 @@ export async function connectToRemoteChrome(host, port, logger, targetUrl, brows
|
|
|
153
358
|
},
|
|
154
359
|
};
|
|
155
360
|
}
|
|
361
|
+
if (options?.fallbackToDefault === false) {
|
|
362
|
+
throw new Error("Unable to create a dedicated remote Chrome tab; refusing to reuse an unrelated conversation.");
|
|
363
|
+
}
|
|
156
364
|
}
|
|
157
365
|
const fallbackClient = await CDP({ host, port });
|
|
158
366
|
logger(`Connected to remote Chrome DevTools protocol at ${host}:${port}`);
|
|
@@ -183,7 +391,7 @@ export async function listRemoteChromeTargets(options) {
|
|
|
183
391
|
const targets = await CDP.List({ host: options.host, port: options.port });
|
|
184
392
|
return targets;
|
|
185
393
|
}
|
|
186
|
-
const browser = await
|
|
394
|
+
const browser = await connectToBrowserWebSocket(options.host, options.port, options.browserWSEndpoint, options.logger ?? (() => { }), options.approvalWaitMs);
|
|
187
395
|
try {
|
|
188
396
|
const result = await browser.Target.getTargets();
|
|
189
397
|
return (result.targetInfos ?? []).map((target) => ({
|
|
@@ -241,33 +449,54 @@ async function connectToBrowserWebSocket(host, port, browserWSEndpoint, logger,
|
|
|
241
449
|
if (!approvalWaitMs || approvalWaitMs <= 0) {
|
|
242
450
|
return (await CDP({ target: browserWSEndpoint, local: true }));
|
|
243
451
|
}
|
|
244
|
-
logger(`Waiting for Chrome remote debugging approval for ${host}:${port}...`);
|
|
245
|
-
const
|
|
452
|
+
logger(`[browser] Waiting for Chrome remote debugging approval for ${host}:${port}...`);
|
|
453
|
+
const startedAt = Date.now();
|
|
454
|
+
const deadline = startedAt + approvalWaitMs;
|
|
455
|
+
const progress = setInterval(() => {
|
|
456
|
+
logger(`[browser] Still waiting for Chrome remote debugging approval for ${host}:${port} (${formatApprovalWait(Date.now() - startedAt)} elapsed). Click Allow in an open Chrome window.`);
|
|
457
|
+
}, 15_000);
|
|
246
458
|
let lastApprovalError;
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
459
|
+
try {
|
|
460
|
+
while (Date.now() < deadline) {
|
|
461
|
+
const remainingMs = Math.max(1, deadline - Date.now());
|
|
462
|
+
let timeout;
|
|
463
|
+
let expired = false;
|
|
464
|
+
try {
|
|
465
|
+
const connecting = CDP({ target: browserWSEndpoint, local: true }).then(async (client) => {
|
|
466
|
+
// An approval arriving after our deadline must not leak a connection.
|
|
467
|
+
if (expired)
|
|
468
|
+
await client.close().catch(() => undefined);
|
|
469
|
+
return client;
|
|
470
|
+
});
|
|
471
|
+
return await Promise.race([
|
|
472
|
+
connecting,
|
|
473
|
+
new Promise((_, reject) => {
|
|
474
|
+
timeout = setTimeout(() => {
|
|
475
|
+
expired = true;
|
|
476
|
+
reject(new Error("__oracle_remote_debugging_approval_timeout__"));
|
|
477
|
+
}, remainingMs);
|
|
478
|
+
}),
|
|
479
|
+
]);
|
|
480
|
+
}
|
|
481
|
+
catch (error) {
|
|
482
|
+
if (error instanceof Error &&
|
|
483
|
+
error.message === "__oracle_remote_debugging_approval_timeout__") {
|
|
484
|
+
break;
|
|
485
|
+
}
|
|
486
|
+
if (!isRemoteDebuggingApprovalError(error)) {
|
|
487
|
+
throw error;
|
|
488
|
+
}
|
|
489
|
+
lastApprovalError = error;
|
|
263
490
|
}
|
|
264
|
-
|
|
265
|
-
|
|
491
|
+
finally {
|
|
492
|
+
clearTimeout(timeout);
|
|
266
493
|
}
|
|
267
|
-
lastApprovalError = error;
|
|
268
494
|
await delay(Math.min(500, Math.max(0, deadline - Date.now())));
|
|
269
495
|
}
|
|
270
496
|
}
|
|
497
|
+
finally {
|
|
498
|
+
clearInterval(progress);
|
|
499
|
+
}
|
|
271
500
|
const suffix = lastApprovalError instanceof Error && lastApprovalError.message
|
|
272
501
|
? ` Last Chrome response: ${lastApprovalError.message}`
|
|
273
502
|
: "";
|
|
@@ -598,7 +827,7 @@ function parseDebugPortEnv() {
|
|
|
598
827
|
}
|
|
599
828
|
return value;
|
|
600
829
|
}
|
|
601
|
-
async function launchWithCustomHost({ chromeFlags, chromePath, userDataDir, host, requestedPort, ignoreDefaultFlags, }) {
|
|
830
|
+
async function launchWithCustomHost({ chromeFlags, chromePath, userDataDir, host, requestedPort, ignoreDefaultFlags, detachSharedChrome, }) {
|
|
602
831
|
const launcher = new Launcher({
|
|
603
832
|
chromePath: chromePath ?? undefined,
|
|
604
833
|
chromeFlags,
|
|
@@ -606,7 +835,7 @@ async function launchWithCustomHost({ chromeFlags, chromePath, userDataDir, host
|
|
|
606
835
|
handleSIGINT: false,
|
|
607
836
|
port: requestedPort ?? undefined,
|
|
608
837
|
ignoreDefaultFlags,
|
|
609
|
-
});
|
|
838
|
+
}, chromeLauncherModuleOverrides(detachSharedChrome));
|
|
610
839
|
if (host) {
|
|
611
840
|
const patched = launcher;
|
|
612
841
|
patched.isDebuggerReady = function patchedIsDebuggerReady() {
|
|
@@ -634,13 +863,8 @@ async function launchWithCustomHost({ chromeFlags, chromePath, userDataDir, host
|
|
|
634
863
|
};
|
|
635
864
|
}
|
|
636
865
|
await launcher.launch();
|
|
637
|
-
const kill = async () => launcher.kill();
|
|
638
866
|
return {
|
|
639
|
-
|
|
640
|
-
port: launcher.port ?? 0,
|
|
641
|
-
process: launcher.chromeProcess,
|
|
642
|
-
kill,
|
|
867
|
+
...launchedChromeFromLauncher(launcher),
|
|
643
868
|
host: host ?? undefined,
|
|
644
|
-
remoteDebuggingPipes: launcher.remoteDebuggingPipes,
|
|
645
869
|
};
|
|
646
870
|
}
|
|
@@ -2,6 +2,7 @@ import { CHATGPT_URL, DEEP_RESEARCH_DEFAULT_TIMEOUT_MS, DEFAULT_MODEL_STRATEGY,
|
|
|
2
2
|
import { normalizeBrowserModelStrategy } from "./modelStrategy.js";
|
|
3
3
|
import { DEFAULT_MAX_CONCURRENT_CHATGPT_TABS, normalizeMaxConcurrentTabs, } from "./tabLeaseRegistry.js";
|
|
4
4
|
import { normalizeChatgptUrl } from "./utils.js";
|
|
5
|
+
import { parseDuration } from "../duration.js";
|
|
5
6
|
import os from "node:os";
|
|
6
7
|
import path from "node:path";
|
|
7
8
|
export const DEFAULT_CHATGPT_COOKIE_NAMES = [
|
|
@@ -26,6 +27,7 @@ export const DEFAULT_BROWSER_CONFIG = {
|
|
|
26
27
|
timeoutMs: 1_200_000,
|
|
27
28
|
debugPort: null,
|
|
28
29
|
inputTimeoutMs: 60_000,
|
|
30
|
+
approvalWaitMs: 20_000,
|
|
29
31
|
attachmentTimeoutMs: 45_000,
|
|
30
32
|
assistantRecheckDelayMs: 0,
|
|
31
33
|
assistantRecheckTimeoutMs: 120_000,
|
|
@@ -82,6 +84,7 @@ export function resolveBrowserConfig(config) {
|
|
|
82
84
|
timeoutMs: config?.timeoutMs ?? defaultTimeoutMs,
|
|
83
85
|
debugPort: config?.debugPort ?? debugPortEnv ?? DEFAULT_BROWSER_CONFIG.debugPort,
|
|
84
86
|
inputTimeoutMs: config?.inputTimeoutMs ?? DEFAULT_BROWSER_CONFIG.inputTimeoutMs,
|
|
87
|
+
approvalWaitMs: resolveBrowserApprovalWait(config?.approvalWaitMs),
|
|
85
88
|
attachmentTimeoutMs: config?.attachmentTimeoutMs ?? DEFAULT_BROWSER_CONFIG.attachmentTimeoutMs,
|
|
86
89
|
assistantRecheckDelayMs: config?.assistantRecheckDelayMs ?? DEFAULT_BROWSER_CONFIG.assistantRecheckDelayMs,
|
|
87
90
|
assistantRecheckTimeoutMs: config?.assistantRecheckTimeoutMs ?? DEFAULT_BROWSER_CONFIG.assistantRecheckTimeoutMs,
|
|
@@ -154,3 +157,13 @@ function resolveManualLoginProfileDir(...candidates) {
|
|
|
154
157
|
}
|
|
155
158
|
return path.join(os.homedir(), ".oracle", "browser-profile");
|
|
156
159
|
}
|
|
160
|
+
export function resolveBrowserApprovalWait(value) {
|
|
161
|
+
const raw = value ?? process.env.ORACLE_BROWSER_APPROVAL_WAIT?.trim();
|
|
162
|
+
if (raw === undefined || raw === "")
|
|
163
|
+
return DEFAULT_BROWSER_CONFIG.approvalWaitMs;
|
|
164
|
+
const parsed = typeof raw === "number" ? raw : parseDuration(raw, Number.NaN);
|
|
165
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0 || parsed > 2_147_483_647) {
|
|
166
|
+
throw new Error("Invalid browser approval wait: use a positive duration up to 2147483647ms (for example --browser-approval-wait 5m, browser.approvalWaitMs: 300000, or ORACLE_BROWSER_APPROVAL_WAIT=5m).");
|
|
167
|
+
}
|
|
168
|
+
return parsed;
|
|
169
|
+
}
|
|
@@ -291,7 +291,7 @@ function deriveDevToolsProfileRoot(activePortPath) {
|
|
|
291
291
|
}
|
|
292
292
|
return parentDir;
|
|
293
293
|
}
|
|
294
|
-
function formatWebSocketHost(host) {
|
|
294
|
+
export function formatWebSocketHost(host) {
|
|
295
295
|
if (host.includes(":") && !host.startsWith("[") && !host.endsWith("]")) {
|
|
296
296
|
return `[${host}]`;
|
|
297
297
|
}
|