@steipete/oracle 0.13.0 → 0.14.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.
Files changed (71) hide show
  1. package/README.md +22 -11
  2. package/dist/bin/oracle-cli.js +71 -71
  3. package/dist/src/browser/actions/assistantResponse.js +93 -10
  4. package/dist/src/browser/actions/deepResearch.js +180 -52
  5. package/dist/src/browser/actions/modelSelection.js +357 -40
  6. package/dist/src/browser/actions/navigation.js +294 -65
  7. package/dist/src/browser/actions/promptComposer.js +50 -4
  8. package/dist/src/browser/actions/thinkingTime.js +517 -105
  9. package/dist/src/browser/artifacts.js +19 -0
  10. package/dist/src/browser/chatgptFiles.js +797 -0
  11. package/dist/src/browser/chatgptImages.js +256 -27
  12. package/dist/src/browser/chromeLifecycle.js +4 -0
  13. package/dist/src/browser/config.js +2 -0
  14. package/dist/src/browser/index.js +404 -41
  15. package/dist/src/browser/pageActions.js +1 -1
  16. package/dist/src/browser/policies.js +2 -6
  17. package/dist/src/browser/projectSourcesRunner.js +16 -3
  18. package/dist/src/browser/prompt.js +161 -36
  19. package/dist/src/browser/reattach.js +6 -2
  20. package/dist/src/browser/reattachability.js +22 -10
  21. package/dist/src/browser/recoverConversation.js +73 -0
  22. package/dist/src/browser/sessionRunner.js +4 -1
  23. package/dist/src/cli/browserConfig.js +6 -2
  24. package/dist/src/cli/browserDefaults.js +2 -1
  25. package/dist/src/cli/browserTabs.js +129 -54
  26. package/dist/src/cli/followup.js +72 -0
  27. package/dist/src/cli/help.js +1 -1
  28. package/dist/src/cli/options.js +24 -0
  29. package/dist/src/cli/reattachGuidance.js +8 -0
  30. package/dist/src/cli/runOptions.js +2 -12
  31. package/dist/src/cli/sessionCommand.js +4 -0
  32. package/dist/src/cli/sessionDisplay.js +17 -3
  33. package/dist/src/cli/sessionLineage.js +7 -4
  34. package/dist/src/cli/sessionRunner.js +16 -1
  35. package/dist/src/gemini-web/client.js +5 -10
  36. package/dist/src/gemini-web/executor.js +1 -24
  37. package/dist/src/gemini-web/models.js +83 -0
  38. package/dist/src/mcp/server.js +2 -0
  39. package/dist/src/mcp/tools/chatgptImage.js +132 -0
  40. package/dist/src/mcp/tools/consult.js +270 -175
  41. package/dist/src/mcp/types.js +13 -2
  42. package/dist/src/mcp/utils.js +87 -1
  43. package/dist/src/oracle/config.js +27 -1
  44. package/dist/src/oracle/files.js +4 -6
  45. package/dist/src/oracle/geminiModels.js +2 -0
  46. package/dist/src/oracle/markdown.js +36 -3
  47. package/dist/src/oracle/modelResolver.js +21 -13
  48. package/dist/src/oracle/promptAssembly.js +2 -4
  49. package/dist/src/oracle/request.js +3 -5
  50. package/dist/src/oracle/thinkingTime.js +40 -0
  51. package/dist/src/oracle/tokenStats.js +5 -1
  52. package/dist/src/oracle.js +1 -1
  53. package/dist/src/sessionManager.js +1 -0
  54. package/dist/vendor/oracle-notifier/build-notifier.sh +0 -0
  55. package/package.json +47 -58
  56. package/vendor/oracle-notifier/build-notifier.sh +0 -0
  57. package/dist/bin/oracle.js +0 -569
  58. package/dist/src/browser/chromeCookies.js +0 -312
  59. package/dist/src/browser/keytarShim.js +0 -56
  60. package/dist/src/browser/windowsCookies.js +0 -219
  61. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  62. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +0 -20
  63. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  64. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
  65. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +0 -128
  66. package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  67. package/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +0 -20
  68. package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  69. package/vendor/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
  70. package/vendor/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +0 -128
  71. package/vendor/oracle-notifier/README.md +0 -26
