@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
|
@@ -65,3 +65,12 @@ export function formatBrowserModelSelectionEvidence(evidence, model) {
|
|
|
65
65
|
const verified = evidence.verified ? "yes" : "no";
|
|
66
66
|
return `requestedKey=${requestedKey}; target=${target}; resolvedLabel=${resolvedLabel}; status=${evidence.status}; strategy=${strategy}; verified=${verified}; source=${evidence.source}; capturedAt=${evidence.capturedAt}`;
|
|
67
67
|
}
|
|
68
|
+
/** Renders the observed effort selection independently of the model picker. */
|
|
69
|
+
export function formatBrowserThinkingSelectionEvidence(evidence) {
|
|
70
|
+
const resolvedLabel = cleanLabel(evidence.resolvedLabel) ?? "(none)";
|
|
71
|
+
const verified = evidence.verified ? "yes" : "no";
|
|
72
|
+
const failClosed = evidence.strictFailClosed ? "yes" : "no";
|
|
73
|
+
const targetKind = evidence.targetModelKind ?? "(none)";
|
|
74
|
+
const observedKind = evidence.observedModelKind ?? "(none)";
|
|
75
|
+
return `requestedLevel=${evidence.requestedLevel}; status=${evidence.status}; resolvedLabel=${resolvedLabel}; verified=${verified}; failClosed=${failClosed}; targetModelKind=${targetKind}; observedModelKind=${observedKind}; source=${evidence.source}; capturedAt=${evidence.capturedAt}`;
|
|
76
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { navigateToChatGPT, navigateToPromptReadyWithFallback, ensureNotBlocked, ensureLoggedIn, ensurePromptReady, ensureChatMode, waitForResumedConversationHydration, installJavaScriptDialogAutoDismissal, } from "./actions/navigation.js";
|
|
2
2
|
export { ensureModelSelection } from "./actions/modelSelection.js";
|
|
3
3
|
export { submitPrompt, clearPromptComposer } from "./actions/promptComposer.js";
|
|
4
|
-
export { clearComposerAttachments, uploadAttachmentFile, waitForAttachmentCompletion, waitForUserTurnAttachments, buildUserTurnAttachmentExpressionForTest, } from "./actions/attachments.js";
|
|
5
|
-
export { waitForAssistantResponse, readAssistantSnapshot, captureAssistantMarkdown, buildAssistantExtractorForTest, buildAssistantSnapshotExpressionForTest, buildConversationDebugExpressionForTest, buildMarkdownFallbackExtractorForTest, buildCopyExpressionForTest, } from "./actions/assistantResponse.js";
|
|
4
|
+
export { assertComposerPlusStayedInPlace, captureComposerNavigationUrl, clearComposerAttachments, uploadAttachmentFile, waitForAttachmentCompletion, waitForUserTurnAttachments, buildUserTurnAttachmentExpressionForTest, } from "./actions/attachments.js";
|
|
5
|
+
export { waitForAssistantResponse, readAssistantSnapshot, captureAssistantMarkdown, buildAssistantExtractorForTest, buildAssistantSnapshotExpressionForTest, buildConversationDebugExpressionForTest, buildMarkdownFallbackExtractorForTest, buildCopyExpressionForTest, isRetryAssistantUiErrorText, } from "./actions/assistantResponse.js";
|
|
@@ -12,6 +12,7 @@ const DEVTOOLS_ACTIVE_PORT_RELATIVE_PATHS = [
|
|
|
12
12
|
const CHROME_PID_FILENAME = "chrome.pid";
|
|
13
13
|
const ORACLE_PROFILE_LOCK_FILENAME = "oracle-automation.lock";
|
|
14
14
|
const execFileAsync = promisify(execFile);
|
|
15
|
+
let ownProcessStartTime;
|
|
15
16
|
export function getDevToolsActivePortPaths(userDataDir) {
|
|
16
17
|
return DEVTOOLS_ACTIVE_PORT_RELATIVE_PATHS.map((relative) => path.join(userDataDir, relative));
|
|
17
18
|
}
|
|
@@ -95,7 +96,7 @@ function findChromeDebugTargetForProfileFromProcessList(processList, userDataDir
|
|
|
95
96
|
continue;
|
|
96
97
|
if (!lower.includes("chrome") && !lower.includes("chromium"))
|
|
97
98
|
continue;
|
|
98
|
-
if (!
|
|
99
|
+
if (!isChromeCommandForUserDataDir(command, userDataDir))
|
|
99
100
|
continue;
|
|
100
101
|
const portMatch = command.match(/--remote-debugging-port(?:=|\s+)(\d+)/);
|
|
101
102
|
const port = Number.parseInt(portMatch?.[1] ?? "", 10);
|
|
@@ -119,11 +120,31 @@ export async function terminateRecordedChromeForProfile(userDataDir, logger) {
|
|
|
119
120
|
return false;
|
|
120
121
|
}
|
|
121
122
|
try {
|
|
122
|
-
process.
|
|
123
|
+
if (process.platform === "win32") {
|
|
124
|
+
await execFileAsync("taskkill.exe", ["/PID", String(pid), "/T", "/F"], {
|
|
125
|
+
maxBuffer: 1024 * 1024,
|
|
126
|
+
windowsHide: true,
|
|
127
|
+
timeout: 5000,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
process.kill(pid, "SIGTERM");
|
|
132
|
+
}
|
|
133
|
+
for (let attempt = 0; attempt < 30 && isProcessAlive(pid); attempt += 1) {
|
|
134
|
+
await delay(100);
|
|
135
|
+
}
|
|
136
|
+
if (isProcessAlive(pid)) {
|
|
137
|
+
logger?.(`Shared manual-login Chrome pid ${pid} remained alive after termination.`);
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
123
140
|
logger?.(`Terminated shared manual-login Chrome pid ${pid}`);
|
|
124
141
|
return true;
|
|
125
142
|
}
|
|
126
143
|
catch (error) {
|
|
144
|
+
if (!isProcessAlive(pid)) {
|
|
145
|
+
logger?.(`Terminated shared manual-login Chrome pid ${pid}`);
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
127
148
|
const message = error instanceof Error ? error.message : String(error);
|
|
128
149
|
logger?.(`Failed to terminate shared manual-login Chrome pid ${pid}: ${message}`);
|
|
129
150
|
return false;
|
|
@@ -132,10 +153,49 @@ export async function terminateRecordedChromeForProfile(userDataDir, logger) {
|
|
|
132
153
|
function isChromeCommandForUserDataDir(command, userDataDir) {
|
|
133
154
|
if (!command)
|
|
134
155
|
return false;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
command.
|
|
156
|
+
// ps flattens POSIX argv: an unquoted profile may contain spaces, so its
|
|
157
|
+
// boundary is the next Chrome switch/startup URL or the end, not the first whitespace.
|
|
158
|
+
const matches = [
|
|
159
|
+
...command.matchAll(/(?:^|\s)(?:"--user-data-dir=([^"]*)"|--user-data-dir(?:=|\s+)(?:"([^"]*)"|'([^']*)'|([^\r\n]+?)(?=\s+(?:"?--|about:blank(?:\s|$))|\s*$)))/giu),
|
|
160
|
+
];
|
|
161
|
+
if (matches.length !== 1)
|
|
162
|
+
return false;
|
|
163
|
+
const match = matches[0];
|
|
164
|
+
const commandProfile = match[1] ?? match[2] ?? match[3] ?? match[4];
|
|
165
|
+
if (!commandProfile || !startsWithChromeExecutable(command))
|
|
166
|
+
return false;
|
|
167
|
+
return (normalizeProfilePathForComparison(commandProfile) ===
|
|
168
|
+
normalizeProfilePathForComparison(userDataDir));
|
|
169
|
+
}
|
|
170
|
+
function startsWithChromeExecutable(command) {
|
|
171
|
+
const names = /^(?:google chrome(?: canary| beta| dev| for testing)?|google-chrome(?:-stable|-beta|-dev|-unstable)?|chrome|chromium|chromium-browser|chrome-headless-shell)(?:\.exe)?$/iu;
|
|
172
|
+
const trimmed = command.trimStart();
|
|
173
|
+
const quoted = trimmed.match(/^(?:"([^"]+)"|'([^']+)')(?=\s|$)/u);
|
|
174
|
+
let executable;
|
|
175
|
+
if (quoted) {
|
|
176
|
+
executable = quoted[1] ?? quoted[2];
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
const optionsStart = trimmed.search(/\s+"?--/u);
|
|
180
|
+
executable = optionsStart < 0 ? trimmed : trimmed.slice(0, optionsStart);
|
|
181
|
+
// macOS ps leaves the standard Google Chrome bundle path unquoted. Other
|
|
182
|
+
// whitespace is ambiguous with argv entries; never mistake a later argument
|
|
183
|
+
// named chrome for the executable that owns this PID.
|
|
184
|
+
if (/\s/u.test(executable.replace(/google chrome(?: canary| beta| dev| for testing)?(?:\.app)?/giu, "chrome")))
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
return names.test(executable.split(/[\\/]/u).at(-1) ?? "");
|
|
188
|
+
}
|
|
189
|
+
function normalizeProfilePathForComparison(value) {
|
|
190
|
+
let normalized = path.normalize(path.resolve(value));
|
|
191
|
+
if (process.platform === "win32") {
|
|
192
|
+
normalized = normalized.replace(/^\\\\\?\\/u, "").toLowerCase();
|
|
193
|
+
}
|
|
194
|
+
const root = path.parse(normalized).root;
|
|
195
|
+
while (normalized.length > root.length && /[\\/]$/u.test(normalized)) {
|
|
196
|
+
normalized = normalized.slice(0, -1);
|
|
197
|
+
}
|
|
198
|
+
return normalized;
|
|
139
199
|
}
|
|
140
200
|
export function isChromeCommandForUserDataDirForTest(command, userDataDir) {
|
|
141
201
|
return isChromeCommandForUserDataDir(command, userDataDir);
|
|
@@ -158,6 +218,39 @@ export function isProcessAlive(pid) {
|
|
|
158
218
|
return false;
|
|
159
219
|
}
|
|
160
220
|
}
|
|
221
|
+
export async function readProcessStartTimeMs(pid) {
|
|
222
|
+
if (!Number.isFinite(pid) || pid <= 0)
|
|
223
|
+
return null;
|
|
224
|
+
if (Math.trunc(pid) === process.pid) {
|
|
225
|
+
// Use the same OS identity as peer controllers, not wall time minus uptime.
|
|
226
|
+
// Cache our own PID only: it cannot be reused during this process's lifetime.
|
|
227
|
+
return (ownProcessStartTime ??= queryProcessStartTimeMs(process.pid));
|
|
228
|
+
}
|
|
229
|
+
return queryProcessStartTimeMs(pid);
|
|
230
|
+
}
|
|
231
|
+
async function queryProcessStartTimeMs(pid) {
|
|
232
|
+
try {
|
|
233
|
+
const executable = process.platform === "win32" ? "powershell.exe" : "ps";
|
|
234
|
+
const args = process.platform === "win32"
|
|
235
|
+
? [
|
|
236
|
+
"-NoProfile",
|
|
237
|
+
"-NonInteractive",
|
|
238
|
+
"-Command",
|
|
239
|
+
`$process = Get-Process -Id ${Math.trunc(pid)} -ErrorAction Stop; $process.StartTime.ToUniversalTime().ToString('o')`,
|
|
240
|
+
]
|
|
241
|
+
: ["-p", String(Math.trunc(pid)), "-o", "lstart="];
|
|
242
|
+
const { stdout } = await execFileAsync(executable, args, {
|
|
243
|
+
maxBuffer: 1024 * 1024,
|
|
244
|
+
windowsHide: true,
|
|
245
|
+
timeout: 5000,
|
|
246
|
+
});
|
|
247
|
+
const startedAt = Date.parse(String(stdout ?? "").trim());
|
|
248
|
+
return Number.isFinite(startedAt) ? startedAt : null;
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
161
254
|
function parseProfileRunLock(payload) {
|
|
162
255
|
if (!payload)
|
|
163
256
|
return null;
|
|
@@ -186,6 +279,7 @@ export async function acquireProfileRunLock(userDataDir, options) {
|
|
|
186
279
|
const startedAt = Date.now();
|
|
187
280
|
let warned = false;
|
|
188
281
|
for (;;) {
|
|
282
|
+
options.signal?.throwIfAborted();
|
|
189
283
|
try {
|
|
190
284
|
const payload = {
|
|
191
285
|
pid: process.pid,
|
|
@@ -210,7 +304,7 @@ export async function acquireProfileRunLock(userDataDir, options) {
|
|
|
210
304
|
let existing = parseProfileRunLock(await readFile(lockPath, "utf8").catch(() => null));
|
|
211
305
|
if (!existing) {
|
|
212
306
|
// Likely partial write / corruption; re-read once, then delete (user preference: delete unreadable lockfiles).
|
|
213
|
-
await delay(200);
|
|
307
|
+
await delay(200, options.signal);
|
|
214
308
|
existing = parseProfileRunLock(await readFile(lockPath, "utf8").catch(() => null));
|
|
215
309
|
if (!existing) {
|
|
216
310
|
options.logger?.("Oracle profile lock unreadable; deleting lockfile.");
|
|
@@ -231,7 +325,7 @@ export async function acquireProfileRunLock(userDataDir, options) {
|
|
|
231
325
|
if (elapsed >= timeoutMs) {
|
|
232
326
|
throw new Error(`Oracle profile lock still held by pid ${existing.pid} after ${Math.round(elapsed / 1000)}s`);
|
|
233
327
|
}
|
|
234
|
-
await delay(Math.min(pollMs, timeoutMs - elapsed));
|
|
328
|
+
await delay(Math.min(pollMs, timeoutMs - elapsed), options.signal);
|
|
235
329
|
}
|
|
236
330
|
}
|
|
237
331
|
}
|
|
@@ -353,8 +447,19 @@ async function isChromeUsingUserDataDir(userDataDir) {
|
|
|
353
447
|
}
|
|
354
448
|
async function readProcessCommand(pid) {
|
|
355
449
|
try {
|
|
356
|
-
const
|
|
450
|
+
const executable = process.platform === "win32" ? "powershell.exe" : "ps";
|
|
451
|
+
const args = process.platform === "win32"
|
|
452
|
+
? [
|
|
453
|
+
"-NoProfile",
|
|
454
|
+
"-NonInteractive",
|
|
455
|
+
"-Command",
|
|
456
|
+
`(Get-CimInstance Win32_Process -Filter 'ProcessId = ${Math.trunc(pid)}').CommandLine`,
|
|
457
|
+
]
|
|
458
|
+
: ["-p", String(Math.trunc(pid)), "-o", "command="];
|
|
459
|
+
const { stdout } = await execFileAsync(executable, args, {
|
|
357
460
|
maxBuffer: 1024 * 1024,
|
|
461
|
+
windowsHide: true,
|
|
462
|
+
timeout: 5000,
|
|
358
463
|
});
|
|
359
464
|
const command = String(stdout ?? "").trim();
|
|
360
465
|
return command || null;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { mkdtemp, mkdir, rm } from "node:fs/promises";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { closeTab, connectWithNewTab, launchChrome, positionChromeWindowOffscreen, registerTerminationHooks, } from "./chromeLifecycle.js";
|
|
4
|
+
import { closeTab, connectWithNewTab, launchChrome, positionChromeWindowOffscreen, positionChromeWindowOnscreen, registerTerminationHooks, } from "./chromeLifecycle.js";
|
|
5
5
|
import { resolveBrowserConfig } from "./config.js";
|
|
6
6
|
import { clearStaleChatGptConversationCookies, syncCookies } from "./cookies.js";
|
|
7
7
|
import { installJavaScriptDialogAutoDismissal, navigateToChatGPT, ensureLoggedIn, } from "./pageActions.js";
|
|
8
|
-
import { acquireBrowserTabLease
|
|
9
|
-
import { acquireProfileRunLock, cleanupStaleProfileState, findRunningChromeDebugTargetForProfile, readChromePid, readDevToolsPort, shouldCleanupManualLoginProfileState, verifyDevToolsReachable, writeChromePid, writeDevToolsActivePort, } from "./profileState.js";
|
|
8
|
+
import { acquireBrowserTabLease } from "./tabLeaseRegistry.js";
|
|
9
|
+
import { acquireProfileRunLock, cleanupStaleProfileState, findRunningChromeDebugTargetForProfile, readChromePid, readDevToolsPort, shouldCleanupManualLoginProfileState, terminateRecordedChromeForProfile, verifyDevToolsReachable, writeChromePid, writeDevToolsActivePort, } from "./profileState.js";
|
|
10
10
|
import { CHATGPT_URL } from "./constants.js";
|
|
11
11
|
import { delay } from "./utils.js";
|
|
12
12
|
import { assertManualLoginProfileReadyForRun, defaultManualLoginProfileDir, formatManualLoginSetupCommand, resolveManualLoginWaitMs, } from "./manualLoginProfile.js";
|
|
@@ -14,6 +14,59 @@ import { openProjectSourcesTab, uploadProjectSources, waitForProjectSourcesReady
|
|
|
14
14
|
import { normalizeProjectSourcesUrl } from "../projectSources/url.js";
|
|
15
15
|
import { buildProjectSourcesUploadPlan, diffAddedProjectSources } from "../projectSources/plan.js";
|
|
16
16
|
import { CHROME_COOKIE_SYNC_WARNING, shouldSyncBrowserCookies } from "./policies.js";
|
|
17
|
+
async function releaseProjectSourcesBrowserTabLease(options) {
|
|
18
|
+
let decisionObserved = false;
|
|
19
|
+
let keepBrowserOpen = false;
|
|
20
|
+
let terminationHandled = false;
|
|
21
|
+
let otherLeasesRemain = false;
|
|
22
|
+
let releaseError;
|
|
23
|
+
try {
|
|
24
|
+
await options.lease.release({
|
|
25
|
+
onRelease: async ({ isLastLease }) => {
|
|
26
|
+
decisionObserved = true;
|
|
27
|
+
if (!isLastLease) {
|
|
28
|
+
keepBrowserOpen = true;
|
|
29
|
+
otherLeasesRemain = true;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (!options.terminateSharedChrome)
|
|
33
|
+
return;
|
|
34
|
+
options.logger(`[browser] ChatGPT browser slot ${options.lease.id.slice(0, 8)} is final; ` +
|
|
35
|
+
`terminating shared Chrome (session=project-sources; controllerPid=${process.pid}; ` +
|
|
36
|
+
`chromePid=${options.chromePid ?? "unknown"}; chromePort=${options.chromePort ?? "unknown"}).`);
|
|
37
|
+
const terminated = await options.terminateSharedChrome().catch(() => false);
|
|
38
|
+
if (terminated) {
|
|
39
|
+
terminationHandled = true;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
keepBrowserOpen = true;
|
|
43
|
+
options.logger("[browser] Could not verify shared Chrome termination; leaving it available for reuse.");
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
releaseError = error instanceof Error ? error : new Error(String(error));
|
|
50
|
+
if (!terminationHandled)
|
|
51
|
+
keepBrowserOpen = true;
|
|
52
|
+
options.logger(`[browser] Failed to release the ChatGPT browser slot registry lock; restart Oracle/Codex MCP before another browser run: ${releaseError.message}`);
|
|
53
|
+
}
|
|
54
|
+
if (!decisionObserved) {
|
|
55
|
+
keepBrowserOpen = true;
|
|
56
|
+
options.logger("[browser] Could not verify final ChatGPT tab lease; leaving shared Chrome running.");
|
|
57
|
+
}
|
|
58
|
+
else if (otherLeasesRemain) {
|
|
59
|
+
options.logger(`[browser] Other ChatGPT tab leases still active; leaving shared Chrome running; ` +
|
|
60
|
+
`browser slot ${options.lease.id.slice(0, 8)} is non-final ` +
|
|
61
|
+
`(session=project-sources; controllerPid=${process.pid}; ` +
|
|
62
|
+
`chromePid=${options.chromePid ?? "unknown"}; chromePort=${options.chromePort ?? "unknown"}).`);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
keepBrowserOpen,
|
|
66
|
+
terminationHandled,
|
|
67
|
+
...(releaseError ? { releaseError } : {}),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
17
70
|
export async function runBrowserProjectSources(request) {
|
|
18
71
|
const startedAt = Date.now();
|
|
19
72
|
const logger = ((message) => request.log?.(message));
|
|
@@ -96,6 +149,7 @@ export async function runBrowserProjectSources(request) {
|
|
|
96
149
|
removeTerminationHooks = registerTerminationHooks(chrome, userDataDir, effectiveKeepBrowser, logger, {
|
|
97
150
|
isInFlight: () => !completed,
|
|
98
151
|
preserveUserDataDir: manualLogin,
|
|
152
|
+
preserveSharedChromeOnSignal: manualLogin,
|
|
99
153
|
});
|
|
100
154
|
const strictTabIsolation = Boolean(manualLogin && reusedChrome);
|
|
101
155
|
const devtoolsRetries = manualLogin ? 6 : 0;
|
|
@@ -128,7 +182,10 @@ export async function runBrowserProjectSources(request) {
|
|
|
128
182
|
}
|
|
129
183
|
await Promise.all(domainEnablers);
|
|
130
184
|
if (!config.headless && config.hideWindow) {
|
|
131
|
-
await positionChromeWindowOffscreen(client, logger);
|
|
185
|
+
await positionChromeWindowOffscreen(client, userDataDir, logger);
|
|
186
|
+
}
|
|
187
|
+
else if (!config.headless) {
|
|
188
|
+
await positionChromeWindowOnscreen(client, userDataDir, logger);
|
|
132
189
|
}
|
|
133
190
|
removeDialogHandler = installJavaScriptDialogAutoDismissal(Page, logger);
|
|
134
191
|
if (!manualLogin) {
|
|
@@ -189,7 +246,8 @@ export async function runBrowserProjectSources(request) {
|
|
|
189
246
|
}
|
|
190
247
|
let keepBrowserOpen = effectiveKeepBrowser;
|
|
191
248
|
let cleanupProfileLock = null;
|
|
192
|
-
let
|
|
249
|
+
let browserTerminationHandledByLease = false;
|
|
250
|
+
let tabLeaseReleaseError;
|
|
193
251
|
if (!keepBrowserOpen && manualLogin && tabLease) {
|
|
194
252
|
const cleanupLockTimeoutMs = Math.max(0, config.profileLockTimeoutMs ?? 0);
|
|
195
253
|
if (cleanupLockTimeoutMs > 0) {
|
|
@@ -199,24 +257,29 @@ export async function runBrowserProjectSources(request) {
|
|
|
199
257
|
sessionId: "project-sources",
|
|
200
258
|
}).catch(() => null);
|
|
201
259
|
}
|
|
202
|
-
keepBrowserOpen = await hasOtherActiveBrowserTabLeases(userDataDir, tabLease.id).catch(() => false);
|
|
203
|
-
if (keepBrowserOpen) {
|
|
204
|
-
logger("[browser] Other ChatGPT tab leases still active; leaving shared Chrome running.");
|
|
205
|
-
}
|
|
206
|
-
else if (reusedChrome && !connectionClosedUnexpectedly) {
|
|
207
|
-
keepBrowserOpen = true;
|
|
208
|
-
logger("[browser] Reused shared Chrome; leaving browser process running.");
|
|
209
|
-
}
|
|
210
260
|
}
|
|
211
261
|
if (tabLease) {
|
|
212
262
|
const handle = tabLease;
|
|
213
263
|
tabLease = null;
|
|
214
|
-
|
|
264
|
+
const chromeHandle = chrome;
|
|
265
|
+
const terminateSharedChrome = !keepBrowserOpen && manualLogin && chromeHandle && !connectionClosedUnexpectedly
|
|
266
|
+
? async () => terminateRecordedChromeForProfile(userDataDir, logger).catch(() => false)
|
|
267
|
+
: undefined;
|
|
268
|
+
const releaseResult = await releaseProjectSourcesBrowserTabLease({
|
|
269
|
+
lease: handle,
|
|
270
|
+
terminateSharedChrome,
|
|
271
|
+
chromePid: chromeHandle?.pid,
|
|
272
|
+
chromePort: chromeHandle?.port,
|
|
273
|
+
logger,
|
|
274
|
+
});
|
|
275
|
+
keepBrowserOpen ||= releaseResult.keepBrowserOpen;
|
|
276
|
+
browserTerminationHandledByLease = releaseResult.terminationHandled;
|
|
277
|
+
tabLeaseReleaseError = releaseResult.releaseError;
|
|
215
278
|
}
|
|
216
279
|
if (!keepBrowserOpen && chrome) {
|
|
217
280
|
if (!connectionClosedUnexpectedly) {
|
|
218
281
|
try {
|
|
219
|
-
if (!
|
|
282
|
+
if (!browserTerminationHandledByLease) {
|
|
220
283
|
await chrome.kill();
|
|
221
284
|
}
|
|
222
285
|
}
|
|
@@ -246,6 +309,10 @@ export async function runBrowserProjectSources(request) {
|
|
|
246
309
|
if (cleanupProfileLock) {
|
|
247
310
|
await cleanupProfileLock.release().catch(() => undefined);
|
|
248
311
|
}
|
|
312
|
+
if (tabLeaseReleaseError) {
|
|
313
|
+
// oxlint-disable-next-line eslint/no-unsafe-finally -- This cleanup failure must override a successful Project Sources result or a live MCP owner can remain locked.
|
|
314
|
+
throw new Error("Failed to release the ChatGPT browser slot registry lock; restart Oracle/Codex MCP before another browser run.", { cause: tabLeaseReleaseError });
|
|
315
|
+
}
|
|
249
316
|
}
|
|
250
317
|
}
|
|
251
318
|
async function applyProjectSourcesCookies({ config, network, manualLogin, logger, }) {
|
|
@@ -392,3 +459,4 @@ async function maybeReuseProjectSourcesChrome(userDataDir, logger, options = {})
|
|
|
392
459
|
export async function maybeReuseProjectSourcesChromeForTest(userDataDir, logger, options = {}) {
|
|
393
460
|
return maybeReuseProjectSourcesChrome(userDataDir, logger, options);
|
|
394
461
|
}
|
|
462
|
+
export const releaseProjectSourcesBrowserTabLeaseForTest = releaseProjectSourcesBrowserTabLease;
|