@steipete/oracle 0.14.1 → 0.15.1
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 +16 -6
- 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 +416 -0
- package/dist/docs-site/browser-mode.html +594 -0
- package/dist/docs-site/chromium-forks.html +347 -0
- package/dist/docs-site/cli-reference.html +346 -0
- package/dist/docs-site/configuration.html +462 -0
- package/dist/docs-site/favicon.svg +14 -0
- package/dist/docs-site/followup.html +375 -0
- package/dist/docs-site/gemini.html +383 -0
- package/dist/docs-site/grok.html +325 -0
- package/dist/docs-site/index.html +360 -0
- package/dist/docs-site/install.html +335 -0
- package/dist/docs-site/linux.html +321 -0
- package/dist/docs-site/llms.txt +43 -0
- package/dist/docs-site/manual-tests.html +596 -0
- package/dist/docs-site/mcp.html +391 -0
- package/dist/docs-site/multimodel.html +364 -0
- package/dist/docs-site/mythical-pro-agents.html +360 -0
- package/dist/docs-site/notifier.html +338 -0
- package/dist/docs-site/openai-endpoints.html +399 -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/assistantResponse.js +40 -31
- package/dist/src/browser/actions/attachments.js +28 -1
- package/dist/src/browser/actions/deepResearch.js +344 -128
- package/dist/src/browser/actions/modelSelection.js +75 -9
- package/dist/src/browser/actions/promptComposer.js +71 -14
- package/dist/src/browser/actions/thinkingStatus.js +19 -1
- package/dist/src/browser/actions/thinkingTime.js +65 -20
- package/dist/src/browser/artifacts.js +193 -14
- package/dist/src/browser/chatgptFiles.js +674 -91
- package/dist/src/browser/chromeCookies.js +312 -0
- package/dist/src/browser/chromeLifecycle.js +35 -4
- package/dist/src/browser/constants.js +5 -0
- package/dist/src/browser/deepResearchResult.js +23 -0
- package/dist/src/browser/index.js +112 -41
- package/dist/src/browser/keytarShim.js +56 -0
- package/dist/src/browser/profileCopy.js +93 -0
- package/dist/src/browser/sessionRunner.js +9 -3
- package/dist/src/browser/windowsCookies.js +219 -0
- package/dist/src/cli/bridge/client.js +4 -1
- package/dist/src/cli/bridge/doctor.js +19 -0
- package/dist/src/cli/browserConfig.js +17 -1
- package/dist/src/cli/runOptions.js +11 -2
- package/dist/src/cli/sessionDisplay.js +6 -1
- package/dist/src/cli/sessionRunner.js +41 -17
- package/dist/src/config.js +3 -0
- package/dist/src/oracle/client.js +2 -0
- package/dist/src/oracle/modelResolver.js +85 -0
- package/dist/src/oracle/multiModelRunner.js +4 -1
- package/dist/src/oracle/run.js +4 -1
- package/dist/src/remote/client.js +253 -22
- package/dist/src/remote/health.js +27 -0
- package/dist/src/remote/server.js +239 -4
- package/dist/src/remote/types.js +1 -1
- package/dist/src/sessionManager.js +1 -0
- 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 +45 -43
- 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,13 +1,14 @@
|
|
|
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";
|
|
6
7
|
/**
|
|
7
|
-
* Activates Deep Research mode through ChatGPT's
|
|
8
|
-
*
|
|
8
|
+
* Activates Deep Research mode through ChatGPT's composer tools menu and
|
|
9
|
+
* verifies the selected tool pill before prompt submission.
|
|
9
10
|
*/
|
|
10
|
-
export async function activateDeepResearch(Runtime,
|
|
11
|
+
export async function activateDeepResearch(Runtime, Input, logger) {
|
|
11
12
|
const expression = buildActivateDeepResearchExpression();
|
|
12
13
|
const outcome = await Runtime.evaluate({
|
|
13
14
|
expression,
|
|
@@ -31,14 +32,48 @@ export async function activateDeepResearch(Runtime, _Input, logger) {
|
|
|
31
32
|
throw new BrowserAutomationError(`"Deep research" option not found in composer dropdown.${hint} ` +
|
|
32
33
|
"This feature may require a ChatGPT Plus or Pro subscription.", { stage: "deep-research-activate", code: "dropdown-item-missing" });
|
|
33
34
|
}
|
|
34
|
-
case "pill-not-confirmed":
|
|
35
|
+
case "pill-not-confirmed": {
|
|
36
|
+
const point = result.clickPoint;
|
|
37
|
+
if (typeof point?.x === "number" && typeof point.y === "number") {
|
|
38
|
+
await clickTrustedPoint(Runtime, Input, point.x, point.y);
|
|
39
|
+
if (await waitForDeepResearchPill(Runtime)) {
|
|
40
|
+
logger("Deep Research mode activated");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
35
44
|
throw new BrowserAutomationError("Deep Research pill did not appear after selection. The UI may have changed.", { stage: "deep-research-activate", code: "pill-not-confirmed" });
|
|
45
|
+
}
|
|
36
46
|
default:
|
|
37
47
|
throw new BrowserAutomationError("Unexpected result from Deep Research activation.", {
|
|
38
48
|
stage: "deep-research-activate",
|
|
39
49
|
});
|
|
40
50
|
}
|
|
41
51
|
}
|
|
52
|
+
async function clickTrustedPoint(Runtime, Input, x, y) {
|
|
53
|
+
if (Input && typeof Input.dispatchMouseEvent === "function") {
|
|
54
|
+
await Input.dispatchMouseEvent({ type: "mouseMoved", x, y });
|
|
55
|
+
await Input.dispatchMouseEvent({ type: "mousePressed", x, y, button: "left", clickCount: 1 });
|
|
56
|
+
await Input.dispatchMouseEvent({ type: "mouseReleased", x, y, button: "left", clickCount: 1 });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
await Runtime.evaluate({
|
|
60
|
+
expression: `(() => {
|
|
61
|
+
const el = document.elementFromPoint(${JSON.stringify(x)}, ${JSON.stringify(y)});
|
|
62
|
+
if (!(el instanceof HTMLElement)) return false;
|
|
63
|
+
el.click();
|
|
64
|
+
return true;
|
|
65
|
+
})()`,
|
|
66
|
+
returnByValue: true,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async function waitForDeepResearchPill(Runtime, timeoutMs = 5000) {
|
|
70
|
+
const { result } = await Runtime.evaluate({
|
|
71
|
+
expression: buildWaitForDeepResearchPillExpression(timeoutMs),
|
|
72
|
+
awaitPromise: true,
|
|
73
|
+
returnByValue: true,
|
|
74
|
+
});
|
|
75
|
+
return Boolean(result?.value);
|
|
76
|
+
}
|
|
42
77
|
/**
|
|
43
78
|
* After prompt submission, waits for the research plan to appear and
|
|
44
79
|
* auto-confirm (~60s countdown + 10s safety margin).
|
|
@@ -116,7 +151,10 @@ export async function waitForDeepResearchCompletion(Runtime, logger, timeoutMs =
|
|
|
116
151
|
: -1;
|
|
117
152
|
const scopedToNewTurns = minTurnLiteral >= 0;
|
|
118
153
|
const ignoredTargetKeys = new Set(options?.ignoredTargetKeys ?? []);
|
|
119
|
-
const requireScopedTargetOwner = options?.requireScopedTargetOwner === true
|
|
154
|
+
const requireScopedTargetOwner = options?.requireScopedTargetOwner === true ||
|
|
155
|
+
(scopedToNewTurns && options?.targetBaselineCaptured !== true);
|
|
156
|
+
let observedResearchEvidence = false;
|
|
157
|
+
let loggedIncompleteResult = false;
|
|
120
158
|
logger(`Monitoring Deep Research (timeout: ${Math.round(timeoutMs / 60_000)}min)...`);
|
|
121
159
|
while (Date.now() - start < timeoutMs) {
|
|
122
160
|
const { result } = await Runtime.evaluate({
|
|
@@ -127,7 +165,6 @@ export async function waitForDeepResearchCompletion(Runtime, logger, timeoutMs =
|
|
|
127
165
|
if (val?.accountBlocked) {
|
|
128
166
|
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
167
|
}
|
|
130
|
-
const activeScopedResearch = Boolean(val?.hasActiveScopedResearch);
|
|
131
168
|
// ChatGPT renders the Deep Research report inside an out-of-process,
|
|
132
169
|
// sandboxed iframe (connector_openai_deep_research.*.oaiusercontent.com),
|
|
133
170
|
// doubly nested and same-origin. That OOPIF does NOT appear in the main
|
|
@@ -136,24 +173,27 @@ export async function waitForDeepResearchCompletion(Runtime, logger, timeoutMs =
|
|
|
136
173
|
// (readDeepResearchTargetResult) attaches to the iframe's own CDP target and
|
|
137
174
|
// walks its nested frames, so it CAN read the report. Prefer the target path
|
|
138
175
|
// and fall back to the in-page frame path for legacy/inline rendering.
|
|
139
|
-
const
|
|
176
|
+
const rawTargetResult = client
|
|
140
177
|
? ((await readDeepResearchTargetResult(client, ignoredTargetKeys, requireScopedTargetOwner ? minTurnLiteral : -1).catch(() => null))?.read ?? null)
|
|
141
178
|
: null;
|
|
179
|
+
const targetResult = filterIncompleteDeepResearchRead(rawTargetResult);
|
|
142
180
|
// A completed target read is authoritative. If the target read is missing or
|
|
143
181
|
// only in-progress, still try the in-page frame path so an incomplete target
|
|
144
182
|
// read does not suppress a completed report there (legacy/inline rendering).
|
|
145
|
-
const
|
|
146
|
-
? await readDeepResearchFrameResult(Runtime, Page).catch(() => null)
|
|
183
|
+
const inPageScan = !targetResult?.completed && Page
|
|
184
|
+
? await readDeepResearchFrameResult(Runtime, Page, client, scopedToNewTurns ? minTurnLiteral : -1).catch(() => null)
|
|
147
185
|
: null;
|
|
186
|
+
const rawInPageResult = inPageScan?.read ?? null;
|
|
187
|
+
const inPageResult = filterIncompleteDeepResearchRead(rawInPageResult);
|
|
148
188
|
const read = pickPreferredDeepResearchRead(targetResult, inPageResult);
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
189
|
+
// Target keys captured before submission are ignored, so a target result is
|
|
190
|
+
// tied to this run. Main-page iframes are not: old reports can remain in the
|
|
191
|
+
// conversation and must never authorize a new normal-response fallback.
|
|
192
|
+
observedResearchEvidence ||= Boolean(rawTargetResult ||
|
|
193
|
+
(scopedToNewTurns && rawInPageResult) ||
|
|
194
|
+
val?.researchActivity ||
|
|
195
|
+
val?.hasActiveScopedResearch);
|
|
196
|
+
if (read?.completed && read.text) {
|
|
157
197
|
logger(`Deep Research completed (${Math.round((Date.now() - start) / 1000)}s elapsed)`);
|
|
158
198
|
return {
|
|
159
199
|
text: read.text,
|
|
@@ -163,9 +203,18 @@ export async function waitForDeepResearchCompletion(Runtime, logger, timeoutMs =
|
|
|
163
203
|
}
|
|
164
204
|
// Completion detected
|
|
165
205
|
if (val?.finished) {
|
|
206
|
+
if (!observedResearchEvidence) {
|
|
207
|
+
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" });
|
|
208
|
+
}
|
|
166
209
|
logger(`Deep Research completed (${Math.round((Date.now() - start) / 1000)}s elapsed)`);
|
|
167
210
|
return await extractDeepResearchResult(Runtime, logger, minTurnIndex ?? undefined);
|
|
168
211
|
}
|
|
212
|
+
const incompleteFrameResult = Boolean((rawTargetResult?.completed && !targetResult?.completed) ||
|
|
213
|
+
(rawInPageResult?.completed && !inPageResult?.completed));
|
|
214
|
+
if ((val?.incompleteResult || incompleteFrameResult) && !loggedIncompleteResult) {
|
|
215
|
+
logger("Deep Research interim status detected; waiting for the final report");
|
|
216
|
+
loggedIncompleteResult = true;
|
|
217
|
+
}
|
|
169
218
|
// Progress logging every 60 seconds
|
|
170
219
|
const now = Date.now();
|
|
171
220
|
if (now - lastLogTime >= 60_000) {
|
|
@@ -204,24 +253,26 @@ export async function extractDeepResearchResult(Runtime, logger, minTurnIndex) {
|
|
|
204
253
|
};
|
|
205
254
|
// Try the copy-button approach first for clean markdown
|
|
206
255
|
const markdown = await captureAssistantMarkdown(Runtime, meta, logger);
|
|
207
|
-
if (markdown && !
|
|
256
|
+
if (markdown && !isDeepResearchIncompleteText(markdown)) {
|
|
208
257
|
return { text: markdown, html: snapshot?.html ?? undefined, meta };
|
|
209
258
|
}
|
|
210
259
|
// Fall back to snapshot text
|
|
211
|
-
if (snapshot?.text && !
|
|
260
|
+
if (snapshot?.text && !isDeepResearchIncompleteText(snapshot.text)) {
|
|
212
261
|
return { text: snapshot.text, html: snapshot.html ?? undefined, meta };
|
|
213
262
|
}
|
|
214
263
|
throw new BrowserAutomationError("Deep Research completed but failed to extract the response text.", { stage: "deep-research-extract", code: "extraction-failed" });
|
|
215
264
|
}
|
|
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
265
|
export function isDeepResearchPlaceholderTextForTest(text) {
|
|
224
|
-
return
|
|
266
|
+
return isDeepResearchIncompleteText(text);
|
|
267
|
+
}
|
|
268
|
+
function filterIncompleteDeepResearchRead(result) {
|
|
269
|
+
if (!result?.completed || !result.text || !isDeepResearchIncompleteText(result.text)) {
|
|
270
|
+
return result;
|
|
271
|
+
}
|
|
272
|
+
return { ...result, completed: false, inProgress: true };
|
|
273
|
+
}
|
|
274
|
+
export function filterIncompleteDeepResearchReadForTest(result) {
|
|
275
|
+
return filterIncompleteDeepResearchRead(result);
|
|
225
276
|
}
|
|
226
277
|
/**
|
|
227
278
|
* Choose the authoritative Deep Research read between the target-attach result
|
|
@@ -243,37 +294,64 @@ function pickPreferredDeepResearchRead(targetResult, inPageResult) {
|
|
|
243
294
|
export function pickPreferredDeepResearchReadForTest(targetResult, inPageResult) {
|
|
244
295
|
return pickPreferredDeepResearchRead(targetResult, inPageResult);
|
|
245
296
|
}
|
|
246
|
-
async function readDeepResearchFrameResult(Runtime, Page) {
|
|
297
|
+
async function readDeepResearchFrameResult(Runtime, Page, client, minTurnIndex = -1) {
|
|
247
298
|
const pageWithFrames = Page;
|
|
248
299
|
if (typeof pageWithFrames.getFrameTree !== "function" ||
|
|
249
300
|
typeof pageWithFrames.createIsolatedWorld !== "function") {
|
|
250
301
|
return null;
|
|
251
302
|
}
|
|
252
303
|
const frameTree = (await pageWithFrames.getFrameTree())?.frameTree;
|
|
253
|
-
const
|
|
254
|
-
if (
|
|
304
|
+
const frameIds = collectPageDeepResearchFrameIds(frameTree);
|
|
305
|
+
if (frameIds.length === 0) {
|
|
255
306
|
return null;
|
|
256
307
|
}
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if (typeof world.executionContextId !== "number") {
|
|
263
|
-
return null;
|
|
308
|
+
const rawClient = client;
|
|
309
|
+
if (minTurnIndex >= 0) {
|
|
310
|
+
if (typeof rawClient?.send !== "function") {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
264
313
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
314
|
+
let best = null;
|
|
315
|
+
for (const frameId of frameIds) {
|
|
316
|
+
let ownerTurnIndex = null;
|
|
317
|
+
if (minTurnIndex >= 0 && rawClient?.send) {
|
|
318
|
+
ownerTurnIndex = await readDeepResearchTargetOwnerTurnIndex(rawClient, frameId, rawClient.oraclePageSessionId);
|
|
319
|
+
if (ownerTurnIndex === null || ownerTurnIndex < minTurnIndex) {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const world = await pageWithFrames.createIsolatedWorld({
|
|
324
|
+
frameId,
|
|
325
|
+
worldName: "oracle-deep-research",
|
|
326
|
+
grantUniveralAccess: true,
|
|
327
|
+
});
|
|
328
|
+
if (typeof world.executionContextId !== "number") {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
const { result } = await Runtime.evaluate({
|
|
332
|
+
expression: buildDeepResearchFrameStatusExpression(),
|
|
333
|
+
contextId: world.executionContextId,
|
|
334
|
+
returnByValue: true,
|
|
335
|
+
});
|
|
336
|
+
const read = result?.value ?? null;
|
|
337
|
+
if (!read) {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
best = { read, ownerTurnIndex };
|
|
341
|
+
if (read.completed) {
|
|
342
|
+
return best;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return best;
|
|
271
346
|
}
|
|
272
347
|
async function readDeepResearchTargetResult(client, ignoredTargetKeys = new Set(), minTurnIndex = -1) {
|
|
273
348
|
const rawClient = client;
|
|
274
349
|
if (typeof rawClient.send !== "function") {
|
|
275
350
|
return null;
|
|
276
351
|
}
|
|
352
|
+
if (typeof client.on !== "function") {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
277
355
|
// On the browser-WSEndpoint path, `client` is a session-bound wrapper whose
|
|
278
356
|
// domain methods target the page session but whose raw `send` is the
|
|
279
357
|
// browser-level send. We must therefore pass the page session id explicitly so
|
|
@@ -299,7 +377,7 @@ async function readDeepResearchTargetResult(client, ignoredTargetKeys = new Set(
|
|
|
299
377
|
ownedSessionIds.add(eventSessionId);
|
|
300
378
|
}
|
|
301
379
|
};
|
|
302
|
-
client.on
|
|
380
|
+
client.on("Target.attachedToTarget", onAttached);
|
|
303
381
|
try {
|
|
304
382
|
// Scope discovery to the current Oracle-controlled page. `client` is
|
|
305
383
|
// connected to the conversation page target, so enabling auto-attach on this
|
|
@@ -311,13 +389,16 @@ async function readDeepResearchTargetResult(client, ignoredTargetKeys = new Set(
|
|
|
311
389
|
// would surface another tab's completed Deep Research report and let it be
|
|
312
390
|
// saved into the current session (cross-tab leak). Only auto-attached,
|
|
313
391
|
// page-scoped sessions are treated as belonging to this run.
|
|
314
|
-
await rawClient
|
|
392
|
+
const autoAttachEnabled = await rawClient
|
|
315
393
|
.send("Target.setAutoAttach", {
|
|
316
394
|
autoAttach: true,
|
|
317
395
|
waitForDebuggerOnStart: false,
|
|
318
396
|
flatten: true,
|
|
319
397
|
}, pageSessionId)
|
|
320
|
-
.
|
|
398
|
+
.then(() => true, () => false);
|
|
399
|
+
if (!autoAttachEnabled) {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
321
402
|
await delay(100);
|
|
322
403
|
if (minTurnIndex >= 0) {
|
|
323
404
|
await rawClient.send("DOM.enable", {}, pageSessionId).catch(() => undefined);
|
|
@@ -374,7 +455,11 @@ async function readDeepResearchTargetResult(client, ignoredTargetKeys = new Set(
|
|
|
374
455
|
}
|
|
375
456
|
}
|
|
376
457
|
export async function captureDeepResearchTargetKeys(client) {
|
|
377
|
-
|
|
458
|
+
const scan = await readDeepResearchTargetResult(client);
|
|
459
|
+
if (!scan) {
|
|
460
|
+
throw new Error("Deep Research target baseline capture unavailable");
|
|
461
|
+
}
|
|
462
|
+
return scan.targetKeys;
|
|
378
463
|
}
|
|
379
464
|
async function readDeepResearchTargetOwnerTurnIndex(rawClient, frameId, pageSessionId) {
|
|
380
465
|
const owner = (await rawClient
|
|
@@ -419,7 +504,7 @@ async function readDeepResearchTargetSession(rawClient, sessionId, targetUrl) {
|
|
|
419
504
|
const frameTree = (await rawClient
|
|
420
505
|
.send("Page.getFrameTree", {}, sessionId)
|
|
421
506
|
.catch(() => null));
|
|
422
|
-
const
|
|
507
|
+
const ownerFrameId = frameTree?.frameTree?.frame?.id;
|
|
423
508
|
if (!isConfirmedDeepResearchTarget(targetUrl, frameTree?.frameTree)) {
|
|
424
509
|
return { confirmed: false, read: null };
|
|
425
510
|
}
|
|
@@ -438,7 +523,7 @@ async function readDeepResearchTargetSession(rawClient, sessionId, targetUrl) {
|
|
|
438
523
|
}
|
|
439
524
|
const value = await evaluateDeepResearchFrameStatus(rawClient, sessionId, world.executionContextId);
|
|
440
525
|
if (value?.completed) {
|
|
441
|
-
return { confirmed: true, read: value, frameId };
|
|
526
|
+
return { confirmed: true, read: value, frameId: ownerFrameId };
|
|
442
527
|
}
|
|
443
528
|
if ((value?.textLength ?? 0) > (best?.textLength ?? 0) || value?.inProgress) {
|
|
444
529
|
best = value;
|
|
@@ -446,12 +531,12 @@ async function readDeepResearchTargetSession(rawClient, sessionId, targetUrl) {
|
|
|
446
531
|
}
|
|
447
532
|
const topFrameValue = await evaluateDeepResearchFrameStatus(rawClient, sessionId);
|
|
448
533
|
if (topFrameValue?.completed) {
|
|
449
|
-
return { confirmed: true, read: topFrameValue, frameId };
|
|
534
|
+
return { confirmed: true, read: topFrameValue, frameId: ownerFrameId };
|
|
450
535
|
}
|
|
451
536
|
if ((topFrameValue?.textLength ?? 0) > (best?.textLength ?? 0) || topFrameValue?.inProgress) {
|
|
452
537
|
best = topFrameValue;
|
|
453
538
|
}
|
|
454
|
-
return { confirmed: true, read: best, frameId };
|
|
539
|
+
return { confirmed: true, read: best, frameId: ownerFrameId };
|
|
455
540
|
}
|
|
456
541
|
async function evaluateDeepResearchFrameStatus(rawClient, sessionId, contextId) {
|
|
457
542
|
const response = (await rawClient
|
|
@@ -474,21 +559,20 @@ function isDeepResearchFrameDescriptor(url, name = "") {
|
|
|
474
559
|
return (descriptor.includes("connector_openai_deep_research") || descriptor.includes("deep-research"));
|
|
475
560
|
}
|
|
476
561
|
function findDeepResearchFrameId(tree) {
|
|
562
|
+
return collectPageDeepResearchFrameIds(tree)[0] ?? null;
|
|
563
|
+
}
|
|
564
|
+
function collectPageDeepResearchFrameIds(tree) {
|
|
477
565
|
if (!tree?.frame) {
|
|
478
|
-
return
|
|
566
|
+
return [];
|
|
479
567
|
}
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
return tree.frame.id ?? null;
|
|
568
|
+
const ids = [];
|
|
569
|
+
if (tree.frame.id && isDeepResearchFrameDescriptor(tree.frame.url ?? "", tree.frame.name ?? "")) {
|
|
570
|
+
ids.push(tree.frame.id);
|
|
484
571
|
}
|
|
485
572
|
for (const child of tree.childFrames ?? []) {
|
|
486
|
-
|
|
487
|
-
if (match) {
|
|
488
|
-
return match;
|
|
489
|
-
}
|
|
573
|
+
ids.push(...collectPageDeepResearchFrameIds(child));
|
|
490
574
|
}
|
|
491
|
-
return
|
|
575
|
+
return ids;
|
|
492
576
|
}
|
|
493
577
|
function collectDeepResearchFrameIds(tree) {
|
|
494
578
|
if (!tree?.frame) {
|
|
@@ -644,20 +728,41 @@ function buildDeepResearchCompletionPollExpression(minTurnIndex) {
|
|
|
644
728
|
const text = (lastTurn?.textContent || '').trim();
|
|
645
729
|
const normalized = text.toLowerCase().replace(/\\s+/g, ' ').trim();
|
|
646
730
|
const textLength = text.length;
|
|
731
|
+
const lines = text.split(/\\n+/).map(line => line.trim()).filter(Boolean);
|
|
732
|
+
const tailIsPlanningPanel = text.length <= 1500 &&
|
|
733
|
+
lines.length >= 4 &&
|
|
734
|
+
lines.length <= 20 &&
|
|
735
|
+
/^update$/i.test(lines[1] || '') &&
|
|
736
|
+
/^stop research$/i.test(lines[lines.length - 1] || '') &&
|
|
737
|
+
/^determining steps for creating a report(?:\\.\\.\\.)?$/i.test(lines[lines.length - 2] || '');
|
|
647
738
|
const isToolStub = normalized === 'called tool' ||
|
|
648
739
|
normalized === 'used tool' ||
|
|
649
740
|
normalized === 'użyto narzędzia' ||
|
|
650
741
|
normalized === 'narzędzie wywołane';
|
|
742
|
+
const incompleteResult = isToolStub ||
|
|
743
|
+
normalized === 'planning' ||
|
|
744
|
+
normalized === 'researching' ||
|
|
745
|
+
normalized === 'searching the web' ||
|
|
746
|
+
(text.trimStart().startsWith('<system-reminder>') &&
|
|
747
|
+
/<system-reminder>[\\s\\S]*#\\s*plan mode\\b/i.test(text)) ||
|
|
748
|
+
tailIsPlanningPanel;
|
|
651
749
|
const finished = Boolean(lastTurn?.querySelector(${finishedSelector})) &&
|
|
652
750
|
textLength >= 40 &&
|
|
653
|
-
!
|
|
751
|
+
!incompleteResult;
|
|
654
752
|
const hasIframe = Array.from(document.querySelectorAll('iframe')).some(f => {
|
|
655
753
|
const rect = f.getBoundingClientRect();
|
|
656
754
|
return rect.width > 200 && rect.height > 200;
|
|
657
755
|
});
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
|
|
756
|
+
const hasScopedDeepResearchIframe = Array.from(lastTurn?.querySelectorAll?.('iframe') || []).some(f => {
|
|
757
|
+
const rect = f.getBoundingClientRect();
|
|
758
|
+
const descriptor = String(f.getAttribute('src') || '') + ' ' + String(f.getAttribute('name') || '');
|
|
759
|
+
return rect.width > 200 && rect.height > 200 &&
|
|
760
|
+
/connector_openai_deep_research|deep-research/i.test(descriptor);
|
|
761
|
+
});
|
|
762
|
+
const hasActiveScopedResearch = scopedToNewTurns && Boolean(lastTurn) &&
|
|
763
|
+
hasScopedDeepResearchIframe &&
|
|
764
|
+
(textLength < 40 || isToolStub || tailIsPlanningPanel || /chatgpt\\s+said:?$/i.test(text));
|
|
765
|
+
return { finished, stopVisible, textLength, hasIframe, isToolStub, incompleteResult, researchActivity: tailIsPlanningPanel || (isToolStub && hasScopedDeepResearchIframe), hasActiveScopedResearch, accountBlocked };
|
|
661
766
|
})()`;
|
|
662
767
|
}
|
|
663
768
|
export function buildDeepResearchStatusExpressionForTest() {
|
|
@@ -666,29 +771,56 @@ export function buildDeepResearchStatusExpressionForTest() {
|
|
|
666
771
|
export function buildDeepResearchCompletionPollExpressionForTest(minTurnIndex = -1) {
|
|
667
772
|
return buildDeepResearchCompletionPollExpression(minTurnIndex);
|
|
668
773
|
}
|
|
669
|
-
function
|
|
670
|
-
const plusBtnSelector = JSON.stringify(DEEP_RESEARCH_PLUS_BUTTON);
|
|
671
|
-
const targetText = JSON.stringify(DEEP_RESEARCH_DROPDOWN_ITEM_TEXT);
|
|
774
|
+
function buildFindDeepResearchPillExpression(functionName = "findDeepResearchPill") {
|
|
672
775
|
const pillLabel = JSON.stringify(DEEP_RESEARCH_PILL_LABEL);
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
776
|
+
return `const ${functionName} = () => {
|
|
777
|
+
const label = ${pillLabel}.toLowerCase();
|
|
778
|
+
const selectors = [
|
|
779
|
+
'.__composer-pill-composite',
|
|
780
|
+
'.__composer-pill',
|
|
781
|
+
'[class*="composer-pill"]',
|
|
782
|
+
].join(',');
|
|
783
|
+
const candidates = Array.from(document.querySelectorAll(selectors));
|
|
784
|
+
const composerRoots = Array.from(document.querySelectorAll('[data-testid="composer"], form, [class*="composer"]'));
|
|
785
|
+
for (const root of composerRoots) {
|
|
786
|
+
candidates.push(...Array.from(root.querySelectorAll('button, [role="button"], [class*="pill"], [class*="composer-pill"]')));
|
|
787
|
+
}
|
|
788
|
+
const seen = new Set();
|
|
789
|
+
for (const pill of candidates) {
|
|
790
|
+
if (!(pill instanceof Element) || seen.has(pill)) continue;
|
|
791
|
+
seen.add(pill);
|
|
792
|
+
const rect = pill.getBoundingClientRect?.();
|
|
793
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) continue;
|
|
794
|
+
const text = (pill.textContent || '').replace(/\\s+/g, ' ').trim().toLowerCase();
|
|
795
|
+
const aria = (
|
|
796
|
+
pill.getAttribute('aria-label') ||
|
|
683
797
|
pill.querySelector('button')?.getAttribute('aria-label') ||
|
|
684
|
-
''
|
|
685
|
-
|
|
686
|
-
|
|
798
|
+
''
|
|
799
|
+
).toLowerCase();
|
|
800
|
+
if (text.includes(label) || aria.includes(label)) {
|
|
687
801
|
return pill;
|
|
688
802
|
}
|
|
689
803
|
}
|
|
690
804
|
return null;
|
|
691
|
-
}
|
|
805
|
+
};`;
|
|
806
|
+
}
|
|
807
|
+
function buildWaitForDeepResearchPillExpression(timeoutMs) {
|
|
808
|
+
return `(async () => {
|
|
809
|
+
${buildFindDeepResearchPillExpression()}
|
|
810
|
+
const deadline = Date.now() + ${JSON.stringify(Math.max(timeoutMs, 0))};
|
|
811
|
+
while (Date.now() < deadline) {
|
|
812
|
+
if (findDeepResearchPill()) return true;
|
|
813
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
814
|
+
}
|
|
815
|
+
return Boolean(findDeepResearchPill());
|
|
816
|
+
})()`;
|
|
817
|
+
}
|
|
818
|
+
function buildActivateDeepResearchExpression() {
|
|
819
|
+
const plusBtnSelector = JSON.stringify(DEEP_RESEARCH_PLUS_BUTTON);
|
|
820
|
+
const targetText = JSON.stringify(DEEP_RESEARCH_DROPDOWN_ITEM_TEXT);
|
|
821
|
+
return `(async () => {
|
|
822
|
+
${buildClickDispatcher()}
|
|
823
|
+
${buildFindDeepResearchPillExpression()}
|
|
692
824
|
|
|
693
825
|
const waitForPill = () => new Promise((resolve) => {
|
|
694
826
|
let elapsed = 0;
|
|
@@ -703,24 +835,105 @@ function buildActivateDeepResearchExpression() {
|
|
|
703
835
|
setTimeout(tick, 200);
|
|
704
836
|
});
|
|
705
837
|
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
838
|
+
const menuItemSelector = [
|
|
839
|
+
'[data-radix-collection-item]',
|
|
840
|
+
'[role="option"]',
|
|
841
|
+
'[cmdk-item]',
|
|
842
|
+
'button',
|
|
843
|
+
'[role="menuitem"]',
|
|
844
|
+
'[role="menuitemradio"]',
|
|
845
|
+
'.__menu-item',
|
|
846
|
+
'[class*="__menu-item"]',
|
|
847
|
+
'[class*="menu-item"]',
|
|
848
|
+
].join(',');
|
|
849
|
+
const dropdownItemSelector = [
|
|
850
|
+
'[data-radix-collection-item]',
|
|
851
|
+
'[role="menuitem"]',
|
|
852
|
+
'[role="menuitemradio"]',
|
|
853
|
+
'[role="option"]',
|
|
854
|
+
'[cmdk-item]',
|
|
855
|
+
'button',
|
|
856
|
+
'.__menu-item',
|
|
857
|
+
'[class*="__menu-item"]',
|
|
858
|
+
'[class*="menu-item"]',
|
|
859
|
+
].join(',');
|
|
860
|
+
const popoverSelector = [
|
|
861
|
+
'.popover',
|
|
862
|
+
'[class*="popover"]',
|
|
863
|
+
'[data-radix-popper-content-wrapper]',
|
|
864
|
+
'[data-floating-ui-portal]',
|
|
865
|
+
].join(',');
|
|
866
|
+
const target = ${targetText}.toLowerCase();
|
|
867
|
+
const normalizeText = (value) => String(value || '').replace(/\\s+/g, ' ').trim().toLowerCase();
|
|
868
|
+
const getText = (item) => normalizeText(item.textContent || item.getAttribute?.('aria-label') || '');
|
|
869
|
+
const isInPopover = (item) => Boolean(item.closest?.(popoverSelector));
|
|
870
|
+
const isVisible = (item) => {
|
|
871
|
+
const rect = item.getBoundingClientRect?.();
|
|
872
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) return false;
|
|
873
|
+
const style = window.getComputedStyle?.(item);
|
|
874
|
+
return !style || (style.visibility !== 'hidden' && style.display !== 'none');
|
|
711
875
|
};
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
876
|
+
const findPopoverSearchInput = () => Array.from(
|
|
877
|
+
document.querySelectorAll('input, textarea, [contenteditable="true"]')
|
|
878
|
+
).find(item => {
|
|
879
|
+
const type = (item.getAttribute?.('type') || '').toLowerCase();
|
|
880
|
+
const testId = (item.getAttribute?.('data-testid') || '').toLowerCase();
|
|
881
|
+
return isInPopover(item) &&
|
|
882
|
+
isVisible(item) &&
|
|
883
|
+
type !== 'file' &&
|
|
884
|
+
testId !== 'upload-photos-input';
|
|
885
|
+
}) || null;
|
|
886
|
+
const setSearchText = (input, text) => {
|
|
887
|
+
input.focus?.();
|
|
888
|
+
if ('value' in input) input.value = text;
|
|
889
|
+
else input.textContent = text;
|
|
890
|
+
input.dispatchEvent(new InputEvent('input', { bubbles: true, inputType: 'insertText', data: text }));
|
|
891
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
718
892
|
};
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
893
|
+
const isDeepResearchText = (text) => (
|
|
894
|
+
text === target ||
|
|
895
|
+
text.startsWith(target + ' ') ||
|
|
896
|
+
text === 'get a detailed report' ||
|
|
897
|
+
text.startsWith('get a detailed report ') ||
|
|
898
|
+
(text.includes(target) && text.includes('detailed report')) ||
|
|
899
|
+
text.replace(/\\s+/g, '').startsWith('deepresearch')
|
|
900
|
+
);
|
|
901
|
+
const getClickableItem = (item) => item.closest?.(
|
|
902
|
+
'[data-radix-collection-item], [role="option"], [cmdk-item], button, [role="menuitem"], [role="menuitemradio"], .__menu-item, [class*="__menu-item"], [class*="menu-item"]'
|
|
903
|
+
) || item;
|
|
904
|
+
const findDeepResearchItem = (options = {}) => {
|
|
905
|
+
const matches = Array.from(document.querySelectorAll(menuItemSelector))
|
|
906
|
+
.filter(item => {
|
|
907
|
+
const text = getText(item);
|
|
908
|
+
return text &&
|
|
909
|
+
text.length <= 180 &&
|
|
910
|
+
isVisible(item) &&
|
|
911
|
+
(!options.requirePopover || isInPopover(item)) &&
|
|
912
|
+
isDeepResearchText(text);
|
|
913
|
+
})
|
|
914
|
+
.map(item => {
|
|
915
|
+
const text = getText(item);
|
|
916
|
+
const clickable = getClickableItem(item);
|
|
917
|
+
const exact = text === target ? 0 : 1;
|
|
918
|
+
const menuRow = /(^|\\s)__menu-item(\\s|$)/.test(clickable.className || '') ? 0 : 1;
|
|
919
|
+
return { item: clickable, score: exact + menuRow, textLength: text.length };
|
|
920
|
+
})
|
|
921
|
+
.sort((a, b) => a.score - b.score || a.textLength - b.textLength);
|
|
922
|
+
return matches[0]?.item || null;
|
|
923
|
+
};
|
|
924
|
+
const collectAvailableItems = (options = {}) => {
|
|
925
|
+
const seen = new Set();
|
|
926
|
+
return Array.from(document.querySelectorAll(dropdownItemSelector))
|
|
927
|
+
.filter(item => !options.requirePopover || isInPopover(item))
|
|
928
|
+
.filter(item => isVisible(item))
|
|
929
|
+
.map(item => (item.textContent || '').replace(/\\s+/g, ' ').trim())
|
|
930
|
+
.filter(text => text && text.length <= 180)
|
|
931
|
+
.filter(text => {
|
|
932
|
+
const key = text.toLowerCase();
|
|
933
|
+
if (seen.has(key)) return false;
|
|
934
|
+
seen.add(key);
|
|
935
|
+
return true;
|
|
936
|
+
});
|
|
724
937
|
};
|
|
725
938
|
|
|
726
939
|
// Step 0: Check if already active
|
|
@@ -728,20 +941,8 @@ function buildActivateDeepResearchExpression() {
|
|
|
728
941
|
return { status: 'already-active' };
|
|
729
942
|
}
|
|
730
943
|
|
|
731
|
-
// Step 1:
|
|
732
|
-
|
|
733
|
-
if (composer) {
|
|
734
|
-
setComposerText(composer, '/Deepresearch');
|
|
735
|
-
await new Promise(resolve => setTimeout(resolve, 600));
|
|
736
|
-
const slashItem = findDeepResearchItem();
|
|
737
|
-
if (slashItem) {
|
|
738
|
-
dispatchClickSequence(slashItem);
|
|
739
|
-
if (await waitForPill()) return { status: 'activated' };
|
|
740
|
-
}
|
|
741
|
-
clearComposer(composer);
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
// Step 2: Fall back to the composer tools menu.
|
|
944
|
+
// Step 1: Open the composer tools menu. Avoid slash commands because they
|
|
945
|
+
// mutate the main composer and can be submitted as normal prompt text.
|
|
745
946
|
const plusBtn = document.querySelector(${plusBtnSelector}) ||
|
|
746
947
|
Array.from(document.querySelectorAll('button')).find(
|
|
747
948
|
b => (b.getAttribute('aria-label') || '').toLowerCase().includes('add files')
|
|
@@ -749,14 +950,21 @@ function buildActivateDeepResearchExpression() {
|
|
|
749
950
|
if (!plusBtn) return { status: 'plus-button-missing' };
|
|
750
951
|
dispatchClickSequence(plusBtn);
|
|
751
952
|
|
|
752
|
-
// Step
|
|
953
|
+
// Step 2: Wait for dropdown
|
|
753
954
|
const waitForDropdown = () => new Promise((resolve) => {
|
|
754
955
|
let elapsed = 0;
|
|
755
956
|
const tick = () => {
|
|
756
|
-
const items =
|
|
757
|
-
if (
|
|
957
|
+
const items = collectAvailableItems({ requirePopover: true });
|
|
958
|
+
if (findDeepResearchItem({ requirePopover: true }) || items.some(text => {
|
|
959
|
+
const normalized = normalizeText(text);
|
|
960
|
+
return normalized.includes('add photos') ||
|
|
961
|
+
normalized.includes('create image') ||
|
|
962
|
+
normalized.includes('web search') ||
|
|
963
|
+
normalized.includes('deep research') ||
|
|
964
|
+
normalized.includes('get a detailed report');
|
|
965
|
+
})) { resolve(items); return; }
|
|
758
966
|
elapsed += 150;
|
|
759
|
-
if (elapsed > 3000) { resolve(null); return; }
|
|
967
|
+
if (elapsed > 3000) { resolve(items.length ? items : null); return; }
|
|
760
968
|
setTimeout(tick, 150);
|
|
761
969
|
};
|
|
762
970
|
setTimeout(tick, 150);
|
|
@@ -764,25 +972,33 @@ function buildActivateDeepResearchExpression() {
|
|
|
764
972
|
const items = await waitForDropdown();
|
|
765
973
|
if (!items) return { status: 'dropdown-item-missing', available: [] };
|
|
766
974
|
|
|
767
|
-
// Step
|
|
768
|
-
|
|
769
|
-
let match =
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
const
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
975
|
+
// Step 3: Find "Deep research" item. Some ChatGPT variants only reveal it
|
|
976
|
+
// after typing in the tools menu search field.
|
|
977
|
+
let match = findDeepResearchItem({ requirePopover: true });
|
|
978
|
+
let available = Array.isArray(items) ? items : collectAvailableItems({ requirePopover: true });
|
|
979
|
+
if (!match) {
|
|
980
|
+
const searchInput = findPopoverSearchInput();
|
|
981
|
+
if (searchInput) {
|
|
982
|
+
setSearchText(searchInput, ${targetText});
|
|
983
|
+
await new Promise(resolve => setTimeout(resolve, 600));
|
|
984
|
+
match = findDeepResearchItem({ requirePopover: true });
|
|
985
|
+
available = collectAvailableItems({ requirePopover: true });
|
|
776
986
|
}
|
|
777
987
|
}
|
|
778
988
|
if (!match) return { status: 'dropdown-item-missing', available };
|
|
779
989
|
|
|
780
|
-
// Step
|
|
990
|
+
// Step 4: Click it
|
|
991
|
+
match.scrollIntoView?.({ block: 'center', inline: 'center' });
|
|
992
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
993
|
+
const rect = match.getBoundingClientRect();
|
|
994
|
+
const clickPoint = rect && rect.width > 0 && rect.height > 0
|
|
995
|
+
? { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }
|
|
996
|
+
: undefined;
|
|
781
997
|
dispatchClickSequence(match);
|
|
782
998
|
|
|
783
|
-
// Step
|
|
999
|
+
// Step 5: Verify pill appeared
|
|
784
1000
|
const pillConfirmed = await waitForPill();
|
|
785
|
-
return pillConfirmed ? { status: 'activated' } : { status: 'pill-not-confirmed' };
|
|
1001
|
+
return pillConfirmed ? { status: 'activated' } : { status: 'pill-not-confirmed', clickPoint };
|
|
786
1002
|
})()`;
|
|
787
1003
|
}
|
|
788
1004
|
export function buildActivateDeepResearchExpressionForTest() {
|