@@ -107,13 +107,16 @@ export async function waitForResearchPlanAutoConfirm(Runtime, logger, autoConfir
107
107
  * Polls for Deep Research completion over 5-30+ minutes.
108
108
  * Returns the full response text, optional HTML, and turn metadata.
109
109
  */
110
- export async function waitForDeepResearchCompletion(Runtime, logger, timeoutMs = DEEP_RESEARCH_DEFAULT_TIMEOUT_MS, minTurnIndex, Page, client) {
110
+ export async function waitForDeepResearchCompletion(Runtime, logger, timeoutMs = DEEP_RESEARCH_DEFAULT_TIMEOUT_MS, minTurnIndex, Page, client, options) {
111
111
  const start = Date.now();
112
112
  let lastLogTime = start;
113
113
  let lastTextLength = 0;
114
114
  const minTurnLiteral = typeof minTurnIndex === "number" && Number.isFinite(minTurnIndex) && minTurnIndex >= 0
115
115
  ? Math.floor(minTurnIndex)
116
116
  : -1;
117
+ const scopedToNewTurns = minTurnLiteral >= 0;
118
+ const ignoredTargetKeys = new Set(options?.ignoredTargetKeys ?? []);
119
+ const requireScopedTargetOwner = options?.requireScopedTargetOwner === true;
117
120
  logger(`Monitoring Deep Research (timeout: ${Math.round(timeoutMs / 60_000)}min)...`);
118
121
  while (Date.now() - start < timeoutMs) {
119
122
  const { result } = await Runtime.evaluate({
@@ -124,19 +127,37 @@ export async function waitForDeepResearchCompletion(Runtime, logger, timeoutMs =
124
127
  if (val?.accountBlocked) {
125
128
  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" });
126
129
  }
127
- const frameResult = Page
130
+ const activeScopedResearch = Boolean(val?.hasActiveScopedResearch);
131
+ // ChatGPT renders the Deep Research report inside an out-of-process,
132
+ // sandboxed iframe (connector_openai_deep_research.*.oaiusercontent.com),
133
+ // doubly nested and same-origin. That OOPIF does NOT appear in the main
134
+ // page's frame tree, so the in-page isolated-world path
135
+ // (readDeepResearchFrameResult) can never see it. The target-attach path
136
+ // (readDeepResearchTargetResult) attaches to the iframe's own CDP target and
137
+ // walks its nested frames, so it CAN read the report. Prefer the target path
138
+ // and fall back to the in-page frame path for legacy/inline rendering.
139
+ const targetResult = client
140
+ ? ((await readDeepResearchTargetResult(client, ignoredTargetKeys, requireScopedTargetOwner ? minTurnLiteral : -1).catch(() => null))?.read ?? null)
141
+ : null;
142
+ // A completed target read is authoritative. If the target read is missing or
143
+ // only in-progress, still try the in-page frame path so an incomplete target
144
+ // read does not suppress a completed report there (legacy/inline rendering).
145
+ const inPageResult = !targetResult?.completed && Page
128
146
  ? await readDeepResearchFrameResult(Runtime, Page).catch(() => null)
129
- : client
130
- ? await readDeepResearchTargetResult(client).catch(() => null)
131
- : null;
132
- const scopedToNewTurns = minTurnLiteral >= 0;
133
- if (frameResult?.completed &&
134
- frameResult.text &&
135
- (!scopedToNewTurns || val?.hasActiveScopedResearch)) {
147
+ : null;
148
+ const read = pickPreferredDeepResearchRead(targetResult, inPageResult);
149
+ // A target-confirmed completion read the live connector iframe directly, so
150
+ // it is authoritative even when the main DOM exposes no assistant turn (the
151
+ // report lives entirely in the OOPIF). The main-DOM hasActiveScopedResearch
152
+ // heuristic no longer holds in that case, so don't gate on it.
153
+ const completedFromTarget = Boolean(targetResult?.completed);
154
+ if (read?.completed &&
155
+ read.text &&
156
+ (completedFromTarget || !scopedToNewTurns || activeScopedResearch)) {
136
157
  logger(`Deep Research completed (${Math.round((Date.now() - start) / 1000)}s elapsed)`);
137
158
  return {
138
- text: frameResult.text,
139
- html: frameResult.html,
159
+ text: read.text,
160
+ html: read.html,
140
161
  meta: { turnId: null, messageId: null },
141
162
  };
142
163
  }
@@ -149,8 +170,8 @@ export async function waitForDeepResearchCompletion(Runtime, logger, timeoutMs =
149
170
  const now = Date.now();
150
171
  if (now - lastLogTime >= 60_000) {
151
172
  const elapsed = Math.round((now - start) / 1000);
152
- const chars = Math.max(val?.textLength ?? 0, frameResult?.textLength ?? 0);
153
- const phase = frameResult?.inProgress || val?.hasIframe
173
+ const chars = Math.max(val?.textLength ?? 0, read?.textLength ?? 0);
174
+ const phase = read?.inProgress || val?.hasIframe
154
175
  ? "researching"
155
176
  : val?.stopVisible
156
177
  ? "generating"
@@ -158,7 +179,7 @@ export async function waitForDeepResearchCompletion(Runtime, logger, timeoutMs =
158
179
  logger(`Deep Research ${phase}... ${elapsed}s elapsed, ~${chars} chars`);
159
180
  lastLogTime = now;
160
181
  }
161
- lastTextLength = Math.max(val?.textLength ?? 0, frameResult?.textLength ?? 0, lastTextLength);
182
+ lastTextLength = Math.max(val?.textLength ?? 0, read?.textLength ?? 0, lastTextLength);
162
183
  await delay(DEEP_RESEARCH_POLL_INTERVAL_MS);
163
184
  }
164
185
  // Timeout — throw with metadata for potential reattach
@@ -202,6 +223,26 @@ function isDeepResearchPlaceholderText(text) {
202
223
  export function isDeepResearchPlaceholderTextForTest(text) {
203
224
  return isDeepResearchPlaceholderText(text);
204
225
  }
226
+ /**
227
+ * Choose the authoritative Deep Research read between the target-attach result
228
+ * and the in-page frame result. A completed read wins (target preferred, since
229
+ * it reads the live OOPIF directly); otherwise the best in-progress/text-bearing
230
+ * read is kept so progress logging still advances. This preserves the legacy
231
+ * Page-first inline behaviour: when the target read is missing or incomplete,
232
+ * a completed in-page result is still returned.
233
+ */
234
+ function pickPreferredDeepResearchRead(targetResult, inPageResult) {
235
+ if (targetResult?.completed) {
236
+ return targetResult;
237
+ }
238
+ if (inPageResult?.completed) {
239
+ return inPageResult;
240
+ }
241
+ return targetResult ?? inPageResult;
242
+ }
243
+ export function pickPreferredDeepResearchReadForTest(targetResult, inPageResult) {
244
+ return pickPreferredDeepResearchRead(targetResult, inPageResult);
245
+ }
205
246
  async function readDeepResearchFrameResult(Runtime, Page) {
206
247
  const pageWithFrames = Page;
207
248
  if (typeof pageWithFrames.getFrameTree !== "function" ||
@@ -228,58 +269,97 @@ async function readDeepResearchFrameResult(Runtime, Page) {
228
269
  });
229
270
  return result?.value ?? null;
230
271
  }
231
- async function readDeepResearchTargetResult(client) {
272
+ async function readDeepResearchTargetResult(client, ignoredTargetKeys = new Set(), minTurnIndex = -1) {
232
273
  const rawClient = client;
233
274
  if (typeof rawClient.send !== "function") {
234
275
  return null;
235
276
  }
236
- const sessionIds = new Set();
277
+ // On the browser-WSEndpoint path, `client` is a session-bound wrapper whose
278
+ // domain methods target the page session but whose raw `send` is the
279
+ // browser-level send. We must therefore pass the page session id explicitly so
280
+ // Target.setAutoAttach binds to THIS page (not the whole browser). For a direct
281
+ // tab client this is undefined and `send` already defaults to the page session.
282
+ const pageSessionId = rawClient.oraclePageSessionId;
283
+ const sessions = new Map();
237
284
  const ownedSessionIds = new Set();
238
- const onAttached = (params, sessionId) => {
239
- const targetInfo = params
240
- ?.targetInfo;
241
- const eventSessionId = params?.sessionId ?? sessionId;
285
+ const onAttached = (params, parentSessionId) => {
286
+ // chrome-remote-interface emits flattened target events both on the
287
+ // session-specific event name and on the shared base event. The second
288
+ // callback argument identifies the parent page session; ignore events from
289
+ // other tabs when this client wraps a shared browser WebSocket.
290
+ if (pageSessionId && parentSessionId !== pageSessionId) {
291
+ return;
292
+ }
293
+ const targetInfo = params?.targetInfo;
294
+ const eventSessionId = params?.sessionId ?? parentSessionId;
242
295
  const url = targetInfo?.url ?? "";
243
296
  const type = targetInfo?.type ?? "";
244
297
  if (eventSessionId && isDeepResearchTarget(url, type)) {
245
- sessionIds.add(eventSessionId);
298
+ sessions.set(eventSessionId, { targetId: targetInfo?.targetId, url });
246
299
  ownedSessionIds.add(eventSessionId);
247
300
  }
248
301
  };
249
302
  client.on?.("Target.attachedToTarget", onAttached);
250
303
  try {
251
- await rawClient.send("Target.setDiscoverTargets", { discover: true }).catch(() => undefined);
304
+ // Scope discovery to the current Oracle-controlled page. `client` is
305
+ // connected to the conversation page target, so enabling auto-attach on this
306
+ // session only attaches THIS page's related targets (its Deep Research OOPIF
307
+ // subframe) and emits Target.attachedToTarget for them.
308
+ //
309
+ // We deliberately do NOT enumerate Target.getTargets / attachToTarget here:
310
+ // that scan is browser-wide, and in a shared/persistent Chrome profile it
311
+ // would surface another tab's completed Deep Research report and let it be
312
+ // saved into the current session (cross-tab leak). Only auto-attached,
313
+ // page-scoped sessions are treated as belonging to this run.
252
314
  await rawClient
253
315
  .send("Target.setAutoAttach", {
254
316
  autoAttach: true,
255
317
  waitForDebuggerOnStart: false,
256
318
  flatten: true,
257
- })
319
+ }, pageSessionId)
258
320
  .catch(() => undefined);
259
321
  await delay(100);
260
- const targets = (await rawClient.send("Target.getTargets", {}));
261
- for (const target of targets?.targetInfos ?? []) {
262
- if (!target.targetId || !isDeepResearchTarget(target.url ?? "", target.type ?? "")) {
322
+ if (minTurnIndex >= 0) {
323
+ await rawClient.send("DOM.enable", {}, pageSessionId).catch(() => undefined);
324
+ await rawClient.send("Runtime.enable", {}, pageSessionId).catch(() => undefined);
325
+ }
326
+ // Baseline targets and owner turns before the submitted prompt are removed
327
+ // first. Among remaining targets, a completed report is authoritative;
328
+ // otherwise retain the newest meaningful progress read for status logging.
329
+ let completed = null;
330
+ let latestProgress = null;
331
+ const targetKeys = [];
332
+ for (const [sessionId, target] of sessions) {
333
+ const sessionResult = await readDeepResearchTargetSession(rawClient, sessionId, target.url);
334
+ if (!sessionResult.confirmed) {
263
335
  continue;
264
336
  }
265
- const attached = (await rawClient
266
- .send("Target.attachToTarget", { targetId: target.targetId, flatten: true })
267
- .catch(() => null));
268
- if (attached?.sessionId) {
269
- sessionIds.add(attached.sessionId);
270
- ownedSessionIds.add(attached.sessionId);
337
+ if (target.targetId) {
338
+ targetKeys.push(target.targetId);
271
339
  }
272
- }
273
- for (const sessionId of sessionIds) {
274
- const value = await readDeepResearchTargetSession(rawClient, sessionId);
340
+ if (target.targetId && ignoredTargetKeys.has(target.targetId)) {
341
+ continue;
342
+ }
343
+ if (minTurnIndex >= 0) {
344
+ const ownerTurnIndex = sessionResult.frameId
345
+ ? await readDeepResearchTargetOwnerTurnIndex(rawClient, sessionResult.frameId, pageSessionId)
346
+ : null;
347
+ if (ownerTurnIndex === null || ownerTurnIndex < minTurnIndex) {
348
+ continue;
349
+ }
350
+ }
351
+ const value = sessionResult.read;
275
352
  if (value?.completed) {
276
- return value;
353
+ completed = value;
277
354
  }
278
- if (value?.inProgress || value?.textLength) {
279
- return value;
355
+ else if (value && (value.inProgress || value.textLength > 0)) {
356
+ latestProgress = value;
280
357
  }
281
358
  }
282
- return null;
359
+ return {
360
+ read: completed ?? latestProgress,
361
+ targetKeys,
362
+ };
283
363
  }
284
364
  finally {
285
365
  await rawClient
@@ -287,18 +367,62 @@ async function readDeepResearchTargetResult(client) {
287
367
  autoAttach: false,
288
368
  waitForDebuggerOnStart: false,
289
369
  flatten: true,
290
- })
370
+ }, pageSessionId)
291
371
  .catch(() => undefined);
292
372
  await Promise.all(Array.from(ownedSessionIds, (sessionId) => rawClient.send("Target.detachFromTarget", { sessionId }).catch(() => undefined)));
293
373
  client.removeListener?.("Target.attachedToTarget", onAttached);
294
374
  }
295
375
  }
296
- async function readDeepResearchTargetSession(rawClient, sessionId) {
376
+ export async function captureDeepResearchTargetKeys(client) {
377
+ return (await readDeepResearchTargetResult(client))?.targetKeys ?? [];
378
+ }
379
+ async function readDeepResearchTargetOwnerTurnIndex(rawClient, frameId, pageSessionId) {
380
+ const owner = (await rawClient
381
+ .send("DOM.getFrameOwner", { frameId }, pageSessionId)
382
+ .catch(() => null));
383
+ if (typeof owner?.backendNodeId !== "number") {
384
+ return null;
385
+ }
386
+ const resolved = (await rawClient
387
+ .send("DOM.resolveNode", { backendNodeId: owner.backendNodeId }, pageSessionId)
388
+ .catch(() => null));
389
+ const objectId = resolved?.object?.objectId;
390
+ if (!objectId) {
391
+ return null;
392
+ }
393
+ try {
394
+ const response = (await rawClient
395
+ .send("Runtime.callFunctionOn", {
396
+ objectId,
397
+ functionDeclaration: `function() {
398
+ const selector = ${JSON.stringify(CONVERSATION_TURN_SELECTOR)};
399
+ const turn = this.closest(selector);
400
+ return turn ? Array.from(document.querySelectorAll(selector)).indexOf(turn) : null;
401
+ }`,
402
+ returnByValue: true,
403
+ }, pageSessionId)
404
+ .catch(() => null));
405
+ const value = response?.result?.value;
406
+ return typeof value === "number" && Number.isFinite(value) && value >= 0
407
+ ? Math.floor(value)
408
+ : null;
409
+ }
410
+ finally {
411
+ await rawClient
412
+ .send("Runtime.releaseObject", { objectId }, pageSessionId)
413
+ .catch(() => undefined);
414
+ }
415
+ }
416
+ async function readDeepResearchTargetSession(rawClient, sessionId, targetUrl) {
297
417
  await rawClient.send("Runtime.enable", {}, sessionId).catch(() => undefined);
298
418
  await rawClient.send("Page.enable", {}, sessionId).catch(() => undefined);
299
419
  const frameTree = (await rawClient
300
420
  .send("Page.getFrameTree", {}, sessionId)
301
421
  .catch(() => null));
422
+ const frameId = frameTree?.frameTree?.frame?.id;
423
+ if (!isConfirmedDeepResearchTarget(targetUrl, frameTree?.frameTree)) {
424
+ return { confirmed: false, read: null };
425
+ }
302
426
  const frameIds = collectDeepResearchFrameIds(frameTree?.frameTree);
303
427
  let best = null;
304
428
  for (const frameId of frameIds) {
@@ -314,7 +438,7 @@ async function readDeepResearchTargetSession(rawClient, sessionId) {
314
438
  }
315
439
  const value = await evaluateDeepResearchFrameStatus(rawClient, sessionId, world.executionContextId);
316
440
  if (value?.completed) {
317
- return value;
441
+ return { confirmed: true, read: value, frameId };
318
442
  }
319
443
  if ((value?.textLength ?? 0) > (best?.textLength ?? 0) || value?.inProgress) {
320
444
  best = value;
@@ -322,12 +446,12 @@ async function readDeepResearchTargetSession(rawClient, sessionId) {
322
446
  }
323
447
  const topFrameValue = await evaluateDeepResearchFrameStatus(rawClient, sessionId);
324
448
  if (topFrameValue?.completed) {
325
- return topFrameValue;
449
+ return { confirmed: true, read: topFrameValue, frameId };
326
450
  }
327
451
  if ((topFrameValue?.textLength ?? 0) > (best?.textLength ?? 0) || topFrameValue?.inProgress) {
328
452
  best = topFrameValue;
329
453
  }
330
- return best;
454
+ return { confirmed: true, read: best, frameId };
331
455
  }
332
456
  async function evaluateDeepResearchFrameStatus(rawClient, sessionId, contextId) {
333
457
  const response = (await rawClient
@@ -340,11 +464,14 @@ async function evaluateDeepResearchFrameStatus(rawClient, sessionId, contextId)
340
464
  return response?.result?.value ?? null;
341
465
  }
342
466
  function isDeepResearchTarget(url, type) {
343
- const lowerUrl = url.toLowerCase();
344
- const lowerType = type.toLowerCase();
345
- return (lowerType === "iframe" ||
346
- lowerUrl.includes("connector_openai_deep_research") ||
347
- lowerUrl.includes("deep-research"));
467
+ return type.toLowerCase() === "iframe" || isDeepResearchFrameDescriptor(url);
468
+ }
469
+ function isConfirmedDeepResearchTarget(targetUrl, tree) {
470
+ return isDeepResearchFrameDescriptor(targetUrl) || Boolean(findDeepResearchFrameId(tree));
471
+ }
472
+ function isDeepResearchFrameDescriptor(url, name = "") {
473
+ const descriptor = `${url}\n${name}`.toLowerCase();
474
+ return (descriptor.includes("connector_openai_deep_research") || descriptor.includes("deep-research"));
348
475
  }
349
476
  function findDeepResearchFrameId(tree) {
350
477
  if (!tree?.frame) {
@@ -352,9 +479,7 @@ function findDeepResearchFrameId(tree) {
352
479
  }
353
480
  const url = tree.frame.url ?? "";
354
481
  const name = tree.frame.name ?? "";
355
- if (url.includes("connector_openai_deep_research") ||
356
- url.includes("deep-research") ||
357
- name.includes("deep-research")) {
482
+ if (isDeepResearchFrameDescriptor(url, name)) {
358
483
  return tree.frame.id ?? null;
359
484
  }
360
485
  for (const child of tree.childFrames ?? []) {
@@ -439,6 +564,9 @@ function buildDeepResearchFrameStatusExpression() {
439
564
  export function findDeepResearchFrameIdForTest(tree) {
440
565
  return findDeepResearchFrameId(tree);
441
566
  }
567
+ export function isConfirmedDeepResearchTargetForTest(targetUrl, tree) {
568
+ return isConfirmedDeepResearchTarget(targetUrl, tree);
569
+ }
442
570
  export function buildDeepResearchFrameStatusExpressionForTest() {
443
571
  return buildDeepResearchFrameStatusExpression();
444
572
  }