@steipete/oracle 0.15.2 → 0.16.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/README.md +1 -1
- package/dist/bin/oracle-cli.js +1 -1
- package/dist/bin/oracle-mcp.js +0 -0
- package/dist/src/browser/actions/assistantResponse.js +218 -126
- package/dist/src/browser/actions/modelSelection.js +133 -22
- package/dist/src/browser/actions/navigation.js +235 -14
- package/dist/src/browser/actions/thinkingStatus.js +228 -0
- package/dist/src/browser/actions/thinkingTime.js +68 -11
- package/dist/src/browser/cdpLiveness.js +78 -0
- package/dist/src/browser/chromeLifecycle.js +112 -30
- package/dist/src/browser/config.js +14 -1
- package/dist/src/browser/constants.js +5 -1
- package/dist/src/browser/controlPlan.js +2 -2
- package/dist/src/browser/conversationUrl.js +16 -0
- package/dist/src/browser/conversationUrlMonitor.js +2 -4
- package/dist/src/browser/cookies.js +6 -4
- package/dist/src/browser/index.js +213 -57
- package/dist/src/browser/liveTabs.js +115 -33
- package/dist/src/browser/pageActions.js +1 -1
- package/dist/src/browser/projectSourcesRunner.js +4 -4
- package/dist/src/browser/reattach.js +2 -2
- package/dist/src/browser/reattachHelpers.js +3 -5
- package/dist/src/browser/reattachability.js +2 -1
- package/dist/src/browser/recoverConversation.js +90 -29
- package/dist/src/browser/tabLeaseRegistry.js +9 -3
- package/dist/src/cli/browserConfig.js +5 -1
- package/dist/src/cli/browserDefaults.js +4 -1
- package/dist/src/cli/browserTabs.js +54 -33
- package/dist/src/cli/options.js +24 -0
- package/dist/src/cli/runOptions.js +6 -1
- package/dist/src/cli/sessionDisplay.js +38 -14
- package/dist/src/cli/sessionRunner.js +49 -2
- package/dist/src/oracle/config.js +25 -0
- package/dist/src/oracle/geminiModels.js +2 -0
- package/dist/src/remote/server.js +8 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/package.json +13 -14
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/dist/bin/oracle.js +0 -569
- package/dist/docs-site/.nojekyll +0 -0
- package/dist/docs-site/CNAME +0 -1
- package/dist/docs-site/RELEASING.html +0 -410
- package/dist/docs-site/agents.html +0 -374
- package/dist/docs-site/anthropic.html +0 -368
- package/dist/docs-site/bridge.html +0 -416
- package/dist/docs-site/browser-mode.html +0 -594
- package/dist/docs-site/chromium-forks.html +0 -347
- package/dist/docs-site/cli-reference.html +0 -346
- package/dist/docs-site/configuration.html +0 -462
- package/dist/docs-site/favicon.svg +0 -14
- package/dist/docs-site/followup.html +0 -375
- package/dist/docs-site/gemini.html +0 -383
- package/dist/docs-site/grok.html +0 -325
- package/dist/docs-site/index.html +0 -360
- package/dist/docs-site/install.html +0 -335
- package/dist/docs-site/linux.html +0 -321
- package/dist/docs-site/llms.txt +0 -43
- package/dist/docs-site/manual-tests.html +0 -596
- package/dist/docs-site/mcp.html +0 -391
- package/dist/docs-site/multimodel.html +0 -364
- package/dist/docs-site/mythical-pro-agents.html +0 -360
- package/dist/docs-site/notifier.html +0 -338
- package/dist/docs-site/openai-endpoints.html +0 -399
- package/dist/docs-site/openrouter.html +0 -344
- package/dist/docs-site/quickstart.html +0 -369
- package/dist/docs-site/refactor/ux.html +0 -532
- package/dist/docs-site/sessions.html +0 -388
- package/dist/docs-site/social-card.png +0 -0
- package/dist/docs-site/social-card.svg +0 -79
- package/dist/docs-site/spec.html +0 -363
- package/dist/docs-site/testing.html +0 -320
- package/dist/docs-site/tui-debug.html +0 -326
- package/dist/docs-site/windows-work.html +0 -323
- package/dist/docs-site/windows.html +0 -320
- package/dist/src/browser/chromeCookies.js +0 -312
- package/dist/src/browser/keytarShim.js +0 -56
- package/dist/src/browser/windowsCookies.js +0 -219
|
@@ -404,6 +404,234 @@ function buildThinkingStatusExpression() {
|
|
|
404
404
|
return null;
|
|
405
405
|
})()`;
|
|
406
406
|
}
|
|
407
|
+
// Present-tense/gerund status labels that mean the model is ACTIVELY working. The
|
|
408
|
+
// past-tense "thought for Xs" summary is deliberately excluded: it persists in the DOM
|
|
409
|
+
// on every completed reasoning turn (and on reattach), so treating its mere presence as
|
|
410
|
+
// "still thinking" would veto completion forever and hang the call. Kept in sync with
|
|
411
|
+
// THINKING_STATUS_LABELS in assistantResponse.ts (the connector phases "searching the
|
|
412
|
+
// web"/"reading"/"finalizing answer" are exactly the GPT-5.5 Pro gaps that produce the
|
|
413
|
+
// preamble->answer window this predicate must cover).
|
|
414
|
+
const ACTIVE_THINKING_LABELS = [
|
|
415
|
+
"thinking",
|
|
416
|
+
"pro thinking",
|
|
417
|
+
"thinking longer for a better answer",
|
|
418
|
+
"reasoning",
|
|
419
|
+
"finalizing answer",
|
|
420
|
+
"finalizing",
|
|
421
|
+
"analyzing",
|
|
422
|
+
"researching",
|
|
423
|
+
"working on it",
|
|
424
|
+
"working",
|
|
425
|
+
"planning",
|
|
426
|
+
"searching the web",
|
|
427
|
+
"searching",
|
|
428
|
+
"reading",
|
|
429
|
+
];
|
|
430
|
+
// buildThinkingActivePredicateJs: a SIDE-EFFECT-FREE injected predicate `${fnName}()` that
|
|
431
|
+
// returns true iff the model is ACTIVELY generating/thinking right now. Unlike
|
|
432
|
+
// buildThinkingStatusExpression it never clicks a disclosure and never keys on the mere
|
|
433
|
+
// PRESENCE of a reasoning container ([data-testid*="reasoning"] persists after completion);
|
|
434
|
+
// it keys only on ACTIVITY signals: a visible stop/interrupt control, a visible animated
|
|
435
|
+
// loading-shimmer skeleton, aria-busy, a visible thinking sidecar panel with live progress,
|
|
436
|
+
// or a visible ACTIVE (present-tense) thinking status label. Used as a completion VETO so a
|
|
437
|
+
// settled preamble is never finalized while the reasoning/tool phase is still running.
|
|
438
|
+
function buildThinkingActivityPredicateJs(fnName, detailed) {
|
|
439
|
+
const stopLiteral = JSON.stringify(STOP_BUTTON_SELECTORS.join(", "));
|
|
440
|
+
const activeLabelsLiteral = JSON.stringify(ACTIVE_THINKING_LABELS);
|
|
441
|
+
const conversationLiteral = JSON.stringify(CONVERSATION_TURN_SELECTOR);
|
|
442
|
+
const assistantLiteral = JSON.stringify(ASSISTANT_ROLE_SELECTOR);
|
|
443
|
+
const strong = detailed ? "{ active: true, strong: true }" : "true";
|
|
444
|
+
const weak = detailed ? "{ active: true, strong: false }" : "true";
|
|
445
|
+
const idle = detailed ? "{ active: false, strong: false }" : "false";
|
|
446
|
+
return `const ${fnName} = () => {
|
|
447
|
+
const STOP_SELECTOR = ${stopLiteral};
|
|
448
|
+
const ACTIVE_LABELS = ${activeLabelsLiteral};
|
|
449
|
+
const CONVERSATION_SELECTOR = ${conversationLiteral};
|
|
450
|
+
const ASSISTANT_SELECTOR = ${assistantLiteral};
|
|
451
|
+
const isVisible = (node) => {
|
|
452
|
+
if (!(node instanceof HTMLElement)) return false;
|
|
453
|
+
const rect = node.getBoundingClientRect();
|
|
454
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) return false;
|
|
455
|
+
const style = window.getComputedStyle(node);
|
|
456
|
+
if (
|
|
457
|
+
style.display === 'none' ||
|
|
458
|
+
style.visibility === 'hidden' ||
|
|
459
|
+
(style.opacity !== '' && Number(style.opacity) === 0)
|
|
460
|
+
) {
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
return true;
|
|
464
|
+
};
|
|
465
|
+
const some = (selector, pred) => {
|
|
466
|
+
let nodes;
|
|
467
|
+
try { nodes = document.querySelectorAll(selector); } catch { return false; }
|
|
468
|
+
return Array.from(nodes).some((node) => pred(node));
|
|
469
|
+
};
|
|
470
|
+
// 1) Stop/interrupt control visible -> generation is active (language-independent).
|
|
471
|
+
if (some(STOP_SELECTOR, isVisible)) return ${strong};
|
|
472
|
+
// 2-3) Shimmer/aria-busy are checked only inside the current turn or verified thinking
|
|
473
|
+
// panel below. Page-global busy UI (history/sidebar/upload) is not model activity.
|
|
474
|
+
const hasBusyIndicator = (scope) => {
|
|
475
|
+
let nodes;
|
|
476
|
+
try {
|
|
477
|
+
nodes = scope.querySelectorAll(
|
|
478
|
+
'span.loading-shimmer, .loading-shimmer, [class*="loading-shimmer"], [aria-busy="true"]',
|
|
479
|
+
);
|
|
480
|
+
} catch { return false; }
|
|
481
|
+
return Array.from(nodes).some((node) => isVisible(node));
|
|
482
|
+
};
|
|
483
|
+
// 4) Active (present-tense) thinking status label near a status/reasoning node.
|
|
484
|
+
const norm = (value) =>
|
|
485
|
+
String(value || '')
|
|
486
|
+
.normalize('NFD')
|
|
487
|
+
.replace(/[\\u0300-\\u036f]/g, '')
|
|
488
|
+
.toLowerCase()
|
|
489
|
+
.replace(/\\s+/g, ' ')
|
|
490
|
+
.trim();
|
|
491
|
+
// Completed reasoning summary: the whole visible label must be a duration summary. Anchoring
|
|
492
|
+
// prevents an early live trace such as "Thought for 2s: Searching the web" from being
|
|
493
|
+
// mistaken for completion before the trace grows beyond an arbitrary length threshold.
|
|
494
|
+
const isCompletedSummary = (text) =>
|
|
495
|
+
/^(?:(?:reasoning|pro thinking)\\s*)?thought for (?:\\d+(?:\\.\\d+)?\\s*(?:s|sec|secs|second|seconds|m|min|mins|minute|minutes|h|hr|hrs|hour|hours)(?:\\s+\\d+(?:\\.\\d+)?\\s*(?:s|sec|secs|second|seconds|m|min|mins|minute|minutes|h|hr|hrs|hour|hours))*|(?:a|an) [a-z]+(?: [a-z]+){0,2})(?: edit)?$/.test(text);
|
|
496
|
+
const isActiveLabel = (raw) => {
|
|
497
|
+
const text = norm(raw);
|
|
498
|
+
if (!text || text.length > 60) return false;
|
|
499
|
+
if (isCompletedSummary(text)) return false;
|
|
500
|
+
return ACTIVE_LABELS.some((label) => text === label || text.startsWith(label + ' '));
|
|
501
|
+
};
|
|
502
|
+
const statusNodes = (() => {
|
|
503
|
+
try {
|
|
504
|
+
return Array.from(
|
|
505
|
+
document.querySelectorAll(
|
|
506
|
+
'[data-testid*="thinking"], [data-testid*="reasoning"]',
|
|
507
|
+
),
|
|
508
|
+
);
|
|
509
|
+
} catch {
|
|
510
|
+
return [];
|
|
511
|
+
}
|
|
512
|
+
})();
|
|
513
|
+
for (const node of statusNodes) {
|
|
514
|
+
if (!(node instanceof HTMLElement) || !isVisible(node)) continue;
|
|
515
|
+
const testId = norm(node.getAttribute('data-testid'));
|
|
516
|
+
const verifiedThinkingChrome = testId.includes('thinking') || testId.includes('reasoning');
|
|
517
|
+
const matches =
|
|
518
|
+
verifiedThinkingChrome &&
|
|
519
|
+
(isActiveLabel(node.textContent) || isActiveLabel(node.getAttribute('aria-label')));
|
|
520
|
+
if (matches) return ${strong};
|
|
521
|
+
}
|
|
522
|
+
// 5) A live progress bar (determinate or indeterminate) is active generation, even when no
|
|
523
|
+
// label or shimmer is present (some connector/tool phases surface only a progress bar).
|
|
524
|
+
const hasLiveProgress = (scope) => {
|
|
525
|
+
let nodes;
|
|
526
|
+
// Only genuine progress indicators — NOT generic [aria-valuenow] range widgets (sliders,
|
|
527
|
+
// spinbuttons), which are not liveness signals and would falsely veto completion forever.
|
|
528
|
+
try {
|
|
529
|
+
nodes = scope.querySelectorAll('progress, [role="progressbar"]');
|
|
530
|
+
} catch { return false; }
|
|
531
|
+
return Array.from(nodes).some((n) => {
|
|
532
|
+
if (!(n instanceof HTMLElement) || !isVisible(n)) return false;
|
|
533
|
+
if (n instanceof HTMLProgressElement) {
|
|
534
|
+
// Determinate <progress> is active only while it has not reached its max.
|
|
535
|
+
if (Number.isFinite(n.value) && Number.isFinite(n.max) && n.max > 0) return n.value < n.max;
|
|
536
|
+
return true; // indeterminate
|
|
537
|
+
}
|
|
538
|
+
const rawNow = n.getAttribute('aria-valuenow');
|
|
539
|
+
if (rawNow != null) {
|
|
540
|
+
const now = Number(rawNow);
|
|
541
|
+
const rawMax = n.getAttribute('aria-valuemax');
|
|
542
|
+
const max = rawMax != null && Number.isFinite(Number(rawMax)) ? Number(rawMax) : 100;
|
|
543
|
+
return Number.isFinite(now) ? now < max : true;
|
|
544
|
+
}
|
|
545
|
+
return true; // role=progressbar with no value -> indeterminate -> active
|
|
546
|
+
});
|
|
547
|
+
};
|
|
548
|
+
// Scoped to the CURRENT assistant turn, not the whole document: unrelated page UI can keep
|
|
549
|
+
// a visible progress bar mounted indefinitely (review P1), and a document-wide veto would
|
|
550
|
+
// then hold thinkingActive true until the watchdog timeout on a completed response. The
|
|
551
|
+
// sidecar check below covers verified reasoning panels; here only the latest turn counts.
|
|
552
|
+
const turns = (() => {
|
|
553
|
+
try { return document.querySelectorAll(CONVERSATION_SELECTOR); } catch { return []; }
|
|
554
|
+
})();
|
|
555
|
+
const lastTurn = turns.length ? turns[turns.length - 1] : null;
|
|
556
|
+
if (
|
|
557
|
+
lastTurn instanceof HTMLElement &&
|
|
558
|
+
(hasBusyIndicator(lastTurn) || hasLiveProgress(lastTurn))
|
|
559
|
+
) return ${strong};
|
|
560
|
+
// 6) A visible thinking/reasoning sidecar panel (the connector/reasoning phase is often
|
|
561
|
+
// exposed ONLY through a right-side panel with no inline label). Match the existing
|
|
562
|
+
// thinking-monitor heuristic: a right-side panel that looks like thinking, or any such
|
|
563
|
+
// container that carries a live progress bar. Presence alone is NOT enough (a collapsed
|
|
564
|
+
// reasoning summary persists post-completion); require the thinking cue or live progress.
|
|
565
|
+
const looksLikeThinking = (node) => {
|
|
566
|
+
// Judge completion on the panel's VISIBLE text only: data-testid values like
|
|
567
|
+
// "reasoning-panel" would otherwise taint the completed-summary check, and a live trace
|
|
568
|
+
// is judged by its full rendered length, not by fragments of it.
|
|
569
|
+
const visible = norm(node.textContent) || norm(node.getAttribute?.('aria-label'));
|
|
570
|
+
if (isCompletedSummary(visible)) return false;
|
|
571
|
+
if (visible.includes('thought for ')) return true;
|
|
572
|
+
const label = norm([
|
|
573
|
+
node.textContent,
|
|
574
|
+
node.getAttribute?.('aria-label'),
|
|
575
|
+
node.getAttribute?.('data-testid'),
|
|
576
|
+
].filter(Boolean).join(' '));
|
|
577
|
+
return label.includes('thinking') || label.includes('reasoning') || label.includes('pro thinking');
|
|
578
|
+
};
|
|
579
|
+
let panels;
|
|
580
|
+
try {
|
|
581
|
+
panels = document.querySelectorAll(
|
|
582
|
+
'aside, [role="complementary"], [role="dialog"], [data-testid*="thinking"], [data-testid*="reasoning"], [class*="sidecar"], [class*="sidebar"]',
|
|
583
|
+
);
|
|
584
|
+
} catch { panels = []; }
|
|
585
|
+
for (const node of Array.from(panels)) {
|
|
586
|
+
if (!(node instanceof HTMLElement) || !isVisible(node)) continue;
|
|
587
|
+
const rect = node.getBoundingClientRect();
|
|
588
|
+
const rightSide = rect.left >= window.innerWidth * 0.35 && rect.width >= 180 && rect.height >= 120;
|
|
589
|
+
const panelLabel = norm([
|
|
590
|
+
node.getAttribute?.('aria-label'),
|
|
591
|
+
node.getAttribute?.('data-testid'),
|
|
592
|
+
node.className,
|
|
593
|
+
].filter(Boolean).join(' '));
|
|
594
|
+
const verifiedThinkingPanel =
|
|
595
|
+
panelLabel.includes('thinking') ||
|
|
596
|
+
panelLabel.includes('reasoning') ||
|
|
597
|
+
panelLabel.includes('sidecar');
|
|
598
|
+
if ((hasBusyIndicator(node) || hasLiveProgress(node)) && verifiedThinkingPanel) return ${strong};
|
|
599
|
+
// A text-only sidecar match is intentionally weak: completed turns can retain a mounted
|
|
600
|
+
// reasoning panel whose shape/text heuristics still look active. The terminal gate may
|
|
601
|
+
// override only this weak evidence after a stable, debounced finished-action bar.
|
|
602
|
+
if (rightSide && looksLikeThinking(node)) return ${weak};
|
|
603
|
+
}
|
|
604
|
+
return ${idle};
|
|
605
|
+
};`;
|
|
606
|
+
}
|
|
607
|
+
export function buildThinkingActivePredicateJs(fnName) {
|
|
608
|
+
return buildThinkingActivityPredicateJs(fnName, false);
|
|
609
|
+
}
|
|
610
|
+
export function buildThinkingActivityDetailsPredicateJs(fnName) {
|
|
611
|
+
return buildThinkingActivityPredicateJs(fnName, true);
|
|
612
|
+
}
|
|
613
|
+
export async function readThinkingActivity(Runtime) {
|
|
614
|
+
try {
|
|
615
|
+
const { result } = await Runtime.evaluate({
|
|
616
|
+
expression: `(() => {
|
|
617
|
+
${buildThinkingActivityDetailsPredicateJs("readThinkingActivity")}
|
|
618
|
+
return readThinkingActivity();
|
|
619
|
+
})()`,
|
|
620
|
+
returnByValue: true,
|
|
621
|
+
});
|
|
622
|
+
const value = result?.value;
|
|
623
|
+
return { active: Boolean(value?.active), strong: Boolean(value?.strong) };
|
|
624
|
+
}
|
|
625
|
+
catch {
|
|
626
|
+
return { active: false, strong: false };
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
export async function isThinkingActive(Runtime) {
|
|
630
|
+
return (await readThinkingActivity(Runtime)).active;
|
|
631
|
+
}
|
|
407
632
|
export const startThinkingStatusMonitorForTest = startThinkingStatusMonitor;
|
|
408
633
|
export const readThinkingStatusForTest = readThinkingStatus;
|
|
409
634
|
export const buildThinkingStatusExpressionForTest = buildThinkingStatusExpression;
|
|
635
|
+
export const buildThinkingActivePredicateJsForTest = buildThinkingActivePredicateJs;
|
|
636
|
+
export const buildThinkingActivityDetailsPredicateJsForTest = buildThinkingActivityDetailsPredicateJs;
|
|
637
|
+
export { ACTIVE_THINKING_LABELS };
|
|
@@ -124,6 +124,7 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
124
124
|
const modelButtonLiteral = JSON.stringify(MODEL_BUTTON_SELECTOR);
|
|
125
125
|
const targetLevelLiteral = JSON.stringify(level.toLowerCase());
|
|
126
126
|
const targetModelKindLiteral = JSON.stringify(inferThinkingTargetModelKind(desiredModel));
|
|
127
|
+
const targetIsGpt56ModelLiteral = JSON.stringify(/(?:^|[^0-9])5[._ -]6(?:[^0-9]|$)/i.test(desiredModel ?? ""));
|
|
127
128
|
return `(async () => {
|
|
128
129
|
${buildClickDispatcher()}
|
|
129
130
|
|
|
@@ -132,13 +133,14 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
132
133
|
const MODEL_BUTTON_SELECTOR = ${modelButtonLiteral};
|
|
133
134
|
const TARGET_LEVEL = ${targetLevelLiteral};
|
|
134
135
|
const TARGET_MODEL_KIND = ${targetModelKindLiteral};
|
|
136
|
+
const TARGET_IS_GPT56_MODEL = ${targetIsGpt56ModelLiteral};
|
|
135
137
|
|
|
136
138
|
// Bilingual matchers: English level token + observed Chinese variants.
|
|
137
139
|
const LEVEL_TOKENS = {
|
|
138
|
-
light: ['light', 'instant', '轻'],
|
|
139
|
-
standard: ['standard', 'medium', '标准'],
|
|
140
|
-
extended: ['extended', 'high', '扩展', '深度', '加强'],
|
|
141
|
-
heavy: ['heavy', 'extra high', '重度', '加重', '
|
|
140
|
+
light: ['light', 'instant', '轻', '极速'],
|
|
141
|
+
standard: ['standard', 'medium', '标准', '中'],
|
|
142
|
+
extended: ['extended', 'high', '扩展', '深度', '加强', '高'],
|
|
143
|
+
heavy: ['heavy', 'extra high', '重度', '加重', '极高'],
|
|
142
144
|
};
|
|
143
145
|
const targetTokens = LEVEL_TOKENS[TARGET_LEVEL] || [TARGET_LEVEL];
|
|
144
146
|
|
|
@@ -166,6 +168,13 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
166
168
|
if (!token) return false;
|
|
167
169
|
if (token === 'high') return hasToken(t, 'high') && !hasToken(t, 'extra');
|
|
168
170
|
if (token === 'extra high') return hasToken(t, 'extra') && hasToken(t, 'high');
|
|
171
|
+
if (token === '极速') {
|
|
172
|
+
const suffix = t.slice(token.length);
|
|
173
|
+
return t === token || hasToken(t, token) || /^[0-9]/.test(suffix);
|
|
174
|
+
}
|
|
175
|
+
if (['中', '高', '极高'].includes(token)) {
|
|
176
|
+
return t === token || hasToken(t, token);
|
|
177
|
+
}
|
|
169
178
|
return t === token || hasToken(t, token) || t.includes(token);
|
|
170
179
|
});
|
|
171
180
|
};
|
|
@@ -334,6 +343,9 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
334
343
|
if (menu?.getAttribute?.('data-testid') === 'composer-intelligence-picker-content') {
|
|
335
344
|
return true;
|
|
336
345
|
}
|
|
346
|
+
if (menu?.querySelector?.(INTELLIGENCE_MENU_SELECTOR)) {
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
337
349
|
const label = menu?.querySelector?.('.__menu-label, [class*="menu-label"]');
|
|
338
350
|
return normalize(label?.textContent ?? '').includes('intelligence');
|
|
339
351
|
};
|
|
@@ -347,6 +359,21 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
347
359
|
const items = Array.from(menu.querySelectorAll(MENU_ITEM_SELECTOR));
|
|
348
360
|
const modelKind = modelKindOverride || effectiveTargetModelKind();
|
|
349
361
|
if (modelKind === 'pro') {
|
|
362
|
+
// GPT-5.6's unified Intelligence picker exposes Pro as the highest
|
|
363
|
+
// effort radio directly. It no longer has a nested "Pro Extended"
|
|
364
|
+
// row, so preserve the legacy request semantics by selecting Pro.
|
|
365
|
+
if (
|
|
366
|
+
TARGET_LEVEL === 'extended' &&
|
|
367
|
+
isIntelligenceEffortMenu(menu) &&
|
|
368
|
+
!document.querySelector(PRO_EFFORT_TRIGGER_SELECTOR)
|
|
369
|
+
) {
|
|
370
|
+
for (const item of items) {
|
|
371
|
+
const itemText = normalize(
|
|
372
|
+
(item.textContent ?? '') + ' ' + (item.getAttribute?.('aria-label') ?? ''),
|
|
373
|
+
);
|
|
374
|
+
if (itemText === 'pro') return item;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
350
377
|
for (const item of items) {
|
|
351
378
|
const itemText = normalize(
|
|
352
379
|
(item.textContent ?? '') + ' ' + (item.getAttribute?.('aria-label') ?? ''),
|
|
@@ -367,7 +394,7 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
367
394
|
const itemText = normalize(
|
|
368
395
|
(item.textContent ?? '') + ' ' + (item.getAttribute?.('aria-label') ?? ''),
|
|
369
396
|
);
|
|
370
|
-
if (modelKind
|
|
397
|
+
if (modelKind !== 'pro' && hasToken(itemText, 'pro')) {
|
|
371
398
|
continue;
|
|
372
399
|
}
|
|
373
400
|
if (
|
|
@@ -377,6 +404,16 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
377
404
|
return item;
|
|
378
405
|
}
|
|
379
406
|
}
|
|
407
|
+
if (TARGET_LEVEL === 'heavy') {
|
|
408
|
+
// Older Chinese layouts used bare 高 for the highest effort. Keep it
|
|
409
|
+
// only as a second-pass exact fallback so a current 高 row can never
|
|
410
|
+
// win before the primary 极高 row.
|
|
411
|
+
for (const item of items) {
|
|
412
|
+
const itemText = normalize(item.textContent ?? '');
|
|
413
|
+
const ariaLabel = normalize(item.getAttribute?.('aria-label') ?? '');
|
|
414
|
+
if (itemText === '高' || ariaLabel === '高') return item;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
380
417
|
return null;
|
|
381
418
|
};
|
|
382
419
|
const countEffortLevels = (menu) => {
|
|
@@ -390,6 +427,7 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
390
427
|
const isEffortMenu = (menu) => {
|
|
391
428
|
if (!isVisible(menu)) return false;
|
|
392
429
|
if (menu.getAttribute?.('data-testid') === 'composer-intelligence-picker-content') return true;
|
|
430
|
+
if (menu.querySelector?.(INTELLIGENCE_MENU_SELECTOR)) return true;
|
|
393
431
|
const label = menu.querySelector?.('.__menu-label, [class*="menu-label"]');
|
|
394
432
|
const labelText = normalize(label?.textContent ?? '');
|
|
395
433
|
return (
|
|
@@ -450,8 +488,16 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
450
488
|
}
|
|
451
489
|
return null;
|
|
452
490
|
};
|
|
491
|
+
const freshComposerTrigger = (trigger) => {
|
|
492
|
+
if (!trigger?.matches?.('button.__composer-pill')) return null;
|
|
493
|
+
// React can replace the composer pill after an effort click. Keep using
|
|
494
|
+
// the captured node while it is live, but re-query once it is detached so
|
|
495
|
+
// verification does not read its stale pre-click label.
|
|
496
|
+
if (trigger.isConnected !== false) return trigger;
|
|
497
|
+
return findComposerEffortPill() || findModelButton() || trigger;
|
|
498
|
+
};
|
|
453
499
|
const currentProEffortPillMatchesTarget = (trigger, modelKindOverride = null) => {
|
|
454
|
-
const button = trigger
|
|
500
|
+
const button = freshComposerTrigger(trigger) || findModelButton();
|
|
455
501
|
if ((modelKindOverride || TARGET_MODEL_KIND || modelKindFromNode(button)) !== 'pro') {
|
|
456
502
|
return false;
|
|
457
503
|
}
|
|
@@ -466,7 +512,7 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
466
512
|
};
|
|
467
513
|
const currentEffortPillMatchesTarget = (trigger, modelKindOverride = null) => {
|
|
468
514
|
if (currentProEffortPillMatchesTarget(trigger, modelKindOverride)) return true;
|
|
469
|
-
const button = trigger
|
|
515
|
+
const button = freshComposerTrigger(trigger) || findModelButton();
|
|
470
516
|
if ((modelKindOverride || TARGET_MODEL_KIND || modelKindFromNode(button)) === 'pro') {
|
|
471
517
|
return false;
|
|
472
518
|
}
|
|
@@ -499,8 +545,9 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
499
545
|
return { status: 'switched', label };
|
|
500
546
|
}
|
|
501
547
|
|
|
502
|
-
|
|
503
|
-
|
|
548
|
+
const reopenTrigger = freshComposerTrigger(trigger) || trigger;
|
|
549
|
+
if (!refreshed && reopenTrigger?.getAttribute?.('aria-expanded') !== 'true') {
|
|
550
|
+
dispatchClickSequence(reopenTrigger);
|
|
504
551
|
await sleep(INITIAL_WAIT_MS);
|
|
505
552
|
}
|
|
506
553
|
const deadline = performance.now() + 2000;
|
|
@@ -556,6 +603,7 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
556
603
|
];
|
|
557
604
|
const findComposerEffortPill = () => {
|
|
558
605
|
const seen = new Set();
|
|
606
|
+
let gpt56Fallback = null;
|
|
559
607
|
for (const selector of COMPOSER_EFFORT_PILL_SELECTORS) {
|
|
560
608
|
for (const button of document.querySelectorAll(selector)) {
|
|
561
609
|
if (seen.has(button) || !isVisible(button)) continue;
|
|
@@ -574,9 +622,16 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
574
622
|
) {
|
|
575
623
|
return button;
|
|
576
624
|
}
|
|
625
|
+
if (
|
|
626
|
+
TARGET_IS_GPT56_MODEL &&
|
|
627
|
+
button.matches?.('button.__composer-pill') &&
|
|
628
|
+
normalize(button.textContent ?? '') === 'pro'
|
|
629
|
+
) {
|
|
630
|
+
gpt56Fallback ||= button;
|
|
631
|
+
}
|
|
577
632
|
}
|
|
578
633
|
}
|
|
579
|
-
return
|
|
634
|
+
return gpt56Fallback;
|
|
580
635
|
};
|
|
581
636
|
let composerEffortPill = findComposerEffortPill();
|
|
582
637
|
let modelBtn = findModelButton();
|
|
@@ -634,7 +689,9 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
634
689
|
}
|
|
635
690
|
if (composerEffortPill) {
|
|
636
691
|
if (attemptedModelButton && attemptedModelButton !== composerEffortPill) closeOpenMenus();
|
|
637
|
-
const composerModelKind =
|
|
692
|
+
const composerModelKind =
|
|
693
|
+
TARGET_MODEL_KIND ||
|
|
694
|
+
(TARGET_IS_GPT56_MODEL ? 'versioned' : modelKindFromNode(composerEffortPill));
|
|
638
695
|
if (composerEffortPill.getAttribute?.('aria-expanded') !== 'true') {
|
|
639
696
|
dispatchClickSequence(composerEffortPill);
|
|
640
697
|
await sleep(INITIAL_WAIT_MS);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { listRemoteChromeTargets } from "./chromeLifecycle.js";
|
|
2
|
+
import { verifyDevToolsReachable } from "./profileState.js";
|
|
3
|
+
/**
|
|
4
|
+
* Probe whether Chrome's DevTools endpoint (and optionally a specific target)
|
|
5
|
+
* is still reachable after a CDP client WebSocket disconnect.
|
|
6
|
+
*/
|
|
7
|
+
export async function probeChromeTargetLiveness(options) {
|
|
8
|
+
const host = options.host || "127.0.0.1";
|
|
9
|
+
const port = options.port;
|
|
10
|
+
if (!Number.isFinite(port) || port <= 0) {
|
|
11
|
+
return { endpointReachable: false, targetFound: null, error: "missing debug port" };
|
|
12
|
+
}
|
|
13
|
+
const verifyEndpoint = options.verifyEndpoint ?? verifyDevToolsReachable;
|
|
14
|
+
const endpoint = await verifyEndpoint({ host, port, attempts: 2, timeoutMs: 1500 });
|
|
15
|
+
if (!endpoint.ok) {
|
|
16
|
+
return {
|
|
17
|
+
endpointReachable: false,
|
|
18
|
+
targetFound: null,
|
|
19
|
+
error: endpoint.error,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const targetId = options.targetId?.trim();
|
|
23
|
+
if (!targetId) {
|
|
24
|
+
return { endpointReachable: true, targetFound: null };
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const listTargets = options.listTargets ?? listRemoteChromeTargets;
|
|
28
|
+
const targets = await listTargets({
|
|
29
|
+
host,
|
|
30
|
+
port,
|
|
31
|
+
browserWSEndpoint: options.browserWSEndpoint,
|
|
32
|
+
});
|
|
33
|
+
const match = targets.find((target) => {
|
|
34
|
+
const id = target.targetId ?? target.id;
|
|
35
|
+
return id === targetId;
|
|
36
|
+
});
|
|
37
|
+
if (!match) {
|
|
38
|
+
return { endpointReachable: true, targetFound: false };
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
endpointReachable: true,
|
|
42
|
+
targetFound: true,
|
|
43
|
+
matchedUrl: typeof match.url === "string" ? match.url : undefined,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
48
|
+
// Endpoint answered /json/version; treat list failures as still recoverable.
|
|
49
|
+
return { endpointReachable: true, targetFound: null, error: message };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function isRecoverableChromeDisconnect(liveness) {
|
|
53
|
+
if (!liveness.endpointReachable) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
// Confirmed live target → recoverable.
|
|
57
|
+
if (liveness.targetFound === true) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
// Confirmed missing target → not recoverable.
|
|
61
|
+
if (liveness.targetFound === false) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
// targetFound === null:
|
|
65
|
+
// - no target id was provided (endpoint-only check) → recoverable
|
|
66
|
+
// - target list failed after a specific id was requested (error set) → fail closed
|
|
67
|
+
return !liveness.error;
|
|
68
|
+
}
|
|
69
|
+
export function connectionLostUserMessage(options) {
|
|
70
|
+
if (options.recoverable) {
|
|
71
|
+
return options.remote
|
|
72
|
+
? "Remote Chrome DevTools client disconnected before oracle finished; the browser target appears still alive."
|
|
73
|
+
: "Chrome DevTools client disconnected before oracle finished; the browser target appears still alive.";
|
|
74
|
+
}
|
|
75
|
+
return options.remote
|
|
76
|
+
? "Remote Chrome connection lost before Oracle finished."
|
|
77
|
+
: "Chrome window closed before oracle finished. Please keep it open until completion.";
|
|
78
|
+
}
|