@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.
- package/README.md +22 -11
- package/dist/bin/oracle-cli.js +71 -71
- package/dist/src/browser/actions/assistantResponse.js +93 -10
- package/dist/src/browser/actions/deepResearch.js +180 -52
- package/dist/src/browser/actions/modelSelection.js +357 -40
- package/dist/src/browser/actions/navigation.js +294 -65
- package/dist/src/browser/actions/promptComposer.js +50 -4
- package/dist/src/browser/actions/thinkingTime.js +517 -105
- package/dist/src/browser/artifacts.js +19 -0
- package/dist/src/browser/chatgptFiles.js +797 -0
- package/dist/src/browser/chatgptImages.js +256 -27
- package/dist/src/browser/chromeLifecycle.js +4 -0
- package/dist/src/browser/config.js +2 -0
- package/dist/src/browser/index.js +404 -41
- package/dist/src/browser/pageActions.js +1 -1
- package/dist/src/browser/policies.js +2 -6
- package/dist/src/browser/projectSourcesRunner.js +16 -3
- package/dist/src/browser/prompt.js +161 -36
- package/dist/src/browser/reattach.js +6 -2
- package/dist/src/browser/reattachability.js +22 -10
- package/dist/src/browser/recoverConversation.js +73 -0
- package/dist/src/browser/sessionRunner.js +4 -1
- package/dist/src/cli/browserConfig.js +6 -2
- package/dist/src/cli/browserDefaults.js +2 -1
- package/dist/src/cli/browserTabs.js +129 -54
- package/dist/src/cli/followup.js +72 -0
- package/dist/src/cli/help.js +1 -1
- package/dist/src/cli/options.js +24 -0
- package/dist/src/cli/reattachGuidance.js +8 -0
- package/dist/src/cli/runOptions.js +2 -12
- package/dist/src/cli/sessionCommand.js +4 -0
- package/dist/src/cli/sessionDisplay.js +17 -3
- package/dist/src/cli/sessionLineage.js +7 -4
- package/dist/src/cli/sessionRunner.js +16 -1
- package/dist/src/gemini-web/client.js +5 -10
- package/dist/src/gemini-web/executor.js +1 -24
- package/dist/src/gemini-web/models.js +83 -0
- package/dist/src/mcp/server.js +2 -0
- package/dist/src/mcp/tools/chatgptImage.js +132 -0
- package/dist/src/mcp/tools/consult.js +270 -175
- package/dist/src/mcp/types.js +13 -2
- package/dist/src/mcp/utils.js +87 -1
- package/dist/src/oracle/config.js +27 -1
- package/dist/src/oracle/files.js +4 -6
- package/dist/src/oracle/geminiModels.js +2 -0
- package/dist/src/oracle/markdown.js +36 -3
- package/dist/src/oracle/modelResolver.js +21 -13
- package/dist/src/oracle/promptAssembly.js +2 -4
- package/dist/src/oracle/request.js +3 -5
- package/dist/src/oracle/thinkingTime.js +40 -0
- package/dist/src/oracle/tokenStats.js +5 -1
- package/dist/src/oracle.js +1 -1
- package/dist/src/sessionManager.js +1 -0
- package/dist/vendor/oracle-notifier/build-notifier.sh +0 -0
- package/package.json +47 -58
- package/vendor/oracle-notifier/build-notifier.sh +0 -0
- package/dist/bin/oracle.js +0 -569
- package/dist/src/browser/chromeCookies.js +0 -312
- package/dist/src/browser/keytarShim.js +0 -56
- package/dist/src/browser/windowsCookies.js +0 -219
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +0 -20
- 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 +0 -128
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +0 -20
- 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 +0 -128
- package/vendor/oracle-notifier/README.md +0 -26
|
@@ -1,33 +1,51 @@
|
|
|
1
1
|
import { MENU_CONTAINER_SELECTOR, MENU_ITEM_SELECTOR, MODEL_BUTTON_SELECTOR, } from "../constants.js";
|
|
2
2
|
import { logDomFailure } from "../domDebug.js";
|
|
3
3
|
import { buildClickDispatcher } from "./domEvents.js";
|
|
4
|
+
const BROWSER_THINKING_LOG_PREFIX = "[browser] Thinking time:";
|
|
5
|
+
function formatBrowserThinkingLog(message) {
|
|
6
|
+
return `${BROWSER_THINKING_LOG_PREFIX} ${message.replace(/^Thinking time:\s*/, "")}`;
|
|
7
|
+
}
|
|
4
8
|
/**
|
|
5
|
-
*
|
|
9
|
+
* Surfaces the model-picker snapshot captured alongside a failed detection.
|
|
6
10
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
* The browser prefix routes this through the session runner's non-verbose
|
|
12
|
+
* always-print path. The injected probe bounds and redacts all text values.
|
|
13
|
+
*/
|
|
14
|
+
function logPickerDiagnostic(result, logger) {
|
|
15
|
+
const diagnostic = result && "diagnostic" in result
|
|
16
|
+
? result.diagnostic
|
|
17
|
+
: undefined;
|
|
18
|
+
if (!diagnostic) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
logger(`[browser] Model picker diagnostic: ${JSON.stringify(diagnostic)}`);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Selects a thinking-time level in ChatGPT's composer.
|
|
11
25
|
*
|
|
12
|
-
*
|
|
26
|
+
* Missing controls remain best-effort except Pro Extended, which fails closed
|
|
27
|
+
* unless the selected option is confirmed.
|
|
13
28
|
*/
|
|
14
29
|
export async function ensureThinkingTime(Runtime, level, logger, desiredModel) {
|
|
15
30
|
const result = await evaluateThinkingTimeSelection(Runtime, level, desiredModel);
|
|
16
31
|
const capitalizedLevel = level.charAt(0).toUpperCase() + level.slice(1);
|
|
17
32
|
const targetModelKind = inferThinkingTargetModelKind(desiredModel);
|
|
18
|
-
const
|
|
33
|
+
const observedModelKind = result && "modelKind" in result ? result.modelKind : null;
|
|
34
|
+
const strictProEffort = (targetModelKind === "pro" || observedModelKind === "pro") && level === "extended";
|
|
19
35
|
switch (result?.status) {
|
|
20
36
|
case "already-selected":
|
|
21
|
-
logger(
|
|
37
|
+
logger(formatBrowserThinkingLog(`${result.label ?? capitalizedLevel} (already selected)`));
|
|
22
38
|
return;
|
|
23
39
|
case "switched":
|
|
24
|
-
logger(
|
|
40
|
+
logger(formatBrowserThinkingLog(result.label ?? capitalizedLevel));
|
|
25
41
|
return;
|
|
26
42
|
case "chip-not-found":
|
|
27
43
|
case "menu-not-found":
|
|
28
44
|
case "option-not-found":
|
|
45
|
+
case "selection-unverified":
|
|
29
46
|
case "model-kind-not-found": {
|
|
30
47
|
await logDomFailure(Runtime, logger, `thinking-${result.status}`);
|
|
48
|
+
logPickerDiagnostic(result, logger);
|
|
31
49
|
const kindHint = result.status === "model-kind-not-found" && result.modelKind
|
|
32
50
|
? ` for ${result.modelKind}`
|
|
33
51
|
: targetModelKind
|
|
@@ -37,15 +55,16 @@ export async function ensureThinkingTime(Runtime, level, logger, desiredModel) {
|
|
|
37
55
|
if (strictProEffort) {
|
|
38
56
|
throw new Error(`${message}; refusing to submit without confirmed Pro Extended.`);
|
|
39
57
|
}
|
|
40
|
-
logger(`${message}; continuing with ChatGPT default.`);
|
|
58
|
+
logger(formatBrowserThinkingLog(`${message}; continuing with ChatGPT default.`));
|
|
41
59
|
return;
|
|
42
60
|
}
|
|
43
61
|
default: {
|
|
44
62
|
await logDomFailure(Runtime, logger, "thinking-time-unknown");
|
|
63
|
+
logPickerDiagnostic(result, logger);
|
|
45
64
|
if (strictProEffort) {
|
|
46
65
|
throw new Error(`Thinking time: unknown outcome selecting ${capitalizedLevel}; refusing to submit without confirmed Pro Extended.`);
|
|
47
66
|
}
|
|
48
|
-
logger(`
|
|
67
|
+
logger(formatBrowserThinkingLog(`unknown outcome selecting ${capitalizedLevel}; continuing with ChatGPT default.`));
|
|
49
68
|
return;
|
|
50
69
|
}
|
|
51
70
|
}
|
|
@@ -61,22 +80,23 @@ export async function ensureThinkingTimeIfAvailable(Runtime, level, logger, desi
|
|
|
61
80
|
const capitalizedLevel = level.charAt(0).toUpperCase() + level.slice(1);
|
|
62
81
|
switch (result?.status) {
|
|
63
82
|
case "already-selected":
|
|
64
|
-
logger(
|
|
83
|
+
logger(formatBrowserThinkingLog(`${result.label ?? capitalizedLevel} (already selected)`));
|
|
65
84
|
return true;
|
|
66
85
|
case "switched":
|
|
67
|
-
logger(
|
|
86
|
+
logger(formatBrowserThinkingLog(result.label ?? capitalizedLevel));
|
|
68
87
|
return true;
|
|
69
88
|
case "chip-not-found":
|
|
70
89
|
case "menu-not-found":
|
|
71
90
|
case "option-not-found":
|
|
91
|
+
case "selection-unverified":
|
|
72
92
|
case "model-kind-not-found":
|
|
73
93
|
if (logger.verbose) {
|
|
74
|
-
logger(
|
|
94
|
+
logger(formatBrowserThinkingLog(`${result.status.replaceAll("-", " ")}; continuing with default.`));
|
|
75
95
|
}
|
|
76
96
|
return false;
|
|
77
97
|
default:
|
|
78
98
|
if (logger.verbose) {
|
|
79
|
-
logger("
|
|
99
|
+
logger(formatBrowserThinkingLog("unknown outcome; continuing with default."));
|
|
80
100
|
}
|
|
81
101
|
return false;
|
|
82
102
|
}
|
|
@@ -84,7 +104,7 @@ export async function ensureThinkingTimeIfAvailable(Runtime, level, logger, desi
|
|
|
84
104
|
catch (error) {
|
|
85
105
|
const message = error instanceof Error ? error.message : String(error);
|
|
86
106
|
if (logger.verbose) {
|
|
87
|
-
logger(`
|
|
107
|
+
logger(formatBrowserThinkingLog(`selection failed (${message}); continuing with default.`));
|
|
88
108
|
await logDomFailure(Runtime, logger, "thinking-time");
|
|
89
109
|
}
|
|
90
110
|
return false;
|
|
@@ -115,16 +135,20 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
115
135
|
|
|
116
136
|
// Bilingual matchers: English level token + observed Chinese variants.
|
|
117
137
|
const LEVEL_TOKENS = {
|
|
118
|
-
light: ['light', '轻'],
|
|
119
|
-
standard: ['standard', '标准'],
|
|
120
|
-
extended: ['extended', '扩展', '深度', '加强'],
|
|
121
|
-
heavy: ['heavy', '重度', '加重', '高'],
|
|
138
|
+
light: ['light', 'instant', '轻'],
|
|
139
|
+
standard: ['standard', 'medium', '标准'],
|
|
140
|
+
extended: ['extended', 'high', '扩展', '深度', '加强'],
|
|
141
|
+
heavy: ['heavy', 'extra high', '重度', '加重', '高'],
|
|
122
142
|
};
|
|
123
143
|
const targetTokens = LEVEL_TOKENS[TARGET_LEVEL] || [TARGET_LEVEL];
|
|
124
144
|
|
|
125
145
|
const INITIAL_WAIT_MS = 150;
|
|
126
146
|
const STEP_WAIT_MS = 200;
|
|
127
147
|
const MAX_WAIT_MS = 8000;
|
|
148
|
+
// The "Intelligence" menu renders right after opening the composer pill, so
|
|
149
|
+
// a short probe is enough; if it's absent this is an older UI and we fall
|
|
150
|
+
// back to the legacy paths without paying the full MAX_WAIT_MS.
|
|
151
|
+
const INTELLIGENCE_WAIT_MS = 2500;
|
|
128
152
|
|
|
129
153
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
130
154
|
// Keep CJK characters so we can match Chinese labels against LEVEL_TOKENS.
|
|
@@ -133,17 +157,51 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
133
157
|
.replace(/[^a-z0-9\\u4e00-\\u9fa5]+/g, ' ')
|
|
134
158
|
.replace(/\\s+/g, ' ')
|
|
135
159
|
.trim();
|
|
160
|
+
const hasToken = (text, token) => normalize(text).split(' ').includes(token);
|
|
136
161
|
const matchesLevel = (text) => {
|
|
137
162
|
const t = normalize(text);
|
|
138
|
-
|
|
163
|
+
if (!t) return false;
|
|
164
|
+
return targetTokens.some((tok) => {
|
|
165
|
+
const token = normalize(tok);
|
|
166
|
+
if (!token) return false;
|
|
167
|
+
if (token === 'high') return hasToken(t, 'high') && !hasToken(t, 'extra');
|
|
168
|
+
if (token === 'extra high') return hasToken(t, 'extra') && hasToken(t, 'high');
|
|
169
|
+
return t === token || hasToken(t, token) || t.includes(token);
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
const matchesAnyEffortLevel = (text) => {
|
|
173
|
+
const normalizedText = normalize(text);
|
|
174
|
+
if (!normalizedText) return false;
|
|
175
|
+
for (const tokens of Object.values(LEVEL_TOKENS)) {
|
|
176
|
+
for (const rawToken of tokens) {
|
|
177
|
+
const token = normalize(rawToken);
|
|
178
|
+
if (!token) continue;
|
|
179
|
+
if (token.includes(' ')) {
|
|
180
|
+
if (token.split(' ').every((part) => hasToken(normalizedText, part))) return true;
|
|
181
|
+
} else if (/^[a-z0-9]+$/.test(token)) {
|
|
182
|
+
if (hasToken(normalizedText, token)) return true;
|
|
183
|
+
} else if (normalizedText.includes(token)) {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return false;
|
|
139
189
|
};
|
|
140
|
-
const hasToken = (text, token) => normalize(text).split(' ').includes(token);
|
|
141
190
|
const optionIsSelected = (node) => {
|
|
142
191
|
if (!(node instanceof HTMLElement)) return false;
|
|
143
192
|
const ariaChecked = node.getAttribute('aria-checked');
|
|
193
|
+
const ariaSelected = node.getAttribute('aria-selected');
|
|
194
|
+
const ariaCurrent = node.getAttribute('aria-current');
|
|
195
|
+
const dataSelected = node.getAttribute('data-selected');
|
|
144
196
|
const dataState = (node.getAttribute('data-state') || '').toLowerCase();
|
|
145
|
-
if (ariaChecked === 'true') return true;
|
|
146
|
-
return
|
|
197
|
+
if (ariaChecked === 'true' || ariaSelected === 'true' || ariaCurrent === 'true') return true;
|
|
198
|
+
return (
|
|
199
|
+
dataSelected === 'true' ||
|
|
200
|
+
dataState === 'checked' ||
|
|
201
|
+
dataState === 'selected' ||
|
|
202
|
+
dataState === 'on' ||
|
|
203
|
+
dataState === 'true'
|
|
204
|
+
);
|
|
147
205
|
};
|
|
148
206
|
const closeOpenMenus = () => {
|
|
149
207
|
try {
|
|
@@ -152,37 +210,166 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
152
210
|
);
|
|
153
211
|
} catch {}
|
|
154
212
|
};
|
|
213
|
+
const dispatchHoverSequence = (target) => {
|
|
214
|
+
if (!target || !(target instanceof EventTarget)) return false;
|
|
215
|
+
const types = ['pointerover', 'pointerenter', 'mouseover', 'mouseenter', 'pointermove', 'mousemove'];
|
|
216
|
+
for (const type of types) {
|
|
217
|
+
try {
|
|
218
|
+
const common = { bubbles: true, cancelable: true, view: window };
|
|
219
|
+
const event =
|
|
220
|
+
type.startsWith('pointer') && 'PointerEvent' in window
|
|
221
|
+
? new PointerEvent(type, { ...common, pointerId: 1, pointerType: 'mouse' })
|
|
222
|
+
: new MouseEvent(type, common);
|
|
223
|
+
target.dispatchEvent(event);
|
|
224
|
+
} catch {}
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
target.focus?.();
|
|
228
|
+
} catch {}
|
|
229
|
+
return true;
|
|
230
|
+
};
|
|
155
231
|
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
232
|
+
const TRAILING_SELECTOR = '[data-model-picker-thinking-effort-action="true"]';
|
|
233
|
+
const INTELLIGENCE_MENU_SELECTOR = '[data-testid="composer-intelligence-picker-content"]';
|
|
234
|
+
const PRO_EFFORT_TRIGGER_SELECTOR = '[data-testid="composer-intelligence-pro-thinking-effort-trigger"]';
|
|
235
|
+
|
|
236
|
+
const findModelButton = () => document.querySelector(MODEL_BUTTON_SELECTOR);
|
|
237
|
+
const findTrailingButtons = () => Array.from(document.querySelectorAll(TRAILING_SELECTOR));
|
|
238
|
+
const KIND_NOT_FOUND = { kindNotFound: true };
|
|
239
|
+
|
|
240
|
+
const isVisible = (node) => {
|
|
241
|
+
if (!node || node.getAttribute?.('aria-hidden') === 'true') return false;
|
|
242
|
+
const rect = node.getBoundingClientRect?.();
|
|
243
|
+
return Boolean(rect && rect.width > 0 && rect.height > 0);
|
|
244
|
+
};
|
|
245
|
+
const redactDiagnosticText = (value, maxLength = 120) =>
|
|
246
|
+
String(value ?? '')
|
|
247
|
+
.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/gi, '[redacted-email]')
|
|
248
|
+
.replace(/\\b[A-Za-z0-9_-]{32,}\\b/g, '[redacted]')
|
|
249
|
+
.replace(/\\s+/g, ' ')
|
|
250
|
+
.trim()
|
|
251
|
+
.slice(0, maxLength);
|
|
252
|
+
const describeNode = (el) => {
|
|
253
|
+
if (!el || typeof el.getAttribute !== 'function') return null;
|
|
254
|
+
let rect = null;
|
|
255
|
+
try {
|
|
256
|
+
const r = el.getBoundingClientRect?.();
|
|
257
|
+
if (r) {
|
|
258
|
+
rect = {
|
|
259
|
+
w: Math.round(r.width),
|
|
260
|
+
h: Math.round(r.height),
|
|
261
|
+
visible: r.width > 0 && r.height > 0,
|
|
262
|
+
};
|
|
170
263
|
}
|
|
264
|
+
} catch {}
|
|
265
|
+
return {
|
|
266
|
+
tag: el.tagName || null,
|
|
267
|
+
testid: el.getAttribute('data-testid'),
|
|
268
|
+
role: el.getAttribute('role'),
|
|
269
|
+
ariaLabel: redactDiagnosticText(el.getAttribute('aria-label')),
|
|
270
|
+
ariaExpanded: el.getAttribute('aria-expanded'),
|
|
271
|
+
ariaChecked: el.getAttribute('aria-checked'),
|
|
272
|
+
ariaSelected: el.getAttribute('aria-selected'),
|
|
273
|
+
ariaHaspopup: el.getAttribute('aria-haspopup'),
|
|
274
|
+
dataState: el.getAttribute('data-state'),
|
|
275
|
+
text: redactDiagnosticText(el.textContent, 80),
|
|
276
|
+
rect,
|
|
277
|
+
};
|
|
278
|
+
};
|
|
279
|
+
const describeMenu = (menu) => {
|
|
280
|
+
if (!menu || typeof menu.querySelectorAll !== 'function') return null;
|
|
281
|
+
const items = Array.from(
|
|
282
|
+
menu.querySelectorAll('[role="menuitem"], [role="menuitemradio"], [role="option"], button, [data-testid]'),
|
|
283
|
+
)
|
|
284
|
+
.slice(0, 30)
|
|
285
|
+
.map(describeNode);
|
|
286
|
+
return {
|
|
287
|
+
role: menu.getAttribute?.('role') ?? null,
|
|
288
|
+
testid: menu.getAttribute?.('data-testid') ?? null,
|
|
289
|
+
itemCount: items.length,
|
|
290
|
+
items,
|
|
291
|
+
};
|
|
292
|
+
};
|
|
293
|
+
const collectPickerDiagnostic = () => {
|
|
294
|
+
try {
|
|
295
|
+
const trailings = findTrailingButtons();
|
|
296
|
+
const switchers = Array.from(document.querySelectorAll('[data-testid*="model-switcher"]'));
|
|
297
|
+
const composerButtons = Array.from(
|
|
298
|
+
document.querySelectorAll(
|
|
299
|
+
'form button[aria-haspopup="menu"], [data-testid="model-switcher-dropdown-button"]',
|
|
300
|
+
),
|
|
301
|
+
);
|
|
302
|
+
const menus = Array.from(document.querySelectorAll(MENU_CONTAINER_SELECTOR)).filter(
|
|
303
|
+
isVisible,
|
|
304
|
+
);
|
|
305
|
+
const modelBtn = findModelButton();
|
|
306
|
+
return {
|
|
307
|
+
targetModelKind: TARGET_MODEL_KIND,
|
|
308
|
+
targetLevel: TARGET_LEVEL,
|
|
309
|
+
modelButton: describeNode(modelBtn),
|
|
310
|
+
composerButtons: composerButtons.slice(0, 12).map(describeNode),
|
|
311
|
+
trailingCount: trailings.length,
|
|
312
|
+
trailings: trailings.slice(0, 12).map(describeNode),
|
|
313
|
+
modelSwitcherCount: switchers.length,
|
|
314
|
+
modelSwitcher: switchers.slice(0, 12).map(describeNode),
|
|
315
|
+
menuCount: menus.length,
|
|
316
|
+
menus: menus.slice(0, 4).map(describeMenu),
|
|
317
|
+
};
|
|
318
|
+
} catch (err) {
|
|
319
|
+
return { error: redactDiagnosticText(err && err.message ? err.message : err) };
|
|
171
320
|
}
|
|
321
|
+
};
|
|
322
|
+
const modelKindFromNode = (button) => {
|
|
323
|
+
const label = normalize(
|
|
324
|
+
(button?.textContent ?? '') + ' ' + (button?.getAttribute?.('aria-label') ?? ''),
|
|
325
|
+
);
|
|
326
|
+
if (hasToken(label, 'pro')) return 'pro';
|
|
327
|
+
if (hasToken(label, 'thinking')) return 'thinking';
|
|
328
|
+
if (hasToken(label, 'instant')) return 'instant';
|
|
172
329
|
return null;
|
|
173
330
|
};
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const text = normalize(menu.textContent ?? '');
|
|
180
|
-
if (text.includes('standard') && text.includes('extended')) return menu;
|
|
331
|
+
const currentModelKind = () => modelKindFromNode(findModelButton());
|
|
332
|
+
const effectiveTargetModelKind = () => TARGET_MODEL_KIND || currentModelKind();
|
|
333
|
+
const isIntelligenceEffortMenu = (menu) => {
|
|
334
|
+
if (menu?.getAttribute?.('data-testid') === 'composer-intelligence-picker-content') {
|
|
335
|
+
return true;
|
|
181
336
|
}
|
|
182
|
-
|
|
337
|
+
const label = menu?.querySelector?.('.__menu-label, [class*="menu-label"]');
|
|
338
|
+
return normalize(label?.textContent ?? '').includes('intelligence');
|
|
183
339
|
};
|
|
184
|
-
const
|
|
185
|
-
|
|
340
|
+
const failure = (status, extra = {}) => ({
|
|
341
|
+
status,
|
|
342
|
+
modelKind: effectiveTargetModelKind(),
|
|
343
|
+
...extra,
|
|
344
|
+
diagnostic: collectPickerDiagnostic(),
|
|
345
|
+
});
|
|
346
|
+
const findOptionInMenu = (menu, modelKindOverride = null) => {
|
|
347
|
+
const items = Array.from(menu.querySelectorAll(MENU_ITEM_SELECTOR));
|
|
348
|
+
const modelKind = modelKindOverride || effectiveTargetModelKind();
|
|
349
|
+
if (modelKind === 'pro') {
|
|
350
|
+
for (const item of items) {
|
|
351
|
+
const itemText = normalize(
|
|
352
|
+
(item.textContent ?? '') + ' ' + (item.getAttribute?.('aria-label') ?? ''),
|
|
353
|
+
);
|
|
354
|
+
if (
|
|
355
|
+
hasToken(itemText, 'pro') &&
|
|
356
|
+
(matchesLevel(item.textContent ?? '') ||
|
|
357
|
+
matchesLevel(item.getAttribute?.('aria-label') ?? ''))
|
|
358
|
+
) {
|
|
359
|
+
return item;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (isIntelligenceEffortMenu(menu)) {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
for (const item of items) {
|
|
367
|
+
const itemText = normalize(
|
|
368
|
+
(item.textContent ?? '') + ' ' + (item.getAttribute?.('aria-label') ?? ''),
|
|
369
|
+
);
|
|
370
|
+
if (modelKind && modelKind !== 'pro' && hasToken(itemText, 'pro')) {
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
186
373
|
if (
|
|
187
374
|
matchesLevel(item.textContent ?? '') ||
|
|
188
375
|
matchesLevel(item.getAttribute?.('aria-label') ?? '')
|
|
@@ -192,41 +379,241 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
192
379
|
}
|
|
193
380
|
return null;
|
|
194
381
|
};
|
|
382
|
+
const countEffortLevels = (menu) => {
|
|
383
|
+
const text = normalize(menu?.textContent ?? '');
|
|
384
|
+
let hits = 0;
|
|
385
|
+
for (const tokens of Object.values(LEVEL_TOKENS)) {
|
|
386
|
+
if (tokens.some((token) => text.includes(String(token).toLowerCase()))) hits += 1;
|
|
387
|
+
}
|
|
388
|
+
return hits;
|
|
389
|
+
};
|
|
390
|
+
const isEffortMenu = (menu) => {
|
|
391
|
+
if (!isVisible(menu)) return false;
|
|
392
|
+
if (menu.getAttribute?.('data-testid') === 'composer-intelligence-picker-content') return true;
|
|
393
|
+
const label = menu.querySelector?.('.__menu-label, [class*="menu-label"]');
|
|
394
|
+
const labelText = normalize(label?.textContent ?? '');
|
|
395
|
+
return (
|
|
396
|
+
labelText.includes('intelligence') ||
|
|
397
|
+
labelText.includes('thinking time') ||
|
|
398
|
+
labelText.includes('thinking effort') ||
|
|
399
|
+
countEffortLevels(menu) >= 2
|
|
400
|
+
);
|
|
401
|
+
};
|
|
402
|
+
const isProEffortMenu = (menu) => {
|
|
403
|
+
if (!isVisible(menu)) return false;
|
|
404
|
+
const text = normalize(menu?.textContent ?? '');
|
|
405
|
+
return text.includes('pro standard') && text.includes('pro extended');
|
|
406
|
+
};
|
|
407
|
+
const controlledMenu = (trigger) => {
|
|
408
|
+
const id = trigger?.getAttribute?.('aria-controls');
|
|
409
|
+
if (!id) return null;
|
|
410
|
+
const menu = document.getElementById?.(id);
|
|
411
|
+
return isEffortMenu(menu) ? menu : null;
|
|
412
|
+
};
|
|
413
|
+
const findVisibleEffortMenu = (trigger) => {
|
|
414
|
+
const controlled = controlledMenu(trigger);
|
|
415
|
+
if (controlled) return controlled;
|
|
416
|
+
for (const menu of document.querySelectorAll(MENU_CONTAINER_SELECTOR)) {
|
|
417
|
+
if (isEffortMenu(menu)) return menu;
|
|
418
|
+
}
|
|
419
|
+
return null;
|
|
420
|
+
};
|
|
421
|
+
const controlledProEffortMenu = (trigger) => {
|
|
422
|
+
const id = trigger?.getAttribute?.('aria-controls');
|
|
423
|
+
if (!id) return null;
|
|
424
|
+
const menu = document.getElementById?.(id);
|
|
425
|
+
return isProEffortMenu(menu) ? menu : null;
|
|
426
|
+
};
|
|
427
|
+
const findVisibleProEffortMenu = (trigger) => {
|
|
428
|
+
const controlled = controlledProEffortMenu(trigger);
|
|
429
|
+
if (controlled) return controlled;
|
|
430
|
+
for (const menu of document.querySelectorAll(MENU_CONTAINER_SELECTOR)) {
|
|
431
|
+
if (isProEffortMenu(menu)) return menu;
|
|
432
|
+
}
|
|
433
|
+
return null;
|
|
434
|
+
};
|
|
435
|
+
const matchesProEffortLevel = (node) => {
|
|
436
|
+
const text = normalize(
|
|
437
|
+
(node?.textContent ?? '') + ' ' + (node?.getAttribute?.('aria-label') ?? ''),
|
|
438
|
+
);
|
|
439
|
+
if (TARGET_LEVEL === 'standard') {
|
|
440
|
+
return text.includes('pro') && text.includes('standard');
|
|
441
|
+
}
|
|
442
|
+
if (TARGET_LEVEL === 'extended') {
|
|
443
|
+
return text.includes('pro') && text.includes('extended');
|
|
444
|
+
}
|
|
445
|
+
return false;
|
|
446
|
+
};
|
|
447
|
+
const findProEffortOptionInMenu = (menu) => {
|
|
448
|
+
for (const item of menu.querySelectorAll(MENU_ITEM_SELECTOR)) {
|
|
449
|
+
if (matchesProEffortLevel(item)) return item;
|
|
450
|
+
}
|
|
451
|
+
return null;
|
|
452
|
+
};
|
|
453
|
+
const currentProEffortPillMatchesTarget = (trigger, modelKindOverride = null) => {
|
|
454
|
+
const button = trigger?.matches?.('button.__composer-pill') ? trigger : findModelButton();
|
|
455
|
+
if ((modelKindOverride || TARGET_MODEL_KIND || modelKindFromNode(button)) !== 'pro') {
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
const label = normalize(button?.textContent ?? '');
|
|
459
|
+
if (TARGET_LEVEL === 'standard') {
|
|
460
|
+
return hasToken(label, 'pro') && !hasToken(label, 'extended');
|
|
461
|
+
}
|
|
462
|
+
if (TARGET_LEVEL === 'extended') {
|
|
463
|
+
return hasToken(label, 'pro') && hasToken(label, 'extended');
|
|
464
|
+
}
|
|
465
|
+
return false;
|
|
466
|
+
};
|
|
467
|
+
const currentEffortPillMatchesTarget = (trigger, modelKindOverride = null) => {
|
|
468
|
+
if (currentProEffortPillMatchesTarget(trigger, modelKindOverride)) return true;
|
|
469
|
+
const button = trigger?.matches?.('button.__composer-pill') ? trigger : findModelButton();
|
|
470
|
+
if ((modelKindOverride || TARGET_MODEL_KIND || modelKindFromNode(button)) === 'pro') {
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
const label = (button?.textContent ?? '') + ' ' + (button?.getAttribute?.('aria-label') ?? '');
|
|
474
|
+
return matchesLevel(label);
|
|
475
|
+
};
|
|
476
|
+
const selectAndVerify = async (trigger, findOption, modelKindOverride = null) => {
|
|
477
|
+
const option = findOption();
|
|
478
|
+
const triggerModelKind =
|
|
479
|
+
modelKindOverride ||
|
|
480
|
+
TARGET_MODEL_KIND ||
|
|
481
|
+
modelKindFromNode(trigger) ||
|
|
482
|
+
effectiveTargetModelKind();
|
|
483
|
+
if (!option) return failure('option-not-found', { modelKind: triggerModelKind });
|
|
484
|
+
const label = option.textContent?.trim?.() || null;
|
|
485
|
+
if (optionIsSelected(option)) {
|
|
486
|
+
closeOpenMenus();
|
|
487
|
+
return { status: 'already-selected', label };
|
|
488
|
+
}
|
|
195
489
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
490
|
+
dispatchClickSequence(option);
|
|
491
|
+
await sleep(STEP_WAIT_MS);
|
|
492
|
+
const refreshed = findOption();
|
|
493
|
+
if (refreshed && optionIsSelected(refreshed)) {
|
|
494
|
+
closeOpenMenus();
|
|
495
|
+
return { status: 'switched', label: refreshed.textContent?.trim?.() || label };
|
|
496
|
+
}
|
|
497
|
+
if (currentEffortPillMatchesTarget(trigger, triggerModelKind)) {
|
|
498
|
+
closeOpenMenus();
|
|
499
|
+
return { status: 'switched', label };
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (!refreshed && trigger && trigger.getAttribute?.('aria-expanded') !== 'true') {
|
|
503
|
+
dispatchClickSequence(trigger);
|
|
504
|
+
await sleep(INITIAL_WAIT_MS);
|
|
505
|
+
}
|
|
506
|
+
const deadline = performance.now() + 2000;
|
|
507
|
+
while (performance.now() < deadline) {
|
|
508
|
+
const selected = findOption();
|
|
509
|
+
if (selected && optionIsSelected(selected)) {
|
|
206
510
|
closeOpenMenus();
|
|
207
|
-
return { status: '
|
|
511
|
+
return { status: 'switched', label: selected.textContent?.trim?.() || label };
|
|
208
512
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
return { status: already ? 'already-selected' : 'switched', label };
|
|
513
|
+
if (currentEffortPillMatchesTarget(trigger, triggerModelKind)) {
|
|
514
|
+
closeOpenMenus();
|
|
515
|
+
return { status: 'switched', label };
|
|
516
|
+
}
|
|
517
|
+
await sleep(100);
|
|
215
518
|
}
|
|
519
|
+
const result = failure('selection-unverified', { modelKind: triggerModelKind });
|
|
216
520
|
closeOpenMenus();
|
|
217
|
-
return
|
|
218
|
-
}
|
|
521
|
+
return result;
|
|
522
|
+
};
|
|
523
|
+
const selectProEffortFromSubmenu = async () => {
|
|
524
|
+
if (TARGET_MODEL_KIND !== 'pro' || (TARGET_LEVEL !== 'standard' && TARGET_LEVEL !== 'extended')) {
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
const trigger = document.querySelector(PRO_EFFORT_TRIGGER_SELECTOR);
|
|
528
|
+
if (!trigger) {
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
dispatchHoverSequence(trigger);
|
|
532
|
+
if (trigger.getAttribute?.('aria-expanded') !== 'true') {
|
|
533
|
+
dispatchClickSequence(trigger);
|
|
534
|
+
}
|
|
535
|
+
const deadline = performance.now() + MAX_WAIT_MS;
|
|
536
|
+
while (performance.now() < deadline) {
|
|
537
|
+
const menu = findVisibleProEffortMenu(trigger);
|
|
538
|
+
if (menu) {
|
|
539
|
+
return selectAndVerify(trigger, () => {
|
|
540
|
+
const currentMenu = findVisibleProEffortMenu(trigger);
|
|
541
|
+
return currentMenu ? findProEffortOptionInMenu(currentMenu) : null;
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
await sleep(100);
|
|
545
|
+
}
|
|
546
|
+
return null;
|
|
547
|
+
};
|
|
219
548
|
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
549
|
+
// Current ChatGPT exposes a standalone Pro or Thinking composer pill whose
|
|
550
|
+
// controlled menu contains the effort levels. Prefer this ownership boundary
|
|
551
|
+
// before probing older model-picker layouts.
|
|
552
|
+
const COMPOSER_EFFORT_PILL_SELECTORS = [
|
|
553
|
+
'form button.__composer-pill',
|
|
554
|
+
'[data-testid="composer-footer-actions"] button.__composer-pill',
|
|
555
|
+
'.__composer-pill-composite button.__composer-pill',
|
|
556
|
+
];
|
|
557
|
+
const findComposerEffortPill = () => {
|
|
558
|
+
const seen = new Set();
|
|
559
|
+
for (const selector of COMPOSER_EFFORT_PILL_SELECTORS) {
|
|
560
|
+
for (const button of document.querySelectorAll(selector)) {
|
|
561
|
+
if (seen.has(button) || !isVisible(button)) continue;
|
|
562
|
+
seen.add(button);
|
|
563
|
+
if (button.getAttribute?.('data-testid') === 'model-switcher-dropdown-button') continue;
|
|
564
|
+
const label = normalize(
|
|
565
|
+
(button.getAttribute?.('aria-label') ?? '') + ' ' +
|
|
566
|
+
(button.getAttribute?.('data-testid') ?? '') + ' ' +
|
|
567
|
+
(button.textContent ?? ''),
|
|
568
|
+
);
|
|
569
|
+
if (
|
|
570
|
+
(TARGET_MODEL_KIND === 'pro' && hasToken(label, 'pro') && !hasToken(label, 'thinking')) ||
|
|
571
|
+
(TARGET_MODEL_KIND === 'thinking' && hasToken(label, 'thinking') && !hasToken(label, 'pro')) ||
|
|
572
|
+
(!TARGET_MODEL_KIND && hasToken(label, 'thinking')) ||
|
|
573
|
+
(button.matches?.('button.__composer-pill') && matchesAnyEffortLevel(label))
|
|
574
|
+
) {
|
|
575
|
+
return button;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return null;
|
|
580
|
+
};
|
|
226
581
|
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
582
|
+
const composerEffortPill = findComposerEffortPill();
|
|
583
|
+
if (composerEffortPill) {
|
|
584
|
+
const composerModelKind = TARGET_MODEL_KIND || modelKindFromNode(composerEffortPill);
|
|
585
|
+
if (composerEffortPill.getAttribute?.('aria-expanded') !== 'true') {
|
|
586
|
+
dispatchClickSequence(composerEffortPill);
|
|
587
|
+
await sleep(INITIAL_WAIT_MS);
|
|
588
|
+
}
|
|
589
|
+
const deadline = performance.now() + MAX_WAIT_MS;
|
|
590
|
+
while (performance.now() < deadline) {
|
|
591
|
+
const menu = findVisibleEffortMenu(composerEffortPill);
|
|
592
|
+
if (menu) {
|
|
593
|
+
const proEffortResult = await selectProEffortFromSubmenu();
|
|
594
|
+
if (proEffortResult) {
|
|
595
|
+
return proEffortResult;
|
|
596
|
+
}
|
|
597
|
+
return selectAndVerify(
|
|
598
|
+
composerEffortPill,
|
|
599
|
+
() => {
|
|
600
|
+
const currentMenu = findVisibleEffortMenu(composerEffortPill);
|
|
601
|
+
return currentMenu ? findOptionInMenu(currentMenu, composerModelKind) : null;
|
|
602
|
+
},
|
|
603
|
+
composerModelKind,
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
await sleep(100);
|
|
607
|
+
}
|
|
608
|
+
const result = failure('menu-not-found', {
|
|
609
|
+
modelKind: composerModelKind,
|
|
610
|
+
});
|
|
611
|
+
closeOpenMenus();
|
|
612
|
+
return result;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Older ChatGPT layouts attach effort controls to rows inside the model
|
|
616
|
+
// picker. Keep these compatibility paths after the standalone pill owner.
|
|
230
617
|
const findEffortRow = (node) => {
|
|
231
618
|
let current = node instanceof HTMLElement ? node.parentElement : null;
|
|
232
619
|
while (current && current !== document.body) {
|
|
@@ -291,12 +678,8 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
291
678
|
}
|
|
292
679
|
return false;
|
|
293
680
|
};
|
|
294
|
-
const hasStableBox = (node) => {
|
|
295
|
-
const r = node.getBoundingClientRect?.();
|
|
296
|
-
return Boolean(r && r.width > 0 && r.height > 0 && node.getAttribute?.('aria-hidden') !== 'true');
|
|
297
|
-
};
|
|
298
681
|
const pickSingleStableTrailing = (trailings) => {
|
|
299
|
-
const visible = trailings.filter((
|
|
682
|
+
const visible = trailings.filter((trailing) => isVisible(trailing));
|
|
300
683
|
return visible.length === 1 ? visible[0] : null;
|
|
301
684
|
};
|
|
302
685
|
const pickTrailingForCurrentModel = () => {
|
|
@@ -317,7 +700,7 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
317
700
|
|
|
318
701
|
const modelBtn = findModelButton();
|
|
319
702
|
if (!modelBtn) {
|
|
320
|
-
return
|
|
703
|
+
return failure('chip-not-found');
|
|
321
704
|
}
|
|
322
705
|
// Open model menu (idempotent — leaves it open if already open).
|
|
323
706
|
if (modelBtn.getAttribute('aria-expanded') !== 'true') {
|
|
@@ -325,6 +708,43 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
325
708
|
await sleep(INITIAL_WAIT_MS);
|
|
326
709
|
}
|
|
327
710
|
|
|
711
|
+
// ---------- COMPATIBILITY UI: unified "Intelligence" effort picker ----------
|
|
712
|
+
// One observed ChatGPT layout replaced the per-model trailing buttons with a single
|
|
713
|
+
// "Intelligence" menu ([data-testid="composer-intelligence-picker-content"]),
|
|
714
|
+
// whose role="menuitemradio" rows are the effort tiers. We verify the checked
|
|
715
|
+
// radio instead of trusting the composer-pill label; non-Pro targets also
|
|
716
|
+
// explicitly skip Pro rows before matching effort labels.
|
|
717
|
+
if (TARGET_MODEL_KIND === 'pro' && TARGET_LEVEL === 'extended') {
|
|
718
|
+
const matchesProExtended = (node) => {
|
|
719
|
+
const text = normalize(
|
|
720
|
+
(node?.textContent ?? '') + ' ' + (node?.getAttribute?.('aria-label') ?? ''),
|
|
721
|
+
);
|
|
722
|
+
return text.includes('pro') && text.includes('extended');
|
|
723
|
+
};
|
|
724
|
+
const findProExtendedOption = () => {
|
|
725
|
+
const menu = document.querySelector(INTELLIGENCE_MENU_SELECTOR);
|
|
726
|
+
if (!isVisible(menu)) return null;
|
|
727
|
+
for (const item of menu.querySelectorAll(
|
|
728
|
+
'[role="menuitemradio"], [role="menuitem"], [role="option"]',
|
|
729
|
+
)) {
|
|
730
|
+
if (matchesProExtended(item)) return item;
|
|
731
|
+
}
|
|
732
|
+
return null;
|
|
733
|
+
};
|
|
734
|
+
let proExtended = null;
|
|
735
|
+
const intelligenceDeadline = performance.now() + INTELLIGENCE_WAIT_MS;
|
|
736
|
+
while (performance.now() < intelligenceDeadline) {
|
|
737
|
+
proExtended = findProExtendedOption();
|
|
738
|
+
if (proExtended) break;
|
|
739
|
+
await sleep(100);
|
|
740
|
+
}
|
|
741
|
+
if (proExtended) {
|
|
742
|
+
return selectAndVerify(modelBtn, findProExtendedOption);
|
|
743
|
+
}
|
|
744
|
+
// Intelligence menu absent (older UI) or its Pro Extended row is missing:
|
|
745
|
+
// fall through to the legacy trailing-button path below.
|
|
746
|
+
}
|
|
747
|
+
|
|
328
748
|
let trailing = null;
|
|
329
749
|
const trailingDeadline = performance.now() + MAX_WAIT_MS;
|
|
330
750
|
while (performance.now() < trailingDeadline) {
|
|
@@ -333,12 +753,14 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
333
753
|
await sleep(100);
|
|
334
754
|
}
|
|
335
755
|
if (!trailing) {
|
|
756
|
+
const result = failure('chip-not-found');
|
|
336
757
|
closeOpenMenus();
|
|
337
|
-
return
|
|
758
|
+
return result;
|
|
338
759
|
}
|
|
339
760
|
if (trailing.kindNotFound) {
|
|
761
|
+
const result = failure('model-kind-not-found', { modelKind: TARGET_MODEL_KIND });
|
|
340
762
|
closeOpenMenus();
|
|
341
|
-
return
|
|
763
|
+
return result;
|
|
342
764
|
}
|
|
343
765
|
|
|
344
766
|
dispatchClickSequence(trailing);
|
|
@@ -349,18 +771,15 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
349
771
|
const resolveEffortMenu = () => {
|
|
350
772
|
const id = trailing.getAttribute('aria-controls');
|
|
351
773
|
if (id) {
|
|
352
|
-
const node = document.getElementById(id);
|
|
353
|
-
if (node) return node;
|
|
774
|
+
const node = document.getElementById?.(id);
|
|
775
|
+
if (isEffortMenu(node)) return node;
|
|
354
776
|
}
|
|
355
|
-
const menus = document.querySelectorAll(MENU_CONTAINER_SELECTOR
|
|
777
|
+
const menus = document.querySelectorAll(MENU_CONTAINER_SELECTOR);
|
|
356
778
|
let best = null;
|
|
357
779
|
for (const menu of menus) {
|
|
358
780
|
if (menu === modelBtn || menu.contains(trailing)) continue;
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
for (const tokens of Object.values(LEVEL_TOKENS)) {
|
|
362
|
-
if (tokens.some((tok) => text.includes(String(tok).toLowerCase()))) hits += 1;
|
|
363
|
-
}
|
|
781
|
+
if (!isVisible(menu)) continue;
|
|
782
|
+
const hits = countEffortLevels(menu);
|
|
364
783
|
if (hits >= 2 && (!best || hits > best.hits)) best = { menu, hits };
|
|
365
784
|
}
|
|
366
785
|
return best?.menu ?? null;
|
|
@@ -374,22 +793,15 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
374
793
|
await sleep(100);
|
|
375
794
|
}
|
|
376
795
|
if (!effortMenu) {
|
|
796
|
+
const result = failure('menu-not-found');
|
|
377
797
|
closeOpenMenus();
|
|
378
|
-
return
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
const targetOption = findOptionInMenu(effortMenu);
|
|
382
|
-
if (!targetOption) {
|
|
383
|
-
closeOpenMenus();
|
|
384
|
-
return { status: 'option-not-found' };
|
|
798
|
+
return result;
|
|
385
799
|
}
|
|
386
800
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
closeOpenMenus();
|
|
392
|
-
return { status: already ? 'already-selected' : 'switched', label };
|
|
801
|
+
return selectAndVerify(trailing, () => {
|
|
802
|
+
const currentMenu = resolveEffortMenu();
|
|
803
|
+
return currentMenu ? findOptionInMenu(currentMenu) : null;
|
|
804
|
+
});
|
|
393
805
|
})()`;
|
|
394
806
|
}
|
|
395
807
|
export function buildThinkingTimeExpressionForTest(level = "extended", desiredModel) {
|