@steipete/oracle 0.20.0 → 0.20.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/oracle-cli.js +66 -25
- package/dist/src/browser/actions/modelSelection.js +36 -4
- package/dist/src/browser/actions/thinkingTime.js +11 -5
- package/dist/src/browser/browserConnection.js +59 -0
- package/dist/src/browser/chatgptImages.js +1 -1
- package/dist/src/browser/chromeLifecycle.js +105 -36
- package/dist/src/browser/index.js +41 -8
- package/dist/src/browser/liveTabs.js +94 -29
- package/dist/src/browser/profileState.js +6 -1
- package/dist/src/browser/promptFingerprint.js +54 -0
- package/dist/src/browser/reattach.js +157 -108
- package/dist/src/browser/recoveryTarget.js +31 -6
- package/dist/src/browser/sessionRunner.js +30 -7
- package/dist/src/browser/targetClaim.js +2 -2
- package/dist/src/cli/browserDefaults.js +3 -0
- package/dist/src/cli/browserTabs.js +105 -20
- package/dist/src/cli/detach.js +10 -1
- package/dist/src/cli/detachedSession.js +36 -0
- package/dist/src/cli/errorUtils.js +9 -0
- package/dist/src/cli/sessionDisplay.js +11 -3
- package/dist/src/cli/sessionRunner.js +150 -37
- package/dist/src/mcp/tools/consult.js +1 -7
- package/dist/src/remote/client.js +71 -14
- package/dist/src/remote/health.js +1 -0
- package/dist/src/remote/server.js +52 -10
- package/dist/src/remote/types.js +13 -0
- package/dist/src/sessionManager.js +8 -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 +9 -9
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
|
@@ -13,9 +13,21 @@ import { readDevToolsActivePortInfo } from "./detect.js";
|
|
|
13
13
|
import { pickTarget, extractConversationIdFromUrl, buildConversationUrl, withTimeout, openConversationFromSidebar, openConversationFromSidebarWithRetry, waitForLocationChange, readConversationTurnIndex, buildPromptEchoMatcher, recoverPromptEcho, alignPromptEchoMarkdown, } from "./reattachHelpers.js";
|
|
14
14
|
import { waitForDeepResearchCompletion } from "./actions/deepResearch.js";
|
|
15
15
|
import { CHROME_COOKIE_SYNC_WARNING, shouldSyncBrowserCookies } from "./policies.js";
|
|
16
|
+
import { BrowserCancellation, withoutBrowserCancellation } from "./cancellation.js";
|
|
17
|
+
import { BrowserRunCancelledError } from "../oracle/errors.js";
|
|
16
18
|
export async function resumeBrowserSession(runtime, config, logger, deps = {}) {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
+
const cancellation = new BrowserCancellation(deps.signal, logger);
|
|
20
|
+
try {
|
|
21
|
+
return await cancellation.run(() => resumeBrowserSessionInternal(runtime, config, logger, deps, cancellation));
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
cancellation.dispose();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function resumeBrowserSessionInternal(runtime, config, logger, deps, cancellation) {
|
|
28
|
+
const recoverSession = deps.recoverSession
|
|
29
|
+
? (runtimeMeta, configMeta) => cancellation.call(() => deps.recoverSession(runtimeMeta, configMeta))
|
|
30
|
+
: (runtimeMeta, configMeta) => resumeBrowserSessionViaNewChrome(runtimeMeta, configMeta, logger, deps, cancellation);
|
|
19
31
|
let closeAttachedConnection = null;
|
|
20
32
|
const closeAttached = async () => {
|
|
21
33
|
const close = closeAttachedConnection;
|
|
@@ -39,17 +51,18 @@ export async function resumeBrowserSession(runtime, config, logger, deps = {}) {
|
|
|
39
51
|
browserWSEndpoint,
|
|
40
52
|
approvalWaitMs,
|
|
41
53
|
logger,
|
|
54
|
+
signal: deps.signal,
|
|
42
55
|
})));
|
|
43
|
-
const targetList = (await listTargets
|
|
56
|
+
const targetList = (await cancellation.call(listTargets));
|
|
44
57
|
const target = pickTarget(targetList, liveRuntime);
|
|
45
|
-
const connection = browserWSEndpoint && !deps.connect
|
|
46
|
-
?
|
|
58
|
+
const connection = await cancellation.acquire(() => browserWSEndpoint && !deps.connect
|
|
59
|
+
? connectToRemoteChromeTarget(host, port ?? 9222, logger, {
|
|
47
60
|
browserWSEndpoint,
|
|
48
61
|
targetId: target?.targetId ?? target?.id,
|
|
49
62
|
closeTargetOnDispose: false,
|
|
50
63
|
approvalWaitMs,
|
|
51
64
|
})
|
|
52
|
-
:
|
|
65
|
+
: (async () => {
|
|
53
66
|
const client = (await (deps.connect ?? ((options) => CDP(options)))(browserWSEndpoint
|
|
54
67
|
? {
|
|
55
68
|
target: browserWSEndpoint,
|
|
@@ -62,9 +75,9 @@ export async function resumeBrowserSession(runtime, config, logger, deps = {}) {
|
|
|
62
75
|
target: target?.targetId ?? target?.id,
|
|
63
76
|
}));
|
|
64
77
|
return { client, close: () => client.close() };
|
|
65
|
-
})();
|
|
78
|
+
})(), (lateConnection) => lateConnection.close());
|
|
66
79
|
closeAttachedConnection = () => connection.close();
|
|
67
|
-
const client = connection.client;
|
|
80
|
+
const client = cancellation.client(connection.client);
|
|
68
81
|
const { Runtime, DOM, Page } = client;
|
|
69
82
|
const captureIdentity = async () => {
|
|
70
83
|
const targetId = target?.targetId ?? target?.id;
|
|
@@ -118,18 +131,18 @@ export async function resumeBrowserSession(runtime, config, logger, deps = {}) {
|
|
|
118
131
|
await ensureConversationOpen();
|
|
119
132
|
const waitForHydration = deps.waitForConversationHydration ?? waitForResumedConversationHydration;
|
|
120
133
|
const expectedConversationUrl = buildConversationUrl(runtime, resolveBrowserConfig(config ?? {}).url);
|
|
121
|
-
await waitForHydration(Runtime, timeoutMs, logger, {
|
|
134
|
+
await cancellation.call(() => waitForHydration(Runtime, timeoutMs, logger, {
|
|
122
135
|
requirePriorTurns: true,
|
|
123
136
|
requirePromptReady: false,
|
|
124
137
|
expectedConversationUrl: expectedConversationUrl ?? undefined,
|
|
125
|
-
});
|
|
138
|
+
}));
|
|
126
139
|
const minTurnIndex = (await readPromptPreviewTurnIndex(Runtime, deps.promptPreview)) ??
|
|
127
140
|
(deps.promptPreview ? null : await readConversationTurnIndex(Runtime, logger));
|
|
128
141
|
if (config?.researchMode === "deep") {
|
|
129
142
|
const waitForDeepResearch = deps.waitForDeepResearchCompletion ?? waitForDeepResearchCompletion;
|
|
130
|
-
const researchResult = await withTimeout(waitForDeepResearch(Runtime, logger, timeoutMs, minTurnIndex ?? undefined, Page, client, {
|
|
143
|
+
const researchResult = await withTimeout(cancellation.call(() => waitForDeepResearch(Runtime, logger, timeoutMs, minTurnIndex ?? undefined, Page, client, {
|
|
131
144
|
requireScopedTargetOwner: true,
|
|
132
|
-
}), timeoutMs + 5_000, "Reattach Deep Research response timed out");
|
|
145
|
+
})), timeoutMs + 5_000, "Reattach Deep Research response timed out");
|
|
133
146
|
const captureTarget = await captureIdentity().catch(() => undefined);
|
|
134
147
|
await closeAttached();
|
|
135
148
|
return {
|
|
@@ -139,9 +152,9 @@ export async function resumeBrowserSession(runtime, config, logger, deps = {}) {
|
|
|
139
152
|
};
|
|
140
153
|
}
|
|
141
154
|
const promptEcho = buildPromptEchoMatcher(deps.promptPreview);
|
|
142
|
-
const answer = await withTimeout(waitForResponse(Runtime, timeoutMs, logger, minTurnIndex ?? undefined), timeoutMs + 5_000, "Reattach response timed out");
|
|
143
|
-
const recovered = await recoverPromptEcho(Runtime, answer, promptEcho, logger, minTurnIndex, timeoutMs);
|
|
144
|
-
const markdown = (await withTimeout(captureMarkdown(Runtime, recovered.meta, logger), 15_000, "Reattach markdown capture timed out")) ?? recovered.text;
|
|
155
|
+
const answer = await withTimeout(cancellation.call(() => waitForResponse(Runtime, timeoutMs, logger, minTurnIndex ?? undefined)), timeoutMs + 5_000, "Reattach response timed out");
|
|
156
|
+
const recovered = await cancellation.call(() => recoverPromptEcho(Runtime, answer, promptEcho, logger, minTurnIndex, timeoutMs));
|
|
157
|
+
const markdown = (await withTimeout(cancellation.call(() => captureMarkdown(Runtime, recovered.meta, logger)), 15_000, "Reattach markdown capture timed out")) ?? recovered.text;
|
|
145
158
|
const aligned = alignPromptEchoMarkdown(recovered.text, markdown, promptEcho, logger);
|
|
146
159
|
const captureTarget = await captureIdentity().catch(() => undefined);
|
|
147
160
|
await closeAttached();
|
|
@@ -153,6 +166,9 @@ export async function resumeBrowserSession(runtime, config, logger, deps = {}) {
|
|
|
153
166
|
}
|
|
154
167
|
catch (error) {
|
|
155
168
|
await closeAttached();
|
|
169
|
+
if (deps.signal?.aborted || error instanceof BrowserRunCancelledError) {
|
|
170
|
+
throw new BrowserRunCancelledError();
|
|
171
|
+
}
|
|
156
172
|
const message = error instanceof Error ? error.message : String(error);
|
|
157
173
|
logger(`Existing Chrome reattach failed (${message}); reopening browser to locate the session.`);
|
|
158
174
|
return recoverSession(runtime, config);
|
|
@@ -192,26 +208,64 @@ function inferPortFromBrowserWSEndpoint(browserWSEndpoint) {
|
|
|
192
208
|
}
|
|
193
209
|
return undefined;
|
|
194
210
|
}
|
|
195
|
-
async function resumeBrowserSessionViaNewChrome(runtime, config, logger, deps) {
|
|
211
|
+
async function resumeBrowserSessionViaNewChrome(runtime, config, logger, deps, cancellation) {
|
|
196
212
|
const resolved = resolveBrowserConfig(config ?? {});
|
|
197
213
|
const manualLogin = Boolean(resolved.manualLogin);
|
|
198
214
|
const userDataDir = manualLogin
|
|
199
215
|
? (resolved.manualLoginProfileDir ?? path.join(os.homedir(), ".oracle", "browser-profile"))
|
|
200
|
-
: await mkdtemp(path.join(os.tmpdir(), "oracle-reattach-"));
|
|
216
|
+
: await (deps.createTemporaryProfile ?? (() => mkdtemp(path.join(os.tmpdir(), "oracle-reattach-"))))();
|
|
201
217
|
if (manualLogin) {
|
|
202
218
|
await mkdir(userDataDir, { recursive: true });
|
|
203
219
|
}
|
|
220
|
+
const cleanupProfile = async () => {
|
|
221
|
+
if (manualLogin) {
|
|
222
|
+
await cleanupStaleProfileState(userDataDir, logger, { lockRemovalMode: "never" }).catch(() => undefined);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
await rm(userDataDir, { recursive: true, force: true }).catch(() => undefined);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
if (deps.signal?.aborted) {
|
|
229
|
+
if (!manualLogin)
|
|
230
|
+
await cleanupProfile();
|
|
231
|
+
cancellation.check();
|
|
232
|
+
}
|
|
204
233
|
const launch = deps.launchChrome ?? launchChrome;
|
|
205
234
|
const connectToLaunchedChrome = deps.connectToChrome ?? connectToChrome;
|
|
206
|
-
const chrome = await launch(resolved, userDataDir, logger)
|
|
235
|
+
const chrome = await cancellation.acquire(() => launch(resolved, userDataDir, logger), async (lateChrome) => {
|
|
236
|
+
if (!resolved.keepBrowser) {
|
|
237
|
+
try {
|
|
238
|
+
await lateChrome.kill();
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// best-effort cleanup for a browser that finished launching after cancellation
|
|
242
|
+
}
|
|
243
|
+
await cleanupProfile();
|
|
244
|
+
}
|
|
245
|
+
});
|
|
207
246
|
const chromeHost = chrome && typeof chrome === "object" && "host" in chrome && typeof chrome.host === "string"
|
|
208
247
|
? chrome.host
|
|
209
248
|
: "127.0.0.1";
|
|
210
|
-
|
|
249
|
+
let rawClient;
|
|
250
|
+
try {
|
|
251
|
+
rawClient = await cancellation.acquire(() => connectToLaunchedChrome(chrome.port, logger, chromeHost), (lateClient) => lateClient.close());
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
if (!resolved.keepBrowser) {
|
|
255
|
+
try {
|
|
256
|
+
await withoutBrowserCancellation(() => chrome.kill());
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
// best-effort cleanup after a failed or cancelled CDP connection
|
|
260
|
+
}
|
|
261
|
+
await cleanupProfile();
|
|
262
|
+
}
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
211
265
|
const cleanup = async () => {
|
|
212
|
-
if (
|
|
266
|
+
if (rawClient && typeof rawClient.close === "function") {
|
|
213
267
|
try {
|
|
214
|
-
await
|
|
268
|
+
await rawClient.close();
|
|
215
269
|
}
|
|
216
270
|
catch {
|
|
217
271
|
// ignore
|
|
@@ -224,34 +278,30 @@ async function resumeBrowserSessionViaNewChrome(runtime, config, logger, deps) {
|
|
|
224
278
|
catch {
|
|
225
279
|
// ignore
|
|
226
280
|
}
|
|
227
|
-
|
|
228
|
-
await cleanupStaleProfileState(userDataDir, logger, { lockRemovalMode: "never" }).catch(() => undefined);
|
|
229
|
-
}
|
|
230
|
-
else {
|
|
231
|
-
await rm(userDataDir, { recursive: true, force: true }).catch(() => undefined);
|
|
232
|
-
}
|
|
281
|
+
await cleanupProfile();
|
|
233
282
|
}
|
|
234
283
|
};
|
|
284
|
+
const client = cancellation.client(rawClient);
|
|
235
285
|
const { Network, Page, Runtime, DOM, Target } = client;
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
else if (!resolved.headless) {
|
|
246
|
-
await positionChromeWindowOnscreen(client, userDataDir, logger);
|
|
247
|
-
}
|
|
248
|
-
let appliedCookies = 0;
|
|
249
|
-
if (shouldSyncBrowserCookies(resolved, { manualLogin })) {
|
|
250
|
-
if (!resolved.inlineCookies) {
|
|
251
|
-
logger(CHROME_COOKIE_SYNC_WARNING);
|
|
286
|
+
try {
|
|
287
|
+
if (Runtime?.enable) {
|
|
288
|
+
await Runtime.enable();
|
|
289
|
+
}
|
|
290
|
+
if (DOM && typeof DOM.enable === "function") {
|
|
291
|
+
await DOM.enable();
|
|
292
|
+
}
|
|
293
|
+
if (!resolved.headless && resolved.hideWindow) {
|
|
294
|
+
await positionChromeWindowOffscreen(client, userDataDir, logger);
|
|
252
295
|
}
|
|
253
|
-
|
|
254
|
-
|
|
296
|
+
else if (!resolved.headless) {
|
|
297
|
+
await positionChromeWindowOnscreen(client, userDataDir, logger);
|
|
298
|
+
}
|
|
299
|
+
let appliedCookies = 0;
|
|
300
|
+
if (shouldSyncBrowserCookies(resolved, { manualLogin })) {
|
|
301
|
+
if (!resolved.inlineCookies) {
|
|
302
|
+
logger(CHROME_COOKIE_SYNC_WARNING);
|
|
303
|
+
}
|
|
304
|
+
const sync = deps.syncCookies ?? syncCookies;
|
|
255
305
|
appliedCookies = await sync(Network, resolved.url, resolved.chromeProfile, logger, {
|
|
256
306
|
allowErrors: resolved.allowCookieErrors,
|
|
257
307
|
filterNames: resolved.cookieNames ?? undefined,
|
|
@@ -260,74 +310,73 @@ async function resumeBrowserSessionViaNewChrome(runtime, config, logger, deps) {
|
|
|
260
310
|
waitMs: resolved.cookieSyncWaitMs ?? 0,
|
|
261
311
|
});
|
|
262
312
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
extractConversationIdFromUrl(runtime.tabUrl ?? ""),
|
|
272
|
-
extractConversationIdFromUrl(resolved.url),
|
|
273
|
-
],
|
|
274
|
-
});
|
|
275
|
-
await navigateToChatGPT(Page, Runtime, CHATGPT_URL, logger);
|
|
276
|
-
await ensureNotBlocked(Runtime, resolved.headless, logger);
|
|
277
|
-
await ensureLoggedIn(Runtime, logger, { appliedCookies });
|
|
278
|
-
if (resolved.url !== CHATGPT_URL) {
|
|
279
|
-
await navigateToChatGPT(Page, Runtime, resolved.url, logger);
|
|
280
|
-
await ensureNotBlocked(Runtime, resolved.headless, logger);
|
|
281
|
-
}
|
|
282
|
-
await ensurePromptReady(Runtime, resolved.inputTimeoutMs, logger);
|
|
283
|
-
const conversationUrl = buildConversationUrl(runtime, resolved.url);
|
|
284
|
-
if (conversationUrl) {
|
|
285
|
-
logger(`Reopening conversation at ${conversationUrl}`);
|
|
286
|
-
await navigateToChatGPT(Page, Runtime, conversationUrl, logger);
|
|
313
|
+
await clearStaleChatGptConversationCookies(Network, Target, logger, {
|
|
314
|
+
preserveConversationIds: [
|
|
315
|
+
runtime.conversationId,
|
|
316
|
+
extractConversationIdFromUrl(runtime.tabUrl ?? ""),
|
|
317
|
+
extractConversationIdFromUrl(resolved.url),
|
|
318
|
+
],
|
|
319
|
+
});
|
|
320
|
+
await navigateToChatGPT(Page, Runtime, CHATGPT_URL, logger);
|
|
287
321
|
await ensureNotBlocked(Runtime, resolved.headless, logger);
|
|
322
|
+
await ensureLoggedIn(Runtime, logger, { appliedCookies });
|
|
323
|
+
if (resolved.url !== CHATGPT_URL) {
|
|
324
|
+
await navigateToChatGPT(Page, Runtime, resolved.url, logger);
|
|
325
|
+
await ensureNotBlocked(Runtime, resolved.headless, logger);
|
|
326
|
+
}
|
|
288
327
|
await ensurePromptReady(Runtime, resolved.inputTimeoutMs, logger);
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
promptPreview: deps.promptPreview,
|
|
296
|
-
}, 15_000);
|
|
297
|
-
if (!opened) {
|
|
298
|
-
throw new Error("Unable to locate prior ChatGPT conversation in sidebar.");
|
|
328
|
+
const conversationUrl = buildConversationUrl(runtime, resolved.url);
|
|
329
|
+
if (conversationUrl) {
|
|
330
|
+
logger(`Reopening conversation at ${conversationUrl}`);
|
|
331
|
+
await navigateToChatGPT(Page, Runtime, conversationUrl, logger);
|
|
332
|
+
await ensureNotBlocked(Runtime, resolved.headless, logger);
|
|
333
|
+
await ensurePromptReady(Runtime, resolved.inputTimeoutMs, logger);
|
|
299
334
|
}
|
|
300
|
-
|
|
335
|
+
else {
|
|
336
|
+
const opened = await openConversationFromSidebarWithRetry(Runtime, {
|
|
337
|
+
conversationId: runtime.conversationId ?? extractConversationIdFromUrl(runtime.tabUrl ?? ""),
|
|
338
|
+
preferProjects: resolved.url !== CHATGPT_URL ||
|
|
339
|
+
Boolean(runtime.tabUrl &&
|
|
340
|
+
(/\/g\//.test(runtime.tabUrl) || runtime.tabUrl.includes("/project"))),
|
|
341
|
+
promptPreview: deps.promptPreview,
|
|
342
|
+
}, 15_000);
|
|
343
|
+
if (!opened) {
|
|
344
|
+
throw new Error("Unable to locate prior ChatGPT conversation in sidebar.");
|
|
345
|
+
}
|
|
346
|
+
await waitForLocationChange(Runtime, 15_000);
|
|
347
|
+
}
|
|
348
|
+
const waitForHydration = deps.waitForConversationHydration ?? waitForResumedConversationHydration;
|
|
349
|
+
await cancellation.call(() => waitForHydration(Runtime, resolved.inputTimeoutMs, logger, {
|
|
350
|
+
requirePriorTurns: true,
|
|
351
|
+
requirePromptReady: false,
|
|
352
|
+
expectedConversationUrl: conversationUrl ?? undefined,
|
|
353
|
+
}));
|
|
354
|
+
const waitForResponse = deps.waitForAssistantResponse ?? waitForAssistantResponse;
|
|
355
|
+
const captureMarkdown = deps.captureAssistantMarkdown ?? captureAssistantMarkdown;
|
|
356
|
+
const timeoutMs = resolved.timeoutMs ?? 120_000;
|
|
357
|
+
const minTurnIndex = (await readPromptPreviewTurnIndex(Runtime, deps.promptPreview)) ??
|
|
358
|
+
(deps.promptPreview ? null : await readConversationTurnIndex(Runtime, logger));
|
|
359
|
+
if (resolved.researchMode === "deep") {
|
|
360
|
+
const waitForDeepResearch = deps.waitForDeepResearchCompletion ?? waitForDeepResearchCompletion;
|
|
361
|
+
const researchResult = await cancellation.call(() => waitForDeepResearch(Runtime, logger, timeoutMs, minTurnIndex ?? undefined, Page, client, {
|
|
362
|
+
requireScopedTargetOwner: true,
|
|
363
|
+
}));
|
|
364
|
+
return {
|
|
365
|
+
answerText: researchResult.text,
|
|
366
|
+
answerMarkdown: researchResult.text,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
const promptEcho = buildPromptEchoMatcher(deps.promptPreview);
|
|
370
|
+
const answer = await cancellation.call(() => waitForResponse(Runtime, timeoutMs, logger, minTurnIndex ?? undefined));
|
|
371
|
+
const recovered = await cancellation.call(() => recoverPromptEcho(Runtime, answer, promptEcho, logger, minTurnIndex, timeoutMs));
|
|
372
|
+
const markdown = (await cancellation.call(() => captureMarkdown(Runtime, recovered.meta, logger))) ??
|
|
373
|
+
recovered.text;
|
|
374
|
+
const aligned = alignPromptEchoMarkdown(recovered.text, markdown, promptEcho, logger);
|
|
375
|
+
return { answerText: aligned.answerText, answerMarkdown: aligned.answerMarkdown };
|
|
301
376
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
requirePriorTurns: true,
|
|
305
|
-
requirePromptReady: false,
|
|
306
|
-
expectedConversationUrl: conversationUrl ?? undefined,
|
|
307
|
-
});
|
|
308
|
-
const waitForResponse = deps.waitForAssistantResponse ?? waitForAssistantResponse;
|
|
309
|
-
const captureMarkdown = deps.captureAssistantMarkdown ?? captureAssistantMarkdown;
|
|
310
|
-
const timeoutMs = resolved.timeoutMs ?? 120_000;
|
|
311
|
-
const minTurnIndex = (await readPromptPreviewTurnIndex(Runtime, deps.promptPreview)) ??
|
|
312
|
-
(deps.promptPreview ? null : await readConversationTurnIndex(Runtime, logger));
|
|
313
|
-
if (resolved.researchMode === "deep") {
|
|
314
|
-
const waitForDeepResearch = deps.waitForDeepResearchCompletion ?? waitForDeepResearchCompletion;
|
|
315
|
-
const researchResult = await waitForDeepResearch(Runtime, logger, timeoutMs, minTurnIndex ?? undefined, Page, client, {
|
|
316
|
-
requireScopedTargetOwner: true,
|
|
317
|
-
});
|
|
318
|
-
await cleanup();
|
|
319
|
-
return {
|
|
320
|
-
answerText: researchResult.text,
|
|
321
|
-
answerMarkdown: researchResult.text,
|
|
322
|
-
};
|
|
377
|
+
finally {
|
|
378
|
+
await withoutBrowserCancellation(cleanup);
|
|
323
379
|
}
|
|
324
|
-
const promptEcho = buildPromptEchoMatcher(deps.promptPreview);
|
|
325
|
-
const answer = await waitForResponse(Runtime, timeoutMs, logger, minTurnIndex ?? undefined);
|
|
326
|
-
const recovered = await recoverPromptEcho(Runtime, answer, promptEcho, logger, minTurnIndex, timeoutMs);
|
|
327
|
-
const markdown = (await captureMarkdown(Runtime, recovered.meta, logger)) ?? recovered.text;
|
|
328
|
-
const aligned = alignPromptEchoMarkdown(recovered.text, markdown, promptEcho, logger);
|
|
329
|
-
await cleanup();
|
|
330
|
-
return { answerText: aligned.answerText, answerMarkdown: aligned.answerMarkdown };
|
|
331
380
|
}
|
|
332
381
|
async function readPromptPreviewTurnIndex(Runtime, promptPreview) {
|
|
333
382
|
const preview = promptPreview?.trim();
|
|
@@ -61,8 +61,28 @@ export function matchesOwnedRecoveryTarget(metadata, capture) {
|
|
|
61
61
|
owned.port === capture.port &&
|
|
62
62
|
owned.browserWSEndpoint === capture.browserWSEndpoint);
|
|
63
63
|
}
|
|
64
|
+
export function recoveryCaptureFromRuntime(runtime) {
|
|
65
|
+
const owned = runtime?.ownedRecoveryTarget;
|
|
66
|
+
const conversationId = runtime?.conversationId ?? extractConversationIdFromUrl(runtime?.tabUrl ?? "");
|
|
67
|
+
return owned && conversationId ? { ...owned, conversationId } : undefined;
|
|
68
|
+
}
|
|
64
69
|
/** Call only after the full recovered answer and completed session have been saved. */
|
|
65
70
|
export async function retireRecoveredBrowserTarget(sessionId, capture, logger, deps = {}) {
|
|
71
|
+
await retireOwnedBrowserTarget(sessionId, capture, logger, {
|
|
72
|
+
...deps,
|
|
73
|
+
terminalStatus: "completed",
|
|
74
|
+
requireIdle: true,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/** Call only after cancellation has been persisted for the owning session. */
|
|
78
|
+
export async function retireCancelledBrowserTarget(sessionId, capture, logger, deps = {}) {
|
|
79
|
+
await retireOwnedBrowserTarget(sessionId, capture, logger, {
|
|
80
|
+
...deps,
|
|
81
|
+
terminalStatus: "cancelled",
|
|
82
|
+
requireIdle: false,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async function retireOwnedBrowserTarget(sessionId, capture, logger, options) {
|
|
66
86
|
if (!capture)
|
|
67
87
|
return;
|
|
68
88
|
let connection;
|
|
@@ -72,14 +92,15 @@ export async function retireRecoveredBrowserTarget(sessionId, capture, logger, d
|
|
|
72
92
|
try {
|
|
73
93
|
const metadata = await sessionStore.readSession(sessionId);
|
|
74
94
|
if (!metadata ||
|
|
75
|
-
metadata.status !==
|
|
95
|
+
metadata.status !== options.terminalStatus ||
|
|
96
|
+
metadata.browser?.config?.keepBrowser === true ||
|
|
76
97
|
hasOtherLiveBrowserController(metadata) ||
|
|
77
98
|
!matchesOwnedRecoveryTarget(metadata, capture))
|
|
78
99
|
return;
|
|
79
100
|
const claimId = metadata.browser?.runtime?.ownedRecoveryTarget?.claimId;
|
|
80
101
|
if (!claimId)
|
|
81
102
|
return;
|
|
82
|
-
const connect =
|
|
103
|
+
const connect = options.connect ?? connectToRemoteChromeTarget;
|
|
83
104
|
connection = await connect(capture.host, capture.port, logger, {
|
|
84
105
|
targetId: capture.targetId,
|
|
85
106
|
browserWSEndpoint: capture.browserWSEndpoint,
|
|
@@ -93,7 +114,7 @@ export async function retireRecoveredBrowserTarget(sessionId, capture, logger, d
|
|
|
93
114
|
});
|
|
94
115
|
const value = state.result?.value;
|
|
95
116
|
if (!value ||
|
|
96
|
-
value.generating !== false ||
|
|
117
|
+
(options.requireIdle && value.generating !== false) ||
|
|
97
118
|
extractConversationIdFromUrl(value.url ?? "") !== capture.conversationId)
|
|
98
119
|
return;
|
|
99
120
|
const { targetInfos } = await Target.getTargets();
|
|
@@ -106,7 +127,9 @@ export async function retireRecoveredBrowserTarget(sessionId, capture, logger, d
|
|
|
106
127
|
return;
|
|
107
128
|
lockedClaim = claimId;
|
|
108
129
|
const locked = await Runtime.evaluate({
|
|
109
|
-
expression: buildTargetRetirementExpression(claimId, capture.conversationId, reservationId
|
|
130
|
+
expression: buildTargetRetirementExpression(claimId, capture.conversationId, reservationId, {
|
|
131
|
+
allowGenerating: !options.requireIdle,
|
|
132
|
+
}),
|
|
110
133
|
returnByValue: true,
|
|
111
134
|
});
|
|
112
135
|
if (locked.exceptionDetails || locked.result?.value !== true)
|
|
@@ -115,10 +138,12 @@ export async function retireRecoveredBrowserTarget(sessionId, capture, logger, d
|
|
|
115
138
|
if (!result.success)
|
|
116
139
|
throw new Error("Chrome refused target retirement");
|
|
117
140
|
retired = true;
|
|
118
|
-
logger(
|
|
141
|
+
logger(options.terminalStatus === "completed"
|
|
142
|
+
? "Retired Oracle-owned browser tab after saving the recovered answer."
|
|
143
|
+
: "Retired Oracle-owned browser tab after cancellation.");
|
|
119
144
|
}
|
|
120
145
|
catch (error) {
|
|
121
|
-
logger(
|
|
146
|
+
logger(`${options.terminalStatus === "completed" ? "Recovered answer is saved" : "Cancellation is saved"}; browser tab cleanup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
122
147
|
}
|
|
123
148
|
finally {
|
|
124
149
|
if (connection && lockedClaim && !retired) {
|
|
@@ -4,7 +4,7 @@ import { formatFinishLine } from "../oracle/finishLine.js";
|
|
|
4
4
|
import { runBrowserMode } from "../browserMode.js";
|
|
5
5
|
import { DEFAULT_BROWSER_CONFIG } from "./config.js";
|
|
6
6
|
import { assembleBrowserPrompt, cleanupGeneratedBrowserBundles, materializeBrowserFallback, } from "./prompt.js";
|
|
7
|
-
import { BrowserAutomationError } from "../oracle/errors.js";
|
|
7
|
+
import { BrowserAutomationError, BrowserRunCancelledError } from "../oracle/errors.js";
|
|
8
8
|
import { appendArtifacts, saveBrowserTranscriptArtifact, saveDeepResearchReportArtifact, } from "./artifacts.js";
|
|
9
9
|
import { formatBrowserModelSelectionEvidence, formatBrowserThinkingSelectionEvidence, formatBrowserModelTarget, resolveBrowserModelDisplayName, } from "./modelDisplay.js";
|
|
10
10
|
const LARGE_PRO_FAST_INPUT_TOKEN_THRESHOLD = 25_000;
|
|
@@ -55,24 +55,28 @@ function buildBrowserRunWarnings(args) {
|
|
|
55
55
|
},
|
|
56
56
|
];
|
|
57
57
|
}
|
|
58
|
-
export async function runBrowserSessionExecution({ runOptions, browserConfig, cwd, log }, deps = {}) {
|
|
58
|
+
export async function runBrowserSessionExecution({ runOptions, browserConfig, cwd, log, signal }, deps = {}) {
|
|
59
59
|
const assemblePrompt = deps.assemblePrompt ?? assembleBrowserPrompt;
|
|
60
60
|
const executeBrowser = deps.executeBrowser ?? runBrowserMode;
|
|
61
61
|
const persistRuntimeHint = deps.persistRuntimeHint ?? (() => { });
|
|
62
62
|
const inputTimeoutMs = browserConfig.inputTimeoutMs ?? DEFAULT_BROWSER_CONFIG.inputTimeoutMs;
|
|
63
|
-
let
|
|
63
|
+
let preparationAbandoned = false;
|
|
64
64
|
let preparationTimeout;
|
|
65
|
+
let removePreparationAbortListener;
|
|
65
66
|
let promptArtifacts;
|
|
67
|
+
if (signal?.aborted) {
|
|
68
|
+
throw new BrowserRunCancelledError();
|
|
69
|
+
}
|
|
66
70
|
try {
|
|
67
71
|
promptArtifacts = await Promise.race([
|
|
68
72
|
assemblePrompt(runOptions, { cwd }).then(async (artifacts) => {
|
|
69
|
-
if (
|
|
73
|
+
if (preparationAbandoned)
|
|
70
74
|
await cleanupGeneratedBrowserBundles(artifacts);
|
|
71
75
|
return artifacts;
|
|
72
76
|
}),
|
|
73
77
|
new Promise((_, reject) => {
|
|
74
78
|
preparationTimeout = setTimeout(() => {
|
|
75
|
-
|
|
79
|
+
preparationAbandoned = true;
|
|
76
80
|
reject(new BrowserAutomationError(`Browser prompt preparation timed out after ${inputTimeoutMs}ms; increase --browser-input-timeout if local files need more time.`, {
|
|
77
81
|
stage: "prepare-prompt",
|
|
78
82
|
code: "prompt-preparation-timeout",
|
|
@@ -80,12 +84,27 @@ export async function runBrowserSessionExecution({ runOptions, browserConfig, cw
|
|
|
80
84
|
}));
|
|
81
85
|
}, inputTimeoutMs);
|
|
82
86
|
}),
|
|
87
|
+
new Promise((_, reject) => {
|
|
88
|
+
if (!signal)
|
|
89
|
+
return;
|
|
90
|
+
const abort = () => {
|
|
91
|
+
preparationAbandoned = true;
|
|
92
|
+
reject(new BrowserRunCancelledError());
|
|
93
|
+
};
|
|
94
|
+
if (signal.aborted) {
|
|
95
|
+
abort();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
99
|
+
removePreparationAbortListener = () => signal.removeEventListener("abort", abort);
|
|
100
|
+
}),
|
|
83
101
|
]);
|
|
84
102
|
}
|
|
85
103
|
finally {
|
|
86
104
|
if (preparationTimeout) {
|
|
87
105
|
clearTimeout(preparationTimeout);
|
|
88
106
|
}
|
|
107
|
+
removePreparationAbortListener?.();
|
|
89
108
|
}
|
|
90
109
|
try {
|
|
91
110
|
return await executeAssembledBrowserSession({
|
|
@@ -95,13 +114,14 @@ export async function runBrowserSessionExecution({ runOptions, browserConfig, cw
|
|
|
95
114
|
promptArtifacts,
|
|
96
115
|
executeBrowser,
|
|
97
116
|
persistRuntimeHint,
|
|
117
|
+
signal,
|
|
98
118
|
});
|
|
99
119
|
}
|
|
100
120
|
finally {
|
|
101
121
|
await cleanupGeneratedBrowserBundles(promptArtifacts);
|
|
102
122
|
}
|
|
103
123
|
}
|
|
104
|
-
async function executeAssembledBrowserSession({ runOptions, browserConfig, log, promptArtifacts, executeBrowser, persistRuntimeHint, }) {
|
|
124
|
+
async function executeAssembledBrowserSession({ runOptions, browserConfig, log, promptArtifacts, executeBrowser, persistRuntimeHint, signal, }) {
|
|
105
125
|
if (runOptions.verbose) {
|
|
106
126
|
log(chalk.dim(`[verbose] Browser config: ${JSON.stringify({
|
|
107
127
|
...browserConfig,
|
|
@@ -180,6 +200,8 @@ async function executeAssembledBrowserSession({ runOptions, browserConfig, log,
|
|
|
180
200
|
generateImagePath: runOptions.generateImage,
|
|
181
201
|
outputPath: runOptions.outputPath,
|
|
182
202
|
followUpPrompts: runOptions.browserFollowUps,
|
|
203
|
+
signal,
|
|
204
|
+
closeOwnedTabOnCancel: !executionBrowserConfig.keepBrowser,
|
|
183
205
|
runtimeHintCb: async (runtime, modelSelection) => {
|
|
184
206
|
const runtimeWithController = {
|
|
185
207
|
...runtime,
|
|
@@ -195,7 +217,7 @@ async function executeAssembledBrowserSession({ runOptions, browserConfig, log,
|
|
|
195
217
|
});
|
|
196
218
|
}
|
|
197
219
|
catch (error) {
|
|
198
|
-
if (error instanceof BrowserAutomationError) {
|
|
220
|
+
if (error instanceof BrowserAutomationError || error instanceof BrowserRunCancelledError) {
|
|
199
221
|
throw error;
|
|
200
222
|
}
|
|
201
223
|
const message = error instanceof Error ? error.message : "Browser automation failed.";
|
|
@@ -282,6 +304,7 @@ async function executeAssembledBrowserSession({ runOptions, browserConfig, log,
|
|
|
282
304
|
tabUrl: browserResult.tabUrl,
|
|
283
305
|
conversationId: browserResult.conversationId,
|
|
284
306
|
promptSubmitted: browserResult.promptSubmitted,
|
|
307
|
+
submittedPromptHash: browserResult.submittedPromptHash,
|
|
285
308
|
researchPlan: browserResult.researchPlan,
|
|
286
309
|
controllerPid: browserResult.controllerPid ?? process.pid,
|
|
287
310
|
},
|
|
@@ -20,7 +20,7 @@ export function buildTargetClaimExpression(claimId) {
|
|
|
20
20
|
} catch { return false; }
|
|
21
21
|
})()`;
|
|
22
22
|
}
|
|
23
|
-
export function buildTargetRetirementExpression(claimId, conversationId, reservationId) {
|
|
23
|
+
export function buildTargetRetirementExpression(claimId, conversationId, reservationId, options = {}) {
|
|
24
24
|
return `(() => {
|
|
25
25
|
let claim;
|
|
26
26
|
try { claim = JSON.parse(sessionStorage.getItem('oracle:target-claim') || 'null'); } catch { return false; }
|
|
@@ -28,7 +28,7 @@ export function buildTargetRetirementExpression(claimId, conversationId, reserva
|
|
|
28
28
|
const id = location.pathname.match(/\\/c\\/([^/]+)/)?.[1];
|
|
29
29
|
if (id !== ${JSON.stringify(conversationId)}) return false;
|
|
30
30
|
const generating = Array.from(document.querySelectorAll(${JSON.stringify(STOP_BUTTON_SELECTORS.join(","))})).some(node => node.getBoundingClientRect().width > 0 && node.getBoundingClientRect().height > 0);
|
|
31
|
-
if (generating) return false;
|
|
31
|
+
if (generating && !${JSON.stringify(options.allowGenerating === true)}) return false;
|
|
32
32
|
claim.retiring = true;
|
|
33
33
|
claim.retirementId = ${JSON.stringify(reservationId)};
|
|
34
34
|
try { sessionStorage.setItem('oracle:target-claim', JSON.stringify(claim)); } catch { return false; }
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CHATGPT_URL } from "../browser/constants.js";
|
|
2
2
|
import { normalizeChatgptUrl } from "../browser/utils.js";
|
|
3
3
|
import { normalizeThinkingTimeLevel } from "../oracle/thinkingTime.js";
|
|
4
|
+
import { isGpt6ProAlias } from "./browserConfig.js";
|
|
4
5
|
export function applyBrowserDefaultsFromConfig(options, config, getSource) {
|
|
5
6
|
const browser = config.browser;
|
|
6
7
|
if (!browser)
|
|
@@ -12,6 +13,7 @@ export function applyBrowserDefaultsFromConfig(options, config, getSource) {
|
|
|
12
13
|
const attachRunningRequested = options.browserAttachRunning === true ||
|
|
13
14
|
(isUnset("browserAttachRunning") && browser.attachRunning === true);
|
|
14
15
|
const currentModelRequestedByCli = options.browserModelStrategy === "current" && getSource("browserModelStrategy") === "cli";
|
|
16
|
+
const gpt6ProRequestedByCli = getSource("model") === "cli" && isGpt6ProAlias(options.model);
|
|
15
17
|
if (!options.copyProfile &&
|
|
16
18
|
isUnset("remoteChrome") &&
|
|
17
19
|
options.remoteChrome === undefined &&
|
|
@@ -104,6 +106,7 @@ export function applyBrowserDefaultsFromConfig(options, config, getSource) {
|
|
|
104
106
|
options.browserModelStrategy = browser.modelStrategy;
|
|
105
107
|
}
|
|
106
108
|
if (!currentModelRequestedByCli &&
|
|
109
|
+
!gpt6ProRequestedByCli &&
|
|
107
110
|
isUnset("browserThinkingTime") &&
|
|
108
111
|
browser.thinkingTime !== undefined) {
|
|
109
112
|
options.browserThinkingTime = normalizeThinkingTimeLevel(browser.thinkingTime) ?? undefined;
|