@steipete/oracle 0.14.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/oracle-cli.js +2 -0
- package/dist/bin/oracle.js +569 -0
- package/dist/docs-site/.nojekyll +0 -0
- package/dist/docs-site/CNAME +1 -0
- package/dist/docs-site/RELEASING.html +410 -0
- package/dist/docs-site/agents.html +374 -0
- package/dist/docs-site/anthropic.html +368 -0
- package/dist/docs-site/bridge.html +400 -0
- package/dist/docs-site/browser-mode.html +594 -0
- package/dist/docs-site/chromium-forks.html +347 -0
- package/dist/docs-site/cli-reference.html +346 -0
- package/dist/docs-site/configuration.html +452 -0
- package/dist/docs-site/favicon.svg +14 -0
- package/dist/docs-site/followup.html +375 -0
- package/dist/docs-site/gemini.html +383 -0
- package/dist/docs-site/grok.html +325 -0
- package/dist/docs-site/index.html +360 -0
- package/dist/docs-site/install.html +335 -0
- package/dist/docs-site/linux.html +321 -0
- package/dist/docs-site/llms.txt +43 -0
- package/dist/docs-site/manual-tests.html +596 -0
- package/dist/docs-site/mcp.html +391 -0
- package/dist/docs-site/multimodel.html +364 -0
- package/dist/docs-site/mythical-pro-agents.html +360 -0
- package/dist/docs-site/notifier.html +338 -0
- package/dist/docs-site/openai-endpoints.html +387 -0
- package/dist/docs-site/openrouter.html +344 -0
- package/dist/docs-site/quickstart.html +369 -0
- package/dist/docs-site/refactor/ux.html +532 -0
- package/dist/docs-site/sessions.html +388 -0
- package/dist/docs-site/social-card.png +0 -0
- package/dist/docs-site/social-card.svg +79 -0
- package/dist/docs-site/spec.html +363 -0
- package/dist/docs-site/testing.html +320 -0
- package/dist/docs-site/tui-debug.html +326 -0
- package/dist/docs-site/windows-work.html +323 -0
- package/dist/docs-site/windows.html +320 -0
- package/dist/src/browser/actions/deepResearch.js +132 -61
- package/dist/src/browser/actions/modelSelection.js +45 -2
- package/dist/src/browser/actions/thinkingTime.js +65 -20
- package/dist/src/browser/artifacts.js +2 -8
- package/dist/src/browser/chatgptFiles.js +198 -49
- package/dist/src/browser/chromeCookies.js +312 -0
- package/dist/src/browser/chromeLifecycle.js +35 -4
- package/dist/src/browser/deepResearchResult.js +23 -0
- package/dist/src/browser/index.js +82 -11
- package/dist/src/browser/keytarShim.js +56 -0
- package/dist/src/browser/profileCopy.js +93 -0
- package/dist/src/browser/windowsCookies.js +219 -0
- package/dist/src/cli/browserConfig.js +17 -1
- package/dist/src/cli/sessionRunner.js +13 -7
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +20 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +128 -0
- package/dist/vendor/oracle-notifier/build-notifier.sh +0 -0
- package/package.json +33 -31
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +20 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +128 -0
- package/vendor/oracle-notifier/README.md +26 -0
- package/vendor/oracle-notifier/build-notifier.sh +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DEEP_RESEARCH_PLUS_BUTTON, DEEP_RESEARCH_DROPDOWN_ITEM_TEXT, DEEP_RESEARCH_PILL_LABEL, DEEP_RESEARCH_POLL_INTERVAL_MS, DEEP_RESEARCH_AUTO_CONFIRM_WAIT_MS, DEEP_RESEARCH_DEFAULT_TIMEOUT_MS, FINISHED_ACTIONS_SELECTOR, STOP_BUTTON_SELECTOR, CONVERSATION_TURN_SELECTOR, } from "../constants.js";
|
|
2
2
|
import { delay } from "../utils.js";
|
|
3
|
+
import { isDeepResearchIncompleteText } from "../deepResearchResult.js";
|
|
3
4
|
import { buildClickDispatcher } from "./domEvents.js";
|
|
4
5
|
import { captureAssistantMarkdown, readAssistantSnapshot } from "./assistantResponse.js";
|
|
5
6
|
import { BrowserAutomationError } from "../../oracle/errors.js";
|
|
@@ -116,7 +117,10 @@ export async function waitForDeepResearchCompletion(Runtime, logger, timeoutMs =
|
|
|
116
117
|
: -1;
|
|
117
118
|
const scopedToNewTurns = minTurnLiteral >= 0;
|
|
118
119
|
const ignoredTargetKeys = new Set(options?.ignoredTargetKeys ?? []);
|
|
119
|
-
const requireScopedTargetOwner = options?.requireScopedTargetOwner === true
|
|
120
|
+
const requireScopedTargetOwner = options?.requireScopedTargetOwner === true ||
|
|
121
|
+
(scopedToNewTurns && options?.targetBaselineCaptured !== true);
|
|
122
|
+
let observedResearchEvidence = false;
|
|
123
|
+
let loggedIncompleteResult = false;
|
|
120
124
|
logger(`Monitoring Deep Research (timeout: ${Math.round(timeoutMs / 60_000)}min)...`);
|
|
121
125
|
while (Date.now() - start < timeoutMs) {
|
|
122
126
|
const { result } = await Runtime.evaluate({
|
|
@@ -127,7 +131,6 @@ export async function waitForDeepResearchCompletion(Runtime, logger, timeoutMs =
|
|
|
127
131
|
if (val?.accountBlocked) {
|
|
128
132
|
throw new BrowserAutomationError("ChatGPT account security block detected during Deep Research. Open chatgpt.com in Chrome, secure the account, then rerun Oracle.", { stage: "chatgpt-account-blocked", code: "chatgpt-account-blocked" });
|
|
129
133
|
}
|
|
130
|
-
const activeScopedResearch = Boolean(val?.hasActiveScopedResearch);
|
|
131
134
|
// ChatGPT renders the Deep Research report inside an out-of-process,
|
|
132
135
|
// sandboxed iframe (connector_openai_deep_research.*.oaiusercontent.com),
|
|
133
136
|
// doubly nested and same-origin. That OOPIF does NOT appear in the main
|
|
@@ -136,24 +139,27 @@ export async function waitForDeepResearchCompletion(Runtime, logger, timeoutMs =
|
|
|
136
139
|
// (readDeepResearchTargetResult) attaches to the iframe's own CDP target and
|
|
137
140
|
// walks its nested frames, so it CAN read the report. Prefer the target path
|
|
138
141
|
// and fall back to the in-page frame path for legacy/inline rendering.
|
|
139
|
-
const
|
|
142
|
+
const rawTargetResult = client
|
|
140
143
|
? ((await readDeepResearchTargetResult(client, ignoredTargetKeys, requireScopedTargetOwner ? minTurnLiteral : -1).catch(() => null))?.read ?? null)
|
|
141
144
|
: null;
|
|
145
|
+
const targetResult = filterIncompleteDeepResearchRead(rawTargetResult);
|
|
142
146
|
// A completed target read is authoritative. If the target read is missing or
|
|
143
147
|
// only in-progress, still try the in-page frame path so an incomplete target
|
|
144
148
|
// read does not suppress a completed report there (legacy/inline rendering).
|
|
145
|
-
const
|
|
146
|
-
? await readDeepResearchFrameResult(Runtime, Page).catch(() => null)
|
|
149
|
+
const inPageScan = !targetResult?.completed && Page
|
|
150
|
+
? await readDeepResearchFrameResult(Runtime, Page, client, scopedToNewTurns ? minTurnLiteral : -1).catch(() => null)
|
|
147
151
|
: null;
|
|
152
|
+
const rawInPageResult = inPageScan?.read ?? null;
|
|
153
|
+
const inPageResult = filterIncompleteDeepResearchRead(rawInPageResult);
|
|
148
154
|
const read = pickPreferredDeepResearchRead(targetResult, inPageResult);
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
// Target keys captured before submission are ignored, so a target result is
|
|
156
|
+
// tied to this run. Main-page iframes are not: old reports can remain in the
|
|
157
|
+
// conversation and must never authorize a new normal-response fallback.
|
|
158
|
+
observedResearchEvidence ||= Boolean(rawTargetResult ||
|
|
159
|
+
(scopedToNewTurns && rawInPageResult) ||
|
|
160
|
+
val?.researchActivity ||
|
|
161
|
+
val?.hasActiveScopedResearch);
|
|
162
|
+
if (read?.completed && read.text) {
|
|
157
163
|
logger(`Deep Research completed (${Math.round((Date.now() - start) / 1000)}s elapsed)`);
|
|
158
164
|
return {
|
|
159
165
|
text: read.text,
|
|
@@ -163,9 +169,18 @@ export async function waitForDeepResearchCompletion(Runtime, logger, timeoutMs =
|
|
|
163
169
|
}
|
|
164
170
|
// Completion detected
|
|
165
171
|
if (val?.finished) {
|
|
172
|
+
if (!observedResearchEvidence) {
|
|
173
|
+
throw new BrowserAutomationError("ChatGPT returned a completed response without starting Deep Research. The Deep Research selection may have silently fallen back to a normal response.", { stage: "deep-research-not-started", code: "deep-research-not-started" });
|
|
174
|
+
}
|
|
166
175
|
logger(`Deep Research completed (${Math.round((Date.now() - start) / 1000)}s elapsed)`);
|
|
167
176
|
return await extractDeepResearchResult(Runtime, logger, minTurnIndex ?? undefined);
|
|
168
177
|
}
|
|
178
|
+
const incompleteFrameResult = Boolean((rawTargetResult?.completed && !targetResult?.completed) ||
|
|
179
|
+
(rawInPageResult?.completed && !inPageResult?.completed));
|
|
180
|
+
if ((val?.incompleteResult || incompleteFrameResult) && !loggedIncompleteResult) {
|
|
181
|
+
logger("Deep Research interim status detected; waiting for the final report");
|
|
182
|
+
loggedIncompleteResult = true;
|
|
183
|
+
}
|
|
169
184
|
// Progress logging every 60 seconds
|
|
170
185
|
const now = Date.now();
|
|
171
186
|
if (now - lastLogTime >= 60_000) {
|
|
@@ -204,24 +219,26 @@ export async function extractDeepResearchResult(Runtime, logger, minTurnIndex) {
|
|
|
204
219
|
};
|
|
205
220
|
// Try the copy-button approach first for clean markdown
|
|
206
221
|
const markdown = await captureAssistantMarkdown(Runtime, meta, logger);
|
|
207
|
-
if (markdown && !
|
|
222
|
+
if (markdown && !isDeepResearchIncompleteText(markdown)) {
|
|
208
223
|
return { text: markdown, html: snapshot?.html ?? undefined, meta };
|
|
209
224
|
}
|
|
210
225
|
// Fall back to snapshot text
|
|
211
|
-
if (snapshot?.text && !
|
|
226
|
+
if (snapshot?.text && !isDeepResearchIncompleteText(snapshot.text)) {
|
|
212
227
|
return { text: snapshot.text, html: snapshot.html ?? undefined, meta };
|
|
213
228
|
}
|
|
214
229
|
throw new BrowserAutomationError("Deep Research completed but failed to extract the response text.", { stage: "deep-research-extract", code: "extraction-failed" });
|
|
215
230
|
}
|
|
216
|
-
function isDeepResearchPlaceholderText(text) {
|
|
217
|
-
const normalized = text.toLowerCase().replace(/\s+/g, " ").trim();
|
|
218
|
-
return (normalized === "called tool" ||
|
|
219
|
-
normalized === "used tool" ||
|
|
220
|
-
normalized === "użyto narzędzia" ||
|
|
221
|
-
normalized === "narzędzie wywołane");
|
|
222
|
-
}
|
|
223
231
|
export function isDeepResearchPlaceholderTextForTest(text) {
|
|
224
|
-
return
|
|
232
|
+
return isDeepResearchIncompleteText(text);
|
|
233
|
+
}
|
|
234
|
+
function filterIncompleteDeepResearchRead(result) {
|
|
235
|
+
if (!result?.completed || !result.text || !isDeepResearchIncompleteText(result.text)) {
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
return { ...result, completed: false, inProgress: true };
|
|
239
|
+
}
|
|
240
|
+
export function filterIncompleteDeepResearchReadForTest(result) {
|
|
241
|
+
return filterIncompleteDeepResearchRead(result);
|
|
225
242
|
}
|
|
226
243
|
/**
|
|
227
244
|
* Choose the authoritative Deep Research read between the target-attach result
|
|
@@ -243,37 +260,64 @@ function pickPreferredDeepResearchRead(targetResult, inPageResult) {
|
|
|
243
260
|
export function pickPreferredDeepResearchReadForTest(targetResult, inPageResult) {
|
|
244
261
|
return pickPreferredDeepResearchRead(targetResult, inPageResult);
|
|
245
262
|
}
|
|
246
|
-
async function readDeepResearchFrameResult(Runtime, Page) {
|
|
263
|
+
async function readDeepResearchFrameResult(Runtime, Page, client, minTurnIndex = -1) {
|
|
247
264
|
const pageWithFrames = Page;
|
|
248
265
|
if (typeof pageWithFrames.getFrameTree !== "function" ||
|
|
249
266
|
typeof pageWithFrames.createIsolatedWorld !== "function") {
|
|
250
267
|
return null;
|
|
251
268
|
}
|
|
252
269
|
const frameTree = (await pageWithFrames.getFrameTree())?.frameTree;
|
|
253
|
-
const
|
|
254
|
-
if (
|
|
270
|
+
const frameIds = collectPageDeepResearchFrameIds(frameTree);
|
|
271
|
+
if (frameIds.length === 0) {
|
|
255
272
|
return null;
|
|
256
273
|
}
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if (typeof world.executionContextId !== "number") {
|
|
263
|
-
return null;
|
|
274
|
+
const rawClient = client;
|
|
275
|
+
if (minTurnIndex >= 0) {
|
|
276
|
+
if (typeof rawClient?.send !== "function") {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
264
279
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
280
|
+
let best = null;
|
|
281
|
+
for (const frameId of frameIds) {
|
|
282
|
+
let ownerTurnIndex = null;
|
|
283
|
+
if (minTurnIndex >= 0 && rawClient?.send) {
|
|
284
|
+
ownerTurnIndex = await readDeepResearchTargetOwnerTurnIndex(rawClient, frameId, rawClient.oraclePageSessionId);
|
|
285
|
+
if (ownerTurnIndex === null || ownerTurnIndex < minTurnIndex) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const world = await pageWithFrames.createIsolatedWorld({
|
|
290
|
+
frameId,
|
|
291
|
+
worldName: "oracle-deep-research",
|
|
292
|
+
grantUniveralAccess: true,
|
|
293
|
+
});
|
|
294
|
+
if (typeof world.executionContextId !== "number") {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
const { result } = await Runtime.evaluate({
|
|
298
|
+
expression: buildDeepResearchFrameStatusExpression(),
|
|
299
|
+
contextId: world.executionContextId,
|
|
300
|
+
returnByValue: true,
|
|
301
|
+
});
|
|
302
|
+
const read = result?.value ?? null;
|
|
303
|
+
if (!read) {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
best = { read, ownerTurnIndex };
|
|
307
|
+
if (read.completed) {
|
|
308
|
+
return best;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return best;
|
|
271
312
|
}
|
|
272
313
|
async function readDeepResearchTargetResult(client, ignoredTargetKeys = new Set(), minTurnIndex = -1) {
|
|
273
314
|
const rawClient = client;
|
|
274
315
|
if (typeof rawClient.send !== "function") {
|
|
275
316
|
return null;
|
|
276
317
|
}
|
|
318
|
+
if (typeof client.on !== "function") {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
277
321
|
// On the browser-WSEndpoint path, `client` is a session-bound wrapper whose
|
|
278
322
|
// domain methods target the page session but whose raw `send` is the
|
|
279
323
|
// browser-level send. We must therefore pass the page session id explicitly so
|
|
@@ -299,7 +343,7 @@ async function readDeepResearchTargetResult(client, ignoredTargetKeys = new Set(
|
|
|
299
343
|
ownedSessionIds.add(eventSessionId);
|
|
300
344
|
}
|
|
301
345
|
};
|
|
302
|
-
client.on
|
|
346
|
+
client.on("Target.attachedToTarget", onAttached);
|
|
303
347
|
try {
|
|
304
348
|
// Scope discovery to the current Oracle-controlled page. `client` is
|
|
305
349
|
// connected to the conversation page target, so enabling auto-attach on this
|
|
@@ -311,13 +355,16 @@ async function readDeepResearchTargetResult(client, ignoredTargetKeys = new Set(
|
|
|
311
355
|
// would surface another tab's completed Deep Research report and let it be
|
|
312
356
|
// saved into the current session (cross-tab leak). Only auto-attached,
|
|
313
357
|
// page-scoped sessions are treated as belonging to this run.
|
|
314
|
-
await rawClient
|
|
358
|
+
const autoAttachEnabled = await rawClient
|
|
315
359
|
.send("Target.setAutoAttach", {
|
|
316
360
|
autoAttach: true,
|
|
317
361
|
waitForDebuggerOnStart: false,
|
|
318
362
|
flatten: true,
|
|
319
363
|
}, pageSessionId)
|
|
320
|
-
.
|
|
364
|
+
.then(() => true, () => false);
|
|
365
|
+
if (!autoAttachEnabled) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
321
368
|
await delay(100);
|
|
322
369
|
if (minTurnIndex >= 0) {
|
|
323
370
|
await rawClient.send("DOM.enable", {}, pageSessionId).catch(() => undefined);
|
|
@@ -374,7 +421,11 @@ async function readDeepResearchTargetResult(client, ignoredTargetKeys = new Set(
|
|
|
374
421
|
}
|
|
375
422
|
}
|
|
376
423
|
export async function captureDeepResearchTargetKeys(client) {
|
|
377
|
-
|
|
424
|
+
const scan = await readDeepResearchTargetResult(client);
|
|
425
|
+
if (!scan) {
|
|
426
|
+
throw new Error("Deep Research target baseline capture unavailable");
|
|
427
|
+
}
|
|
428
|
+
return scan.targetKeys;
|
|
378
429
|
}
|
|
379
430
|
async function readDeepResearchTargetOwnerTurnIndex(rawClient, frameId, pageSessionId) {
|
|
380
431
|
const owner = (await rawClient
|
|
@@ -419,7 +470,7 @@ async function readDeepResearchTargetSession(rawClient, sessionId, targetUrl) {
|
|
|
419
470
|
const frameTree = (await rawClient
|
|
420
471
|
.send("Page.getFrameTree", {}, sessionId)
|
|
421
472
|
.catch(() => null));
|
|
422
|
-
const
|
|
473
|
+
const ownerFrameId = frameTree?.frameTree?.frame?.id;
|
|
423
474
|
if (!isConfirmedDeepResearchTarget(targetUrl, frameTree?.frameTree)) {
|
|
424
475
|
return { confirmed: false, read: null };
|
|
425
476
|
}
|
|
@@ -438,7 +489,7 @@ async function readDeepResearchTargetSession(rawClient, sessionId, targetUrl) {
|
|
|
438
489
|
}
|
|
439
490
|
const value = await evaluateDeepResearchFrameStatus(rawClient, sessionId, world.executionContextId);
|
|
440
491
|
if (value?.completed) {
|
|
441
|
-
return { confirmed: true, read: value, frameId };
|
|
492
|
+
return { confirmed: true, read: value, frameId: ownerFrameId };
|
|
442
493
|
}
|
|
443
494
|
if ((value?.textLength ?? 0) > (best?.textLength ?? 0) || value?.inProgress) {
|
|
444
495
|
best = value;
|
|
@@ -446,12 +497,12 @@ async function readDeepResearchTargetSession(rawClient, sessionId, targetUrl) {
|
|
|
446
497
|
}
|
|
447
498
|
const topFrameValue = await evaluateDeepResearchFrameStatus(rawClient, sessionId);
|
|
448
499
|
if (topFrameValue?.completed) {
|
|
449
|
-
return { confirmed: true, read: topFrameValue, frameId };
|
|
500
|
+
return { confirmed: true, read: topFrameValue, frameId: ownerFrameId };
|
|
450
501
|
}
|
|
451
502
|
if ((topFrameValue?.textLength ?? 0) > (best?.textLength ?? 0) || topFrameValue?.inProgress) {
|
|
452
503
|
best = topFrameValue;
|
|
453
504
|
}
|
|
454
|
-
return { confirmed: true, read: best, frameId };
|
|
505
|
+
return { confirmed: true, read: best, frameId: ownerFrameId };
|
|
455
506
|
}
|
|
456
507
|
async function evaluateDeepResearchFrameStatus(rawClient, sessionId, contextId) {
|
|
457
508
|
const response = (await rawClient
|
|
@@ -474,21 +525,20 @@ function isDeepResearchFrameDescriptor(url, name = "") {
|
|
|
474
525
|
return (descriptor.includes("connector_openai_deep_research") || descriptor.includes("deep-research"));
|
|
475
526
|
}
|
|
476
527
|
function findDeepResearchFrameId(tree) {
|
|
528
|
+
return collectPageDeepResearchFrameIds(tree)[0] ?? null;
|
|
529
|
+
}
|
|
530
|
+
function collectPageDeepResearchFrameIds(tree) {
|
|
477
531
|
if (!tree?.frame) {
|
|
478
|
-
return
|
|
532
|
+
return [];
|
|
479
533
|
}
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
return tree.frame.id ?? null;
|
|
534
|
+
const ids = [];
|
|
535
|
+
if (tree.frame.id && isDeepResearchFrameDescriptor(tree.frame.url ?? "", tree.frame.name ?? "")) {
|
|
536
|
+
ids.push(tree.frame.id);
|
|
484
537
|
}
|
|
485
538
|
for (const child of tree.childFrames ?? []) {
|
|
486
|
-
|
|
487
|
-
if (match) {
|
|
488
|
-
return match;
|
|
489
|
-
}
|
|
539
|
+
ids.push(...collectPageDeepResearchFrameIds(child));
|
|
490
540
|
}
|
|
491
|
-
return
|
|
541
|
+
return ids;
|
|
492
542
|
}
|
|
493
543
|
function collectDeepResearchFrameIds(tree) {
|
|
494
544
|
if (!tree?.frame) {
|
|
@@ -644,20 +694,41 @@ function buildDeepResearchCompletionPollExpression(minTurnIndex) {
|
|
|
644
694
|
const text = (lastTurn?.textContent || '').trim();
|
|
645
695
|
const normalized = text.toLowerCase().replace(/\\s+/g, ' ').trim();
|
|
646
696
|
const textLength = text.length;
|
|
697
|
+
const lines = text.split(/\\n+/).map(line => line.trim()).filter(Boolean);
|
|
698
|
+
const tailIsPlanningPanel = text.length <= 1500 &&
|
|
699
|
+
lines.length >= 4 &&
|
|
700
|
+
lines.length <= 20 &&
|
|
701
|
+
/^update$/i.test(lines[1] || '') &&
|
|
702
|
+
/^stop research$/i.test(lines[lines.length - 1] || '') &&
|
|
703
|
+
/^determining steps for creating a report(?:\\.\\.\\.)?$/i.test(lines[lines.length - 2] || '');
|
|
647
704
|
const isToolStub = normalized === 'called tool' ||
|
|
648
705
|
normalized === 'used tool' ||
|
|
649
706
|
normalized === 'użyto narzędzia' ||
|
|
650
707
|
normalized === 'narzędzie wywołane';
|
|
708
|
+
const incompleteResult = isToolStub ||
|
|
709
|
+
normalized === 'planning' ||
|
|
710
|
+
normalized === 'researching' ||
|
|
711
|
+
normalized === 'searching the web' ||
|
|
712
|
+
(text.trimStart().startsWith('<system-reminder>') &&
|
|
713
|
+
/<system-reminder>[\\s\\S]*#\\s*plan mode\\b/i.test(text)) ||
|
|
714
|
+
tailIsPlanningPanel;
|
|
651
715
|
const finished = Boolean(lastTurn?.querySelector(${finishedSelector})) &&
|
|
652
716
|
textLength >= 40 &&
|
|
653
|
-
!
|
|
717
|
+
!incompleteResult;
|
|
654
718
|
const hasIframe = Array.from(document.querySelectorAll('iframe')).some(f => {
|
|
655
719
|
const rect = f.getBoundingClientRect();
|
|
656
720
|
return rect.width > 200 && rect.height > 200;
|
|
657
721
|
});
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
|
|
722
|
+
const hasScopedDeepResearchIframe = Array.from(lastTurn?.querySelectorAll?.('iframe') || []).some(f => {
|
|
723
|
+
const rect = f.getBoundingClientRect();
|
|
724
|
+
const descriptor = String(f.getAttribute('src') || '') + ' ' + String(f.getAttribute('name') || '');
|
|
725
|
+
return rect.width > 200 && rect.height > 200 &&
|
|
726
|
+
/connector_openai_deep_research|deep-research/i.test(descriptor);
|
|
727
|
+
});
|
|
728
|
+
const hasActiveScopedResearch = scopedToNewTurns && Boolean(lastTurn) &&
|
|
729
|
+
hasScopedDeepResearchIframe &&
|
|
730
|
+
(textLength < 40 || isToolStub || tailIsPlanningPanel || /chatgpt\\s+said:?$/i.test(text));
|
|
731
|
+
return { finished, stopVisible, textLength, hasIframe, isToolStub, incompleteResult, researchActivity: tailIsPlanningPanel || (isToolStub && hasScopedDeepResearchIframe), hasActiveScopedResearch, accountBlocked };
|
|
661
732
|
})()`;
|
|
662
733
|
}
|
|
663
734
|
export function buildDeepResearchStatusExpressionForTest() {
|
|
@@ -659,10 +659,27 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
659
659
|
normalizedTestId.includes('pro');
|
|
660
660
|
const candidateHasInstant =
|
|
661
661
|
normalizedText.includes('instant') || normalizedTestId.includes('instant');
|
|
662
|
+
const candidateSelectsConfiguredVersion =
|
|
663
|
+
Boolean(getConfigurationDialog()) &&
|
|
664
|
+
candidateSelectsDesiredVersion &&
|
|
665
|
+
(node?.getAttribute?.('role') === 'option' ||
|
|
666
|
+
node?.getAttribute?.('role') === 'menuitemradio');
|
|
667
|
+
const candidateOpensInstantSubmenu =
|
|
668
|
+
wantsInstant &&
|
|
669
|
+
candidateSelectsDesiredVersion &&
|
|
670
|
+
!candidateHasInstant &&
|
|
671
|
+
(normalizedTestId.includes('submenu') ||
|
|
672
|
+
node?.getAttribute?.('aria-haspopup') === 'menu' ||
|
|
673
|
+
node?.getAttribute?.('data-has-submenu') !== null);
|
|
662
674
|
if (wantsPro && candidateHasThinking) return 0;
|
|
663
675
|
if (wantsPro && candidateHasLegacyProVersion && !candidateSelectsDesiredVersion) return 0;
|
|
664
676
|
if (wantsPro && !candidateHasPro && !candidateSelectsDesiredVersion) return 0;
|
|
665
|
-
if (
|
|
677
|
+
if (
|
|
678
|
+
wantsInstant &&
|
|
679
|
+
!candidateHasInstant &&
|
|
680
|
+
!candidateOpensInstantSubmenu &&
|
|
681
|
+
!candidateSelectsConfiguredVersion
|
|
682
|
+
) return 0;
|
|
666
683
|
if (wantsThinking && candidateHasPro) return 0;
|
|
667
684
|
if (wantsThinking && !candidateHasThinking && !candidateSelectsDesiredVersion) return 0;
|
|
668
685
|
if (desiredVersion === '5-5' && normalizedText && !candidateGpt55VisibleAlias) {
|
|
@@ -701,10 +718,14 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
701
718
|
if (
|
|
702
719
|
desiredVersion &&
|
|
703
720
|
candidateTextVersion === desiredVersion &&
|
|
721
|
+
!candidateOpensInstantSubmenu &&
|
|
704
722
|
!(wantsThinking && desiredVersion === '5-5' && normalizedText === 'gpt 5 5')
|
|
705
723
|
) {
|
|
706
724
|
score += 1200;
|
|
707
725
|
}
|
|
726
|
+
if (candidateOpensInstantSubmenu) {
|
|
727
|
+
score += 300;
|
|
728
|
+
}
|
|
708
729
|
if (candidateOpensVersionSubmenu) {
|
|
709
730
|
score += 500;
|
|
710
731
|
}
|
|
@@ -809,6 +830,9 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
809
830
|
const currentButtonLabel = normalizeText(getButtonLabel());
|
|
810
831
|
return !labelHasProWord(currentButtonLabel) && !hasProComposerPill();
|
|
811
832
|
};
|
|
833
|
+
const openedSubmenuKeys = new Set();
|
|
834
|
+
const submenuKey = (normalizedText, testid) =>
|
|
835
|
+
normalizeText(testid ?? '') + '|' + normalizedText;
|
|
812
836
|
|
|
813
837
|
const findBestOption = () => {
|
|
814
838
|
// Walk through every menu item and keep whichever earns the highest score.
|
|
@@ -823,6 +847,10 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
823
847
|
const text = option.textContent ?? '';
|
|
824
848
|
const normalizedText = normalizeText(text);
|
|
825
849
|
const testid = option.getAttribute('data-testid') ?? '';
|
|
850
|
+
const optionSubmenuKey = submenuKey(normalizedText, testid);
|
|
851
|
+
if (isSubmenuOption(option, testid) && openedSubmenuKeys.has(optionSubmenuKey)) {
|
|
852
|
+
continue;
|
|
853
|
+
}
|
|
826
854
|
let score = scoreOption(normalizedText, testid, option);
|
|
827
855
|
if (score <= 0) {
|
|
828
856
|
continue;
|
|
@@ -832,7 +860,14 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
832
860
|
}
|
|
833
861
|
const label = getOptionLabel(option);
|
|
834
862
|
if (!bestMatch || score > bestMatch.score) {
|
|
835
|
-
bestMatch = {
|
|
863
|
+
bestMatch = {
|
|
864
|
+
node: option,
|
|
865
|
+
label,
|
|
866
|
+
score,
|
|
867
|
+
testid,
|
|
868
|
+
normalizedText,
|
|
869
|
+
submenuKey: optionSubmenuKey,
|
|
870
|
+
};
|
|
836
871
|
}
|
|
837
872
|
}
|
|
838
873
|
}
|
|
@@ -924,6 +959,13 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
924
959
|
const openDelay = () => new Promise((r) => setTimeout(r, INITIAL_WAIT_MS));
|
|
925
960
|
let initialized = false;
|
|
926
961
|
const attempt = async () => {
|
|
962
|
+
if (performance.now() - start > MAX_WAIT_MS) {
|
|
963
|
+
resolve({
|
|
964
|
+
status: 'option-not-found',
|
|
965
|
+
hint: { temporaryChat: detectTemporaryChat(), availableOptions: collectAvailableOptions() },
|
|
966
|
+
});
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
927
969
|
if (!initialized) {
|
|
928
970
|
initialized = true;
|
|
929
971
|
await openDelay();
|
|
@@ -946,6 +988,7 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
946
988
|
// Keep scanning once the submenu opens instead of treating the submenu click as a final switch.
|
|
947
989
|
const isSubmenu = isSubmenuOption(match.node, match.testid);
|
|
948
990
|
if (isSubmenu) {
|
|
991
|
+
openedSubmenuKeys.add(match.submenuKey);
|
|
949
992
|
openSubmenuOption(match.node);
|
|
950
993
|
setTimeout(attempt, REOPEN_INTERVAL_MS / 2);
|
|
951
994
|
return;
|
|
@@ -578,9 +578,62 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
578
578
|
}
|
|
579
579
|
return null;
|
|
580
580
|
};
|
|
581
|
-
|
|
582
|
-
|
|
581
|
+
let composerEffortPill = findComposerEffortPill();
|
|
582
|
+
let modelBtn = findModelButton();
|
|
583
|
+
const modelKindFromLegacyTrailing = (trailing) => {
|
|
584
|
+
const row = trailing.closest?.(
|
|
585
|
+
'[role="menuitem"], [role="menuitemradio"], [data-radix-collection-item]',
|
|
586
|
+
);
|
|
587
|
+
const idText = normalize(
|
|
588
|
+
(row?.getAttribute?.('data-testid') ?? '') + ' ' +
|
|
589
|
+
(trailing.getAttribute?.('data-testid') ?? '')
|
|
590
|
+
);
|
|
591
|
+
if (!idText.includes('model switcher')) return null;
|
|
592
|
+
const modelPart = normalize(idText.replace(/\\bthinking effort\\b.*$/, ''));
|
|
593
|
+
if (hasToken(modelPart, 'pro')) return 'pro';
|
|
594
|
+
if (hasToken(modelPart, 'thinking')) return 'thinking';
|
|
595
|
+
if (hasToken(modelPart, 'instant')) return 'instant';
|
|
596
|
+
return null;
|
|
597
|
+
};
|
|
598
|
+
const legacyEffortOwnerIsReady = () => {
|
|
599
|
+
if (
|
|
600
|
+
TARGET_MODEL_KIND === 'pro' &&
|
|
601
|
+
TARGET_LEVEL === 'extended' &&
|
|
602
|
+
isVisible(document.querySelector(INTELLIGENCE_MENU_SELECTOR))
|
|
603
|
+
) {
|
|
604
|
+
return true;
|
|
605
|
+
}
|
|
606
|
+
const expectedKind = TARGET_MODEL_KIND || modelKindFromNode(modelBtn);
|
|
607
|
+
return Boolean(
|
|
608
|
+
expectedKind &&
|
|
609
|
+
findTrailingButtons().some(
|
|
610
|
+
(button) => isVisible(button) && modelKindFromLegacyTrailing(button) === expectedKind,
|
|
611
|
+
),
|
|
612
|
+
);
|
|
613
|
+
};
|
|
614
|
+
let attemptedModelButton =
|
|
615
|
+
modelBtn?.getAttribute?.('aria-expanded') === 'true' ? modelBtn : null;
|
|
616
|
+
const effortOwnerDeadline = performance.now() + MAX_WAIT_MS;
|
|
617
|
+
while (!composerEffortPill && performance.now() < effortOwnerDeadline) {
|
|
618
|
+
if (
|
|
619
|
+
modelBtn &&
|
|
620
|
+
attemptedModelButton !== modelBtn &&
|
|
621
|
+
modelBtn.getAttribute?.('aria-expanded') !== 'true'
|
|
622
|
+
) {
|
|
623
|
+
dispatchClickSequence(modelBtn);
|
|
624
|
+
attemptedModelButton = modelBtn;
|
|
625
|
+
await sleep(INITIAL_WAIT_MS);
|
|
626
|
+
}
|
|
627
|
+
if (modelBtn && legacyEffortOwnerIsReady()) break;
|
|
628
|
+
await sleep(100);
|
|
629
|
+
composerEffortPill = findComposerEffortPill();
|
|
630
|
+
modelBtn = findModelButton();
|
|
631
|
+
if (modelBtn?.getAttribute?.('aria-expanded') === 'true') {
|
|
632
|
+
attemptedModelButton = modelBtn;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
583
635
|
if (composerEffortPill) {
|
|
636
|
+
if (attemptedModelButton && attemptedModelButton !== composerEffortPill) closeOpenMenus();
|
|
584
637
|
const composerModelKind = TARGET_MODEL_KIND || modelKindFromNode(composerEffortPill);
|
|
585
638
|
if (composerEffortPill.getAttribute?.('aria-expanded') !== 'true') {
|
|
586
639
|
dispatchClickSequence(composerEffortPill);
|
|
@@ -646,22 +699,7 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
646
699
|
(trailing.getAttribute?.('data-testid') ?? '')
|
|
647
700
|
);
|
|
648
701
|
};
|
|
649
|
-
const
|
|
650
|
-
const row = rowForTrailing(trailing) || findEffortRow(trailing);
|
|
651
|
-
return normalize(
|
|
652
|
-
(row?.getAttribute?.('data-testid') ?? '') + ' ' +
|
|
653
|
-
(trailing.getAttribute?.('data-testid') ?? '')
|
|
654
|
-
);
|
|
655
|
-
};
|
|
656
|
-
const modelKindFromTrailing = (trailing) => {
|
|
657
|
-
const idText = testIdTextForTrailing(trailing);
|
|
658
|
-
if (!idText.includes('model switcher')) return null;
|
|
659
|
-
const modelPart = normalize(idText.replace(/\\bthinking effort\\b.*$/, ''));
|
|
660
|
-
if (hasToken(modelPart, 'pro')) return 'pro';
|
|
661
|
-
if (hasToken(modelPart, 'thinking')) return 'thinking';
|
|
662
|
-
if (hasToken(modelPart, 'instant')) return 'instant';
|
|
663
|
-
return null;
|
|
664
|
-
};
|
|
702
|
+
const modelKindFromTrailing = modelKindFromLegacyTrailing;
|
|
665
703
|
const trailingMatchesTargetModelKind = (trailing) => {
|
|
666
704
|
if (!TARGET_MODEL_KIND) return false;
|
|
667
705
|
const idKind = modelKindFromTrailing(trailing);
|
|
@@ -698,12 +736,19 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
698
736
|
return null;
|
|
699
737
|
};
|
|
700
738
|
|
|
701
|
-
const
|
|
739
|
+
const modelButtonDeadline = performance.now() + MAX_WAIT_MS;
|
|
740
|
+
while (!modelBtn && performance.now() < modelButtonDeadline) {
|
|
741
|
+
await sleep(100);
|
|
742
|
+
modelBtn = findModelButton();
|
|
743
|
+
}
|
|
702
744
|
if (!modelBtn) {
|
|
703
745
|
return failure('chip-not-found');
|
|
704
746
|
}
|
|
705
747
|
// Open model menu (idempotent — leaves it open if already open).
|
|
706
|
-
if (
|
|
748
|
+
if (
|
|
749
|
+
modelBtn.getAttribute('aria-expanded') !== 'true' &&
|
|
750
|
+
!legacyEffortOwnerIsReady()
|
|
751
|
+
) {
|
|
707
752
|
dispatchClickSequence(modelBtn);
|
|
708
753
|
await sleep(INITIAL_WAIT_MS);
|
|
709
754
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { getOracleHomeDir } from "../oracleHome.js";
|
|
4
|
+
import { isDeepResearchIncompleteText } from "./deepResearchResult.js";
|
|
4
5
|
const ARTIFACTS_DIRNAME = "artifacts";
|
|
5
6
|
function sanitizePathSegment(value, fallback) {
|
|
6
7
|
const sanitized = value
|
|
@@ -85,16 +86,9 @@ export async function writeBinaryBrowserArtifact(params) {
|
|
|
85
86
|
sourceUrl: params.sourceUrl,
|
|
86
87
|
};
|
|
87
88
|
}
|
|
88
|
-
function isToolOnlyPlaceholder(text) {
|
|
89
|
-
const normalized = text.toLowerCase().replace(/\s+/g, " ").trim();
|
|
90
|
-
return (normalized === "called tool" ||
|
|
91
|
-
normalized === "used tool" ||
|
|
92
|
-
normalized === "użyto narzędzia" ||
|
|
93
|
-
normalized === "narzędzie wywołane");
|
|
94
|
-
}
|
|
95
89
|
export async function saveDeepResearchReportArtifact(params) {
|
|
96
90
|
const report = params.reportMarkdown.trim();
|
|
97
|
-
if (report.length < 40 ||
|
|
91
|
+
if (report.length < 40 || isDeepResearchIncompleteText(report)) {
|
|
98
92
|
return null;
|
|
99
93
|
}
|
|
100
94
|
return writeTextBrowserArtifact({
|